diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7d58ebe85..274d5b862 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,10 +20,10 @@ jobs: if: github.ref == 'refs/heads/master' steps: - uses: actions/checkout@v4 - - name: Use Node.js 18.x + - name: Use Node.js 22.x uses: actions/setup-node@v4 with: - node-version: 18.x + node-version: 22.x - run: npm ci - run: node utils/codeMetrics.js env: @@ -33,10 +33,10 @@ jobs: name: "Compile TypeScript" steps: - uses: actions/checkout@v4 - - name: Use Node.js 18.x + - name: Use Node.js 22.x uses: actions/setup-node@v4 with: - node-version: 18.x + node-version: 22.x - run: npm ci - run: npm run compile - run: npm run compile:strict @@ -45,10 +45,10 @@ jobs: name: "Check Format" steps: - uses: actions/checkout@v4 - - name: Use Node.js 18.x + - name: Use Node.js 22.x uses: actions/setup-node@v4 with: - node-version: 18.x + node-version: 22.x - run: npm ci - run: npm run format:check lint: @@ -56,10 +56,10 @@ jobs: name: "Lint" steps: - uses: actions/checkout@v4 - - name: Use Node.js 18.x + - name: Use Node.js 22.x uses: actions/setup-node@v4 with: - node-version: 18.x + node-version: 22.x - run: npm ci - run: npm run lint unittest: @@ -67,10 +67,10 @@ jobs: name: "Unit Tests" steps: - uses: actions/checkout@v4 - - name: Use Node.js 18.x + - name: Use Node.js 22.x uses: actions/setup-node@v4 with: - node-version: 18.x + node-version: 22.x - run: npm ci - run: npm run test build: @@ -78,10 +78,10 @@ jobs: name: "Build" steps: - uses: actions/checkout@v4 - - name: Use Node.js 18.x + - name: Use Node.js 22.x uses: actions/setup-node@v4 with: - node-version: 18.x + node-version: 22.x - run: npm ci - run: npm run build:contracts - name: Restore Build Cache @@ -101,13 +101,13 @@ jobs: - name: "Az CLI login" uses: azure/login@v1 with: - client-id: ${{ secrets.AZURE_CLIENT_ID }} - tenant-id: ${{ secrets.AZURE_TENANT_ID }} - subscription-id: ${{ secrets.PREVIEW_SUBSCRIPTION_ID }} + client-id: ${{ secrets.E2ETESTS_CLIENT_ID }} + tenant-id: ${{ secrets.E2ETESTS_TENANT_ID }} + subscription-id: ${{ secrets.E2ETESTS_SUBSCRIPTION_ID }} - name: Upload build to preview blob storage - run: az storage blob upload-batch -d '$web' -s 'dist' --account-name ${{ secrets.PREVIEW_STORAGE_ACCOUNT_NAME }} --destination-path "${{github.event.pull_request.head.sha || github.sha}}" --auth-mode login --overwrite true + run: az storage blob upload-batch -d '$web' -s 'dist' --account-name ${{ secrets.E2ETESTS_STORAGEACCOUNT_NAME }} --destination-path "${{github.event.pull_request.head.sha || github.sha}}" --auth-mode login --overwrite true - name: Upload preview config to blob storage - run: az storage blob upload -c '$web' -f ./preview/config.json --account-name ${{ secrets.PREVIEW_STORAGE_ACCOUNT_NAME }} --name "${{github.event.pull_request.head.sha || github.sha}}/config.json" --auth-mode login --overwrite true + run: az storage blob upload -c '$web' -f ./preview/config.json --account-name ${{ secrets.E2ETESTS_STORAGEACCOUNT_NAME }} --name "${{github.event.pull_request.head.sha || github.sha}}/config.json" --auth-mode login --overwrite true nuget: name: Publish Nuget if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/') @@ -162,7 +162,10 @@ jobs: runs-on: ubuntu-latest env: NODE_TLS_REJECT_UNAUTHORIZED: 0 - AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + AZURE_SUBSCRIPTION_ID: ${{ secrets.E2ETESTS_SUBSCRIPTION_ID }} + AZURE_TENANT_ID: ${{ secrets.E2ETESTS_TENANT_ID }} + DE_ACCOUNT_PREFIX: ${{ secrets.E2ETESTS_ACCOUNT_PREFIX }} + DE_TEST_RESOURCE_GROUP: ${{ secrets.E2ETESTS_RESOURCEGROUP_NAME }} strategy: fail-fast: false matrix: @@ -170,129 +173,56 @@ jobs: shardTotal: [20] steps: - uses: actions/checkout@v4 - - name: Use Node.js 18.x + - name: Use Node.js 22.x uses: actions/setup-node@v4 with: - node-version: 18.x + node-version: 22.x - run: npm ci - run: npx playwright install --with-deps - name: "Az CLI login" uses: Azure/login@v2 with: - client-id: ${{ secrets.E2E_TESTS_CLIENT_ID }} - tenant-id: ${{ secrets.AZURE_TENANT_ID }} - subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + client-id: ${{ secrets.E2ETESTS_CLIENT_ID }} + tenant-id: ${{ secrets.E2ETESTS_TENANT_ID }} + subscription-id: ${{ secrets.E2ETESTS_SUBSCRIPTION_ID }} # We can't use MSAL within playwright so we acquire tokens prior to running the tests - name: "Acquire RBAC tokens for test accounts" uses: azure/cli@v2 + env: + DE_ACCOUNT_PREFIX: ${{ secrets.E2ETESTS_ACCOUNT_PREFIX }} + DE_TEST_RESOURCE_GROUP: ${{ secrets.E2ETESTS_RESOURCEGROUP_NAME }} with: azcliversion: latest inlineScript: | SHARD_INDEX=${{ matrix.shardIndex }} echo PLAYWRIGHT_SHARD_INDEX=$SHARD_INDEX >> $GITHUB_ENV - NOSQL_TESTACCOUNT_1_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-1.documents.azure.com/.default" -o tsv --query accessToken) - echo "::add-mask::$NOSQL_TESTACCOUNT_1_TOKEN" - echo NOSQL_TESTACCOUNT_1_TOKEN=$NOSQL_TESTACCOUNT_1_TOKEN >> $GITHUB_ENV - - NOSQL_TESTACCOUNT_2_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-2.documents.azure.com/.default" -o tsv --query accessToken) - echo "::add-mask::$NOSQL_TESTACCOUNT_2_TOKEN" - echo NOSQL_TESTACCOUNT_2_TOKEN=$NOSQL_TESTACCOUNT_2_TOKEN >> $GITHUB_ENV - - NOSQL_TESTACCOUNT_3_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-3.documents.azure.com/.default" -o tsv --query accessToken) - echo "::add-mask::$NOSQL_TESTACCOUNT_3_TOKEN" - echo NOSQL_TESTACCOUNT_3_TOKEN=$NOSQL_TESTACCOUNT_3_TOKEN >> $GITHUB_ENV - - NOSQL_TESTACCOUNT_4_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-4.documents.azure.com/.default" -o tsv --query accessToken) - echo "::add-mask::$NOSQL_TESTACCOUNT_4_TOKEN" - echo NOSQL_TESTACCOUNT_4_TOKEN=$NOSQL_TESTACCOUNT_4_TOKEN >> $GITHUB_ENV - - NOSQL_TESTACCOUNT_5_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-5.documents.azure.com/.default" -o tsv --query accessToken) - echo "::add-mask::$NOSQL_TESTACCOUNT_5_TOKEN" - echo NOSQL_TESTACCOUNT_5_TOKEN=$NOSQL_TESTACCOUNT_5_TOKEN >> $GITHUB_ENV - - NOSQL_TESTACCOUNT_6_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-6.documents.azure.com/.default" -o tsv --query accessToken) - echo "::add-mask::$NOSQL_TESTACCOUNT_6_TOKEN" - echo NOSQL_TESTACCOUNT_6_TOKEN=$NOSQL_TESTACCOUNT_6_TOKEN >> $GITHUB_ENV - - NOSQL_TESTACCOUNT_7_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-7.documents.azure.com/.default" -o tsv --query accessToken) - echo "::add-mask::$NOSQL_TESTACCOUNT_7_TOKEN" - echo NOSQL_TESTACCOUNT_7_TOKEN=$NOSQL_TESTACCOUNT_7_TOKEN >> $GITHUB_ENV - - NOSQL_TESTACCOUNT_8_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-8.documents.azure.com/.default" -o tsv --query accessToken) - echo "::add-mask::$NOSQL_TESTACCOUNT_8_TOKEN" - echo NOSQL_TESTACCOUNT_8_TOKEN=$NOSQL_TESTACCOUNT_8_TOKEN >> $GITHUB_ENV - - NOSQL_TESTACCOUNT_9_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-9.documents.azure.com/.default" -o tsv --query accessToken) - echo "::add-mask::$NOSQL_TESTACCOUNT_9_TOKEN" - echo NOSQL_TESTACCOUNT_9_TOKEN=$NOSQL_TESTACCOUNT_9_TOKEN >> $GITHUB_ENV - - NOSQL_TESTACCOUNT_10_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-10.documents.azure.com/.default" -o tsv --query accessToken) - echo "::add-mask::$NOSQL_TESTACCOUNT_10_TOKEN" - echo NOSQL_TESTACCOUNT_10_TOKEN=$NOSQL_TESTACCOUNT_10_TOKEN >> $GITHUB_ENV - - NOSQL_TESTACCOUNT_11_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-11.documents.azure.com/.default" -o tsv --query accessToken) - echo "::add-mask::$NOSQL_TESTACCOUNT_11_TOKEN" - echo NOSQL_TESTACCOUNT_11_TOKEN=$NOSQL_TESTACCOUNT_11_TOKEN >> $GITHUB_ENV - - NOSQL_TESTACCOUNT_12_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-12.documents.azure.com/.default" -o tsv --query accessToken) - echo "::add-mask::$NOSQL_TESTACCOUNT_12_TOKEN" - echo NOSQL_TESTACCOUNT_12_TOKEN=$NOSQL_TESTACCOUNT_12_TOKEN >> $GITHUB_ENV - - NOSQL_TESTACCOUNT_13_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-13.documents.azure.com/.default" -o tsv --query accessToken) - echo "::add-mask::$NOSQL_TESTACCOUNT_13_TOKEN" - echo NOSQL_TESTACCOUNT_13_TOKEN=$NOSQL_TESTACCOUNT_13_TOKEN >> $GITHUB_ENV - - NOSQL_TESTACCOUNT_14_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-14.documents.azure.com/.default" -o tsv --query accessToken) - echo "::add-mask::$NOSQL_TESTACCOUNT_14_TOKEN" - echo NOSQL_TESTACCOUNT_14_TOKEN=$NOSQL_TESTACCOUNT_14_TOKEN >> $GITHUB_ENV - - NOSQL_TESTACCOUNT_15_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-15.documents.azure.com/.default" -o tsv --query accessToken) - echo "::add-mask::$NOSQL_TESTACCOUNT_15_TOKEN" - echo NOSQL_TESTACCOUNT_15_TOKEN=$NOSQL_TESTACCOUNT_15_TOKEN >> $GITHUB_ENV - - NOSQL_TESTACCOUNT_16_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-16.documents.azure.com/.default" -o tsv --query accessToken) - echo "::add-mask::$NOSQL_TESTACCOUNT_16_TOKEN" - echo NOSQL_TESTACCOUNT_16_TOKEN=$NOSQL_TESTACCOUNT_16_TOKEN >> $GITHUB_ENV - - NOSQL_TESTACCOUNT_17_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-17.documents.azure.com/.default" -o tsv --query accessToken) - echo "::add-mask::$NOSQL_TESTACCOUNT_17_TOKEN" - echo NOSQL_TESTACCOUNT_17_TOKEN=$NOSQL_TESTACCOUNT_17_TOKEN >> $GITHUB_ENV - - NOSQL_TESTACCOUNT_18_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-18.documents.azure.com/.default" -o tsv --query accessToken) - echo "::add-mask::$NOSQL_TESTACCOUNT_18_TOKEN" - echo NOSQL_TESTACCOUNT_18_TOKEN=$NOSQL_TESTACCOUNT_18_TOKEN >> $GITHUB_ENV - - NOSQL_TESTACCOUNT_19_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-19.documents.azure.com/.default" -o tsv --query accessToken) - echo "::add-mask::$NOSQL_TESTACCOUNT_19_TOKEN" - echo NOSQL_TESTACCOUNT_19_TOKEN=$NOSQL_TESTACCOUNT_19_TOKEN >> $GITHUB_ENV - - NOSQL_TESTACCOUNT_20_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-20.documents.azure.com/.default" -o tsv --query accessToken) - echo "::add-mask::$NOSQL_TESTACCOUNT_20_TOKEN" - echo NOSQL_TESTACCOUNT_20_TOKEN=$NOSQL_TESTACCOUNT_20_TOKEN >> $GITHUB_ENV - - NOSQL_READONLY_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-readonly.documents.azure.com/.default" -o tsv --query accessToken) + NOSQL_TESTACCOUNT=${DE_ACCOUNT_PREFIX}-de-test-sql-${SHARD_INDEX}.documents.azure.com + NOSQL_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://$NOSQL_TESTACCOUNT/.default" -o tsv --query accessToken) + echo "::add-mask::$NOSQL_TESTACCOUNT_TOKEN" + echo NOSQL_TESTACCOUNT_TOKEN=$NOSQL_TESTACCOUNT_TOKEN >> $GITHUB_ENV + NOSQL_READONLY_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://$DE_ACCOUNT_PREFIX-de-test-sql-readonly.documents.azure.com/.default" -o tsv --query accessToken) echo "::add-mask::$NOSQL_READONLY_TESTACCOUNT_TOKEN" echo NOSQL_READONLY_TESTACCOUNT_TOKEN=$NOSQL_READONLY_TESTACCOUNT_TOKEN >> $GITHUB_ENV - NOSQL_CONTAINERCOPY_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-containercopyonly.documents.azure.com/.default" -o tsv --query accessToken) + NOSQL_CONTAINERCOPY_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://${DE_ACCOUNT_PREFIX}-de-test-sql-containercopy.documents.azure.com/.default" -o tsv --query accessToken) echo "::add-mask::$NOSQL_CONTAINERCOPY_TESTACCOUNT_TOKEN" echo NOSQL_CONTAINERCOPY_TESTACCOUNT_TOKEN=$NOSQL_CONTAINERCOPY_TESTACCOUNT_TOKEN >> $GITHUB_ENV - TABLE_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-tables.documents.azure.com/.default" -o tsv --query accessToken) + TABLE_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://${DE_ACCOUNT_PREFIX}-de-test-table-1.documents.azure.com/.default" -o tsv --query accessToken) echo "::add-mask::$TABLE_TESTACCOUNT_TOKEN" echo TABLE_TESTACCOUNT_TOKEN=$TABLE_TESTACCOUNT_TOKEN >> $GITHUB_ENV - GREMLIN_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-gremlin.documents.azure.com/.default" -o tsv --query accessToken) + GREMLIN_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://${DE_ACCOUNT_PREFIX}-de-test-gremlin-1.documents.azure.com/.default" -o tsv --query accessToken) echo "::add-mask::$GREMLIN_TESTACCOUNT_TOKEN" echo GREMLIN_TESTACCOUNT_TOKEN=$GREMLIN_TESTACCOUNT_TOKEN >> $GITHUB_ENV - CASSANDRA_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-cassandra.documents.azure.com/.default" -o tsv --query accessToken) + CASSANDRA_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://${DE_ACCOUNT_PREFIX}-de-test-cassandra-1.documents.azure.com/.default" -o tsv --query accessToken) echo "::add-mask::$CASSANDRA_TESTACCOUNT_TOKEN" echo CASSANDRA_TESTACCOUNT_TOKEN=$CASSANDRA_TESTACCOUNT_TOKEN >> $GITHUB_ENV - MONGO_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-mongo.documents.azure.com/.default" -o tsv --query accessToken) + MONGO_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://${DE_ACCOUNT_PREFIX}-de-test-mongo-1.documents.azure.com/.default" -o tsv --query accessToken) echo "::add-mask::$MONGO_TESTACCOUNT_TOKEN" echo MONGO_TESTACCOUNT_TOKEN=$MONGO_TESTACCOUNT_TOKEN >> $GITHUB_ENV - MONGO32_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-mongo32.documents.azure.com/.default" -o tsv --query accessToken) + MONGO32_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://${DE_ACCOUNT_PREFIX}-de-test-mongo32-1.documents.azure.com/.default" -o tsv --query accessToken) echo "::add-mask::$MONGO32_TESTACCOUNT_TOKEN" echo MONGO32_TESTACCOUNT_TOKEN=$MONGO32_TESTACCOUNT_TOKEN >> $GITHUB_ENV - MONGO_READONLY_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-mongo-readonly.documents.azure.com/.default" -o tsv --query accessToken) + MONGO_READONLY_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://${DE_ACCOUNT_PREFIX}-de-test-mongo-readonly.documents.azure.com/.default" -o tsv --query accessToken) echo "::add-mask::$MONGO_READONLY_TESTACCOUNT_TOKEN" echo MONGO_READONLY_TESTACCOUNT_TOKEN=$MONGO_READONLY_TESTACCOUNT_TOKEN >> $GITHUB_ENV - name: List test files for shard ${{ matrix['shardIndex'] }} of ${{ matrix['shardTotal']}} @@ -303,9 +233,9 @@ jobs: if: ${{ !cancelled() }} uses: Azure/login@v2 with: - client-id: ${{ secrets.E2E_TESTS_CLIENT_ID }} - tenant-id: ${{ secrets.AZURE_TENANT_ID }} - subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + client-id: ${{ secrets.E2ETESTS_CLIENT_ID }} + tenant-id: ${{ secrets.E2ETESTS_TENANT_ID }} + subscription-id: ${{ secrets.E2ETESTS_SUBSCRIPTION_ID }} - name: Upload shard blob-report to Azure Storage if: ${{ !cancelled() }} env: @@ -313,7 +243,7 @@ jobs: SHARD: ${{ matrix.shardIndex }} run: | az storage blob upload-batch \ - --account-name ${{ secrets.PREVIEW_STORAGE_ACCOUNT_NAME }} \ + --account-name ${{ secrets.E2ETESTS_STORAGEACCOUNT_NAME }} \ --auth-mode login \ -d playwright-reports \ -s blob-report \ @@ -335,16 +265,16 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 22.x - name: Install dependencies run: npm ci - 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 }} + client-id: ${{ secrets.E2ETESTS_CLIENT_ID }} + tenant-id: ${{ secrets.E2ETESTS_TENANT_ID }} + subscription-id: ${{ secrets.E2ETESTS_SUBSCRIPTION_ID }} - name: Download all shard reports from Azure Storage env: @@ -352,7 +282,7 @@ jobs: run: | mkdir -p all-blob-reports az storage blob download-batch \ - --account-name ${{ secrets.PREVIEW_STORAGE_ACCOUNT_NAME }} \ + --account-name ${{ secrets.E2ETESTS_STORAGEACCOUNT_NAME }} \ --auth-mode login \ -s playwright-reports \ --pattern "${KEY}/shards/*" \ @@ -375,7 +305,7 @@ jobs: KEY: ${{ github.run_id }}-${{ github.run_attempt }} run: | az storage blob upload \ - --account-name ${{ secrets.PREVIEW_STORAGE_ACCOUNT_NAME }} \ + --account-name ${{ secrets.E2ETESTS_STORAGEACCOUNT_NAME }} \ --auth-mode login \ -c playwright-reports \ -n "${KEY}/report.zip" \ @@ -388,7 +318,7 @@ jobs: KEY: ${{ github.run_id }}-${{ github.run_attempt }} run: | az storage blob delete-batch \ - --account-name ${{ secrets.PREVIEW_STORAGE_ACCOUNT_NAME }} \ + --account-name ${{ secrets.E2ETESTS_STORAGEACCOUNT_NAME }} \ --auth-mode login \ -s playwright-reports \ --pattern "${KEY}/shards/*" @@ -398,7 +328,7 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} PR: ${{ github.event.pull_request.number }} - SA_ID: /subscriptions/${{ secrets.PREVIEW_SUBSCRIPTION_ID }}/resourceGroups/dataexplorer-preview/providers/Microsoft.Storage/storageAccounts/dataexplorerpreview + SA_ID: /subscriptions/${{ secrets.E2ETESTS_SUBSCRIPTION_ID }}/resourceGroups/${{ secrets.E2ETESTS_RESOURCEGROUP_NAME }}/providers/Microsoft.Storage/storageAccounts/${{ secrets.E2ETESTS_STORAGEACCOUNT_NAME }} KEY: ${{ github.run_id }}-${{ github.run_attempt }} RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} run: | diff --git a/.github/workflows/cleanup.yml b/.github/workflows/cleanup.yml index 7dfa8a996..add9f15e1 100644 --- a/.github/workflows/cleanup.yml +++ b/.github/workflows/cleanup.yml @@ -20,20 +20,20 @@ jobs: name: "Cleanup Test Database Accounts" runs-on: ubuntu-latest env: - AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + AZURE_SUBSCRIPTION_ID: ${{ secrets.E2ETESTS_SUBSCRIPTION_ID }} + E2ETESTS_RESOURCEGROUP_NAME : ${{ secrets.E2ETESTS_RESOURCEGROUP_NAME }} steps: - uses: actions/checkout@v4 - - name: "Az CLI login" uses: azure/login@v2 with: - client-id: ${{ secrets.E2E_TESTS_CLIENT_ID }} - tenant-id: ${{ secrets.AZURE_TENANT_ID }} - subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + client-id: ${{ secrets.E2ETESTS_CLIENT_ID }} + tenant-id: ${{ secrets.E2ETESTS_TENANT_ID }} + subscription-id: ${{ secrets.E2ETESTS_SUBSCRIPTION_ID }} - - name: Use Node.js 20.x + - name: Use Node.js 22.x uses: actions/setup-node@v4 with: - node-version: 20.x + node-version: 22.x - run: npm ci - run: node utils/cleanupDBs.js \ No newline at end of file diff --git a/README.md b/README.md index 797ab6c2b..8b0a5c0fc 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ UI for Azure Cosmos DB. Powers the [Azure Portal](https://portal.azure.com/), ht ## Getting Started +- Install Node.js 22.x. - `npm install` - `npm run build` diff --git a/docs/remove-notebooks-plan.md b/docs/remove-notebooks-plan.md new file mode 100644 index 000000000..6ff123c93 --- /dev/null +++ b/docs/remove-notebooks-plan.md @@ -0,0 +1,319 @@ +# 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 / Cosmos DB NoSQL), which today +share the Terminal infrastructure with notebooks. + +This document is the implementation plan only. No code changes are made here. + +## Scope decisions (confirmed) + +- **Database shells**: Keep the Mongo/Cassandra/Postgres/VCoreMongo/Cosmos DB NoSQL shells, but migrate + them to use the **CloudShell** path exclusively. Remove the legacy Phoenix + notebook-server shell path. +- **GitHub integration**: Remove entirely (it exists only for notebook pinned repos). +- **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//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//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 five shells +still open. + +### Phase 1 — Decouple database shells to CloudShell-only ✅ COMPLETED +> **Status:** Completed and merged in `d19c7e0c` ("Remove Phoenix & Notebooks — Phase 1: +> Decouple database shells to CloudShell (#2513)"). + +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 ✅ COMPLETED +> **Status:** Implemented on branch `users/jawelton/removenotebooks-phase2-061526`. Full +> verification sweep is green: `compile`, `compile:strict`, `lint` (0 errors), `format:check`, +> `test` (1945 passing), and `build:ci` (webpack) all pass. +> +> **Implementation notes / deviations:** +> - `src/Explorer/Panes/StringInputPane/` was also deleted — it became unused once +> `Explorer.renameNotebook` / `onCreateDirectory` were removed. +> - Removing notebook-only npm deps stripped several **phantom (transitively-hoisted)** +> packages that surviving code still imports, so these were re-added as explicit direct +> dependencies: `xterm@4.19.0` + `xterm-addon-fit@0.5.0` (CloudShell), `d3-collection@1.0.7` +> (Graph `D3ForceGraph`), and `@nteract/myths@0.1.9` (needed by kept `@nteract/core`). +> - Jest snapshots regenerated: `SettingsComponent`, `AddGlobalSecondaryIndexPanel`, +> `GitHubReposPanel` (all just dropped the removed `copyNotebook` function from the +> serialized Explorer); deleted-component snapshots removed with their dirs. +> - `CommandBarComponentButtonFactory` / `ContextMenuButtonFactory` required no notebook-button +> removal — they only contained shell entries (kept). + +Delete the notebook tabs, the nteract rendering engine, panes, the read-only viewer, and +Schema Analyzer (see decision below), and remove all UI entry points that open notebooks. + +> **Cross-phase coupling — revised approach (confirmed):** Several files the original plan +> deferred to later phases are hard-coupled to the deletions above and must be handled here: +> - **Schema Analyzer is pulled forward from Phase 3 into Phase 2.** `SchemaAnalyzer.tsx` +> renders through the nteract engine (`NotebookComponent/loadTransform`, +> `NotebookRenderer/outputs/SandboxOutputs`) and `SchemaAnalyzerTab extends NotebookTabBase`, +> so it cannot compile once the engine is deleted. +> - **`NotebookContentClient.ts` and `FileSystemUtil.ts` are deleted here** (they depend on +> the deleted `@nteract` content providers and only serve removed authoring paths). +> - **`NotebookManager.tsx` / `useNotebook.ts` get minimal edits** (full deletion stays in +> Phase 5): strip the content-provider / content-client / CopyNotebookPane / SchemaAnalyzer +> usages, keeping the GitHub / Juno / `NotebookContainerClient` wiring later phases need. +> - **`NotebookContentItem.ts` and `NotebookUtil.ts` are DEFERRED, not deleted here.** They +> are still used by the surviving `useNotebook` store and the "My Notebooks" tree +> (`ResourceTreeAdapter`), and `NotebookUtil.getName`/`isNotebookFile` are still used by the +> GitHub client/content provider. They are removed in Phase 4/5 with their consumers. + +- Delete (engine/content): `src/Explorer/Notebook/NotebookComponent/`, + `src/Explorer/Notebook/NotebookRenderer/`, `src/Explorer/Notebook/SecurityWarningBar/`, + `NotebookClientV2.ts`, `notebookClientV2.test.ts`, `NotebookContentClient.ts`, + `NTeractUtil.ts`, `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/inline plugins). +- Delete (Schema Analyzer, pulled forward): `src/Explorer/Notebook/SchemaAnalyzer/` and + `src/Explorer/Tabs/SchemaAnalyzerTab.ts`; remove its command-bar button and any tree/menu + entry points. +- Edit to decouple survivors: `NotebookManager.tsx` (see note above), `Explorer.tsx` + (remove `openNotebook*`, import/copy/rename/createDir/read/download/upload/delete/refresh + notebook methods + `notebookToImport`; KEEP `openNotebookTerminal` / + `connectToNotebookTerminal` and the Phoenix container methods for Phase 5), + `ResourceTreeAdapter.tsx` (drop `NotebookV2Tab` import + open/copy actions; neutralize node + click handlers — full "My Notebooks" tree removal remains Phase 5), `useTabs.ts` (drop + `NotebookTabV2`), `Contracts/ViewModels.ts` (fix notebook-tab interfaces that break compile; + keep `CollectionTabKind` enum values). +- Remove notebook entry points: "New Notebook"/open-notebook buttons in + `CommandBarComponentButtonFactory` (+ test), `OpenActions.tsx` (`OpenSampleNotebook`), + `ContextMenuButtonFactory.tsx`, splash-screen notebook cards & recent-notebook items + (`MostRecentActivity` `OpenNotebook` type). +- `tsconfig.strict.json`: remove entries for deleted files (keep `NotebookContentItem.ts`). +- Remove from `package.json` ONLY the notebook-only deps with **zero** remaining importers + after the deletions (e.g. `@jupyterlab/*`, `@phosphor/widgets`, and any `@nteract/*` not + still used). DEFER `@nteract/core`, `@nteract/commutable`, and `rx-jupyter` (still imported + by the kept `NotebookManager` / GitHub code) to Phase 4/5. +- Update/delete affected tests; regenerate Jest snapshots (`treeNodeUtil`, + `SettingsComponent`, `StringInputPane`). +- **Outcome:** Notebooks and Schema Analyzer can no longer be authored, opened, or rendered. + +### Phase 3 — (folded into Phase 2) +Schema Analyzer removal was pulled forward into Phase 2 because it is rendered by the nteract +engine deleted there. No separate work remains for this phase. + +### Phase 4 — Remove GitHub integration ✅ COMPLETED +> **Status:** Implemented on branch `users/jawelton/removenotebooks-phase3-062226`. Full +> verification sweep is green: `compile`, `compile:strict`, `lint` (0 errors), `format:check`, +> `test` (1900 passing), and `build:ci` (webpack) all pass. +> +> **Implementation notes / deviations:** +> - **Localization was a no-op:** there are **zero** GitHub keys in any locale +> `Resources.json`. The GitHub UI strings were hardcoded literals that were deleted with +> their components, so no resource-file edits were needed. +> - **`JunoClient` was trimmed, not deleted** (deletion stays in Phase 6). Removed only the +> GitHub-only members (`cachedPinnedRepos`, `subscribeToPinnedRepos`, `getPinnedRepos`, +> `updatePinnedRepos`, `deleteGitHubInfo`, `getGitHubToken`, `deleteAppAuthorization`, +> `getGitHubClientParams`, `IPinnedRepo`/`IPinnedBranch`) plus the now-orphaned private +> helpers `getNotebooksSubscriptionIdAccountUrl`/`getAccount`/`getSubscriptionId` and the +> `knockout` import. Kept the Schema (`requestSchema`/`getSchema`, used by `Database.tsx`) +> and gallery methods. Deleted `JunoClient.test.ts` (it only covered removed GitHub methods). +> - **`src/Utils/JunoUtils.ts` (+ test) was also deleted** — its `toPinnedRepo`/`toGitHubRepo` +> helpers only converted GitHub pinned-repo shapes and had no other consumers. +> - **`NotebookUtil.ts` (deferred to Phase 5) was edited here** to drop its `GitHubUtils` +> dependency: the dead `github://` content-URI branches in `getFilePath`/`getParentPath`/ +> `getName`/`replaceName` were removed (its `isNotebookFile` consumer in `ResourceTreeAdapter` +> is unaffected). `NotebookUtil.test.ts` lost its github-uri cases. +> - **`ConfigContext.ts`:** removed the GitHub-only config fields `GITHUB_CLIENT_ID`, +> `GITHUB_TEST_ENV_CLIENT_ID`, `GITHUB_CLIENT_SECRET` (their only consumers were deleted). +> - **Telemetry deferred (as planned):** the `Action.NotebooksGitHub*` enum members were left +> in `TelemetryConstants.ts` for the Phase 6 cleanup (enum-numbering-safe); only their +> now-deleted usages were removed. + +- 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 ✅ COMPLETED +> **Status:** Implemented on branch `users/jawelton/remove-notebooks-phase5-081426`. +> The automated verification sweep is green: `compile`, `compile:strict`, `lint`, +> `format:check`, `test` (1931 passing, 5 skipped), and `build:ci` (webpack) all pass. +> +> **Implementation notes / deviations:** +> - Removed the Phoenix-only connection status and memory tracker command-bar UI, which +> depended entirely on the deleted `useNotebook` container state. +> - Moved the unrelated `isSynapseLinkUpdating` flag from `useNotebook` into the existing +> `useCommandBar` Zustand store and made static command creation react to it. +> - Finished the deferred Phase 1 cleanup by replacing remaining mixed +> `isShellEnabled`/notebook availability checks with the `enableCloudShell` feature gate. +> The non-Phoenix Mongo fallback remains available when CloudShell is disabled. +> - Removed the remaining direct nteract/runtime dependencies with no surviving importers: +> `@nteract/commutable`, `@nteract/core`, `@nteract/fixtures`, `@nteract/myths`, and +> `rx-jupyter`. Added `@types/uuid` explicitly because surviving CloudShell/UserContext +> code had previously received UUID declarations through the removed transitive graph. +> - Preserved the newer Cosmos DB (NoSQL) Cloud Shell added after this roadmap was written; +> all terminal tabs still instantiate `CloudShellTerminalComponentAdapter`. +> - Residual notebook/Phoenix config, contracts, telemetry, localization, Juno, generated +> ARM clients, and images remain intentionally deferred to Phase 6. + +- Delete `src/Phoenix/`, `src/Explorer/Notebook/NotebookContainerClient.ts`, + `src/Explorer/Notebook/NotebookManager.tsx`, `src/Explorer/Notebook/useNotebook.ts`, + `src/Explorer/Notebook/NotebookContentItem.ts`, `src/Explorer/Notebook/NotebookUtil.ts` + (+ `NotebookUtil.test.ts`), `src/Utils/NotebookConfigurationUtils.ts`, + `src/hooks/useNotebookSnapshotStore.ts`. (`NotebookContentItem.ts` and `NotebookUtil.ts` + deletion was deferred from Phase 2 because the "My Notebooks" tree/store and the GitHub + client still referenced them; remove them here once those consumers are gone. If GitHub + was removed in Phase 4, `NotebookUtil`'s `getName`/`isNotebookFile` helpers were inlined + there.) +- 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, VCoreMongo, and Cosmos DB NoSQL 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. diff --git a/jest.config.js b/jest.config.js index d265f685d..8f6c5b0c9 100644 --- a/jest.config.js +++ b/jest.config.js @@ -70,7 +70,6 @@ module.exports = { moduleNameMapper: { "^.*[.](png|gif|less|css)$": "/mockModule", "(.*)$[.](svg)": "/mockModule/$1", - "@nteract/stateful-components/(.*)$": "/mockModule", "@fluentui/react/lib/(.*)$": "@fluentui/react/lib-commonjs/$1", // https://github.com/microsoft/fluentui/wiki/Version-8-release-notes "monaco-editor/(.*)$": "/__mocks__/monaco-editor", "^dnd-core$": "dnd-core/dist/cjs", diff --git a/package-lock.json b/package-lock.json index 06535b293..67e5ff124 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,35 +18,10 @@ "@babel/plugin-proposal-decorators": "7.12.12", "@fluentui/react": "8.119.0", "@fluentui/react-components": "9.54.2", - "@jupyterlab/services": "6.0.2", - "@jupyterlab/terminal": "3.6.8", "@microsoft/applicationinsights-web": "2.6.1", - "@nteract/commutable": "7.5.1", - "@nteract/connected-components": "6.8.2", - "@nteract/core": "15.1.9", - "@nteract/data-explorer": "8.2.12", - "@nteract/directory-listing": "2.0.6", - "@nteract/dropdown-menu": "1.0.1", - "@nteract/editor": "10.1.12", - "@nteract/fixtures": "2.3.0", - "@nteract/iron-icons": "1.0.0", - "@nteract/jupyter-widgets": "2.0.0", - "@nteract/logos": "1.0.0", "@nteract/markdown": "4.6.0", - "@nteract/monaco-editor": "3.2.2", - "@nteract/octicons": "2.0.0", - "@nteract/outputs": "3.0.9", - "@nteract/presentational-components": "3.4.12", - "@nteract/stateful-components": "1.7.0", - "@nteract/styles": "2.0.2", - "@nteract/transform-geojson": "5.1.8", - "@nteract/transform-model-debug": "5.0.1", - "@nteract/transform-plotly": "6.1.6", - "@nteract/transform-vdom": "4.0.11", - "@nteract/transform-vega": "7.0.6", "@octokit/request": "8.4.1", "@octokit/rest": "17.9.2", - "@phosphor/widgets": "1.9.3", "@testing-library/jest-dom": "6.4.6", "@types/lodash": "4.14.171", "@types/mkdirp": "1.0.1", @@ -60,10 +35,11 @@ "canvas": "3.2.1", "clean-webpack-plugin": "4.0.0", "clipboard-copy": "4.0.1", - "copy-webpack-plugin": "11.0.0", + "copy-webpack-plugin": "14.0.0", "crossroads": "0.12.2", "css-element-queries": "1.1.1", "d3": "7.9.0", + "d3-collection": "1.0.7", "datatables.net-colreorder-dt": "1.7.0", "datatables.net-dt": "1.13.8", "date-fns": "1.29.0", @@ -80,7 +56,7 @@ "i18next-resources-to-backend": "1.2.1", "iframe-resizer-react": "1.1.0", "immer": "9.0.6", - "immutable": "4.0.0-rc.12", + "immutable": "4.3.9", "is-ci": "2.0.0", "jquery": "3.7.1", "jquery-typeahead": "2.11.1", @@ -92,7 +68,7 @@ "mkdirp": "1.0.4", "monaco-editor": "0.44.0", "ms": "2.1.3", - "nanoid": "3.3.8", + "nanoid": "3.3.18", "p-retry": "6.2.1", "patch-package": "8.0.1", "plotly.js-cartesian-dist-min": "1.52.3", @@ -112,21 +88,22 @@ "react-window": "1.8.10", "react-youtube": "9.0.1", "reflect-metadata": "0.1.13", - "rx-jupyter": "5.5.12", - "shell-quote": "1.7.3", + "shell-quote": "1.10.0", "styled-components": "5.0.1", "swr": "0.4.0", - "terser-webpack-plugin": "5.3.9", + "terser-webpack-plugin": "5.6.1", "tinykeys": "2.1.0", "underscore": "1.13.8", "utility-types": "3.10.0", "uuid": "9.0.0", "web-vitals": "4.2.4", - "ws": "8.17.1", + "ws": "8.21.3", + "xterm": "4.19.0", + "xterm-addon-fit": "0.5.0", "zustand": "3.5.0" }, "devDependencies": { - "@babel/core": "7.29.0", + "@babel/core": "7.29.6", "@babel/preset-env": "7.24.7", "@babel/preset-react": "7.24.7", "@babel/preset-typescript": "7.24.7", @@ -144,7 +121,7 @@ "@types/hasher": "0.0.31", "@types/jest": "29.5.12", "@types/jquery": "3.5.29", - "@types/node": "18.19.0", + "@types/node": "22.14.1", "@types/post-robot": "10.0.1", "@types/q": "1.5.1", "@types/react": "17.0.44", @@ -157,13 +134,14 @@ "@types/sinon": "2.3.3", "@types/styled-components": "5.1.32", "@types/underscore": "1.7.36", + "@types/uuid": "9.0.8", "@types/youtube-player": "5.5.6", "@typescript-eslint/eslint-plugin": "6.7.4", "@typescript-eslint/parser": "6.7.4", "@webpack-cli/serve": "2.0.5", "babel-jest": "29.7.0", "babel-loader": "8.1.0", - "brace-expansion": "1.1.15", + "brace-expansion": "1.1.18", "buffer": "5.1.0", "case-sensitive-paths-webpack-plugin": "2.4.0", "create-file-webpack": "1.0.2", @@ -189,7 +167,7 @@ "jest-html-loader": "1.0.0", "jest-react-hooks-shallow": "1.5.1", "jest-trx-results-processor": "3.0.2", - "js-yaml": "3.14.2", + "js-yaml": "3.15.1", "less": "4.5.1", "less-loader": "11.1.3", "less-vars-loader": "1.1.0", @@ -213,8 +191,11 @@ "webpack": "5.104.1", "webpack-bundle-analyzer": "5.2.0", "webpack-cli": "5.1.4", - "webpack-dev-server": "5.2.3", - "ws": "8.17.1" + "webpack-dev-server": "5.2.5", + "ws": "8.21.3" + }, + "engines": { + "node": "22.x" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -640,11 +621,12 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", + "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -661,16 +643,17 @@ } }, "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "version": "7.29.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.6.tgz", + "integrity": "sha512-QdxmAo/ikZqqRGA8s43ww8lcql6naWRvEz0FFrl6MIlc7Gi6TroXnSdWa5U/kq6fzcpqpHesicQxFZIieZbyIA==", + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", + "@babel/generator": "^7.29.6", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", + "@babel/helpers": "^7.29.2", + "@babel/parser": "^7.29.3", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", @@ -703,12 +686,13 @@ } }, "node_modules/@babel/generator": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.0.tgz", - "integrity": "sha512-vSH118/wwM/pLR38g/Sgk05sNtro6TlTJKuiMXDaZqPUfjTFcudpCOt00IhOfj+1BFAX+UFAlzCU+6WXr3GLFQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -942,17 +926,19 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -980,23 +966,25 @@ } }, "node_modules/@babel/helpers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", - "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "license": "MIT", "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.29.0" + "@babel/types": "^7.29.7" }, "bin": { "parser": "bin/babel-parser.js" @@ -2476,13 +2464,14 @@ } }, "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -2506,12 +2495,13 @@ } }, "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -4451,13 +4441,6 @@ "react": ">=0.14.0" } }, - "node_modules/@icons/material": { - "version": "0.2.4", - "license": "MIT", - "peerDependencies": { - "react": "*" - } - }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -6281,471 +6264,12 @@ "version": "3.4.0", "license": "Apache-2.0" }, - "node_modules/@jupyterlab/apputils": { - "version": "3.6.8", - "resolved": "https://registry.npmjs.org/@jupyterlab/apputils/-/apputils-3.6.8.tgz", - "integrity": "sha512-8BxyDbpBFOD3vjlUEZc39xNPtnKMQIFzUHH2S1oLECT0jcHNK08wuymPsYBcbvGO2s3YI57z0iz+JIXUmMhdSg==", - "dependencies": { - "@jupyterlab/coreutils": "^5.6.8", - "@jupyterlab/observables": "^4.6.8", - "@jupyterlab/services": "^6.6.8", - "@jupyterlab/settingregistry": "^3.6.8", - "@jupyterlab/statedb": "^3.6.8", - "@jupyterlab/translation": "^3.6.8", - "@jupyterlab/ui-components": "^3.6.8", - "@lumino/algorithm": "^1.9.0", - "@lumino/commands": "^1.19.0", - "@lumino/coreutils": "^1.11.0", - "@lumino/disposable": "^1.10.0", - "@lumino/domutils": "^1.8.0", - "@lumino/messaging": "^1.10.0", - "@lumino/polling": "^1.9.0", - "@lumino/properties": "^1.8.0", - "@lumino/signaling": "^1.10.0", - "@lumino/virtualdom": "^1.14.0", - "@lumino/widgets": "^1.37.2", - "@types/react": "^17.0.0", - "react": "^17.0.1", - "react-dom": "^17.0.1", - "sanitize-html": "~2.7.3", - "url": "^0.11.0" - } - }, - "node_modules/@jupyterlab/apputils/node_modules/@jupyterlab/services": { - "version": "6.6.8", - "resolved": "https://registry.npmjs.org/@jupyterlab/services/-/services-6.6.8.tgz", - "integrity": "sha512-zuW6UvWeYcDK0QIIHyr2Cs7EXlXgfPhlMiuaIUD4CGMRQmWW9ATGeeyozOvNCaswIN/2ErDFITgF1SwH+dsmOw==", - "dependencies": { - "@jupyterlab/coreutils": "^5.6.8", - "@jupyterlab/nbformat": "^3.6.8", - "@jupyterlab/observables": "^4.6.8", - "@jupyterlab/settingregistry": "^3.6.8", - "@jupyterlab/statedb": "^3.6.8", - "@lumino/algorithm": "^1.9.0", - "@lumino/coreutils": "^1.11.0", - "@lumino/disposable": "^1.10.0", - "@lumino/polling": "^1.9.0", - "@lumino/signaling": "^1.10.0", - "node-fetch": "^2.6.0", - "ws": "^7.4.6" - } - }, - "node_modules/@jupyterlab/apputils/node_modules/react": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", - "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@jupyterlab/apputils/node_modules/react-dom": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", - "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "scheduler": "^0.20.2" - }, - "peerDependencies": { - "react": "17.0.2" - } - }, - "node_modules/@jupyterlab/apputils/node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/@jupyterlab/coreutils": { - "version": "5.6.8", - "resolved": "https://registry.npmjs.org/@jupyterlab/coreutils/-/coreutils-5.6.8.tgz", - "integrity": "sha512-lWrDeIJRkdmlBZ2Yw+wDLxQVjO8MWojOxsu+KcWQF0iuETGSoa4i+HNJKb9yv+JJSIbXHyBBrM0EAY7Z6XRb6w==", - "dependencies": { - "@lumino/coreutils": "^1.11.0", - "@lumino/disposable": "^1.10.0", - "@lumino/signaling": "^1.10.0", - "minimist": "~1.2.0", - "moment": "^2.24.0", - "path-browserify": "^1.0.0", - "url-parse": "~1.5.1" - } - }, - "node_modules/@jupyterlab/nbformat": { - "version": "3.6.8", - "resolved": "https://registry.npmjs.org/@jupyterlab/nbformat/-/nbformat-3.6.8.tgz", - "integrity": "sha512-HprEmm1jUJAMFlhFWZOWv8XITXUBK3hRn7kzRft5gdFZ2T7ClpDLMLunobOlJAQOP01H8GBm5Je27KIoukWgfg==", - "dependencies": { - "@lumino/coreutils": "^1.11.0" - } - }, - "node_modules/@jupyterlab/observables": { - "version": "4.6.8", - "resolved": "https://registry.npmjs.org/@jupyterlab/observables/-/observables-4.6.8.tgz", - "integrity": "sha512-Gcqw1iQd315mrs5sD02n6NcYVeH+TXD9G2K+MbEWmQ4WWqwhfMGC17BnuTZ8bjHGcKQ7wNjZJ8zpvLolCaJBvA==", - "dependencies": { - "@lumino/algorithm": "^1.9.0", - "@lumino/coreutils": "^1.11.0", - "@lumino/disposable": "^1.10.0", - "@lumino/messaging": "^1.10.0", - "@lumino/signaling": "^1.10.0" - } - }, - "node_modules/@jupyterlab/services": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@jupyterlab/services/-/services-6.0.2.tgz", - "integrity": "sha512-J0wghVeqMlG71VXkOhlh3ZcyG787wzz9hSFHMGu/1ATlIDtn9z8dbdCAn92rd12jD1nDX1L2KcyjH5pLT+CKeA==", - "dependencies": { - "@jupyterlab/coreutils": "^5.0.1", - "@jupyterlab/nbformat": "^3.0.1", - "@jupyterlab/observables": "^4.0.1", - "@jupyterlab/settingregistry": "^3.0.1", - "@jupyterlab/statedb": "^3.0.1", - "@lumino/algorithm": "^1.3.3", - "@lumino/coreutils": "^1.5.3", - "@lumino/disposable": "^1.4.3", - "@lumino/polling": "^1.3.3", - "@lumino/signaling": "^1.4.3", - "node-fetch": "^2.6.0", - "ws": "^7.2.0" - } - }, - "node_modules/@jupyterlab/services/node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/@jupyterlab/settingregistry": { - "version": "3.6.8", - "resolved": "https://registry.npmjs.org/@jupyterlab/settingregistry/-/settingregistry-3.6.8.tgz", - "integrity": "sha512-dwcsLWP8qy9N0cuGZJg3pW0oVRHXhD7blHMSgyszwlCpCD26H0C4DC3Udz0c0FMoiQ5lK/cv0u+TFSqyoMNtzA==", - "dependencies": { - "@jupyterlab/statedb": "^3.6.8", - "@lumino/commands": "^1.19.0", - "@lumino/coreutils": "^1.11.0", - "@lumino/disposable": "^1.10.0", - "@lumino/signaling": "^1.10.0", - "ajv": "^6.12.3", - "json5": "^2.1.1" - } - }, - "node_modules/@jupyterlab/statedb": { - "version": "3.6.8", - "resolved": "https://registry.npmjs.org/@jupyterlab/statedb/-/statedb-3.6.8.tgz", - "integrity": "sha512-5zeyUhKpgbKFZdx8NA9f9kzP8kin4w33CyJdm/MyFySezn5zBwFbF3zeYAtwRGy5QQYFXgc79jgTCWkjsamYSg==", - "dependencies": { - "@lumino/commands": "^1.19.0", - "@lumino/coreutils": "^1.11.0", - "@lumino/disposable": "^1.10.0", - "@lumino/properties": "^1.8.0", - "@lumino/signaling": "^1.10.0" - } - }, - "node_modules/@jupyterlab/terminal": { - "version": "3.6.8", - "resolved": "https://registry.npmjs.org/@jupyterlab/terminal/-/terminal-3.6.8.tgz", - "integrity": "sha512-eIHBS7qwm5WFW0zGCaYEaGQJu4hWlkF5PskQzhurYtWQF4SybvIZ8jo3+nPV3cc2zLanx+Wqc4bpnOTtlpRXPg==", - "dependencies": { - "@jupyterlab/apputils": "^3.6.8", - "@jupyterlab/services": "^6.6.8", - "@jupyterlab/translation": "^3.6.8", - "@lumino/coreutils": "^1.11.0", - "@lumino/domutils": "^1.8.0", - "@lumino/messaging": "^1.10.0", - "@lumino/widgets": "^1.37.2", - "xterm": "~4.19.0", - "xterm-addon-fit": "~0.5.0", - "xterm-addon-web-links": "~0.6.0" - } - }, - "node_modules/@jupyterlab/terminal/node_modules/@jupyterlab/services": { - "version": "6.6.8", - "resolved": "https://registry.npmjs.org/@jupyterlab/services/-/services-6.6.8.tgz", - "integrity": "sha512-zuW6UvWeYcDK0QIIHyr2Cs7EXlXgfPhlMiuaIUD4CGMRQmWW9ATGeeyozOvNCaswIN/2ErDFITgF1SwH+dsmOw==", - "dependencies": { - "@jupyterlab/coreutils": "^5.6.8", - "@jupyterlab/nbformat": "^3.6.8", - "@jupyterlab/observables": "^4.6.8", - "@jupyterlab/settingregistry": "^3.6.8", - "@jupyterlab/statedb": "^3.6.8", - "@lumino/algorithm": "^1.9.0", - "@lumino/coreutils": "^1.11.0", - "@lumino/disposable": "^1.10.0", - "@lumino/polling": "^1.9.0", - "@lumino/signaling": "^1.10.0", - "node-fetch": "^2.6.0", - "ws": "^7.4.6" - } - }, - "node_modules/@jupyterlab/terminal/node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/@jupyterlab/translation": { - "version": "3.6.8", - "resolved": "https://registry.npmjs.org/@jupyterlab/translation/-/translation-3.6.8.tgz", - "integrity": "sha512-tbnpu2jxCINJIK1GmzdfJCbDi6NQcSQA1sd2Or0dfmt1yJ6hIXfgyA+NHyAdSX8kc8ra8tNga+ZZfF3IaoC7wg==", - "dependencies": { - "@jupyterlab/coreutils": "^5.6.8", - "@jupyterlab/services": "^6.6.8", - "@jupyterlab/statedb": "^3.6.8", - "@lumino/coreutils": "^1.11.0" - } - }, - "node_modules/@jupyterlab/translation/node_modules/@jupyterlab/services": { - "version": "6.6.8", - "resolved": "https://registry.npmjs.org/@jupyterlab/services/-/services-6.6.8.tgz", - "integrity": "sha512-zuW6UvWeYcDK0QIIHyr2Cs7EXlXgfPhlMiuaIUD4CGMRQmWW9ATGeeyozOvNCaswIN/2ErDFITgF1SwH+dsmOw==", - "dependencies": { - "@jupyterlab/coreutils": "^5.6.8", - "@jupyterlab/nbformat": "^3.6.8", - "@jupyterlab/observables": "^4.6.8", - "@jupyterlab/settingregistry": "^3.6.8", - "@jupyterlab/statedb": "^3.6.8", - "@lumino/algorithm": "^1.9.0", - "@lumino/coreutils": "^1.11.0", - "@lumino/disposable": "^1.10.0", - "@lumino/polling": "^1.9.0", - "@lumino/signaling": "^1.10.0", - "node-fetch": "^2.6.0", - "ws": "^7.4.6" - } - }, - "node_modules/@jupyterlab/translation/node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/@jupyterlab/ui-components": { - "version": "3.6.8", - "resolved": "https://registry.npmjs.org/@jupyterlab/ui-components/-/ui-components-3.6.8.tgz", - "integrity": "sha512-qUComc04A08EJRtYGPnoNaQ3xJZEu6Jxupb+3G80V8eYVCrFm4wouUqzPoWGP/59spy/TKT5fTlezFhj7rDrGg==", - "dependencies": { - "@blueprintjs/core": "^3.36.0", - "@blueprintjs/select": "^3.15.0", - "@jupyterlab/coreutils": "^5.6.8", - "@jupyterlab/translation": "^3.6.8", - "@lumino/algorithm": "^1.9.0", - "@lumino/commands": "^1.19.0", - "@lumino/coreutils": "^1.11.0", - "@lumino/disposable": "^1.10.0", - "@lumino/signaling": "^1.10.0", - "@lumino/virtualdom": "^1.14.0", - "@lumino/widgets": "^1.37.2", - "@rjsf/core": "^3.1.0", - "react": "^17.0.1", - "react-dom": "^17.0.1", - "typestyle": "^2.0.4" - }, - "peerDependencies": { - "react": "^17.0.1" - } - }, - "node_modules/@jupyterlab/ui-components/node_modules/react": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", - "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@jupyterlab/ui-components/node_modules/react-dom": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", - "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "scheduler": "^0.20.2" - }, - "peerDependencies": { - "react": "17.0.2" - } - }, "node_modules/@leichtgewicht/ip-codec": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", "dev": true }, - "node_modules/@lumino/algorithm": { - "version": "1.9.2", - "license": "BSD-3-Clause" - }, - "node_modules/@lumino/collections": { - "version": "1.9.3", - "license": "BSD-3-Clause", - "dependencies": { - "@lumino/algorithm": "^1.9.2" - } - }, - "node_modules/@lumino/commands": { - "version": "1.21.1", - "license": "BSD-3-Clause", - "dependencies": { - "@lumino/algorithm": "^1.9.2", - "@lumino/coreutils": "^1.12.1", - "@lumino/disposable": "^1.10.4", - "@lumino/domutils": "^1.8.2", - "@lumino/keyboard": "^1.8.2", - "@lumino/signaling": "^1.11.1", - "@lumino/virtualdom": "^1.14.3" - } - }, - "node_modules/@lumino/coreutils": { - "version": "1.12.1", - "license": "BSD-3-Clause", - "peerDependencies": { - "crypto": "1.0.1" - } - }, - "node_modules/@lumino/disposable": { - "version": "1.10.4", - "license": "BSD-3-Clause", - "dependencies": { - "@lumino/algorithm": "^1.9.2", - "@lumino/signaling": "^1.11.1" - } - }, - "node_modules/@lumino/domutils": { - "version": "1.8.2", - "license": "BSD-3-Clause" - }, - "node_modules/@lumino/dragdrop": { - "version": "1.14.5", - "resolved": "https://registry.npmjs.org/@lumino/dragdrop/-/dragdrop-1.14.5.tgz", - "integrity": "sha512-LC5xB82+xGF8hFyl716TMpV32OIMIMl+s3RU1PaqDkD6B7PkgiVk6NkJ4X9/GcEvl2igkvlGQt/3L7qxDAJNxw==", - "dependencies": { - "@lumino/coreutils": "^1.12.1", - "@lumino/disposable": "^1.10.4" - } - }, - "node_modules/@lumino/keyboard": { - "version": "1.8.2", - "license": "BSD-3-Clause" - }, - "node_modules/@lumino/messaging": { - "version": "1.10.3", - "license": "BSD-3-Clause", - "dependencies": { - "@lumino/algorithm": "^1.9.2", - "@lumino/collections": "^1.9.3" - } - }, - "node_modules/@lumino/polling": { - "version": "1.11.4", - "license": "BSD-3-Clause", - "dependencies": { - "@lumino/coreutils": "^1.12.1", - "@lumino/disposable": "^1.10.4", - "@lumino/signaling": "^1.11.1" - } - }, - "node_modules/@lumino/properties": { - "version": "1.8.2", - "license": "BSD-3-Clause" - }, - "node_modules/@lumino/signaling": { - "version": "1.11.1", - "license": "BSD-3-Clause", - "dependencies": { - "@lumino/algorithm": "^1.9.2", - "@lumino/properties": "^1.8.2" - } - }, - "node_modules/@lumino/virtualdom": { - "version": "1.14.3", - "license": "BSD-3-Clause", - "dependencies": { - "@lumino/algorithm": "^1.9.2" - } - }, - "node_modules/@lumino/widgets": { - "version": "1.37.2", - "resolved": "https://registry.npmjs.org/@lumino/widgets/-/widgets-1.37.2.tgz", - "integrity": "sha512-NHKu1NBDo6ETBDoNrqSkornfUCwc8EFFzw6+LWBfYVxn2PIwciq2SdiJGEyNqL+0h/A9eVKb5ui5z4cwpRekmQ==", - "dependencies": { - "@lumino/algorithm": "^1.9.2", - "@lumino/commands": "^1.21.1", - "@lumino/coreutils": "^1.12.1", - "@lumino/disposable": "^1.10.4", - "@lumino/domutils": "^1.8.2", - "@lumino/dragdrop": "^1.14.5", - "@lumino/keyboard": "^1.8.2", - "@lumino/messaging": "^1.10.3", - "@lumino/properties": "^1.8.2", - "@lumino/signaling": "^1.11.1", - "@lumino/virtualdom": "^1.14.3" - } - }, "node_modules/@microsoft/applicationinsights-analytics-js": { "version": "2.6.1", "license": "MIT", @@ -6871,601 +6395,6 @@ "node": ">= 8" } }, - "node_modules/@nteract/actions": { - "version": "7.1.0", - "license": "BSD-3-Clause", - "dependencies": { - "@nteract/commutable": "^7.4.5", - "@nteract/messaging": "^7.0.19", - "@nteract/types": "^7.1.9", - "immutable": "^4.0.0-rc.12", - "rx-jupyter": "^5.5.21" - } - }, - "node_modules/@nteract/actions/node_modules/rx-jupyter": { - "version": "5.5.21", - "license": "BSD-3-Clause", - "dependencies": { - "@nteract/commutable": "^7.4.5", - "@nteract/messaging": "^7.0.19", - "@types/ungap__url-search-params": "^0.1.0", - "@ungap/url-search-params": "^0.2.0", - "js-cookie": "^2.2.0", - "rxjs": "^6.6.0", - "url-join": "^4.0.0" - } - }, - "node_modules/@nteract/any-vega": { - "version": "1.0.1", - "license": "MIT" - }, - "node_modules/@nteract/commutable": { - "version": "7.5.1", - "license": "BSD-3-Clause", - "dependencies": { - "immutable": "^4.0.0-rc.12", - "uuid": "^8.0.0" - } - }, - "node_modules/@nteract/commutable/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/@nteract/connected-components": { - "version": "6.8.2", - "license": "BSD-3-Clause", - "dependencies": { - "@blueprintjs/core": "^3.7.0", - "@nteract/commutable": "^7.3.2", - "@nteract/core": "^15.1.0", - "@nteract/types": "^7.1.0", - "react-redux": "^6.0.0", - "redux": "^4.0.0" - }, - "peerDependencies": { - "immutable": "^4.0.0-rc.12", - "react": "^16.3.2", - "styled-components": ">= 5.0.1" - } - }, - "node_modules/@nteract/connected-components/node_modules/react-is": { - "version": "16.13.1", - "license": "MIT" - }, - "node_modules/@nteract/connected-components/node_modules/react-redux": { - "version": "6.0.1", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.3.1", - "hoist-non-react-statics": "^3.3.0", - "invariant": "^2.2.4", - "loose-envify": "^1.4.0", - "prop-types": "^15.7.2", - "react-is": "^16.8.2" - }, - "peerDependencies": { - "react": "^16.4.0-0", - "redux": "^2.0.0 || ^3.0.0 || ^4.0.0-0" - } - }, - "node_modules/@nteract/core": { - "version": "15.1.9", - "resolved": "https://registry.npmjs.org/@nteract/core/-/core-15.1.9.tgz", - "integrity": "sha512-kQf+7d1Qi27aVOvu+q07pXw8dXFUsU7JJLZ3JeAzSxVizNE/PjbEPn9Ruc+AV/I/FqXZovGp7Q04AGr25hsuyA==", - "dependencies": { - "@nteract/actions": "^7.0.11", - "@nteract/commutable": "^7.4.5", - "@nteract/epics": "^5.0.11", - "@nteract/reducers": "^5.1.9", - "@nteract/selectors": "^3.1.9", - "@nteract/types": "^7.1.9", - "redux-logger": "^3.0.6" - }, - "peerDependencies": { - "immutable": "^4.0.0-rc.12" - } - }, - "node_modules/@nteract/data-explorer": { - "version": "8.2.12", - "resolved": "https://registry.npmjs.org/@nteract/data-explorer/-/data-explorer-8.2.12.tgz", - "integrity": "sha512-p88YyzJ6eOgrslJ8X9ILURlSDeyRz8j3VvLbbSGrsDKMmz7mpfLDqAZBuUHRzQZTZF9QalfTnKW+ajA0DX79BA==", - "dependencies": { - "@nteract/octicons": "^2.0.0", - "d3-collection": "^1.0.7", - "d3-scale": "^3.0.0", - "d3-shape": "^1.2.2", - "numeral": "^2.0.6", - "react-color": "^2.14.1", - "react-table": "6.11.5", - "react-table-hoc-fixed-columns": "2.1.3", - "semiotic": "^2.0.0-rc.8" - }, - "peerDependencies": { - "numeral": "^2.0.6", - "react": "^16.13.1", - "styled-components": ">= 4" - } - }, - "node_modules/@nteract/data-explorer/node_modules/d3-array": { - "version": "1.2.4", - "license": "BSD-3-Clause" - }, - "node_modules/@nteract/data-explorer/node_modules/d3-chord": { - "version": "1.0.6", - "license": "BSD-3-Clause", - "dependencies": { - "d3-array": "1", - "d3-path": "1" - } - }, - "node_modules/@nteract/data-explorer/node_modules/d3-contour": { - "version": "1.3.2", - "license": "BSD-3-Clause", - "dependencies": { - "d3-array": "^1.1.1" - } - }, - "node_modules/@nteract/data-explorer/node_modules/d3-dispatch": { - "version": "1.0.6", - "license": "BSD-3-Clause" - }, - "node_modules/@nteract/data-explorer/node_modules/d3-force": { - "version": "1.2.1", - "license": "BSD-3-Clause", - "dependencies": { - "d3-collection": "1", - "d3-dispatch": "1", - "d3-quadtree": "1", - "d3-timer": "1" - } - }, - "node_modules/@nteract/data-explorer/node_modules/d3-format": { - "version": "1.4.5", - "license": "BSD-3-Clause" - }, - "node_modules/@nteract/data-explorer/node_modules/d3-hierarchy": { - "version": "1.1.9", - "license": "BSD-3-Clause" - }, - "node_modules/@nteract/data-explorer/node_modules/d3-interpolate": { - "version": "1.4.0", - "license": "BSD-3-Clause", - "dependencies": { - "d3-color": "1" - } - }, - "node_modules/@nteract/data-explorer/node_modules/d3-path": { - "version": "1.0.9", - "license": "BSD-3-Clause" - }, - "node_modules/@nteract/data-explorer/node_modules/d3-polygon": { - "version": "1.0.6", - "license": "BSD-3-Clause" - }, - "node_modules/@nteract/data-explorer/node_modules/d3-quadtree": { - "version": "1.0.7", - "license": "BSD-3-Clause" - }, - "node_modules/@nteract/data-explorer/node_modules/d3-scale": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.3.0.tgz", - "integrity": "sha512-1JGp44NQCt5d1g+Yy+GeOnZP7xHo0ii8zsQp6PGzd+C1/dl0KGsp9A7Mxwp+1D1o4unbTTxVdU/ZOIEBoeZPbQ==", - "dependencies": { - "d3-array": "^2.3.0", - "d3-format": "1 - 2", - "d3-interpolate": "1.2.0 - 2", - "d3-time": "^2.1.1", - "d3-time-format": "2 - 3" - } - }, - "node_modules/@nteract/data-explorer/node_modules/d3-scale/node_modules/d3-array": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", - "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", - "dependencies": { - "internmap": "^1.0.0" - } - }, - "node_modules/@nteract/data-explorer/node_modules/d3-scale/node_modules/d3-time": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-2.1.1.tgz", - "integrity": "sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ==", - "dependencies": { - "d3-array": "2" - } - }, - "node_modules/@nteract/data-explorer/node_modules/d3-time": { - "version": "1.1.0", - "license": "BSD-3-Clause" - }, - "node_modules/@nteract/data-explorer/node_modules/d3-time-format": { - "version": "2.3.0", - "license": "BSD-3-Clause", - "dependencies": { - "d3-time": "1" - } - }, - "node_modules/@nteract/data-explorer/node_modules/d3-timer": { - "version": "1.0.10", - "license": "BSD-3-Clause" - }, - "node_modules/@nteract/data-explorer/node_modules/dequal": { - "version": "2.0.3", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/@nteract/data-explorer/node_modules/react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", - "license": "MIT", - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" - }, - "peerDependencies": { - "react": "^18.3.1" - } - }, - "node_modules/@nteract/data-explorer/node_modules/scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0" - } - }, - "node_modules/@nteract/data-explorer/node_modules/semiotic": { - "version": "2.0.1", - "license": "Apache-2.0", - "dependencies": { - "d3-array": "^1.2.0", - "d3-brush": "^2.1.0", - "d3-chord": "^1.0.4", - "d3-collection": "^1.0.1", - "d3-contour": "^1.1.1", - "d3-force": "^1.0.2", - "d3-glyphedge": "^1.2.0", - "d3-hexbin": "^0.2.2", - "d3-hierarchy": "^1.1.3", - "d3-interpolate": "^1.1.5", - "d3-path-arrows": "^0.4.0", - "d3-polygon": "^1.0.5", - "d3-sankey-circular": "0.25.0", - "d3-scale": "^1.0.3", - "d3-selection": "2", - "d3-shape": "^1.3.5", - "d3-voronoi": "^1.0.2", - "dequal": "^2.0.3", - "labella": "1.1.4", - "memoize-one": "^5.1.1", - "object-assign": "4.1.1", - "polygon-offset": "0.3.1", - "react-annotation": "3.0.0-beta.4", - "regression": "^2.0.1", - "svg-path-bounding-box": "1.0.4" - }, - "peerDependencies": { - "react": "^18.1.0", - "react-dom": "^18.1.0" - } - }, - "node_modules/@nteract/data-explorer/node_modules/semiotic/node_modules/d3-scale": { - "version": "1.0.7", - "license": "BSD-3-Clause", - "dependencies": { - "d3-array": "^1.2.0", - "d3-collection": "1", - "d3-color": "1", - "d3-format": "1", - "d3-interpolate": "1", - "d3-time": "1", - "d3-time-format": "2" - } - }, - "node_modules/@nteract/directory-listing": { - "version": "2.0.6", - "license": "BSD-3-Clause", - "dependencies": { - "@nteract/octicons": "^2.0.0", - "react-hot-loader": "^4.1.2", - "react-timeago": "^4.1.9", - "styled-components": "^4.1.3" - }, - "peerDependencies": { - "react": "^16.3.2" - } - }, - "node_modules/@nteract/directory-listing/node_modules/css-to-react-native": { - "version": "2.3.2", - "license": "MIT", - "dependencies": { - "camelize": "^1.0.0", - "css-color-keywords": "^1.0.0", - "postcss-value-parser": "^3.3.0" - } - }, - "node_modules/@nteract/directory-listing/node_modules/postcss-value-parser": { - "version": "3.3.1", - "license": "MIT" - }, - "node_modules/@nteract/directory-listing/node_modules/react-is": { - "version": "16.13.1", - "license": "MIT" - }, - "node_modules/@nteract/directory-listing/node_modules/styled-components": { - "version": "4.4.1", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.0.0", - "@babel/traverse": "^7.0.0", - "@emotion/is-prop-valid": "^0.8.1", - "@emotion/unitless": "^0.7.0", - "babel-plugin-styled-components": ">= 1", - "css-to-react-native": "^2.2.2", - "memoize-one": "^5.0.0", - "merge-anything": "^2.2.4", - "prop-types": "^15.5.4", - "react-is": "^16.6.0", - "stylis": "^3.5.0", - "stylis-rule-sheet": "^0.0.10", - "supports-color": "^5.5.0" - }, - "peerDependencies": { - "react": ">= 16.3.0", - "react-dom": ">= 16.3.0" - } - }, - "node_modules/@nteract/directory-listing/node_modules/stylis": { - "version": "3.5.4", - "license": "MIT" - }, - "node_modules/@nteract/directory-listing/node_modules/stylis-rule-sheet": { - "version": "0.0.10", - "license": "MIT", - "peerDependencies": { - "stylis": "^3.5.0" - } - }, - "node_modules/@nteract/dropdown-menu": { - "version": "1.0.1", - "license": "BSD-3-Clause", - "dependencies": { - "react-hot-loader": "^4.1.2", - "styled-components": "^4.1.3" - }, - "peerDependencies": { - "react": "^16.3.2", - "react-dom": "^16.3.2" - } - }, - "node_modules/@nteract/dropdown-menu/node_modules/css-to-react-native": { - "version": "2.3.2", - "license": "MIT", - "dependencies": { - "camelize": "^1.0.0", - "css-color-keywords": "^1.0.0", - "postcss-value-parser": "^3.3.0" - } - }, - "node_modules/@nteract/dropdown-menu/node_modules/postcss-value-parser": { - "version": "3.3.1", - "license": "MIT" - }, - "node_modules/@nteract/dropdown-menu/node_modules/react-is": { - "version": "16.13.1", - "license": "MIT" - }, - "node_modules/@nteract/dropdown-menu/node_modules/styled-components": { - "version": "4.4.1", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.0.0", - "@babel/traverse": "^7.0.0", - "@emotion/is-prop-valid": "^0.8.1", - "@emotion/unitless": "^0.7.0", - "babel-plugin-styled-components": ">= 1", - "css-to-react-native": "^2.2.2", - "memoize-one": "^5.0.0", - "merge-anything": "^2.2.4", - "prop-types": "^15.5.4", - "react-is": "^16.6.0", - "stylis": "^3.5.0", - "stylis-rule-sheet": "^0.0.10", - "supports-color": "^5.5.0" - }, - "peerDependencies": { - "react": ">= 16.3.0", - "react-dom": ">= 16.3.0" - } - }, - "node_modules/@nteract/dropdown-menu/node_modules/stylis": { - "version": "3.5.4", - "license": "MIT" - }, - "node_modules/@nteract/dropdown-menu/node_modules/stylis-rule-sheet": { - "version": "0.0.10", - "license": "MIT", - "peerDependencies": { - "stylis": "^3.5.0" - } - }, - "node_modules/@nteract/editor": { - "version": "10.1.12", - "license": "BSD-3-Clause", - "dependencies": { - "@nteract/messaging": "^7.0.19", - "@nteract/outputs": "^3.0.11", - "codemirror": "5.61.1", - "rxjs": "^6.3.3" - }, - "peerDependencies": { - "immutable": "^4.0.0-rc.12", - "react": "^16.3.2", - "react-dom": "^16.3.2", - "styled-components": ">= 5.0.1" - } - }, - "node_modules/@nteract/editor/node_modules/@nteract/outputs": { - "version": "3.0.11", - "license": "BSD-3-Clause", - "dependencies": { - "@nteract/markdown": "^4.5.2", - "@nteract/mathjax": "^4.0.11", - "ansi-to-react": "^6.0.5", - "react-json-tree": "^0.12.1" - }, - "peerDependencies": { - "react": "^16.3.2", - "styled-components": ">= 4" - } - }, - "node_modules/@nteract/editor/node_modules/react-base16-styling": { - "version": "0.7.0", - "license": "MIT", - "dependencies": { - "base16": "^1.0.0", - "lodash.curry": "^4.1.1", - "lodash.flow": "^3.5.0", - "pure-color": "^1.3.0" - } - }, - "node_modules/@nteract/editor/node_modules/react-json-tree": { - "version": "0.12.1", - "license": "MIT", - "dependencies": { - "prop-types": "^15.7.2", - "react-base16-styling": "^0.7.0" - }, - "peerDependencies": { - "react": "^16.3.0", - "react-dom": "^16.3.0" - } - }, - "node_modules/@nteract/epics": { - "version": "5.1.0", - "license": "BSD-3-Clause", - "dependencies": { - "@nteract/actions": "^7.0.11", - "@nteract/commutable": "^7.4.5", - "@nteract/messaging": "^7.0.19", - "@nteract/mythic-configuration": "^1.0.11", - "@nteract/mythic-notifications": "^0.2.11", - "@nteract/selectors": "^3.1.9", - "@nteract/types": "^7.1.9", - "file-saver": "^2.0.0", - "redux": "^4.0.1", - "redux-observable": "^2.0.0-alpha.0", - "rx-jupyter": "^5.5.21", - "rxjs": "^6.3.3" - } - }, - "node_modules/@nteract/epics/node_modules/rx-jupyter": { - "version": "5.5.21", - "license": "BSD-3-Clause", - "dependencies": { - "@nteract/commutable": "^7.4.5", - "@nteract/messaging": "^7.0.19", - "@types/ungap__url-search-params": "^0.1.0", - "@ungap/url-search-params": "^0.2.0", - "js-cookie": "^2.2.0", - "rxjs": "^6.6.0", - "url-join": "^4.0.0" - } - }, - "node_modules/@nteract/fixtures": { - "version": "2.3.0", - "license": "BSD-3-Clause", - "dependencies": { - "@nteract/commutable": "^7.2.6", - "@nteract/reducers": "^3.1.0", - "@nteract/types": "^6.0.0" - } - }, - "node_modules/@nteract/fixtures/node_modules/@nteract/actions": { - "version": "5.0.1", - "license": "BSD-3-Clause", - "dependencies": { - "@nteract/commutable": "^7.2.11", - "@nteract/messaging": "^7.0.6", - "@nteract/types": "^6.0.6", - "immutable": "^4.0.0-rc.12", - "rx-jupyter": "^5.5.8" - } - }, - "node_modules/@nteract/fixtures/node_modules/@nteract/reducers": { - "version": "3.2.1", - "license": "BSD-3-Clause", - "dependencies": { - "@nteract/actions": "^5.0.1", - "@nteract/commutable": "^7.2.11", - "@nteract/types": "^6.0.6", - "escape-carriage": "^1.3.0", - "immutable": "^4.0.0-rc.12", - "lodash.has": "^4.5.2", - "redux": "^4.0.1", - "redux-immutable": "^4.0.0", - "uuid": "^7.0.0" - } - }, - "node_modules/@nteract/fixtures/node_modules/@nteract/reducers/node_modules/uuid": { - "version": "7.0.3", - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/@nteract/fixtures/node_modules/@nteract/types": { - "version": "6.0.7", - "license": "MIT", - "dependencies": { - "@nteract/commutable": "^7.2.12", - "immutable": "^4.0.0-rc.12", - "rxjs": "^6.3.3", - "uuid": "^8.0.0" - } - }, - "node_modules/@nteract/fixtures/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/@nteract/iron-icons": { - "version": "1.0.0", - "license": "BSD-3-Clause", - "peerDependencies": { - "react": "^16.3.2" - } - }, - "node_modules/@nteract/jupyter-widgets": { - "version": "2.0.0", - "license": "BSD-3-Clause", - "peerDependencies": { - "react": "^16.3.2" - } - }, - "node_modules/@nteract/logos": { - "version": "1.0.0", - "license": "BSD-3-Clause", - "peerDependencies": { - "react": "^16.3.2", - "react-dom": "^16.3.2" - } - }, "node_modules/@nteract/markdown": { "version": "4.6.0", "license": "BSD-3-Clause", @@ -7490,322 +6419,6 @@ "react-dom": "^16.13.0 || ^17.0.0" } }, - "node_modules/@nteract/messaging": { - "version": "7.0.20", - "license": "BSD-3-Clause", - "dependencies": { - "@nteract/types": "^7.1.9", - "@types/uuid": "^8.0.0", - "lodash.clonedeep": "^4.5.0", - "rxjs": "^6.6.0", - "uuid": "^8.0.0" - } - }, - "node_modules/@nteract/messaging/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/@nteract/monaco-editor": { - "version": "3.2.2", - "license": "BSD-3-Clause", - "dependencies": { - "@nteract/core": "^14.0.0", - "@nteract/messaging": "^7.0.12", - "lodash.debounce": "^4.0.6", - "monaco-editor": "0.18.1", - "rxjs": "^6.3.3" - }, - "peerDependencies": { - "immutable": "^4.0.0-rc.12", - "react": "^16.3.2", - "react-dom": "^16.3.2", - "styled-components": ">= 4" - } - }, - "node_modules/@nteract/monaco-editor/node_modules/@nteract/actions": { - "version": "6.0.0", - "license": "BSD-3-Clause", - "dependencies": { - "@nteract/commutable": "^7.2.12", - "@nteract/messaging": "^7.0.7", - "@nteract/types": "^6.0.7", - "immutable": "^4.0.0-rc.12", - "rx-jupyter": "^5.5.9" - } - }, - "node_modules/@nteract/monaco-editor/node_modules/@nteract/core": { - "version": "14.0.0", - "license": "BSD-3-Clause", - "dependencies": { - "@nteract/actions": "^6.0.0", - "@nteract/commutable": "^7.2.12", - "@nteract/epics": "^4.0.9", - "@nteract/reducers": "^4.0.0", - "@nteract/selectors": "^2.8.10", - "@nteract/types": "^6.0.7", - "redux-logger": "^3.0.6" - }, - "peerDependencies": { - "immutable": "^4.0.0-rc.12" - } - }, - "node_modules/@nteract/monaco-editor/node_modules/@nteract/epics": { - "version": "4.0.9", - "license": "BSD-3-Clause", - "dependencies": { - "@nteract/actions": "^6.0.0", - "@nteract/commutable": "^7.2.12", - "@nteract/messaging": "^7.0.7", - "@nteract/mythic-notifications": "^0.1.9", - "@nteract/selectors": "^2.8.10", - "@nteract/types": "^6.0.7", - "file-saver": "^2.0.0", - "redux": "^4.0.1", - "redux-observable": "^2.0.0-alpha.0", - "rx-jupyter": "^5.5.9", - "rxjs": "^6.3.3" - } - }, - "node_modules/@nteract/monaco-editor/node_modules/@nteract/mythic-notifications": { - "version": "0.1.9", - "license": "BSD-3-Clause", - "dependencies": { - "@blueprintjs/core": "^3.7.0", - "@nteract/myths": "^0.1.9" - }, - "peerDependencies": { - "immutable": "^4.0.0-rc.12", - "react": "^16.3.2", - "react-dom": "^16.3.2", - "styled-components": ">= 4" - } - }, - "node_modules/@nteract/monaco-editor/node_modules/@nteract/reducers": { - "version": "4.0.0", - "license": "BSD-3-Clause", - "dependencies": { - "@nteract/actions": "^6.0.0", - "@nteract/commutable": "^7.2.12", - "@nteract/types": "^6.0.7", - "escape-carriage": "^1.3.0", - "immutable": "^4.0.0-rc.12", - "lodash.has": "^4.5.2", - "redux": "^4.0.1", - "redux-immutable": "^4.0.0", - "uuid": "^8.0.0" - } - }, - "node_modules/@nteract/monaco-editor/node_modules/@nteract/selectors": { - "version": "2.8.10", - "license": "BSD-3-Clause", - "dependencies": { - "@nteract/commutable": "^7.2.12", - "@nteract/types": "^6.0.7", - "immutable": "^4.0.0-rc.12", - "reselect": "^4.0.0", - "rx-jupyter": "^5.5.9" - } - }, - "node_modules/@nteract/monaco-editor/node_modules/@nteract/types": { - "version": "6.0.7", - "license": "MIT", - "dependencies": { - "@nteract/commutable": "^7.2.12", - "immutable": "^4.0.0-rc.12", - "rxjs": "^6.3.3", - "uuid": "^8.0.0" - } - }, - "node_modules/@nteract/monaco-editor/node_modules/monaco-editor": { - "version": "0.18.1", - "license": "MIT" - }, - "node_modules/@nteract/monaco-editor/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/@nteract/mythic-configuration": { - "version": "1.0.12", - "license": "BSD-3-Clause", - "dependencies": { - "@nteract/myths": "^0.2.11", - "fs-observable": "^4.1.14" - } - }, - "node_modules/@nteract/mythic-configuration/node_modules/@nteract/myths": { - "version": "0.2.13", - "license": "BSD-3-Clause", - "dependencies": { - "redux": "^4.0.0", - "redux-observable": "^2.0.0-alpha.0", - "rxjs": "^6.3.3" - }, - "peerDependencies": { - "immutable": "^4.0.0-rc.12", - "react": "^16.3.2", - "react-dom": "^16.3.2", - "react-redux": "^7.2.0" - } - }, - "node_modules/@nteract/mythic-configuration/node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "license": "MIT", - "peer": true - }, - "node_modules/@nteract/mythic-configuration/node_modules/react-redux": { - "version": "7.2.9", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz", - "integrity": "sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/runtime": "^7.15.4", - "@types/react-redux": "^7.1.20", - "hoist-non-react-statics": "^3.3.2", - "loose-envify": "^1.4.0", - "prop-types": "^15.7.2", - "react-is": "^17.0.2" - }, - "peerDependencies": { - "react": "^16.8.3 || ^17 || ^18" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - }, - "react-native": { - "optional": true - } - } - }, - "node_modules/@nteract/mythic-notifications": { - "version": "0.2.11", - "license": "BSD-3-Clause", - "dependencies": { - "@blueprintjs/core": "^3.7.0", - "@nteract/myths": "^0.2.11" - }, - "peerDependencies": { - "immutable": "^4.0.0-rc.12", - "react": "^16.3.2", - "react-dom": "^16.3.2", - "styled-components": ">= 5.0.1" - } - }, - "node_modules/@nteract/mythic-notifications/node_modules/@nteract/myths": { - "version": "0.2.13", - "license": "BSD-3-Clause", - "dependencies": { - "redux": "^4.0.0", - "redux-observable": "^2.0.0-alpha.0", - "rxjs": "^6.3.3" - }, - "peerDependencies": { - "immutable": "^4.0.0-rc.12", - "react": "^16.3.2", - "react-dom": "^16.3.2", - "react-redux": "^7.2.0" - } - }, - "node_modules/@nteract/mythic-notifications/node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "license": "MIT", - "peer": true - }, - "node_modules/@nteract/mythic-notifications/node_modules/react-redux": { - "version": "7.2.9", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz", - "integrity": "sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/runtime": "^7.15.4", - "@types/react-redux": "^7.1.20", - "hoist-non-react-statics": "^3.3.2", - "loose-envify": "^1.4.0", - "prop-types": "^15.7.2", - "react-is": "^17.0.2" - }, - "peerDependencies": { - "react": "^16.8.3 || ^17 || ^18" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - }, - "react-native": { - "optional": true - } - } - }, - "node_modules/@nteract/myths": { - "version": "0.1.9", - "license": "BSD-3-Clause", - "dependencies": { - "react-redux": "^6.0.0", - "redux": "^4.0.0", - "redux-observable": "^2.0.0-alpha.0", - "rxjs": "^6.3.3" - }, - "peerDependencies": { - "immutable": "^4.0.0-rc.12", - "react": "^16.3.2", - "react-dom": "^16.3.2" - } - }, - "node_modules/@nteract/myths/node_modules/react-is": { - "version": "16.13.1", - "license": "MIT" - }, - "node_modules/@nteract/myths/node_modules/react-redux": { - "version": "6.0.1", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.3.1", - "hoist-non-react-statics": "^3.3.0", - "invariant": "^2.2.4", - "loose-envify": "^1.4.0", - "prop-types": "^15.7.2", - "react-is": "^16.8.2" - }, - "peerDependencies": { - "react": "^16.4.0-0", - "redux": "^2.0.0 || ^3.0.0 || ^4.0.0-0" - } - }, - "node_modules/@nteract/octicons": { - "version": "2.0.0", - "license": "BSD-3-Clause", - "peerDependencies": { - "react": "^16.3.2" - } - }, - "node_modules/@nteract/outputs": { - "version": "3.0.9", - "license": "BSD-3-Clause", - "dependencies": { - "@nteract/markdown": "^4.3.10-alpha.0", - "@nteract/mathjax": "^4.0.7", - "ansi-to-react": "^6.0.5", - "react-json-tree": "^0.11.0" - }, - "peerDependencies": { - "react": "^16.3.2", - "styled-components": ">= 4" - } - }, "node_modules/@nteract/presentational-components": { "version": "3.4.12", "resolved": "https://registry.npmjs.org/@nteract/presentational-components/-/presentational-components-3.4.12.tgz", @@ -7823,160 +6436,6 @@ "styled-components": ">= 5.0.1" } }, - "node_modules/@nteract/reducers": { - "version": "5.1.13", - "license": "BSD-3-Clause", - "dependencies": { - "@nteract/actions": "^7.0.11", - "@nteract/commutable": "^7.4.5", - "@nteract/types": "^7.1.9", - "escape-carriage": "^1.3.0", - "immutable": "^4.0.0-rc.12", - "lodash.has": "^4.5.2", - "redux": "^4.0.1", - "redux-immutable": "^4.0.0", - "uuid": "^8.0.0" - } - }, - "node_modules/@nteract/reducers/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/@nteract/selectors": { - "version": "3.2.0", - "license": "BSD-3-Clause", - "dependencies": { - "@nteract/commutable": "^7.4.5", - "@nteract/types": "^7.1.9", - "immutable": "^4.0.0-rc.12", - "reselect": "^4.0.0", - "rx-jupyter": "^5.5.21" - } - }, - "node_modules/@nteract/selectors/node_modules/rx-jupyter": { - "version": "5.5.21", - "license": "BSD-3-Clause", - "dependencies": { - "@nteract/commutable": "^7.4.5", - "@nteract/messaging": "^7.0.19", - "@types/ungap__url-search-params": "^0.1.0", - "@ungap/url-search-params": "^0.2.0", - "js-cookie": "^2.2.0", - "rxjs": "^6.6.0", - "url-join": "^4.0.0" - } - }, - "node_modules/@nteract/stateful-components": { - "version": "1.7.0", - "license": "BSD-3-Clause", - "dependencies": { - "@nteract/commutable": "^7.3.2", - "@nteract/core": "^15.1.0", - "@nteract/fixtures": "^2.3.10", - "@nteract/markdown": "4.5.1", - "@nteract/mythic-configuration": "^1.0.2", - "@nteract/outputs": "^3.0.9", - "@nteract/presentational-components": "^3.4.2", - "immutable": "^4.0.0-rc.12", - "redux": "^4.0.4" - } - }, - "node_modules/@nteract/stateful-components/node_modules/@nteract/fixtures": { - "version": "2.3.19", - "license": "BSD-3-Clause", - "dependencies": { - "@nteract/commutable": "^7.4.5", - "@nteract/reducers": "^5.1.9", - "@nteract/types": "^7.1.9" - } - }, - "node_modules/@nteract/stateful-components/node_modules/@nteract/markdown": { - "version": "4.5.1", - "license": "BSD-3-Clause", - "dependencies": { - "@nteract/mathjax": "^4.0.7", - "@nteract/presentational-components": "^3.3.11", - "react-markdown": "^4.0.0" - }, - "peerDependencies": { - "react": "^16.11.0", - "react-dom": "^16.3.2" - } - }, - "node_modules/@nteract/styles": { - "version": "2.0.2", - "license": "BSD-3-Clause" - }, - "node_modules/@nteract/transform-geojson": { - "version": "5.1.8", - "license": "BSD-3-Clause", - "dependencies": { - "leaflet": "^1.3.4" - }, - "peerDependencies": { - "react": "^16.3.2" - } - }, - "node_modules/@nteract/transform-model-debug": { - "version": "5.0.1", - "license": "BSD-3-Clause", - "peerDependencies": { - "react": "^16.3.2" - } - }, - "node_modules/@nteract/transform-plotly": { - "version": "6.1.6", - "license": "BSD-3-Clause", - "dependencies": { - "lodash.clonedeep": "^4.5.0", - "plotly.js-dist": "^1.51.3" - }, - "peerDependencies": { - "react": "^16.3.2" - } - }, - "node_modules/@nteract/transform-vdom": { - "version": "4.0.11", - "license": "BSD-3-Clause", - "dependencies": { - "lodash.clonedeep": "^4.5.0" - }, - "peerDependencies": { - "react": "^16.3.2" - } - }, - "node_modules/@nteract/transform-vega": { - "version": "7.0.6", - "license": "BSD-3-Clause", - "dependencies": { - "@nteract/any-vega": "^1.0.1" - }, - "peerDependencies": { - "react": "^16.3.2" - } - }, - "node_modules/@nteract/types": { - "version": "7.1.9", - "license": "MIT", - "dependencies": { - "@nteract/commutable": "^7.4.5", - "immutable": "^4.0.0-rc.12", - "rxjs": "^6.6.0", - "uuid": "^8.0.0" - } - }, - "node_modules/@nteract/types/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/@octokit/auth-token": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", @@ -8492,100 +6951,6 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true }, - "node_modules/@phosphor/algorithm": { - "version": "1.2.0", - "license": "BSD-3-Clause" - }, - "node_modules/@phosphor/collections": { - "version": "1.2.0", - "license": "BSD-3-Clause", - "dependencies": { - "@phosphor/algorithm": "^1.2.0" - } - }, - "node_modules/@phosphor/commands": { - "version": "1.7.2", - "license": "BSD-3-Clause", - "dependencies": { - "@phosphor/algorithm": "^1.2.0", - "@phosphor/coreutils": "^1.3.1", - "@phosphor/disposable": "^1.3.1", - "@phosphor/domutils": "^1.1.4", - "@phosphor/keyboard": "^1.1.3", - "@phosphor/signaling": "^1.3.1" - } - }, - "node_modules/@phosphor/coreutils": { - "version": "1.3.1", - "license": "BSD-3-Clause" - }, - "node_modules/@phosphor/disposable": { - "version": "1.3.1", - "license": "BSD-3-Clause", - "dependencies": { - "@phosphor/algorithm": "^1.2.0", - "@phosphor/signaling": "^1.3.1" - } - }, - "node_modules/@phosphor/domutils": { - "version": "1.1.4", - "license": "BSD-3-Clause" - }, - "node_modules/@phosphor/dragdrop": { - "version": "1.4.1", - "license": "BSD-3-Clause", - "dependencies": { - "@phosphor/coreutils": "^1.3.1", - "@phosphor/disposable": "^1.3.1" - } - }, - "node_modules/@phosphor/keyboard": { - "version": "1.1.3", - "license": "BSD-3-Clause" - }, - "node_modules/@phosphor/messaging": { - "version": "1.3.0", - "license": "BSD-3-Clause", - "dependencies": { - "@phosphor/algorithm": "^1.2.0", - "@phosphor/collections": "^1.2.0" - } - }, - "node_modules/@phosphor/properties": { - "version": "1.1.3", - "license": "BSD-3-Clause" - }, - "node_modules/@phosphor/signaling": { - "version": "1.3.1", - "license": "BSD-3-Clause", - "dependencies": { - "@phosphor/algorithm": "^1.2.0" - } - }, - "node_modules/@phosphor/virtualdom": { - "version": "1.2.0", - "license": "BSD-3-Clause", - "dependencies": { - "@phosphor/algorithm": "^1.2.0" - } - }, - "node_modules/@phosphor/widgets": { - "version": "1.9.3", - "license": "BSD-3-Clause", - "dependencies": { - "@phosphor/algorithm": "^1.2.0", - "@phosphor/commands": "^1.7.2", - "@phosphor/coreutils": "^1.3.1", - "@phosphor/disposable": "^1.3.1", - "@phosphor/domutils": "^1.1.4", - "@phosphor/dragdrop": "^1.4.1", - "@phosphor/keyboard": "^1.1.3", - "@phosphor/messaging": "^1.3.0", - "@phosphor/properties": "^1.1.3", - "@phosphor/signaling": "^1.3.1", - "@phosphor/virtualdom": "^1.2.0" - } - }, "node_modules/@playwright/test": { "version": "1.59.1", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.59.1.tgz", @@ -8619,33 +6984,6 @@ "version": "2.0.0", "license": "MIT" }, - "node_modules/@rjsf/core": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@rjsf/core/-/core-3.2.1.tgz", - "integrity": "sha512-dk8ihvxFbcuIwU7G+HiJbFgwyIvaumPt5g5zfnuC26mwTUPlaDGFXKK2yITp8tJ3+hcwS5zEXtAN9wUkfuM4jA==", - "dependencies": { - "@types/json-schema": "^7.0.7", - "ajv": "^6.7.0", - "core-js-pure": "^3.6.5", - "json-schema-merge-allof": "^0.6.0", - "jsonpointer": "^5.0.0", - "lodash": "^4.17.15", - "nanoid": "^3.1.23", - "prop-types": "^15.7.2", - "react-is": "^16.9.0" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "react": ">=16" - } - }, - "node_modules/@rjsf/core/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, "node_modules/@shikijs/core": { "version": "1.12.0", "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.12.0.tgz", @@ -9634,6 +7972,7 @@ }, "node_modules/@types/hoist-non-react-statics": { "version": "3.3.5", + "devOptional": true, "license": "MIT", "dependencies": { "@types/react": "*", @@ -9752,11 +8091,12 @@ } }, "node_modules/@types/node": { - "version": "18.19.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.0.tgz", - "integrity": "sha512-667KNhaD7U29mT5wf+TZUnrzPrlL2GNQ5N0BMjO2oNULhBxX0/FKCkm6JMu0Jh7Z+1LwUlR21ekd7KhIboNFNw==", + "version": "22.14.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.1.tgz", + "integrity": "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw==", + "license": "MIT", "dependencies": { - "undici-types": "~5.26.4" + "undici-types": "~6.21.0" } }, "node_modules/@types/node-fetch": { @@ -9825,6 +8165,7 @@ }, "node_modules/@types/react-redux": { "version": "7.1.7", + "dev": true, "license": "MIT", "dependencies": { "@types/hoist-non-react-statics": "^3.3.0", @@ -9841,13 +8182,6 @@ "@types/react": "*" } }, - "node_modules/@types/react-table": { - "version": "6.8.14", - "license": "MIT", - "dependencies": { - "@types/react": "*" - } - }, "node_modules/@types/react-window": { "version": "1.8.8", "dev": true, @@ -9955,16 +8289,15 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/ungap__url-search-params": { - "version": "0.1.2", - "license": "MIT" - }, "node_modules/@types/unist": { "version": "2.0.10", "license": "MIT" }, "node_modules/@types/uuid": { - "version": "8.3.4", + "version": "9.0.8", + "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha1-dUW6T8PAA9bHVvZR878WPY8PKbo=", + "dev": true, "license": "MIT" }, "node_modules/@types/ws": { @@ -10278,10 +8611,6 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" }, - "node_modules/@ungap/url-search-params": { - "version": "0.2.2", - "license": "ISC" - }, "node_modules/@webassemblyjs/ast": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", @@ -10640,6 +8969,7 @@ }, "node_modules/ajv-keywords": { "version": "3.5.2", + "dev": true, "license": "MIT", "peerDependencies": { "ajv": "^6.9.1" @@ -10665,10 +8995,6 @@ "version": "5.0.1", "license": "MIT" }, - "node_modules/anser": { - "version": "1.4.10", - "license": "MIT" - }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -10713,18 +9039,6 @@ "node": ">=4" } }, - "node_modules/ansi-to-react": { - "version": "6.1.6", - "license": "BSD-3-Clause", - "dependencies": { - "anser": "^1.4.1", - "escape-carriage": "^1.3.0" - }, - "peerDependencies": { - "react": "^16.3.2 || ^17.0.0", - "react-dom": "^16.3.2 || ^17.0.0" - } - }, "node_modules/antlr4": { "version": "4.7.1", "license": "BSD" @@ -10993,14 +9307,15 @@ } }, "node_modules/axios": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.0.tgz", - "integrity": "sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.19.0.tgz", + "integrity": "sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw==", "dev": true, "license": "MIT", "dependencies": { "follow-redirects": "^1.16.0", - "form-data": "^4.0.5", + "form-data": "^4.0.6", + "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, @@ -11463,18 +9778,6 @@ "@babel/core": "^7.0.0" } }, - "node_modules/babel-runtime": { - "version": "6.26.0", - "license": "MIT", - "dependencies": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - } - }, - "node_modules/babel-runtime/node_modules/regenerator-runtime": { - "version": "0.11.1", - "license": "MIT" - }, "node_modules/bail": { "version": "1.0.5", "license": "MIT", @@ -11487,10 +9790,6 @@ "version": "1.0.2", "license": "MIT" }, - "node_modules/base16": { - "version": "1.0.0", - "license": "MIT" - }, "node_modules/base64-arraybuffer": { "version": "0.2.0", "engines": { @@ -11543,6 +9842,7 @@ }, "node_modules/big.js": { "version": "5.2.2", + "dev": true, "license": "MIT", "engines": { "node": "*" @@ -11556,10 +9856,6 @@ "node": ">=8" } }, - "node_modules/bintrees": { - "version": "1.0.2", - "license": "MIT" - }, "node_modules/bl": { "version": "4.1.0", "license": "MIT", @@ -11683,9 +9979,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", - "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -12188,10 +10484,6 @@ "node": ">= 0.12.0" } }, - "node_modules/codemirror": { - "version": "5.61.1", - "license": "MIT" - }, "node_modules/collapse-white-space": { "version": "1.0.6", "license": "MIT", @@ -12302,27 +10594,6 @@ "node": ">= 0.6" } }, - "node_modules/compute-gcd": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/compute-gcd/-/compute-gcd-1.2.1.tgz", - "integrity": "sha512-TwMbxBNz0l71+8Sc4czv13h4kEqnchV9igQZBi6QUaz09dnz13juGnnaWWJTRsP3brxOoxeB4SA2WELLw1hCtg==", - "dependencies": { - "validate.io-array": "^1.0.3", - "validate.io-function": "^1.0.2", - "validate.io-integer-array": "^1.0.0" - } - }, - "node_modules/compute-lcm": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/compute-lcm/-/compute-lcm-1.1.2.tgz", - "integrity": "sha512-OFNPdQAXnQhDSKioX8/XYT6sdUlXwpeMjfd6ApxMJfyZ4GxmLR1xvMERctlYhlHwIiz6CSpBc2+qYKjHGZw4TQ==", - "dependencies": { - "compute-gcd": "^1.2.1", - "validate.io-array": "^1.0.3", - "validate.io-function": "^1.0.2", - "validate.io-integer-array": "^1.0.0" - } - }, "node_modules/concat-map": { "version": "0.0.1", "license": "MIT" @@ -12393,18 +10664,19 @@ } }, "node_modules/copy-webpack-plugin": { - "version": "11.0.0", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-14.0.0.tgz", + "integrity": "sha512-3JLW90aBGeaTLpM7mYQKpnVdgsUZRExY55giiZgLuX/xTQRUs1dOCwbBnWnvY6Q6rfZoXMNwzOQJCSZPppfqXA==", "license": "MIT", "dependencies": { - "fast-glob": "^3.2.11", "glob-parent": "^6.0.1", - "globby": "^13.1.1", "normalize-path": "^3.0.0", - "schema-utils": "^4.0.0", - "serialize-javascript": "^6.0.0" + "schema-utils": "^4.2.0", + "serialize-javascript": "^7.0.3", + "tinyglobby": "^0.2.12" }, "engines": { - "node": ">= 14.15.0" + "node": ">= 20.9.0" }, "funding": { "type": "opencollective", @@ -12438,47 +10710,6 @@ "ajv": "^8.8.2" } }, - "node_modules/copy-webpack-plugin/node_modules/fast-glob": { - "version": "3.3.2", - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/copy-webpack-plugin/node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/copy-webpack-plugin/node_modules/globby": { - "version": "13.2.2", - "license": "MIT", - "dependencies": { - "dir-glob": "^3.0.1", - "fast-glob": "^3.3.0", - "ignore": "^5.2.4", - "merge2": "^1.4.1", - "slash": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/copy-webpack-plugin/node_modules/json-schema-traverse": { "version": "1.0.0", "license": "MIT" @@ -12500,21 +10731,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/copy-webpack-plugin/node_modules/slash": { - "version": "4.0.0", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/core-js": { - "version": "2.6.12", - "hasInstallScript": true, - "license": "MIT" - }, "node_modules/core-js-compat": { "version": "3.38.0", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.38.0.tgz", @@ -12530,6 +10746,7 @@ }, "node_modules/core-js-pure": { "version": "3.33.3", + "dev": true, "hasInstallScript": true, "license": "MIT", "funding": { @@ -12778,14 +10995,6 @@ "signals": "<2.0" } }, - "node_modules/crypto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz", - "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==", - "deprecated": "This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in.", - "license": "ISC", - "peer": true - }, "node_modules/css-color-keywords": { "version": "1.0.0", "license": "ISC", @@ -12973,17 +11182,6 @@ "node": ">=12" } }, - "node_modules/d3-brush": { - "version": "2.1.0", - "license": "BSD-3-Clause", - "dependencies": { - "d3-dispatch": "1 - 2", - "d3-drag": "2", - "d3-interpolate": "1 - 2", - "d3-selection": "2", - "d3-transition": "2" - } - }, "node_modules/d3-chord": { "version": "3.0.1", "license": "ISC", @@ -12996,6 +11194,8 @@ }, "node_modules/d3-collection": { "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.7.tgz", + "integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==", "license": "BSD-3-Clause" }, "node_modules/d3-color": { @@ -13118,14 +11318,6 @@ "node": ">=12" } }, - "node_modules/d3-glyphedge": { - "version": "1.2.0", - "license": "Unlicense" - }, - "node_modules/d3-hexbin": { - "version": "0.2.2", - "license": "BSD-3-Clause" - }, "node_modules/d3-hierarchy": { "version": "3.1.2", "license": "ISC", @@ -13147,22 +11339,6 @@ "node": ">=12" } }, - "node_modules/d3-path-arrows": { - "version": "0.4.0", - "license": "MIT", - "dependencies": { - "d3-array": "^1.2.1", - "d3-selection": "^1.1.0" - } - }, - "node_modules/d3-path-arrows/node_modules/d3-array": { - "version": "1.2.4", - "license": "BSD-3-Clause" - }, - "node_modules/d3-path-arrows/node_modules/d3-selection": { - "version": "1.4.2", - "license": "BSD-3-Clause" - }, "node_modules/d3-polygon": { "version": "3.0.1", "license": "ISC", @@ -13184,19 +11360,6 @@ "node": ">=12" } }, - "node_modules/d3-sankey-circular": { - "version": "0.25.0", - "license": "MIT", - "dependencies": { - "d3-array": "^1.2.1", - "d3-collection": "^1.0.4", - "d3-shape": "^1.2.0" - } - }, - "node_modules/d3-sankey-circular/node_modules/d3-array": { - "version": "1.2.4", - "license": "BSD-3-Clause" - }, "node_modules/d3-scale": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", @@ -13227,17 +11390,6 @@ "version": "2.0.0", "license": "BSD-3-Clause" }, - "node_modules/d3-shape": { - "version": "1.3.7", - "license": "BSD-3-Clause", - "dependencies": { - "d3-path": "1" - } - }, - "node_modules/d3-shape/node_modules/d3-path": { - "version": "1.0.9", - "license": "BSD-3-Clause" - }, "node_modules/d3-time": { "version": "2.1.1", "license": "BSD-3-Clause", @@ -13270,10 +11422,6 @@ "d3-selection": "2" } }, - "node_modules/d3-voronoi": { - "version": "1.1.4", - "license": "BSD-3-Clause" - }, "node_modules/d3-zoom": { "version": "3.0.0", "license": "ISC", @@ -13502,6 +11650,7 @@ }, "node_modules/debounce": { "version": "1.2.1", + "dev": true, "license": "MIT" }, "node_modules/debug": { @@ -13557,10 +11706,6 @@ } } }, - "node_modules/deep-diff": { - "version": "0.3.8", - "license": "MIT" - }, "node_modules/deep-equal": { "version": "1.1.2", "license": "MIT", @@ -13593,6 +11738,7 @@ }, "node_modules/deepmerge": { "version": "4.3.1", + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -14064,6 +12210,7 @@ }, "node_modules/emojis-list": { "version": "3.0.0", + "dev": true, "license": "MIT", "engines": { "node": ">= 4" @@ -14419,10 +12566,6 @@ "node": ">=6" } }, - "node_modules/escape-carriage": { - "version": "1.3.1", - "license": "MIT" - }, "node_modules/escape-html": { "version": "1.0.3", "dev": true, @@ -15350,9 +13493,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", - "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", + "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", "funding": [ { "type": "github", @@ -15428,10 +13571,6 @@ "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/file-saver": { - "version": "2.0.5", - "license": "MIT" - }, "node_modules/filesize": { "version": "8.0.7", "dev": true, @@ -15440,13 +13579,6 @@ "node": ">= 0.4.0" } }, - "node_modules/filewatcher": { - "version": "3.0.1", - "license": "MIT", - "dependencies": { - "debounce": "^1.0.0" - } - }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -15813,20 +13945,33 @@ } }, "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" + "hasown": "^2.0.4", + "mime-types": "^2.1.35" }, "engines": { "node": ">= 6" } }, + "node_modules/form-data/node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/format": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", @@ -15852,11 +13997,6 @@ "node": ">= 0.6" } }, - "node_modules/free-style": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/free-style/-/free-style-3.1.0.tgz", - "integrity": "sha512-vJujYSIyT30iDoaoeigNAxX4yB1RUrh+N2ZMhIElMr3BvCuGXOw7XNJMEEJkDUeamK2Rnb/IKFGKRKlTWIGRWA==" - }, "node_modules/fresh": { "version": "0.5.2", "dev": true, @@ -15888,27 +14028,6 @@ "dev": true, "license": "Unlicense" }, - "node_modules/fs-observable": { - "version": "4.1.14", - "license": "BSD-3-Clause", - "dependencies": { - "filewatcher": "^3.0.1", - "mkdirp": "^0.5.1" - }, - "peerDependencies": { - "rxjs": "^6.3.3" - } - }, - "node_modules/fs-observable/node_modules/mkdirp": { - "version": "0.5.6", - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, "node_modules/fs.realpath": { "version": "1.0.0", "license": "ISC" @@ -16080,14 +14199,6 @@ "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" }, - "node_modules/global": { - "version": "4.4.0", - "license": "MIT", - "dependencies": { - "min-document": "^2.19.0", - "process": "^0.11.10" - } - }, "node_modules/global-modules": { "version": "2.0.0", "dev": true, @@ -16831,10 +14942,11 @@ } }, "node_modules/http-proxy-middleware": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", - "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.10.tgz", + "integrity": "sha512-RKzRWNPxUZqbuk3BC5mGVJbBnWgr+diEnjJexIOytFbBzDy88Fbh/YvBr3DsNrl1jYAfjWfpATEv0NO35FDuPQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/http-proxy": "^1.17.8", "http-proxy": "^1.18.1", @@ -17081,7 +15193,9 @@ } }, "node_modules/immutable": { - "version": "4.0.0-rc.12", + "version": "4.3.9", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.9.tgz", + "integrity": "sha512-ObHy4YN7ycwZOUCLI1/6svfyAFu7vL8RhAvVu/bh/RZW9EPlOyDaQ9jDQWCtdqzaXUjgXZCW1migtHE7YI7UGQ==", "license": "MIT" }, "node_modules/import-fresh": { @@ -17602,13 +15716,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-plain-object": { - "version": "5.0.0", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -17742,6 +15849,7 @@ }, "node_modules/is-what": { "version": "3.14.1", + "dev": true, "license": "MIT" }, "node_modules/is-whitespace-character": { @@ -19466,27 +17574,6 @@ "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": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", @@ -22090,19 +20177,16 @@ "jquery": ">=1.8.0 <4.0.0" } }, - "node_modules/js-cookie": { - "version": "2.2.1", - "license": "MIT" - }, "node_modules/js-tokens": { "version": "4.0.0", "license": "MIT" }, "node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "version": "3.15.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.1.tgz", + "integrity": "sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag==", "devOptional": true, + "license": "MIT", "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -22140,24 +20224,6 @@ "version": "2.3.1", "license": "MIT" }, - "node_modules/json-schema-compare": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/json-schema-compare/-/json-schema-compare-0.2.2.tgz", - "integrity": "sha512-c4WYmDKyJXhs7WWvAWm3uIYnfyWFoIp+JEoX34rctVvEkMYCPGhXtvmFFXiffBbxfZsvQ0RNnV5H7GvDF5HCqQ==", - "dependencies": { - "lodash": "^4.17.4" - } - }, - "node_modules/json-schema-merge-allof": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/json-schema-merge-allof/-/json-schema-merge-allof-0.6.0.tgz", - "integrity": "sha512-LEw4VMQVRceOPLuGRWcxW5orTTiR9ZAtqTAe4rQUjNADTeR81bezBVFa0MqIwp0YmHIM1KkhSjZM7o+IQhaPbQ==", - "dependencies": { - "compute-lcm": "^1.1.0", - "json-schema-compare": "^0.2.2", - "lodash": "^4.17.4" - } - }, "node_modules/json-schema-traverse": { "version": "0.4.1", "license": "MIT" @@ -22211,14 +20277,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jsonpointer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", - "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/jsonwebtoken": { "version": "9.0.3", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", @@ -22319,10 +20377,6 @@ "version": "3.5.1", "license": "MIT" }, - "node_modules/labella": { - "version": "1.1.4", - "license": "Apache-2.0" - }, "node_modules/launch-editor": { "version": "2.14.1", "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.14.1.tgz", @@ -22334,23 +20388,6 @@ "shell-quote": "^1.8.4" } }, - "node_modules/launch-editor/node_modules/shell-quote": { - "version": "1.8.4", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.4.tgz", - "integrity": "sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/leaflet": { - "version": "1.9.4", - "license": "BSD-2-Clause" - }, "node_modules/less": { "version": "4.5.1", "resolved": "https://registry.npmjs.org/less/-/less-4.5.1.tgz", @@ -22475,10 +20512,21 @@ "license": "MIT" }, "node_modules/linkify-it": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", - "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.2.tgz", + "integrity": "sha512-ONTm2jCMAVZjgQa/Fy1kScXsuOoF5NPTsoFBdE1KVIZ2vAh/r9+Bqo+0jINCBYnavTPQZz38QzFTme79ENoN3Q==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/markdown-it" + } + ], + "license": "MIT", "dependencies": { "uc.micro": "^2.0.0" } @@ -22556,14 +20604,6 @@ "version": "4.0.3", "license": "MIT" }, - "node_modules/lodash.clonedeep": { - "version": "4.5.0", - "license": "MIT" - }, - "node_modules/lodash.curry": { - "version": "4.1.1", - "license": "MIT" - }, "node_modules/lodash.debounce": { "version": "4.0.8", "license": "MIT" @@ -22578,14 +20618,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lodash.flow": { - "version": "3.5.0", - "license": "MIT" - }, - "node_modules/lodash.has": { - "version": "4.5.2", - "license": "MIT" - }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -22722,14 +20754,25 @@ } }, "node_modules/markdown-it": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", - "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "version": "14.3.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.3.0.tgz", + "integrity": "sha512-RCEsPjR+sr0x+AuYp601tKTkgFG4YEPLCzHST3cQ/fhlJkqAkz1L2/Qbp1j9qw5SBwQHFBoW8+hoN5xssOF0Tw==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/markdown-it" + } + ], + "license": "MIT", "dependencies": { "argparse": "^2.0.1", - "entities": "^4.4.0", - "linkify-it": "^5.0.0", + "entities": "^4.5.0", + "linkify-it": "^5.0.2", "mdurl": "^2.0.0", "punycode.js": "^2.3.1", "uc.micro": "^2.1.0" @@ -22738,18 +20781,6 @@ "markdown-it": "bin/markdown-it.mjs" } }, - "node_modules/martinez-polygon-clipping": { - "version": "0.1.5", - "license": "MIT", - "dependencies": { - "bintrees": "^1.0.1", - "tinyqueue": "^1.1.0" - } - }, - "node_modules/material-colors": { - "version": "1.2.6", - "license": "ISC" - }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -22796,13 +20827,6 @@ "version": "5.2.1", "license": "MIT" }, - "node_modules/merge-anything": { - "version": "2.4.4", - "license": "MIT", - "dependencies": { - "is-what": "^3.3.1" - } - }, "node_modules/merge-descriptors": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", @@ -22980,13 +21004,6 @@ "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" }, - "node_modules/moment": { - "version": "2.29.4", - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/monaco-editor": { "version": "0.44.0", "license": "MIT" @@ -23061,15 +21078,16 @@ } }, "node_modules/nanoid": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", - "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -23199,6 +21217,7 @@ }, "node_modules/node-fetch": { "version": "2.6.7", + "dev": true, "license": "MIT", "dependencies": { "whatwg-url": "^5.0.0" @@ -23258,13 +21277,6 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, - "node_modules/numeral": { - "version": "2.0.6", - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/nwsapi": { "version": "2.2.7", "dev": true, @@ -23594,10 +21606,6 @@ "node": ">= 0.10" } }, - "node_modules/parse-srcset": { - "version": "1.0.2", - "license": "MIT" - }, "node_modules/parse5": { "version": "7.1.2", "dev": true, @@ -23827,10 +21835,6 @@ "util": "^0.10.3" } }, - "node_modules/path-browserify": { - "version": "1.0.1", - "license": "MIT" - }, "node_modules/path-exists": { "version": "3.0.0", "license": "MIT", @@ -24011,17 +22015,6 @@ "version": "1.52.3", "license": "MIT" }, - "node_modules/plotly.js-dist": { - "version": "1.58.5", - "license": "MIT" - }, - "node_modules/polygon-offset": { - "version": "0.3.1", - "license": "MIT", - "dependencies": { - "martinez-polygon-clipping": "^0.1.5" - } - }, "node_modules/popper.js": { "version": "1.16.1", "license": "MIT", @@ -24048,7 +22041,10 @@ } }, "node_modules/postcss": { - "version": "8.4.31", + "version": "8.5.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", + "dev": true, "funding": [ { "type": "opencollective", @@ -24065,9 +22061,9 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.6", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "nanoid": "^3.3.17", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -24336,6 +22332,7 @@ }, "node_modules/process": { "version": "0.11.10", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6.0" @@ -24460,10 +22457,6 @@ "node": ">=6" } }, - "node_modules/pure-color": { - "version": "1.3.0", - "license": "MIT" - }, "node_modules/pure-rand": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", @@ -24516,6 +22509,7 @@ "version": "6.15.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -24536,6 +22530,7 @@ }, "node_modules/querystringify": { "version": "2.2.0", + "dev": true, "license": "MIT" }, "node_modules/queue-microtask": { @@ -24581,13 +22576,6 @@ "node": ">=0.12" } }, - "node_modules/randombytes": { - "version": "2.1.0", - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, "node_modules/range-parser": { "version": "1.2.1", "dev": true, @@ -24683,44 +22671,6 @@ "react-dom": ">=15.6.2" } }, - "node_modules/react-annotation": { - "version": "3.0.0-beta.4", - "license": "Apache-2.0", - "dependencies": { - "prop-types": "^15", - "viz-annotation": "0.0.5" - }, - "peerDependencies": { - "react": "^16", - "react-dom": "^16" - } - }, - "node_modules/react-base16-styling": { - "version": "0.5.3", - "license": "MIT", - "dependencies": { - "base16": "^1.0.0", - "lodash.curry": "^4.0.1", - "lodash.flow": "^3.3.0", - "pure-color": "^1.2.0" - } - }, - "node_modules/react-color": { - "version": "2.19.3", - "license": "MIT", - "dependencies": { - "@icons/material": "^0.2.4", - "lodash": "^4.17.15", - "lodash-es": "^4.17.15", - "material-colors": "^1.2.1", - "prop-types": "^15.5.10", - "reactcss": "^1.2.0", - "tinycolor2": "^1.4.1" - }, - "peerDependencies": { - "react": "*" - } - }, "node_modules/react-dev-utils": { "version": "12.0.1", "dev": true, @@ -24989,52 +22939,6 @@ "dev": true, "license": "MIT" }, - "node_modules/react-hot-loader": { - "version": "4.13.1", - "license": "MIT", - "dependencies": { - "fast-levenshtein": "^2.0.6", - "global": "^4.3.0", - "hoist-non-react-statics": "^3.3.0", - "loader-utils": "^2.0.3", - "prop-types": "^15.6.1", - "react-lifecycles-compat": "^3.0.4", - "shallowequal": "^1.1.0", - "source-map": "^0.7.3" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "@types/react": "^15.0.0 || ^16.0.0 || ^17.0.0", - "react": "^15.0.0 || ^16.0.0 || ^17.0.0", - "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/react-hot-loader/node_modules/loader-utils": { - "version": "2.0.4", - "license": "MIT", - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, - "engines": { - "node": ">=8.9.0" - } - }, - "node_modules/react-hot-loader/node_modules/source-map": { - "version": "0.7.4", - "license": "BSD-3-Clause", - "engines": { - "node": ">= 8" - } - }, "node_modules/react-hotkeys": { "version": "2.0.0", "license": "ISC", @@ -25070,19 +22974,6 @@ "version": "18.2.0", "license": "MIT" }, - "node_modules/react-json-tree": { - "version": "0.11.2", - "license": "MIT", - "dependencies": { - "babel-runtime": "^6.6.1", - "prop-types": "^15.5.8", - "react-base16-styling": "^0.5.1" - }, - "peerDependencies": { - "react": "^15.0.0 || ^16.0.0", - "react-dom": "^15.0.0 || ^16.0.0" - } - }, "node_modules/react-lifecycles-compat": { "version": "3.0.4", "license": "MIT" @@ -25192,36 +23083,6 @@ "react": ">= 0.14.0" } }, - "node_modules/react-table": { - "version": "6.11.5", - "license": "MIT", - "dependencies": { - "@types/react-table": "^6.8.5", - "classnames": "^2.2.5", - "react-is": "^16.8.1" - }, - "peerDependencies": { - "prop-types": "^15.7.0", - "react": "^16.x.x", - "react-dom": "^16.x.x" - } - }, - "node_modules/react-table-hoc-fixed-columns": { - "version": "2.1.3", - "license": "MIT", - "dependencies": { - "classnames": "^2.2.6", - "uniqid": "^5.0.3" - }, - "peerDependencies": { - "react": "^15.3.0 || ^16.2.0", - "react-dom": "^15.3.0 || ^16.2.0" - } - }, - "node_modules/react-table/node_modules/react-is": { - "version": "16.13.1", - "license": "MIT" - }, "node_modules/react-test-renderer": { "version": "16.14.0", "dev": true, @@ -25250,13 +23111,6 @@ "object-assign": "^4.1.1" } }, - "node_modules/react-timeago": { - "version": "4.4.0", - "license": "MIT", - "peerDependencies": { - "react": "^15.0.0 || ^16.0.0" - } - }, "node_modules/react-toggle": { "version": "4.1.3", "license": "MIT", @@ -25313,13 +23167,6 @@ "react": ">=0.14.1" } }, - "node_modules/reactcss": { - "version": "1.2.3", - "license": "MIT", - "dependencies": { - "lodash": "^4.0.1" - } - }, "node_modules/readable-stream": { "version": "2.3.8", "dev": true, @@ -25409,42 +23256,6 @@ "@babel/runtime": "^7.9.2" } }, - "node_modules/redux-immutable": { - "version": "4.0.0", - "license": "BSD-3-Clause", - "peerDependencies": { - "immutable": "^3.8.1 || ^4.0.0-rc.1" - } - }, - "node_modules/redux-logger": { - "version": "3.0.6", - "license": "MIT", - "dependencies": { - "deep-diff": "^0.3.5" - } - }, - "node_modules/redux-observable": { - "version": "2.0.0", - "license": "MIT", - "dependencies": { - "rxjs": "^7.0.0", - "tslib": "~2.1.0" - }, - "peerDependencies": { - "redux": ">=4 <5" - } - }, - "node_modules/redux-observable/node_modules/rxjs": { - "version": "7.8.1", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/redux-observable/node_modules/tslib": { - "version": "2.1.0", - "license": "0BSD" - }, "node_modules/reflect-metadata": { "version": "0.1.13", "license": "Apache-2.0" @@ -25583,10 +23394,6 @@ "jsesc": "bin/jsesc" } }, - "node_modules/regression": { - "version": "2.0.1", - "license": "MIT" - }, "node_modules/relateurl": { "version": "0.2.7", "dev": true, @@ -25740,10 +23547,7 @@ }, "node_modules/requires-port": { "version": "1.0.0", - "license": "MIT" - }, - "node_modules/reselect": { - "version": "4.1.8", + "dev": true, "license": "MIT" }, "node_modules/resolve": { @@ -25888,29 +23692,6 @@ "version": "1.3.3", "license": "BSD-3-Clause" }, - "node_modules/rx-jupyter": { - "version": "5.5.12", - "license": "BSD-3-Clause", - "dependencies": { - "@nteract/commutable": "^7.3.2", - "@nteract/messaging": "^7.0.10", - "@types/ungap__url-search-params": "^0.1.0", - "@ungap/url-search-params": "^0.2.0", - "js-cookie": "^2.2.0", - "rxjs": "^6.6.0", - "url-join": "^4.0.0" - } - }, - "node_modules/rxjs": { - "version": "6.6.7", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" - } - }, "node_modules/safe-array-concat": { "version": "1.1.2", "license": "MIT", @@ -25973,48 +23754,6 @@ "dev": true, "license": "BSD-3-Clause" }, - "node_modules/sanitize-html": { - "version": "2.17.0", - "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.17.0.tgz", - "integrity": "sha512-dLAADUSS8rBwhaevT12yCezvioCA+bmUTPH/u57xKPT8d++voeYE6HeluA/bPbQ15TwDBG2ii+QZIEmYx8VdxA==", - "dependencies": { - "deepmerge": "^4.2.2", - "escape-string-regexp": "^4.0.0", - "htmlparser2": "^8.0.0", - "is-plain-object": "^5.0.0", - "parse-srcset": "^1.0.2", - "postcss": "^8.3.11" - } - }, - "node_modules/sanitize-html/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/sanitize-html/node_modules/htmlparser2": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", - "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1", - "entities": "^4.4.0" - } - }, "node_modules/sax": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz", @@ -26047,6 +23786,7 @@ "node_modules/scheduler": { "version": "0.20.2", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" @@ -26149,11 +23889,12 @@ } }, "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dependencies": { - "randombytes": "^2.1.0" + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.1.0.tgz", + "integrity": "sha512-RNEqWOyhhUQYN9V1GfHwu9AR/g+NTciH6Z5u3/no6X3/w+04J2lVDL+svFQVXgXrEGBMG2puMVN3gq2SNGuTGw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=20.0.0" } }, "node_modules/serve-index": { @@ -26321,8 +24062,16 @@ } }, "node_modules/shell-quote": { - "version": "1.7.3", - "license": "MIT" + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.10.0.tgz", + "integrity": "sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/shiki": { "version": "1.12.0", @@ -26542,7 +24291,10 @@ } }, "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==", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -26902,23 +24654,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/svg-path-bounding-box": { - "version": "1.0.4", - "license": "MIT", - "dependencies": { - "svgpath": "^2.0.0" - }, - "bin": { - "svg-path-bounding-box": "bin/caster.js" - } - }, - "node_modules/svgpath": { - "version": "2.6.0", - "license": "MIT", - "funding": { - "url": "https://github.com/fontello/svg2ttf?sponsor=1" - } - }, "node_modules/swr": { "version": "0.4.0", "license": "MIT", @@ -27015,14 +24750,15 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.9", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.6.1.tgz", + "integrity": "sha512-201R5j+sJpK8nFWwKVyNfZot8FaJbLZDq5evriVzbV1wDtSXDjRUDRfJzHpAaxFDMEhsZL1QkeqM61wgsS3KaQ==", "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.17", + "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.1", - "terser": "^5.16.8" + "schema-utils": "^4.3.0", + "terser": "^5.31.1" }, "engines": { "node": ">= 10.13.0" @@ -27035,17 +24771,72 @@ "webpack": "^5.1.0" }, "peerDependenciesMeta": { + "@minify-html/node": { + "optional": true + }, "@swc/core": { "optional": true }, + "@swc/css": { + "optional": true + }, + "@swc/html": { + "optional": true + }, + "clean-css": { + "optional": true + }, + "cssnano": { + "optional": true + }, + "csso": { + "optional": true + }, "esbuild": { "optional": true }, + "html-minifier-terser": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "postcss": { + "optional": true + }, "uglify-js": { "optional": true } } }, + "node_modules/terser-webpack-plugin/node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, "node_modules/terser-webpack-plugin/node_modules/has-flag": { "version": "4.0.0", "license": "MIT", @@ -27065,13 +24856,22 @@ "node": ">= 10.13.0" } }, + "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, "node_modules/terser-webpack-plugin/node_modules/schema-utils": { - "version": "3.3.0", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", "license": "MIT", "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" }, "engines": { "node": ">= 10.13.0" @@ -27115,18 +24915,55 @@ "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", "dev": true }, - "node_modules/tinycolor2": { - "version": "1.6.0", - "license": "MIT" + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } }, "node_modules/tinykeys": { "version": "2.1.0", "license": "MIT" }, - "node_modules/tinyqueue": { - "version": "1.2.3", - "license": "ISC" - }, "node_modules/tmp": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", @@ -27518,9 +25355,9 @@ } }, "node_modules/typedoc/node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", "dev": true, "license": "MIT", "dependencies": { @@ -27555,25 +25392,12 @@ "node": ">=4.2.0" } }, - "node_modules/typestyle": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/typestyle/-/typestyle-2.4.0.tgz", - "integrity": "sha512-/d1BL6Qi+YlMLEydnUEB8KL/CAjAN8cyt3/UyGnOyBrWf7bLGcR/6yhmsaUstO2IcYwZfagjE7AIzuI2vUW9mg==", - "dependencies": { - "csstype": "3.0.10", - "free-style": "3.1.0" - } - }, - "node_modules/typestyle/node_modules/csstype": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz", - "integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==" - }, "node_modules/uc.micro": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/uglify-js": { "version": "3.4.10", @@ -27626,9 +25450,10 @@ "license": "MIT" }, "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" }, "node_modules/unherit": { "version": "1.1.3", @@ -27690,10 +25515,6 @@ "x-is-string": "^0.1.0" } }, - "node_modules/uniqid": { - "version": "5.4.0", - "license": "MIT" - }, "node_modules/unist-util-is": { "version": "3.0.0", "license": "MIT" @@ -27796,22 +25617,6 @@ "punycode": "^2.1.0" } }, - "node_modules/url": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.4.tgz", - "integrity": "sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==", - "dependencies": { - "punycode": "^1.4.1", - "qs": "^6.12.3" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/url-join": { - "version": "4.0.1", - "license": "MIT" - }, "node_modules/url-loader": { "version": "4.1.1", "dev": true, @@ -27870,17 +25675,13 @@ }, "node_modules/url-parse": { "version": "1.5.10", + "dev": true, "license": "MIT", "dependencies": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" } }, - "node_modules/url/node_modules/punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==" - }, "node_modules/use-disposable": { "version": "1.0.2", "license": "MIT", @@ -27974,38 +25775,6 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "devOptional": true }, - "node_modules/validate.io-array": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/validate.io-array/-/validate.io-array-1.0.6.tgz", - "integrity": "sha512-DeOy7CnPEziggrOO5CZhVKJw6S3Yi7e9e65R1Nl/RTN1vTQKnzjfvks0/8kQ40FP/dsjRAOd4hxmJ7uLa6vxkg==" - }, - "node_modules/validate.io-function": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/validate.io-function/-/validate.io-function-1.0.2.tgz", - "integrity": "sha512-LlFybRJEriSuBnUhQyG5bwglhh50EpTL2ul23MPIuR1odjO7XaMLFV8vHGwp7AZciFxtYOeiSCT5st+XSPONiQ==" - }, - "node_modules/validate.io-integer": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/validate.io-integer/-/validate.io-integer-1.0.5.tgz", - "integrity": "sha512-22izsYSLojN/P6bppBqhgUDjCkr5RY2jd+N2a3DCAUey8ydvrZ/OkGvFPR7qfOpwR2LC5p4Ngzxz36g5Vgr/hQ==", - "dependencies": { - "validate.io-number": "^1.0.3" - } - }, - "node_modules/validate.io-integer-array": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/validate.io-integer-array/-/validate.io-integer-array-1.0.0.tgz", - "integrity": "sha512-mTrMk/1ytQHtCY0oNO3dztafHYyGU88KL+jRxWuzfOmQb+4qqnWmI+gykvGp8usKZOM0H7keJHEbRaFiYA0VrA==", - "dependencies": { - "validate.io-array": "^1.0.3", - "validate.io-integer": "^1.0.4" - } - }, - "node_modules/validate.io-number": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/validate.io-number/-/validate.io-number-1.0.3.tgz", - "integrity": "sha512-kRAyotcbNaSYoDnXvb4MHg/0a1egJdLwS6oJ38TJY7aw9n93Fl/3blIXdyYvPOp55CNxywooG/3BcrwNrBpcSg==" - }, "node_modules/values-to-keys": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/values-to-keys/-/values-to-keys-1.1.0.tgz", @@ -28048,24 +25817,6 @@ "unist-util-stringify-position": "^1.1.1" } }, - "node_modules/viz-annotation": { - "version": "0.0.5", - "license": "ISC", - "dependencies": { - "d3-shape": "~1.0.4" - } - }, - "node_modules/viz-annotation/node_modules/d3-path": { - "version": "1.0.9", - "license": "BSD-3-Clause" - }, - "node_modules/viz-annotation/node_modules/d3-shape": { - "version": "1.0.6", - "license": "BSD-3-Clause", - "dependencies": { - "d3-path": "1" - } - }, "node_modules/void-elements": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", @@ -28297,27 +26048,6 @@ "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": { "version": "5.1.4", "dev": true, @@ -28989,10 +26719,11 @@ "dev": true }, "node_modules/webpack-dev-server": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.3.tgz", - "integrity": "sha512-9Gyu2F7+bg4Vv+pjbovuYDhHX+mqdqITykfzdM9UyKqKHlsE5aAjRhR+oOEfXW5vBeu8tarzlJFIZva4ZjAdrQ==", + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.5.tgz", + "integrity": "sha512-4wZtCquSuv9CKX8oybo+mqxtxZqWz47uM1Ch94lxowBztOhWCbhqvRbfC/mODOwxgV2brY+JGZpHq58/SuVFYg==", "dev": true, + "license": "MIT", "dependencies": { "@types/bonjour": "^3.5.13", "@types/connect-history-api-fallback": "^1.5.4", @@ -29124,27 +26855,6 @@ "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": { "version": "5.10.0", "dev": true, @@ -29210,27 +26920,6 @@ "node": ">=4.0" } }, - "node_modules/webpack/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/webpack/node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, "node_modules/webpack/node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -29254,55 +26943,10 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/webpack/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/webpack/node_modules/terser-webpack-plugin": { - "version": "5.3.16", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz", - "integrity": "sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "jest-worker": "^27.4.5", - "schema-utils": "^4.3.0", - "serialize-javascript": "^6.0.2", - "terser": "^5.31.1" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "uglify-js": { - "optional": true - } - } - }, "node_modules/websocket-driver": { - "version": "0.7.4", + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.5.tgz", + "integrity": "sha512-ZL2+3c7kMBdIRCMz6l8jQMHyGVxj+UL+xVk74Ombiciboca8rHa15L86B19E5oh1pL9Ii/uj54gtsIrZGMo6zA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -29522,10 +27166,11 @@ } }, "node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", + "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", "dev": true, + "license": "MIT", "engines": { "node": ">=10.0.0" }, @@ -29601,22 +27246,15 @@ "version": "4.19.0", "resolved": "https://registry.npmjs.org/xterm/-/xterm-4.19.0.tgz", "integrity": "sha512-c3Cp4eOVsYY5Q839dR5IejghRPpxciGmLWWaP9g+ppfMeBChMeLa1DCA+pmX/jyDZ+zxFOmlJL/82qVdayVoGQ==", - "deprecated": "This package is now deprecated. Move to @xterm/xterm instead." + "deprecated": "This package is now deprecated. Move to @xterm/xterm instead.", + "license": "MIT" }, "node_modules/xterm-addon-fit": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/xterm-addon-fit/-/xterm-addon-fit-0.5.0.tgz", "integrity": "sha512-DsS9fqhXHacEmsPxBJZvfj2la30Iz9xk+UKjhQgnYNkrUIN5CYLbw7WEfz117c7+S86S/tpHPfvNxJsF5/G8wQ==", "deprecated": "This package is now deprecated. Move to @xterm/addon-fit instead.", - "peerDependencies": { - "xterm": "^4.0.0" - } - }, - "node_modules/xterm-addon-web-links": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/xterm-addon-web-links/-/xterm-addon-web-links-0.6.0.tgz", - "integrity": "sha512-H6XzjWWZu8FBo+fnYpxdPk9w5M6drbsvwPEJZGRS38MihiQaVFpKlCMKdfRgDbKGE530tw1yH54rhpZfHgt2/A==", - "deprecated": "This package is now deprecated. Move to @xterm/addon-web-links instead.", + "license": "MIT", "peerDependencies": { "xterm": "^4.0.0" } diff --git a/package.json b/package.json index 84299d2cc..832342c01 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,9 @@ "version": "1.0.0", "description": "Cosmos Explorer", "main": "index.js", + "engines": { + "node": "22.x" + }, "dependencies": { "@azure/arm-cosmosdb": "16.4.0", "@azure/cosmos": "4.7.0", @@ -13,35 +16,10 @@ "@babel/plugin-proposal-decorators": "7.12.12", "@fluentui/react": "8.119.0", "@fluentui/react-components": "9.54.2", - "@jupyterlab/services": "6.0.2", - "@jupyterlab/terminal": "3.6.8", "@microsoft/applicationinsights-web": "2.6.1", - "@nteract/commutable": "7.5.1", - "@nteract/connected-components": "6.8.2", - "@nteract/core": "15.1.9", - "@nteract/data-explorer": "8.2.12", - "@nteract/directory-listing": "2.0.6", - "@nteract/dropdown-menu": "1.0.1", - "@nteract/editor": "10.1.12", - "@nteract/fixtures": "2.3.0", - "@nteract/iron-icons": "1.0.0", - "@nteract/jupyter-widgets": "2.0.0", - "@nteract/logos": "1.0.0", "@nteract/markdown": "4.6.0", - "@nteract/monaco-editor": "3.2.2", - "@nteract/octicons": "2.0.0", - "@nteract/outputs": "3.0.9", - "@nteract/presentational-components": "3.4.12", - "@nteract/stateful-components": "1.7.0", - "@nteract/styles": "2.0.2", - "@nteract/transform-geojson": "5.1.8", - "@nteract/transform-model-debug": "5.0.1", - "@nteract/transform-plotly": "6.1.6", - "@nteract/transform-vdom": "4.0.11", - "@nteract/transform-vega": "7.0.6", "@octokit/request": "8.4.1", "@octokit/rest": "17.9.2", - "@phosphor/widgets": "1.9.3", "@testing-library/jest-dom": "6.4.6", "@types/lodash": "4.14.171", "@types/mkdirp": "1.0.1", @@ -55,10 +33,11 @@ "canvas": "3.2.1", "clean-webpack-plugin": "4.0.0", "clipboard-copy": "4.0.1", - "copy-webpack-plugin": "11.0.0", + "copy-webpack-plugin": "14.0.0", "crossroads": "0.12.2", "css-element-queries": "1.1.1", "d3": "7.9.0", + "d3-collection": "1.0.7", "datatables.net-colreorder-dt": "1.7.0", "datatables.net-dt": "1.13.8", "date-fns": "1.29.0", @@ -75,7 +54,7 @@ "i18next-resources-to-backend": "1.2.1", "iframe-resizer-react": "1.1.0", "immer": "9.0.6", - "immutable": "4.0.0-rc.12", + "immutable": "4.3.9", "is-ci": "2.0.0", "jquery": "3.7.1", "jquery-typeahead": "2.11.1", @@ -87,7 +66,7 @@ "mkdirp": "1.0.4", "monaco-editor": "0.44.0", "ms": "2.1.3", - "nanoid": "3.3.8", + "nanoid": "3.3.18", "p-retry": "6.2.1", "patch-package": "8.0.1", "plotly.js-cartesian-dist-min": "1.52.3", @@ -107,17 +86,18 @@ "react-window": "1.8.10", "react-youtube": "9.0.1", "reflect-metadata": "0.1.13", - "rx-jupyter": "5.5.12", - "shell-quote": "1.7.3", + "shell-quote": "1.10.0", "styled-components": "5.0.1", "swr": "0.4.0", - "terser-webpack-plugin": "5.3.9", + "terser-webpack-plugin": "5.6.1", "tinykeys": "2.1.0", "underscore": "1.13.8", "utility-types": "3.10.0", "uuid": "9.0.0", "web-vitals": "4.2.4", - "ws": "8.17.1", + "ws": "8.21.3", + "xterm": "4.19.0", + "xterm-addon-fit": "0.5.0", "zustand": "3.5.0" }, "overrides": { @@ -136,7 +116,7 @@ "@types/react-redux": "7.1.7" }, "devDependencies": { - "@babel/core": "7.29.0", + "@babel/core": "7.29.6", "@babel/preset-env": "7.24.7", "@babel/preset-react": "7.24.7", "@babel/preset-typescript": "7.24.7", @@ -154,7 +134,7 @@ "@types/hasher": "0.0.31", "@types/jest": "29.5.12", "@types/jquery": "3.5.29", - "@types/node": "18.19.0", + "@types/node": "22.14.1", "@types/post-robot": "10.0.1", "@types/q": "1.5.1", "@types/react": "17.0.44", @@ -167,13 +147,14 @@ "@types/sinon": "2.3.3", "@types/styled-components": "5.1.32", "@types/underscore": "1.7.36", + "@types/uuid": "9.0.8", "@types/youtube-player": "5.5.6", "@typescript-eslint/eslint-plugin": "6.7.4", "@typescript-eslint/parser": "6.7.4", "@webpack-cli/serve": "2.0.5", "babel-jest": "29.7.0", "babel-loader": "8.1.0", - "brace-expansion": "1.1.15", + "brace-expansion": "1.1.18", "buffer": "5.1.0", "case-sensitive-paths-webpack-plugin": "2.4.0", "create-file-webpack": "1.0.2", @@ -199,7 +180,7 @@ "jest-html-loader": "1.0.0", "jest-react-hooks-shallow": "1.5.1", "jest-trx-results-processor": "3.0.2", - "js-yaml": "3.14.2", + "js-yaml": "3.15.1", "less": "4.5.1", "less-loader": "11.1.3", "less-vars-loader": "1.1.0", @@ -223,8 +204,8 @@ "webpack": "5.104.1", "webpack-bundle-analyzer": "5.2.0", "webpack-cli": "5.1.4", - "webpack-dev-server": "5.2.3", - "ws": "8.17.1" + "webpack-dev-server": "5.2.5", + "ws": "8.21.3" }, "scripts": { "postinstall": "patch-package && npm run generate:i18n-keys", diff --git a/patches/@phosphor+virtualdom+1.2.0.patch b/patches/@phosphor+virtualdom+1.2.0.patch deleted file mode 100644 index cb409335c..000000000 --- a/patches/@phosphor+virtualdom+1.2.0.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/node_modules/@phosphor/virtualdom/lib/index.d.ts b/node_modules/@phosphor/virtualdom/lib/index.d.ts -index 95682b9..73e2daa 100644 ---- a/node_modules/@phosphor/virtualdom/lib/index.d.ts -+++ b/node_modules/@phosphor/virtualdom/lib/index.d.ts -@@ -58,7 +58,7 @@ export declare type ElementEventMap = { - ondrop: DragEvent; - ondurationchange: Event; - onemptied: Event; -- onended: MediaStreamErrorEvent; -+ onended: ErrorEvent; - onerror: ErrorEvent; - onfocus: FocusEvent; - oninput: Event; diff --git a/preview/index.js b/preview/index.js index 1320e3cb2..be44c63ed 100644 --- a/preview/index.js +++ b/preview/index.js @@ -5,7 +5,7 @@ const fetch = require("node-fetch"); const backendEndpoint = "https://cdb-ms-mpac-pbe.cosmos.azure.com"; const previewSiteEndpoint = "https://dataexplorer-preview.azurewebsites.net"; -const previewStorageWebsiteEndpoint = "https://dataexplorerpreview.z5.web.core.windows.net/"; +const previewStorageWebsiteEndpoint = "_REPLACE_STORAGE_WEBSITE_ENDPOINT_"; const githubApiUrl = "https://api.github.com/repos/Azure/cosmos-explorer"; const azurePortalMpacEndpoint = "https://ms.portal.azure.com/"; @@ -46,7 +46,7 @@ const app = express(); app.use("/api", api); app.use("/proxy", proxy); app.use("/commit", commit); -app.get("/pull/:pr(\\d+)", (req, res) => { +app.get("/pull/:pr", (req, res) => { const pr = req.params.pr; if (!/^\d+$/.test(pr)) { return res.status(400).send("Invalid pull request number"); diff --git a/preview/package-lock.json b/preview/package-lock.json index 93cf042fd..2e9127d1b 100644 --- a/preview/package-lock.json +++ b/preview/package-lock.json @@ -11,10 +11,8 @@ "body-parser": "^2.2.2", "express": "^5.2.1", "follow-redirects": "^1.16.0", - "http-proxy-middleware": "^3.0.5", - "node": "^20.19.5", - "node-fetch": "^2.6.1", - "path-to-regexp": "^0.1.13" + "http-proxy-middleware": "^3.0.7", + "node-fetch": "^2.6.1" } }, "node_modules/@types/http-proxy": { @@ -416,9 +414,10 @@ } }, "node_modules/http-proxy-middleware": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.5.tgz", - "integrity": "sha512-GLZZm1X38BPY4lkXA01jhwxvDoOkkXqjgVyUzVxiEK4iuRu03PZoYHhHRwxnfhQMDuaxi3vVri0YgSro/1oWqg==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.7.tgz", + "integrity": "sha512-iwbQltVlx8bCrqePUM8C+hllHvdawVhQJaLrj1X7qllkvFQdXFsr16pW/mo9+JDVjN+QO2XUx9jd8SmoFkE5qw==", + "license": "MIT", "dependencies": { "@types/http-proxy": "^1.17.15", "debug": "^4.3.6", @@ -428,7 +427,7 @@ "micromatch": "^4.0.8" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^14.18.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/http-proxy-middleware/node_modules/braces": { @@ -588,20 +587,6 @@ "node": ">= 0.6" } }, - "node_modules/node": { - "version": "20.19.5", - "hasInstallScript": true, - "license": "ISC", - "dependencies": { - "node-bin-setup": "^1.0.0" - }, - "bin": { - "node": "bin/node.exe" - }, - "engines": { - "npm": ">=5.0.0" - } - }, "node_modules/node-fetch": { "version": "2.6.7", "license": "MIT", @@ -620,10 +605,6 @@ } } }, - "node_modules/node/node_modules/node-bin-setup": { - "version": "1.1.4", - "license": "ISC" - }, "node_modules/object-inspect": { "version": "1.13.4", "license": "MIT", @@ -660,12 +641,6 @@ "node": ">= 0.8" } }, - "node_modules/path-to-regexp": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", - "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", - "license": "MIT" - }, "node_modules/picomatch": { "version": "2.3.1", "license": "MIT", diff --git a/preview/package.json b/preview/package.json index 5812e4659..3f21f0dd2 100644 --- a/preview/package.json +++ b/preview/package.json @@ -4,7 +4,7 @@ "description": "", "main": "index.js", "scripts": { - "deploy": "az webapp up --name \"dataexplorer-preview\" --subscription \"cosmosdb-portalteam-runners\" --resource-group \"dataexplorer-preview\" --runtime \"NODE:20-lts\" --sku P1V2", + "deploy": "az webapp up --name \"dataexplorer-preview\" --subscription \"cosmosdb-portalteam-runners\" --resource-group \"dataexplorer-preview\" --runtime \"NODE:22-lts\" --sku P1V2", "start": "node index.js", "test": "echo \"Error: no test specified\" && exit 1" }, @@ -14,9 +14,7 @@ "body-parser": "^2.2.2", "express": "^5.2.1", "follow-redirects": "^1.16.0", - "http-proxy-middleware": "^3.0.5", - "node": "^20.19.5", - "node-fetch": "^2.6.1", - "path-to-regexp": "^0.1.13" + "http-proxy-middleware": "^3.0.7", + "node-fetch": "^2.6.1" } } diff --git a/src/CellOutputViewer/CellOutputViewer.less b/src/CellOutputViewer/CellOutputViewer.less deleted file mode 100644 index 0be451c1a..000000000 --- a/src/CellOutputViewer/CellOutputViewer.less +++ /dev/null @@ -1,15 +0,0 @@ -.schema-analyzer-cell-outputs { - padding: 10px 2px; -} - -// Mimic FluentUI8's DocumentCard style -.schema-analyzer-cell-output { - margin-bottom: 20px; - padding: 14px 20px; - border: 1px solid rgb(237, 235, 233); -} - -.schema-analyzer-cell-output:hover { - border-color: rgb(200, 198, 196); - box-shadow: inset 0 0 0 1px rgb(200, 198, 196) -} \ No newline at end of file diff --git a/src/CellOutputViewer/CellOutputViewer.tsx b/src/CellOutputViewer/CellOutputViewer.tsx deleted file mode 100644 index 4deccb7a2..000000000 --- a/src/CellOutputViewer/CellOutputViewer.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import { createImmutableOutput, JSONObject, OnDiskOutput } from "@nteract/commutable"; -// import outputs individually to avoid increasing the bundle size -import { KernelOutputError } from "@nteract/outputs/lib/components/kernel-output-error"; -import { Output } from "@nteract/outputs/lib/components/output"; -import { StreamText } from "@nteract/outputs/lib/components/stream-text"; -import { ContentRef } from "@nteract/types"; -import "bootstrap/dist/css/bootstrap.css"; -import postRobot from "post-robot"; -import * as React from "react"; -import * as ReactDOM from "react-dom"; -import "../../externals/iframeResizer.contentWindow.min.js"; // Required for iFrameResizer to work -import { SnapshotRequest } from "../Explorer/Notebook/NotebookComponent/types"; -import "../Explorer/Notebook/NotebookRenderer/base.css"; -import "../Explorer/Notebook/NotebookRenderer/default.css"; -import { NotebookUtil } from "../Explorer/Notebook/NotebookUtil"; -import "./CellOutputViewer.less"; -import { TransformMedia } from "./TransformMedia"; - -export interface SnapshotResponse { - imageSrc: string; - requestId: string; -} -export interface CellOutputViewerProps { - id: string; - contentRef: ContentRef; - outputsContainerClassName: string; - outputClassName: string; - outputs: OnDiskOutput[]; - onMetadataChange: (metadata: JSONObject, mediaType: string, index?: number) => void; -} - -const onInit = async () => { - postRobot.on( - "props", - { - window: window.parent, - domain: window.location.origin, - }, - (event) => { - // Typescript definition for event is wrong. So read props by casting to - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const props = (event as any).data as CellOutputViewerProps; - const outputs = ( -
- {props.outputs?.map((output, index) => ( -
- - props.onMetadataChange(metadata, mediaType, index)} - /> - props.onMetadataChange(metadata, mediaType, index)} - /> - - - -
- ))} -
- ); - - ReactDOM.render(outputs, document.getElementById("cellOutput")); - }, - ); - - postRobot.on( - "snapshotRequest", - { - window: window.parent, - domain: window.location.origin, - }, - async (event): Promise => { - const topNode = document.getElementById("cellOutput"); - if (!topNode) { - const errorMsg = "No top node to snapshot"; - return Promise.reject(new Error(errorMsg)); - } - - // Typescript definition for event is wrong. So read props by casting to - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const snapshotRequest = (event as any).data as SnapshotRequest; - const result = await NotebookUtil.takeScreenshotDomToImage( - topNode, - snapshotRequest.aspectRatio, - undefined, - snapshotRequest.downloadFilename, - ); - - return { - imageSrc: result.imageSrc, - requestId: snapshotRequest.requestId, - }; - }, - ); -}; - -// Entry point -window.addEventListener("load", onInit); diff --git a/src/CellOutputViewer/TransformMedia.tsx b/src/CellOutputViewer/TransformMedia.tsx deleted file mode 100644 index 1f1c7f67b..000000000 --- a/src/CellOutputViewer/TransformMedia.tsx +++ /dev/null @@ -1,138 +0,0 @@ -import { ImmutableDisplayData, ImmutableExecuteResult, JSONObject } from "@nteract/commutable"; -// import outputs individually to avoid increasing the bundle size -import { HTML } from "@nteract/outputs/lib/components/media/html"; -import { Image } from "@nteract/outputs/lib/components/media/image"; -import { JavaScript } from "@nteract/outputs/lib/components/media/javascript"; -import { Json } from "@nteract/outputs/lib/components/media/json"; -import { LaTeX } from "@nteract/outputs/lib/components/media/latex"; -import { Plain } from "@nteract/outputs/lib/components/media/plain"; -import { SVG } from "@nteract/outputs/lib/components/media/svg"; -import { ContentRef } from "@nteract/types"; -import React, { Suspense } from "react"; - -const EmptyTransform = (): JSX.Element => <>; - -const displayOrder = [ - "application/vnd.jupyter.widget-view+json", - "application/vnd.vega.v5+json", - "application/vnd.vega.v4+json", - "application/vnd.vega.v3+json", - "application/vnd.vega.v2+json", - "application/vnd.vegalite.v4+json", - "application/vnd.vegalite.v3+json", - "application/vnd.vegalite.v2+json", - "application/vnd.vegalite.v1+json", - "application/geo+json", - "application/vnd.plotly.v1+json", - "text/vnd.plotly.v1+html", - "application/x-nteract-model-debug+json", - "application/vnd.dataresource+json", - "application/vdom.v1+json", - "application/json", - "application/javascript", - "text/html", - "text/markdown", - "text/latex", - "image/svg+xml", - "image/gif", - "image/png", - "image/jpeg", - "text/plain", -]; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const transformsById = new Map>([ - ["text/vnd.plotly.v1+html", React.lazy(() => import("@nteract/transform-plotly"))], - ["application/vnd.plotly.v1+json", React.lazy(() => import("@nteract/transform-plotly"))], - ["application/geo+json", EmptyTransform], // TODO: The geojson transform will likely need some work because of the basemap URL(s) - ["application/x-nteract-model-debug+json", React.lazy(() => import("@nteract/transform-model-debug"))], - ["application/vnd.dataresource+json", React.lazy(() => import("@nteract/data-explorer"))], - ["application/vnd.jupyter.widget-view+json", React.lazy(() => import("./transforms/WidgetDisplay"))], - ["application/vnd.vegalite.v1+json", React.lazy(() => import("./transforms/VegaLite1"))], - ["application/vnd.vegalite.v2+json", React.lazy(() => import("./transforms/VegaLite2"))], - ["application/vnd.vegalite.v3+json", React.lazy(() => import("./transforms/VegaLite3"))], - ["application/vnd.vegalite.v4+json", React.lazy(() => import("./transforms/VegaLite4"))], - ["application/vnd.vega.v2+json", React.lazy(() => import("./transforms/Vega2"))], - ["application/vnd.vega.v3+json", React.lazy(() => import("./transforms/Vega3"))], - ["application/vnd.vega.v4+json", React.lazy(() => import("./transforms/Vega4"))], - ["application/vnd.vega.v5+json", React.lazy(() => import("./transforms/Vega5"))], - ["application/vdom.v1+json", React.lazy(() => import("@nteract/transform-vdom"))], - ["application/json", Json], - ["application/javascript", JavaScript], - ["text/html", HTML], - ["text/markdown", React.lazy(() => import("@nteract/outputs/lib/components/media/markdown"))], // Markdown increases the bundle size so lazy load it - ["text/latex", LaTeX], - ["image/svg+xml", SVG], - ["image/gif", Image], - ["image/png", Image], - ["image/jpeg", Image], - ["text/plain", Plain], -]); - -interface TransformMediaProps { - output_type: string; - id: string; - contentRef: ContentRef; - output?: ImmutableDisplayData | ImmutableExecuteResult; - onMetadataChange: (metadata: JSONObject, mediaType: string) => void; -} - -export const TransformMedia = (props: TransformMediaProps): JSX.Element => { - const { Media, mediaType, data, metadata } = getMediaInfo(props); - - // If we had no valid result, return an empty output - if (!mediaType || !data) { - return <>; - } - - return ( - Loading...}> - - - ); -}; - -const getMediaInfo = (props: TransformMediaProps) => { - const { output, output_type } = props; - // This component should only be used with display data and execute result - if (!output || !(output_type === "display_data" || output_type === "execute_result")) { - console.warn("connected transform media managed to get a non media bundle output"); - return { - Media: EmptyTransform, - }; - } - - // Find the first mediaType in the output data that we support with a handler - const mediaType = displayOrder.find( - (key) => - Object.prototype.hasOwnProperty.call(output.data, key) && - (Object.prototype.hasOwnProperty.call(transformsById, key) || transformsById.get(key)), - ); - - if (mediaType) { - const metadata = output.metadata.get(mediaType); - const data = output.data[mediaType]; - - const Media = transformsById.get(mediaType); - return { - Media, - mediaType, - data, - metadata, - }; - } - - return { - Media: EmptyTransform, - mediaType, - output, - }; -}; - -export default TransformMedia; diff --git a/src/CellOutputViewer/cellOutputViewer.html b/src/CellOutputViewer/cellOutputViewer.html deleted file mode 100644 index b801b8113..000000000 --- a/src/CellOutputViewer/cellOutputViewer.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - Cell Output Viewer - - - -
- - diff --git a/src/CellOutputViewer/transforms/Vega2.ts b/src/CellOutputViewer/transforms/Vega2.ts deleted file mode 100644 index 08a358f54..000000000 --- a/src/CellOutputViewer/transforms/Vega2.ts +++ /dev/null @@ -1 +0,0 @@ -export { Vega2 as default } from "@nteract/transform-vega"; diff --git a/src/CellOutputViewer/transforms/Vega3.ts b/src/CellOutputViewer/transforms/Vega3.ts deleted file mode 100644 index 87289b52f..000000000 --- a/src/CellOutputViewer/transforms/Vega3.ts +++ /dev/null @@ -1 +0,0 @@ -export { Vega3 as default } from "@nteract/transform-vega"; diff --git a/src/CellOutputViewer/transforms/Vega4.ts b/src/CellOutputViewer/transforms/Vega4.ts deleted file mode 100644 index a95100d63..000000000 --- a/src/CellOutputViewer/transforms/Vega4.ts +++ /dev/null @@ -1 +0,0 @@ -export { Vega4 as default } from "@nteract/transform-vega"; diff --git a/src/CellOutputViewer/transforms/Vega5.ts b/src/CellOutputViewer/transforms/Vega5.ts deleted file mode 100644 index 6fa20b452..000000000 --- a/src/CellOutputViewer/transforms/Vega5.ts +++ /dev/null @@ -1 +0,0 @@ -export { Vega5 as default } from "@nteract/transform-vega"; diff --git a/src/CellOutputViewer/transforms/VegaLite1.ts b/src/CellOutputViewer/transforms/VegaLite1.ts deleted file mode 100644 index fb615d92c..000000000 --- a/src/CellOutputViewer/transforms/VegaLite1.ts +++ /dev/null @@ -1 +0,0 @@ -export { VegaLite1 as default } from "@nteract/transform-vega"; diff --git a/src/CellOutputViewer/transforms/VegaLite2.ts b/src/CellOutputViewer/transforms/VegaLite2.ts deleted file mode 100644 index f664e0bda..000000000 --- a/src/CellOutputViewer/transforms/VegaLite2.ts +++ /dev/null @@ -1 +0,0 @@ -export { VegaLite2 as default } from "@nteract/transform-vega"; diff --git a/src/CellOutputViewer/transforms/VegaLite3.ts b/src/CellOutputViewer/transforms/VegaLite3.ts deleted file mode 100644 index 8a57abbc4..000000000 --- a/src/CellOutputViewer/transforms/VegaLite3.ts +++ /dev/null @@ -1 +0,0 @@ -export { VegaLite3 as default } from "@nteract/transform-vega"; diff --git a/src/CellOutputViewer/transforms/VegaLite4.ts b/src/CellOutputViewer/transforms/VegaLite4.ts deleted file mode 100644 index fca92c80f..000000000 --- a/src/CellOutputViewer/transforms/VegaLite4.ts +++ /dev/null @@ -1 +0,0 @@ -export { VegaLite4 as default } from "@nteract/transform-vega"; diff --git a/src/CellOutputViewer/transforms/WidgetDisplay.ts b/src/CellOutputViewer/transforms/WidgetDisplay.ts deleted file mode 100644 index 4387a4f5d..000000000 --- a/src/CellOutputViewer/transforms/WidgetDisplay.ts +++ /dev/null @@ -1 +0,0 @@ -export { WidgetDisplay as default } from "@nteract/jupyter-widgets"; diff --git a/src/Common/Constants.ts b/src/Common/Constants.ts index eb1cb44c8..c4d0e1cd5 100644 --- a/src/Common/Constants.ts +++ b/src/Common/Constants.ts @@ -6,7 +6,7 @@ export class EndpointsRegex { public static readonly mongo = "mongodb://.*:(.*)@(.*).documents.azure.com"; public static readonly mongoCompute = "mongodb://.*:(.*)@(.*).mongo.cosmos.azure.com"; public static readonly sql = "AccountEndpoint=https://(.*).documents.azure.com"; - public static readonly table = "TableEndpoint=https://(.*).table.cosmosdb.azure.com"; + public static readonly table = "TableEndpoint=https://(.*).table.cosmos(?:db)?.azure.com"; } export class ApiEndpoints { diff --git a/src/Common/CosmosClient.ts b/src/Common/CosmosClient.ts index 54444d09e..1bff3f80b 100644 --- a/src/Common/CosmosClient.ts +++ b/src/Common/CosmosClient.ts @@ -143,6 +143,7 @@ export async function getTokenFromAuthService( headers: { "content-type": "application/json", "x-ms-encrypted-auth-token": userContext.accessToken, + authorization: userContext.accessToken, }, body: JSON.stringify({ verb, diff --git a/src/Common/MongoProxyClient.ts b/src/Common/MongoProxyClient.ts index b4287e0ee..2e13b434e 100644 --- a/src/Common/MongoProxyClient.ts +++ b/src/Common/MongoProxyClient.ts @@ -22,7 +22,10 @@ const defaultHeaders = { function authHeaders() { if (userContext.authType === AuthType.EncryptedToken) { - return { [HttpHeaders.guestAccessToken]: userContext.accessToken }; + return { + [HttpHeaders.guestAccessToken]: userContext.accessToken, + [HttpHeaders.authorization]: userContext.accessToken, + }; } else { const headers: { [key: string]: string } = { [HttpHeaders.authorization]: userContext.authorizationToken, diff --git a/src/Common/PortalBackendClient.ts b/src/Common/PortalBackendClient.ts new file mode 100644 index 000000000..e2e13afa3 --- /dev/null +++ b/src/Common/PortalBackendClient.ts @@ -0,0 +1,45 @@ +import { configContext } from "../ConfigContext"; +import { AccessInputMetadata } from "../Contracts/DataModels"; +import { HttpHeaders } from "./Constants"; + +export async function fetchAccessData(portalToken: string): Promise { + const headers = new Headers(); + // Portal encrypted token API quirk: The token header must be URL encoded + headers.append(HttpHeaders.guestAccessToken, encodeURIComponent(portalToken)); + headers.append(HttpHeaders.authorization, encodeURIComponent(portalToken)); + const url: string = `${configContext.PORTAL_BACKEND_ENDPOINT}/api/connectionstring/runtimeproxy/accessinputmetadata`; + const options = { + method: "GET", + headers: headers, + }; + + return fetch(url, options) + .then((response) => response.json()) + .catch((error) => console.error(error)); +} + +export async function fetchEncryptedToken(connectionString: string): Promise { + const headers = new Headers(); + headers.append(HttpHeaders.connectionString, connectionString); + headers.append(HttpHeaders.authorization, connectionString); + const url = configContext.PORTAL_BACKEND_ENDPOINT + "/api/connectionstring/token/generatetoken"; + const response = await fetch(url, { headers, method: "POST" }); + if (!response.ok) { + throw response; + } + + const encryptedTokenResponse: string = await response.json(); + return decodeURIComponent(encryptedTokenResponse); +} + +export async function isAccountRestrictedForConnectionStringLogin(connectionString: string): Promise { + const headers = new Headers(); + headers.append(HttpHeaders.connectionString, connectionString); + const url = configContext.PORTAL_BACKEND_ENDPOINT + "/api/guest/accountrestrictions/checkconnectionstringlogin"; + const response = await fetch(url, { headers, method: "POST" }); + if (!response.ok) { + throw response; + } + + return (await response.text()).toLowerCase() === "true"; +} diff --git a/src/ConfigContext.ts b/src/ConfigContext.ts index 969d6617f..76574a117 100644 --- a/src/ConfigContext.ts +++ b/src/ConfigContext.ts @@ -48,9 +48,6 @@ export interface ConfigContext { CASSANDRA_PROXY_ENDPOINT: string; PROXY_PATH?: string; JUNO_ENDPOINT: string; - GITHUB_CLIENT_ID: string; - GITHUB_TEST_ENV_CLIENT_ID: string; - GITHUB_CLIENT_SECRET?: string; // No need to inject secret for prod. Juno already knows it. isPhoenixEnabled: boolean; hostedExplorerURL: string; armAPIVersion?: string; @@ -95,8 +92,6 @@ let configContext: Readonly = { CATALOG_API_KEY: "", ARCADIA_ENDPOINT: "https://workspaceartifacts.projectarcadia.net", ARCADIA_LIVY_ENDPOINT_DNS_ZONE: "dev.azuresynapse.net", - GITHUB_CLIENT_ID: "6cb2f63cf6f7b5cbdeca", // Registered OAuth app: https://github.com/organizations/AzureCosmosDBNotebooks/settings/applications/1189306 - GITHUB_TEST_ENV_CLIENT_ID: "b63fc8cbf87fd3c6e2eb", // Registered OAuth app: https://github.com/organizations/AzureCosmosDBNotebooks/settings/applications/1777772 JUNO_ENDPOINT: JunoEndpoints.Prod, PORTAL_BACKEND_ENDPOINT: PortalBackendEndpoints.Prod, MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod, diff --git a/src/Contracts/ViewModels.ts b/src/Contracts/ViewModels.ts index cfeaf1b95..dcb19f1a8 100644 --- a/src/Contracts/ViewModels.ts +++ b/src/Contracts/ViewModels.ts @@ -160,7 +160,6 @@ export interface Collection extends CollectionBase { onTableEntitiesClick(): void; onGraphDocumentsClick(): void; onMongoDBDocumentsClick(): void; - onSchemaAnalyzerClick(): void; openTab(): void; onSettingsClick: () => Promise; @@ -407,6 +406,7 @@ export enum TerminalKind { Cassandra = 2, Postgres = 3, VCoreMongo = 4, + CosmosDB = 5, } export interface DataExplorerInputsFrame { diff --git a/src/Explorer/ContextMenuButtonFactory.tsx b/src/Explorer/ContextMenuButtonFactory.tsx index 59c49e5ac..f1e85d6cd 100644 --- a/src/Explorer/ContextMenuButtonFactory.tsx +++ b/src/Explorer/ContextMenuButtonFactory.tsx @@ -28,7 +28,6 @@ import { userContext } from "../UserContext"; import { getCollectionName, getDatabaseName } from "../Utils/APITypeUtils"; import { useSidePanel } from "../hooks/useSidePanel"; import Explorer from "./Explorer"; -import { useNotebook } from "./Notebook/useNotebook"; import { DeleteCollectionConfirmationPane } from "./Panes/DeleteCollectionConfirmationPane/DeleteCollectionConfirmationPane"; import { DeleteDatabaseConfirmationPanel } from "./Panes/DeleteDatabaseConfirmationPanel"; import StoredProcedure from "./Tree/StoredProcedure"; @@ -120,23 +119,17 @@ export const createCollectionContextMenuButton = ( iconSrc: HostedTerminalIcon, onClick: () => { const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection(); - if (useNotebook.getState().isShellEnabled || userContext.features.enableCloudShell) { + if (userContext.features.enableCloudShell) { container.openNotebookTerminal(ViewModels.TerminalKind.Mongo); } else { selectedCollection && selectedCollection.onNewMongoShellClick(); } }, - label: - useNotebook.getState().isShellEnabled || userContext.features.enableCloudShell - ? t(Keys.contextMenu.openMongoShell) - : t(Keys.contextMenu.newShell), + label: userContext.features.enableCloudShell ? t(Keys.contextMenu.openMongoShell) : t(Keys.contextMenu.newShell), }); } - if ( - (useNotebook.getState().isShellEnabled || userContext.features.enableCloudShell) && - userContext.apiType === "Cassandra" - ) { + if (userContext.features.enableCloudShell && userContext.apiType === "Cassandra") { items.push({ iconSrc: HostedTerminalIcon, onClick: () => { diff --git a/src/Explorer/Controls/GitHub/AddRepoComponent.tsx b/src/Explorer/Controls/GitHub/AddRepoComponent.tsx deleted file mode 100644 index 08f227ff1..000000000 --- a/src/Explorer/Controls/GitHub/AddRepoComponent.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import { DefaultButton, IButtonProps, ITextFieldProps, TextField } from "@fluentui/react"; -import * as React from "react"; -import * as Constants from "../../../Common/Constants"; -import * as UrlUtility from "../../../Common/UrlUtility"; -import { IGitHubRepo } from "../../../GitHub/GitHubClient"; -import { Action } from "../../../Shared/Telemetry/TelemetryConstants"; -import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor"; -import * as GitHubUtils from "../../../Utils/GitHubUtils"; -import Explorer from "../../Explorer"; -import { RepoListItem } from "./GitHubReposComponent"; -import { ChildrenMargin } from "./GitHubStyleConstants"; - -export interface AddRepoComponentProps { - container: Explorer; - getRepo: (owner: string, repo: string) => Promise; - pinRepo: (item: RepoListItem) => void; -} - -interface AddRepoComponentState { - textFieldValue: string; - textFieldErrorMessage: string; -} - -export class AddRepoComponent extends React.Component { - private static readonly DescriptionText = - "Don't see what you're looking for? Add your repo/branch, or any public repo (read-access only) by entering the URL: "; - private static readonly ButtonText = "Add"; - private static readonly TextFieldPlaceholder = "https://github.com/owner/repo/tree/branch"; - private static readonly TextFieldErrorMessage = "Invalid url"; - - constructor(props: AddRepoComponentProps) { - super(props); - - this.state = { - textFieldValue: "", - textFieldErrorMessage: undefined, - }; - } - - public render(): JSX.Element { - const textFieldProps: ITextFieldProps = { - placeholder: AddRepoComponent.TextFieldPlaceholder, - autoFocus: true, - value: this.state.textFieldValue, - errorMessage: this.state.textFieldErrorMessage, - onChange: this.onTextFieldChange, - }; - - const buttonProps: IButtonProps = { - text: AddRepoComponent.ButtonText, - ariaLabel: AddRepoComponent.ButtonText, - onClick: this.onAddRepoButtonClick, - }; - - return ( - <> -

{AddRepoComponent.DescriptionText}

- - - - ); - } - - private onTextFieldChange = ( - event: React.FormEvent, - newValue?: string, - ): void => { - this.setState({ - textFieldValue: newValue || "", - textFieldErrorMessage: undefined, - }); - }; - - private onAddRepoButtonClick = async (): Promise => { - const startKey: number = TelemetryProcessor.traceStart(Action.NotebooksGitHubManualRepoAdd, { - dataExplorerArea: Constants.Areas.Notebook, - }); - let enteredUrl = this.state.textFieldValue; - if (enteredUrl.indexOf("/tree/") === -1) { - enteredUrl = UrlUtility.createUri(enteredUrl, `tree/`); - } - - const repoInfo = GitHubUtils.fromRepoUri(enteredUrl); - if (repoInfo) { - this.setState({ - textFieldValue: "", - textFieldErrorMessage: undefined, - }); - - const repo = await this.props.getRepo(repoInfo.owner, repoInfo.repo); - if (repo) { - const item: RepoListItem = { - key: GitHubUtils.toRepoFullName(repo.owner, repo.name), - repo, - branches: repoInfo.branch ? [{ name: repoInfo.branch }] : [], - }; - - TelemetryProcessor.traceSuccess( - Action.NotebooksGitHubManualRepoAdd, - { - dataExplorerArea: Constants.Areas.Notebook, - }, - startKey, - ); - return this.props.pinRepo(item); - } - } - - this.setState({ - textFieldErrorMessage: AddRepoComponent.TextFieldErrorMessage, - }); - TelemetryProcessor.traceFailure( - Action.NotebooksGitHubManualRepoAdd, - { - dataExplorerArea: Constants.Areas.Notebook, - error: AddRepoComponent.TextFieldErrorMessage, - }, - startKey, - ); - }; -} diff --git a/src/Explorer/Controls/GitHub/AuthorizeAccessComponent.tsx b/src/Explorer/Controls/GitHub/AuthorizeAccessComponent.tsx deleted file mode 100644 index 2241bd081..000000000 --- a/src/Explorer/Controls/GitHub/AuthorizeAccessComponent.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { ChoiceGroup, IButtonProps, IChoiceGroupProps, PrimaryButton, IChoiceGroupOption } from "@fluentui/react"; -import * as React from "react"; -import { ChildrenMargin } from "./GitHubStyleConstants"; - -export interface AuthorizeAccessComponentProps { - scope: string; - authorizeAccess: (scope: string) => void; -} - -export interface AuthorizeAccessComponentState { - scope: string; -} - -export class AuthorizeAccessComponent extends React.Component< - AuthorizeAccessComponentProps, - AuthorizeAccessComponentState -> { - // Scopes supported by GitHub OAuth. We're only interested in ones which allow us access to repos. - // https://developer.github.com/apps/building-oauth-apps/understanding-scopes-for-oauth-apps/ - public static readonly Scopes = { - Public: { - key: "public_repo", - text: "Public repos only", - }, - PublicAndPrivate: { - key: "repo", - text: "Public and private repos", - }, - }; - - private static readonly DescriptionPara1 = - "Connect your notebooks workspace to GitHub. You'll be able to view, edit, and run notebooks stored in your GitHub repositories in Data Explorer."; - private static readonly DescriptionPara2 = - "Complete setup by authorizing Azure Cosmos DB to access the repositories in your GitHub account: "; - private static readonly AuthorizeButtonText = "Authorize access"; - - private onChoiceGroupChange = (event: React.SyntheticEvent, option: IChoiceGroupOption): void => - this.setState({ - scope: option.key, - }); - - private onButtonClick = (): void => this.props.authorizeAccess(this.state.scope); - - constructor(props: AuthorizeAccessComponentProps) { - super(props); - - this.state = { - scope: this.props.scope, - }; - } - - public render(): JSX.Element { - const choiceGroupProps: IChoiceGroupProps = { - options: [ - { - key: AuthorizeAccessComponent.Scopes.Public.key, - text: AuthorizeAccessComponent.Scopes.Public.text, - ariaLabel: AuthorizeAccessComponent.Scopes.Public.text, - }, - { - key: AuthorizeAccessComponent.Scopes.PublicAndPrivate.key, - text: AuthorizeAccessComponent.Scopes.PublicAndPrivate.text, - ariaLabel: AuthorizeAccessComponent.Scopes.PublicAndPrivate.text, - }, - ], - selectedKey: this.state.scope, - onChange: this.onChoiceGroupChange, - }; - - const buttonProps: IButtonProps = { - text: AuthorizeAccessComponent.AuthorizeButtonText, - ariaLabel: AuthorizeAccessComponent.AuthorizeButtonText, - onClick: this.onButtonClick, - }; - - return ( - <> -

{AuthorizeAccessComponent.DescriptionPara1}

-

{AuthorizeAccessComponent.DescriptionPara2}

- - - - ); - } -} diff --git a/src/Explorer/Controls/GitHub/GitHubReposComponent.tsx b/src/Explorer/Controls/GitHub/GitHubReposComponent.tsx deleted file mode 100644 index d6b28775d..000000000 --- a/src/Explorer/Controls/GitHub/GitHubReposComponent.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { DefaultButton, IButtonProps, Link, PrimaryButton } from "@fluentui/react"; -import * as React from "react"; -import { IGitHubBranch, IGitHubRepo } from "../../../GitHub/GitHubClient"; -import { AddRepoComponent, AddRepoComponentProps } from "./AddRepoComponent"; -import { AuthorizeAccessComponent, AuthorizeAccessComponentProps } from "./AuthorizeAccessComponent"; -import { ButtonsFooterStyle, ChildrenMargin, ContentFooterStyle } from "./GitHubStyleConstants"; -import { ReposListComponent, ReposListComponentProps } from "./ReposListComponent"; - -export interface GitHubReposComponentProps { - showAuthorizeAccess: boolean; - authorizeAccessProps: AuthorizeAccessComponentProps; - reposListProps: ReposListComponentProps; - addRepoProps: AddRepoComponentProps; - resetConnection: () => void; - onOkClick: () => void; - onCancelClick: () => void; -} - -export interface RepoListItem { - key: string; - repo: IGitHubRepo; - branches: IGitHubBranch[]; -} - -export class GitHubReposComponent extends React.Component { - private static readonly ManageGitHubRepoDescription = - "Select your GitHub repos and branch(es) to pin to your notebooks workspace."; - private static readonly ManageGitHubRepoResetConnection = "View or change your GitHub authorization settings."; - private static readonly OKButtonText = "OK"; - private static readonly CancelButtonText = "Cancel"; - - public render(): JSX.Element { - const content: JSX.Element = this.props.showAuthorizeAccess ? ( - - ) : ( - <> -

{GitHubReposComponent.ManageGitHubRepoDescription}

- - {GitHubReposComponent.ManageGitHubRepoResetConnection} - - - - ); - - const okProps: IButtonProps = { - text: GitHubReposComponent.OKButtonText, - ariaLabel: GitHubReposComponent.OKButtonText, - onClick: this.props.onOkClick, - }; - - const cancelProps: IButtonProps = { - text: GitHubReposComponent.CancelButtonText, - ariaLabel: GitHubReposComponent.CancelButtonText, - onClick: this.props.onCancelClick, - }; - - return ( - <> -
{content}
- {!this.props.showAuthorizeAccess && ( - <> -
- -
-
- - -
- - )} - - ); - } -} diff --git a/src/Explorer/Controls/GitHub/GitHubStyleConstants.ts b/src/Explorer/Controls/GitHub/GitHubStyleConstants.ts deleted file mode 100644 index 8018b866b..000000000 --- a/src/Explorer/Controls/GitHub/GitHubStyleConstants.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { - ICheckboxStyleProps, - ICheckboxStyles, - IDropdownStyleProps, - IDropdownStyles, - IStyleFunctionOrObject, -} from "@fluentui/react"; - -export const ButtonsFooterStyle: React.CSSProperties = { - paddingTop: 14, - height: "auto", - borderTop: "2px solid lightGray", -}; - -export const ContentFooterStyle: React.CSSProperties = { - paddingTop: "10px", - height: "auto", - borderTop: "2px solid lightGray", -}; - -export const ChildrenMargin = 10; -export const FontSize = 12; - -export const ReposListCheckboxStyles: IStyleFunctionOrObject = { - label: { - margin: 0, - padding: "2 0 2 0", - }, - text: { - fontSize: FontSize, - }, -}; - -export const BranchesDropdownCheckboxStyles: IStyleFunctionOrObject = { - label: { - margin: 0, - padding: 0, - fontSize: FontSize, - }, - root: { - padding: 0, - }, - text: { - fontSize: FontSize, - }, -}; - -export const BranchesDropdownStyles: IStyleFunctionOrObject = { - title: { - fontSize: FontSize, - }, -}; - -export const BranchesDropdownOptionContainerStyle: React.CSSProperties = { - padding: 8, -}; - -export const ContentMainStyle: React.CSSProperties = { - display: "flex", - flexDirection: "column", -}; - -export const ReposListRepoColumnMinWidth = 192; -export const ReposListBranchesColumnWidth = 116; -export const BranchesDropdownWidth = 200; diff --git a/src/Explorer/Controls/GitHub/ReposListComponent.tsx b/src/Explorer/Controls/GitHub/ReposListComponent.tsx deleted file mode 100644 index 7614e95c4..000000000 --- a/src/Explorer/Controls/GitHub/ReposListComponent.tsx +++ /dev/null @@ -1,309 +0,0 @@ -import { - Checkbox, - DetailsList, - DetailsRow, - Dropdown, - ICheckboxProps, - IDetailsFooterProps, - IDetailsListProps, - IDetailsRowBaseProps, - IDropdown, - IDropdownOption, - IDropdownProps, - ILinkProps, - ISelectableDroppableTextProps, - Link, - ResponsiveMode, - SelectionMode, - Text, -} from "@fluentui/react"; -import * as React from "react"; -import { IGitHubBranch, IGitHubPageInfo } from "../../../GitHub/GitHubClient"; -import * as GitHubUtils from "../../../Utils/GitHubUtils"; -import { RepoListItem } from "./GitHubReposComponent"; -import { - BranchesDropdownCheckboxStyles, - BranchesDropdownOptionContainerStyle, - BranchesDropdownStyles, - BranchesDropdownWidth, - ReposListBranchesColumnWidth, - ReposListCheckboxStyles, - ReposListRepoColumnMinWidth, -} from "./GitHubStyleConstants"; - -export interface ReposListComponentProps { - branchesProps: Record; // key'd by repo key - pinnedReposProps: PinnedReposProps; - unpinnedReposProps: UnpinnedReposProps; - pinRepo: (repo: RepoListItem) => void; - unpinRepo: (repo: RepoListItem) => void; -} - -export interface BranchesProps { - branches: IGitHubBranch[]; - lastPageInfo?: IGitHubPageInfo; - hasMore: boolean; - isLoading: boolean; - defaultBranchName: string; - loadMore: () => void; -} - -export interface PinnedReposProps { - repos: RepoListItem[]; -} - -export interface UnpinnedReposProps { - repos: RepoListItem[]; - hasMore: boolean; - isLoading: boolean; - loadMore: () => void; -} - -export class ReposListComponent extends React.Component { - private static readonly PinnedReposColumnName = "Pinned repos"; - private static readonly UnpinnedReposColumnName = "Unpinned repos"; - private static readonly BranchesColumnName = "Branches"; - private static readonly LoadingText = "Loading..."; - private static readonly LoadMoreText = "Load more"; - private static readonly DefaultBranchNames = "master/main"; - private static readonly FooterIndex = -1; - - public render(): JSX.Element { - const pinnedReposListProps: IDetailsListProps = { - styles: { - contentWrapper: { - height: this.props.pinnedReposProps.repos.length ? undefined : 0, - }, - }, - items: this.props.pinnedReposProps.repos, - getKey: ReposListComponent.getKey, - selectionMode: SelectionMode.none, - compact: true, - columns: [ - { - key: ReposListComponent.PinnedReposColumnName, - name: ReposListComponent.PinnedReposColumnName, - ariaLabel: ReposListComponent.PinnedReposColumnName, - minWidth: ReposListRepoColumnMinWidth, - onRender: this.onRenderPinnedReposColumnItem, - }, - { - key: ReposListComponent.BranchesColumnName, - name: ReposListComponent.BranchesColumnName, - ariaLabel: ReposListComponent.BranchesColumnName, - minWidth: ReposListBranchesColumnWidth, - maxWidth: ReposListBranchesColumnWidth, - onRender: this.onRenderPinnedReposBranchesColumnItem, - }, - ], - onRenderDetailsFooter: this.props.pinnedReposProps.repos.length ? undefined : this.onRenderReposFooter, - }; - - const unpinnedReposListProps: IDetailsListProps = { - items: this.props.unpinnedReposProps.repos, - getKey: ReposListComponent.getKey, - selectionMode: SelectionMode.none, - compact: true, - columns: [ - { - key: ReposListComponent.UnpinnedReposColumnName, - name: ReposListComponent.UnpinnedReposColumnName, - ariaLabel: ReposListComponent.UnpinnedReposColumnName, - minWidth: ReposListRepoColumnMinWidth, - onRender: this.onRenderUnpinnedReposColumnItem, - }, - { - key: ReposListComponent.BranchesColumnName, - name: ReposListComponent.BranchesColumnName, - ariaLabel: ReposListComponent.BranchesColumnName, - minWidth: ReposListBranchesColumnWidth, - maxWidth: ReposListBranchesColumnWidth, - onRender: this.onRenderUnpinnedReposBranchesColumnItem, - }, - ], - onRenderDetailsFooter: - this.props.unpinnedReposProps.isLoading || this.props.unpinnedReposProps.hasMore - ? this.onRenderReposFooter - : undefined, - }; - - return ( - <> - - - - ); - } - - private onRenderPinnedReposColumnItem = (item: RepoListItem, index: number): JSX.Element => { - if (index === ReposListComponent.FooterIndex) { - return None; - } - - const checkboxProps: ICheckboxProps = { - ...ReposListComponent.getCheckboxPropsForLabel(GitHubUtils.toRepoFullName(item.repo.owner, item.repo.name)), - styles: ReposListCheckboxStyles, - defaultChecked: true, - onChange: () => this.props.unpinRepo(item), - }; - - return ; - }; - - private onRenderPinnedReposBranchesColumnItem = (item: RepoListItem, index: number): JSX.Element => { - if (index === ReposListComponent.FooterIndex) { - return <>; - } - - const branchesProps = this.props.branchesProps[GitHubUtils.toRepoFullName(item.repo.owner, item.repo.name)]; - if (item.branches.length === 0 && branchesProps.defaultBranchName) { - item.branches = [{ name: branchesProps.defaultBranchName }]; - } - - const options: IDropdownOption[] = branchesProps.branches.map((branch) => ({ - key: branch.name, - text: branch.name, - data: item, - disabled: item.branches.length === 1 && branch.name === item.branches[0].name, - selected: item.branches.findIndex((element) => element.name === branch.name) !== -1, - })); - - if (branchesProps.hasMore || branchesProps.isLoading) { - const text = branchesProps.isLoading ? ReposListComponent.LoadingText : ReposListComponent.LoadMoreText; - options.push({ - key: text, - text, - data: item, - index: ReposListComponent.FooterIndex, - }); - } - - const dropdownProps: IDropdownProps = { - styles: BranchesDropdownStyles, - dropdownWidth: BranchesDropdownWidth, - responsiveMode: ResponsiveMode.large, - options, - onRenderList: this.onRenderBranchesDropdownList, - }; - - if (item.branches.length === 1) { - dropdownProps.placeholder = item.branches[0].name; - } else if (item.branches.length > 1) { - dropdownProps.placeholder = `${item.branches.length} branches`; - } - - return ; - }; - - private onRenderUnpinnedReposBranchesColumnItem = (item: RepoListItem, index: number): JSX.Element => { - if (index === ReposListComponent.FooterIndex) { - return <>; - } - - const dropdownProps: IDropdownProps = { - styles: BranchesDropdownStyles, - options: [], - placeholder: ReposListComponent.DefaultBranchNames, - disabled: true, - }; - - return ; - }; - - private onRenderBranchesDropdownList = ( - props: ISelectableDroppableTextProps, - ): JSX.Element => { - const renderedList: JSX.Element[] = []; - props.options.forEach((option: IDropdownOption) => { - const item = ( -
- {this.onRenderPinnedReposBranchesDropdownOption(option)} -
- ); - renderedList.push(item); - }); - - return <>{renderedList}; - }; - - private onRenderPinnedReposBranchesDropdownOption(option: IDropdownOption): JSX.Element { - const item: RepoListItem = option.data; - const branchesProps = this.props.branchesProps[GitHubUtils.toRepoFullName(item.repo.owner, item.repo.name)]; - - if (option.index === ReposListComponent.FooterIndex) { - const linkProps: ILinkProps = { - disabled: branchesProps.isLoading, - onClick: branchesProps.loadMore, - }; - - return {option.text}; - } - - const checkboxProps: ICheckboxProps = { - ...ReposListComponent.getCheckboxPropsForLabel(option.text), - styles: BranchesDropdownCheckboxStyles, - defaultChecked: option.selected, - disabled: option.disabled, - onChange: (event, checked) => { - const repoListItem = { ...item }; - const branch: IGitHubBranch = { name: option.text }; - repoListItem.branches = repoListItem.branches.filter((element) => element.name !== branch.name); - if (checked) { - repoListItem.branches.push(branch); - } - - this.props.pinRepo(repoListItem); - }, - }; - - return ; - } - - private onRenderUnpinnedReposColumnItem = (item: RepoListItem, index: number): JSX.Element => { - if (index === ReposListComponent.FooterIndex) { - const linkProps: ILinkProps = { - disabled: this.props.unpinnedReposProps.isLoading, - onClick: this.props.unpinnedReposProps.loadMore, - }; - - const linkText = this.props.unpinnedReposProps.isLoading - ? ReposListComponent.LoadingText - : ReposListComponent.LoadMoreText; - return {linkText}; - } - - const checkboxProps: ICheckboxProps = { - ...ReposListComponent.getCheckboxPropsForLabel(GitHubUtils.toRepoFullName(item.repo.owner, item.repo.name)), - styles: ReposListCheckboxStyles, - onChange: () => { - const repoListItem = { ...item }; - repoListItem.branches = []; - this.props.pinRepo(repoListItem); - }, - }; - - return ; - }; - - private onRenderReposFooter = (detailsFooterProps: IDetailsFooterProps): JSX.Element => { - const props: IDetailsRowBaseProps = { - ...detailsFooterProps, - item: {}, - itemIndex: ReposListComponent.FooterIndex, - }; - - return ; - }; - - private static getCheckboxPropsForLabel(label: string): ICheckboxProps { - return { - label, - title: label, - ariaLabel: label, - }; - } - - private static getKey(item: RepoListItem): string { - return item.key; - } -} diff --git a/src/Explorer/Controls/Notebook/NotebookTerminalComponent.less b/src/Explorer/Controls/Notebook/NotebookTerminalComponent.less deleted file mode 100644 index 305c8827c..000000000 --- a/src/Explorer/Controls/Notebook/NotebookTerminalComponent.less +++ /dev/null @@ -1,13 +0,0 @@ -@import "../../../../less/Common/Constants"; - -.notebookTerminalContainer { - padding: @DefaultSpace; - height: 100%; - width: 100%; - - iframe { - border: none; - height: 100%; - width: 100%; - } -} \ No newline at end of file diff --git a/src/Explorer/Controls/Notebook/NotebookTerminalComponent.test.tsx b/src/Explorer/Controls/Notebook/NotebookTerminalComponent.test.tsx deleted file mode 100644 index bd816ac31..000000000 --- a/src/Explorer/Controls/Notebook/NotebookTerminalComponent.test.tsx +++ /dev/null @@ -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(); - expect(wrapper).toMatchSnapshot(); - }); - - it("renders mongo 3.2 shell", () => { - const props: NotebookTerminalComponentProps = { - databaseAccount: testMongo32Account, - notebookServerInfo: testMongoNotebookServerInfo, - tabId: undefined, - }; - - const wrapper = shallow(); - expect(wrapper).toMatchSnapshot(); - }); - - it("renders mongo 3.6 shell", () => { - const props: NotebookTerminalComponentProps = { - databaseAccount: testMongo36Account, - notebookServerInfo: testMongoNotebookServerInfo, - tabId: undefined, - }; - - const wrapper = shallow(); - expect(wrapper).toMatchSnapshot(); - }); - - it("renders cassandra shell", () => { - const props: NotebookTerminalComponentProps = { - databaseAccount: testCassandraAccount, - notebookServerInfo: testCassandraNotebookServerInfo, - tabId: undefined, - }; - - const wrapper = shallow(); - expect(wrapper).toMatchSnapshot(); - }); - - it("renders Postgres shell", () => { - const props: NotebookTerminalComponentProps = { - databaseAccount: testPostgresAccount, - notebookServerInfo: testPostgresNotebookServerInfo, - tabId: undefined, - }; - - const wrapper = shallow(); - expect(wrapper).toMatchSnapshot(); - }); - - it("renders vCore Mongo shell", () => { - const props: NotebookTerminalComponentProps = { - databaseAccount: testVCoreMongoAccount, - notebookServerInfo: testVCoreMongoNotebookServerInfo, - tabId: undefined, - username: "username", - }; - - const wrapper = shallow(); - expect(wrapper).toMatchSnapshot(); - }); -}); diff --git a/src/Explorer/Controls/Notebook/NotebookTerminalComponent.tsx b/src/Explorer/Controls/Notebook/NotebookTerminalComponent.tsx deleted file mode 100644 index 96d41155e..000000000 --- a/src/Explorer/Controls/Notebook/NotebookTerminalComponent.tsx +++ /dev/null @@ -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 { - private terminalWindow: Window; - - constructor(props: NotebookTerminalComponentProps) { - super(props); - } - - componentDidMount(): void { - this.sendPropsToTerminalFrame(); - } - - public render(): JSX.Element { - return ( -
- )} - {!isLoggedIn && !encryptedTokenMetadata && ( - + {!isLoggedIn && !accountMetadata && ( + )} {isLoggedIn && authFailure && } {isLoggedIn && !authFailure && ( diff --git a/src/HostedExplorerChildFrame.ts b/src/HostedExplorerChildFrame.ts index 2cff6c862..3a38170ac 100644 --- a/src/HostedExplorerChildFrame.ts +++ b/src/HostedExplorerChildFrame.ts @@ -14,10 +14,12 @@ export interface AAD { export interface ConnectionString { authType: AuthType.ConnectionString; - // Connection string uses still use encrypted token for Cassandra/Mongo APIs as they us the portal backend proxy - encryptedToken: string; + // SQL, Table, and Gremlin sign data-plane requests client-side with the master key and do not need the + // proxies, so they carry no encrypted token. Mongo and Cassandra still use the encrypted + // token because their operations go through the proxies. + encryptedToken?: string; encryptedTokenMetadata: AccessInputMetadata; - // Master key is currently only used by Graph API. All other APIs use encrypted tokens and proxy with connection string + // Master key is used for the client-side signing path (SQL, Table, Gremlin). Mongo/Cassandra leave it undefined. masterKey?: string; } diff --git a/src/Juno/JunoClient.test.ts b/src/Juno/JunoClient.test.ts deleted file mode 100644 index ae7542926..000000000 --- a/src/Juno/JunoClient.test.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { HttpStatusCodes } from "../Common/Constants"; -import { IPinnedRepo, JunoClient } from "./JunoClient"; - -const samplePinnedRepos: IPinnedRepo[] = [ - { - owner: "owner", - name: "name", - private: false, - branches: [ - { - name: "name", - }, - ], - }, -]; - -describe("Pinned repos", () => { - const junoClient = new JunoClient(); - - beforeEach(() => { - window.fetch = jest.fn().mockImplementation(() => { - return { - status: HttpStatusCodes.OK, - text: () => JSON.stringify(samplePinnedRepos), - }; - }); - }); - - afterEach(() => { - jest.resetAllMocks(); - }); - - it("updatePinnedRepos invokes pinned repos subscribers", async () => { - // eslint-disable-next-line @typescript-eslint/no-empty-function - const callback = jest.fn().mockImplementation(() => {}); - - junoClient.subscribeToPinnedRepos(callback); - const response = await junoClient.updatePinnedRepos(samplePinnedRepos); - - expect(response.status).toBe(HttpStatusCodes.OK); - expect(callback).toHaveBeenCalledWith(samplePinnedRepos); - }); - - it("getPinnedRepos invokes pinned repos subscribers", async () => { - // eslint-disable-next-line @typescript-eslint/no-empty-function - const callback = jest.fn().mockImplementation(() => {}); - - junoClient.subscribeToPinnedRepos(callback); - const response = await junoClient.getPinnedRepos("scope"); - - expect(response.status).toBe(HttpStatusCodes.OK); - expect(callback).toHaveBeenCalledWith(samplePinnedRepos); - }); -}); - -describe("GitHub", () => { - const junoClient = new JunoClient(); - - afterEach(() => { - jest.resetAllMocks(); - }); - - it("getGitHubToken", async () => { - let fetchUrl: string; - window.fetch = jest.fn().mockImplementation((url: string) => { - fetchUrl = url; - - return { - status: HttpStatusCodes.OK, - text: () => JSON.stringify({ access_token: "token" }), - }; - }); - - const response = await junoClient.getGitHubToken("code"); - - expect(response.status).toBe(HttpStatusCodes.OK); - expect(response.data.access_token).toBeDefined(); - expect(window.fetch).toHaveBeenCalled(); - - const fetchUrlParams = new URLSearchParams(new URL(fetchUrl).search); - let fetchUrlParamsCount = 0; - fetchUrlParams.forEach(() => fetchUrlParamsCount++); - - expect(fetchUrlParamsCount).toBe(2); - expect(fetchUrlParams.get("code")).toBeDefined(); - expect(fetchUrlParams.get("client_id")).toBeDefined(); - }); - - it("deleteAppauthorization", async () => { - let fetchUrl: string; - window.fetch = jest.fn().mockImplementation((url: string) => { - fetchUrl = url; - - return { - status: HttpStatusCodes.NoContent, - text: () => undefined as string, - }; - }); - - const response = await junoClient.deleteAppAuthorization("token"); - - expect(response.status).toBe(HttpStatusCodes.NoContent); - expect(window.fetch).toHaveBeenCalled(); - - const fetchUrlParams = new URLSearchParams(new URL(fetchUrl).search); - let fetchUrlParamsCount = 0; - fetchUrlParams.forEach(() => fetchUrlParamsCount++); - - expect(fetchUrlParamsCount).toBe(2); - expect(fetchUrlParams.get("access_token")).toBeDefined(); - expect(fetchUrlParams.get("client_id")).toBeDefined(); - }); -}); diff --git a/src/Juno/JunoClient.ts b/src/Juno/JunoClient.ts index 77ba61764..839ba22f9 100644 --- a/src/Juno/JunoClient.ts +++ b/src/Juno/JunoClient.ts @@ -1,12 +1,7 @@ import { allowedJunoOrigins, validateEndpoint } from "Utils/EndpointUtils"; -import { GetGithubClientId } from "Utils/GitHubUtils"; -import ko from "knockout"; import { HttpHeaders, HttpStatusCodes } from "../Common/Constants"; import { configContext } from "../ConfigContext"; import * as DataModels from "../Contracts/DataModels"; -import { AuthorizeAccessComponent } from "../Explorer/Controls/GitHub/AuthorizeAccessComponent"; -import { IGitHubResponse } from "../GitHub/GitHubClient"; -import { IGitHubOAuthToken } from "../GitHub/GitHubOAuthService"; import { userContext } from "../UserContext"; import { getAuthorizationHeader } from "../Utils/AuthorizationUtils"; @@ -15,17 +10,6 @@ export interface IJunoResponse { data: T; } -export interface IPinnedRepo { - owner: string; - name: string; - private: boolean; - branches: IPinnedBranch[]; -} - -export interface IPinnedBranch { - name: string; -} - export interface IGalleryItem { id: string; name: string; @@ -45,113 +29,6 @@ export interface IGalleryItem { } export class JunoClient { - private cachedPinnedRepos: ko.Observable; - - constructor() { - this.cachedPinnedRepos = ko.observable([]); - } - - public subscribeToPinnedRepos(callback: ko.SubscriptionCallback): ko.Subscription { - return this.cachedPinnedRepos.subscribe(callback); - } - - public async getPinnedRepos(scope: string): Promise> { - const response = await window.fetch(`${this.getNotebooksSubscriptionIdAccountUrl()}/github/pinnedrepos`, { - headers: JunoClient.getHeaders(), - }); - - let pinnedRepos: IPinnedRepo[]; - if (response.status === HttpStatusCodes.OK) { - pinnedRepos = JSON.parse(await response.text()); - - // In case we're restricted to public only scope, we return only public repos - if (scope === AuthorizeAccessComponent.Scopes.Public.key) { - pinnedRepos = pinnedRepos.filter((repo) => !repo.private); - } - - this.cachedPinnedRepos(pinnedRepos); - } - - return { - status: response.status, - data: pinnedRepos, - }; - } - - public async updatePinnedRepos(repos: IPinnedRepo[]): Promise> { - const response = await window.fetch(`${this.getNotebooksSubscriptionIdAccountUrl()}/github/pinnedrepos`, { - method: "PUT", - body: JSON.stringify(repos), - headers: JunoClient.getHeaders(), - }); - - if (response.status === HttpStatusCodes.OK) { - this.cachedPinnedRepos(repos); - } - - return { - status: response.status, - data: undefined, - }; - } - - public async deleteGitHubInfo(): Promise> { - const response = await window.fetch(`${this.getNotebooksSubscriptionIdAccountUrl()}/github`, { - method: "DELETE", - headers: JunoClient.getHeaders(), - }); - - return { - status: response.status, - data: undefined, - }; - } - - public async getGitHubToken(code: string): Promise> { - const githubParams = JunoClient.getGitHubClientParams(); - githubParams.append("code", code); - - const response = await window.fetch( - `${this.getNotebooksSubscriptionIdAccountUrl()}/github/token?${githubParams.toString()}`, - { - headers: JunoClient.getHeaders(), - }, - ); - - let data: IGitHubOAuthToken; - const body = await response.text(); - if (body) { - data = JSON.parse(body); - } else { - data = { - error: response.statusText, - }; - } - - return { - status: response.status, - data, - }; - } - - public async deleteAppAuthorization(token: string): Promise> { - const githubParams = JunoClient.getGitHubClientParams(); - githubParams.append("access_token", token); - - const response = await window.fetch( - `${this.getNotebooksSubscriptionIdAccountUrl()}/github/token?${githubParams.toString()}`, - { - method: "DELETE", - headers: JunoClient.getHeaders(), - }, - ); - - return { - status: response.status, - data: await response.text(), - }; - } - public async increaseNotebookViews(id: string): Promise> { const response = await window.fetch(`${this.getNotebooksUrl()}/gallery/${id}/views`, { method: "PATCH", @@ -245,18 +122,6 @@ export class JunoClient { return `${JunoClient.getJunoEndpoint()}/api/notebooks`; } - private getAccount(): string { - return userContext?.databaseAccount?.name; - } - - private getSubscriptionId(): string { - return userContext.subscriptionId; - } - - private getNotebooksSubscriptionIdAccountUrl(): string { - return `${this.getNotebooksUrl()}/subscriptions/${this.getSubscriptionId()}/databaseAccounts/${this.getAccount()}`; - } - private getAnalyticsUrl(): string { return `${JunoClient.getJunoEndpoint()}/api/analytics`; } @@ -268,16 +133,4 @@ export class JunoClient { [HttpHeaders.contentType]: "application/json", }; } - - private static getGitHubClientParams(): URLSearchParams { - const githubParams = new URLSearchParams({ - client_id: GetGithubClientId(), - }); - - if (configContext.GITHUB_CLIENT_SECRET) { - githubParams.append("client_secret", configContext.GITHUB_CLIENT_SECRET); - } - - return githubParams; - } } diff --git a/src/Localization/en/Resources.json b/src/Localization/en/Resources.json index 6ac748936..f0301eb47 100644 --- a/src/Localization/en/Resources.json +++ b/src/Localization/en/Resources.json @@ -990,6 +990,8 @@ "quantizationByteSizeTooltipContainerName": "container", "quantizationByteSizeTooltipGlobalSecondaryIndexName": "global secondary index", "quantizerType": "Quantizer type", + "quantizerTypeProduct": "Product", + "quantizerTypeSpherical": "Spherical", "quantizerTypeTooltip": "The quantization method used by the vector index.", "indexingSearchListSize": "Indexing search list size", "vectorIndexShardKey": "Vector index shard key", @@ -1173,4 +1175,4 @@ } } } -} \ No newline at end of file +} diff --git a/src/Main.tsx b/src/Main.tsx index 0c18c9561..e60a5e863 100644 --- a/src/Main.tsx +++ b/src/Main.tsx @@ -47,14 +47,11 @@ import "./Explorer/Controls/CollapsiblePanel/CollapsiblePanelComponent.less"; import { Dialog } from "./Explorer/Controls/Dialog"; import "./Explorer/Controls/ErrorDisplayComponent/ErrorDisplayComponent.less"; import "./Explorer/Controls/JsonEditor/JsonEditorComponent.less"; -import "./Explorer/Controls/Notebook/NotebookTerminalComponent.less"; import "./Explorer/Controls/TreeComponent/treeComponent.less"; import { ErrorBoundary } from "./Explorer/ErrorBoundary"; import "./Explorer/Graph/GraphExplorerComponent/graphExplorer.less"; import "./Explorer/Menus/CommandBar/CommandBarComponent.less"; import { CommandBar } from "./Explorer/Menus/CommandBar/CommandBarComponentAdapter"; -import "./Explorer/Menus/CommandBar/ConnectionStatusComponent.less"; -import "./Explorer/Menus/CommandBar/MemoryTrackerComponent.less"; import "./Explorer/Menus/NotificationConsole/NotificationConsole.less"; import { NotificationConsole } from "./Explorer/Menus/NotificationConsole/NotificationConsoleComponent"; import "./Explorer/Panes/PanelComponent.less"; diff --git a/src/Phoenix/PhoenixClient.ts b/src/Phoenix/PhoenixClient.ts deleted file mode 100644 index f1d722e3b..000000000 --- a/src/Phoenix/PhoenixClient.ts +++ /dev/null @@ -1,256 +0,0 @@ -import { configContext } from "ConfigContext"; -import { useDialog } from "Explorer/Controls/Dialog"; -import { Action } from "Shared/Telemetry/TelemetryConstants"; -import { userContext } from "UserContext"; -import { allowedJunoOrigins, validateEndpoint } from "Utils/EndpointUtils"; -import promiseRetry, { AbortError, Options } from "p-retry"; -import { - Areas, - ConnectionStatusType, - ContainerStatusType, - HttpHeaders, - HttpStatusCodes, - Notebook, -} from "../Common/Constants"; -import { getErrorMessage } from "../Common/ErrorHandlingUtils"; -import * as Logger from "../Common/Logger"; -import { - ContainerConnectionInfo, - ContainerInfo, - IContainerData, - IMaxAllocationTimeExceeded, - IPhoenixConnectionInfoResult, - IPhoenixError, - IPhoenixServiceInfo, - IProvisionData, - IResponse, - PhoenixErrorType, -} from "../Contracts/DataModels"; -import { useNotebook } from "../Explorer/Notebook/useNotebook"; -import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor"; -import { getAuthorizationHeader } from "../Utils/AuthorizationUtils"; - -export class PhoenixClient { - private armResourceId: string; - private containerHealthHandler: NodeJS.Timeout; - private retryOptions: Options = { - retries: Notebook.retryAttempts, - maxTimeout: Notebook.retryAttemptDelayMs, - minTimeout: Notebook.retryAttemptDelayMs, - }; - private abortController: AbortController; - private abortSignal: AbortSignal; - - constructor(armResourceId: string) { - this.armResourceId = armResourceId; - } - - public async allocateContainer(provisionData: IProvisionData): Promise> { - this.initializeCancelEventListener(); - - return promiseRetry(() => this.executeContainerAssignmentOperation(provisionData, "allocate"), { - retries: 4, - maxTimeout: 20000, - minTimeout: 20000, - signal: this.abortSignal, - }); - } - - public async resetContainer(provisionData: IProvisionData): Promise> { - return this.executeContainerAssignmentOperation(provisionData, "reset"); - } - - private async executeContainerAssignmentOperation( - provisionData: IProvisionData, - operation: string, - ): Promise> { - let response; - try { - response = await fetch(`${this.getPhoenixControlPlanePathPrefix()}/containerconnections/multicontainer`, { - method: operation === "allocate" ? "POST" : "PATCH", - headers: PhoenixClient.getHeaders(), - body: JSON.stringify(provisionData), - }); - const responseJson = await response?.json(); - if (response.ok) { - const phoenixConnectionInfoResult = responseJson as IPhoenixConnectionInfoResult[]; - if ( - !phoenixConnectionInfoResult || - phoenixConnectionInfoResult.length === 0 || - !phoenixConnectionInfoResult[0] - ) { - throw new Error("Received invalid phoenix connection response."); - } - return { - status: response.status, - data: phoenixConnectionInfoResult[0].phoenixServiceInfo, - }; - } - const phoenixError = responseJson as IPhoenixError; - if (response.status === HttpStatusCodes.Forbidden) { - if (phoenixError.message === "Sequence contains no elements") { - throw Error("Phoenix container allocation failed, please try again later."); - } - throw new AbortError(this.ConvertToForbiddenErrorString(phoenixError)); - } - throw new AbortError(phoenixError.message); - } catch (error) { - error.status = response?.status; - throw error; - } - } - - public async initiateContainerHeartBeat(shouldUseNotebookStates: boolean, containerData: IContainerData) { - if (this.containerHealthHandler) { - clearTimeout(this.containerHealthHandler); - } - await this.getContainerHealth(shouldUseNotebookStates, Notebook.containerStatusHeartbeatDelayMs, containerData); - } - - private scheduleContainerHeartbeat( - shouldUseNotebookStates: boolean, - delayMs: number, - containerData: IContainerData, - ): void { - this.containerHealthHandler = setTimeout(async () => { - await this.getContainerHealth(shouldUseNotebookStates, delayMs, containerData); - }, delayMs); - } - - private async getContainerStatusAsync( - shouldUseNotebookStates: boolean, - containerData: IContainerData, - ): Promise { - try { - const runContainerStatusAsync = async () => { - const response = await window.fetch( - `${this.getPhoenixControlPlanePathPrefix()}/${containerData.forwardingId}`, - { - method: "GET", - headers: PhoenixClient.getHeaders(), - }, - ); - if (response.status === HttpStatusCodes.OK) { - const containerStatus = await response.json(); - return { - durationLeftInMinutes: containerStatus?.durationLeftInMinutes, - phoenixServerInfo: containerStatus?.phoenixServerInfo, - status: ContainerStatusType.Active, - }; - } else if (response.status === HttpStatusCodes.NotFound) { - const error = "Disconnected from compute workspace"; - Logger.logError(error, ""); - const connectionStatus: ContainerConnectionInfo = { - status: ConnectionStatusType.Reconnect, - }; - TelemetryProcessor.traceMark(Action.PhoenixHeartBeat, { - dataExplorerArea: Areas.Notebook, - message: getErrorMessage(error), - }); - shouldUseNotebookStates ? useNotebook.getState().resetContainerConnection(connectionStatus) : undefined; - shouldUseNotebookStates && useNotebook.getState().setIsRefreshed(!useNotebook.getState().isRefreshed); - shouldUseNotebookStates && - useDialog - .getState() - .showOkModalDialog( - "Disconnected", - "Disconnected from temporary workspace. Please click on connect button to connect to temporary workspace.", - ); - throw new AbortError(response.statusText); - } else if (response?.status === HttpStatusCodes.Forbidden) { - const validationMessage = this.ConvertToForbiddenErrorString(await response.json()); - if (validationMessage) { - useDialog.getState().showOkModalDialog("Connection Failed", `${validationMessage}`); - } - throw new AbortError(response.statusText); - } - throw new Error(response.statusText); - }; - return await promiseRetry(runContainerStatusAsync, this.retryOptions); - } catch (error) { - TelemetryProcessor.traceFailure(Action.PhoenixHeartBeat, { - dataExplorerArea: Areas.Notebook, - }); - Logger.logError(getErrorMessage(error), ""); - const connectionStatus: ContainerConnectionInfo = { - status: ConnectionStatusType.Failed, - }; - shouldUseNotebookStates ? useNotebook.getState().resetContainerConnection(connectionStatus) : undefined; - shouldUseNotebookStates && useNotebook.getState().setIsRefreshed(!useNotebook.getState().isRefreshed); - return { - durationLeftInMinutes: undefined, - phoenixServerInfo: undefined, - status: ContainerStatusType.Disconnected, - }; - } - } - - private async getContainerHealth(shouldUseNotebookStates: boolean, delayMs: number, containerData: IContainerData) { - const containerInfo = await this.getContainerStatusAsync(shouldUseNotebookStates, containerData); - shouldUseNotebookStates ? useNotebook.getState().setContainerStatus(containerInfo) : undefined; - - const containerStatus = shouldUseNotebookStates ? useNotebook.getState().containerStatus?.status : undefined; - if (containerStatus === ContainerStatusType.Active) { - this.scheduleContainerHeartbeat(shouldUseNotebookStates, delayMs, containerData); - } - } - - private getPhoenixControlPlanePathPrefix(): string { - if (!this.armResourceId) { - throw new Error("The Phoenix client was not initialized properly: missing ARM resource id"); - } - - const toolsEndpoint = - userContext.features.phoenixEndpoint ?? userContext.features.junoEndpoint ?? configContext.JUNO_ENDPOINT; - - if (!validateEndpoint(toolsEndpoint, allowedJunoOrigins)) { - const error = `${toolsEndpoint} not allowed as tools endpoint`; - console.error(error); - throw new Error(error); - } - - return `${toolsEndpoint}/api/controlplane/toolscontainer/cosmosaccounts${this.armResourceId}`; - } - - private static getHeaders(): HeadersInit { - const authorizationHeader = getAuthorizationHeader(); - return { - [authorizationHeader.header]: authorizationHeader.token, - [HttpHeaders.contentType]: "application/json", - }; - } - - private initializeCancelEventListener(): void { - this.abortController = new AbortController(); - this.abortSignal = this.abortController.signal; - - document.addEventListener("keydown", (event: KeyboardEvent) => { - if (event.ctrlKey && (event.key === "c" || event.key === "z")) { - this.abortController.abort(new AbortError("Request canceled")); - } - }); - } - - public ConvertToForbiddenErrorString(jsonData: IPhoenixError): string { - const errInfo = jsonData; - switch (errInfo?.type) { - case PhoenixErrorType.MaxAllocationTimeExceeded: { - const maxAllocationTimeExceeded = errInfo as IMaxAllocationTimeExceeded; - const allocateAfterTimestamp = new Date(maxAllocationTimeExceeded?.earliestAllocationTimestamp); - allocateAfterTimestamp.setDate(allocateAfterTimestamp.getDate() + 1); - return `${errInfo.message}` + ". Please try again after " + `${allocateAfterTimestamp.toLocaleString()}`; - } - case PhoenixErrorType.MaxDbAccountsPerUserExceeded: - case PhoenixErrorType.MaxUsersPerDbAccountExceeded: - case PhoenixErrorType.AllocationValidationResult: - case PhoenixErrorType.RegionNotServicable: - case PhoenixErrorType.UserMissingPermissionsError: - case PhoenixErrorType.SubscriptionNotAllowed: { - return `${errInfo.message}`; - } - default: { - return undefined; - } - } - } -} diff --git a/src/Platform/Hosted/Components/ConnectExplorer.test.tsx b/src/Platform/Hosted/Components/ConnectExplorer.test.tsx index dc54a5f2a..a98a89ccc 100644 --- a/src/Platform/Hosted/Components/ConnectExplorer.test.tsx +++ b/src/Platform/Hosted/Components/ConnectExplorer.test.tsx @@ -12,8 +12,20 @@ it("shows the connect form", () => { const setConnectionString = jest.fn(); const setEncryptedToken = jest.fn(); const setAuthType = jest.fn(); + const setAccountMetadata = jest.fn(); - render(); + render( + , + ); expect(screen.queryByPlaceholderText("Please enter a connection string")).toBeNull(); fireEvent.click(screen.getByText("Connect to your account with connection string")); expect(screen.queryByPlaceholderText("Please enter a connection string")).toBeDefined(); @@ -25,6 +37,7 @@ it("hides the connection string link when feature.disableConnectionStringLogin i const setConnectionString = jest.fn(); const setEncryptedToken = jest.fn(); const setAuthType = jest.fn(); + const setAccountMetadata = jest.fn(); const oldFeatures = userContext.features; const params = new URLSearchParams({ @@ -34,7 +47,18 @@ it("hides the connection string link when feature.disableConnectionStringLogin i const testFeatures = extractFeatures(params); updateUserContext({ features: testFeatures }); - render(); + render( + , + ); expect(screen.queryByPlaceholderText("Connect to your account with connection string")).toBeNull(); updateUserContext({ features: oldFeatures }); diff --git a/src/Platform/Hosted/Components/ConnectExplorer.tsx b/src/Platform/Hosted/Components/ConnectExplorer.tsx index 768fddbb6..99ce638cd 100644 --- a/src/Platform/Hosted/Components/ConnectExplorer.tsx +++ b/src/Platform/Hosted/Components/ConnectExplorer.tsx @@ -1,12 +1,15 @@ import { useBoolean } from "@fluentui/react-hooks"; +import { getErrorMessage } from "Common/ErrorHandlingUtils"; import { userContext } from "UserContext"; import * as React from "react"; import ConnectImage from "../../../../images/HdeConnectCosmosDB.svg"; import ErrorImage from "../../../../images/error.svg"; import { AuthType } from "../../../AuthType"; -import { HttpHeaders } from "../../../Common/Constants"; -import { configContext } from "../../../ConfigContext"; +import { fetchEncryptedToken, isAccountRestrictedForConnectionStringLogin } from "../../../Common/PortalBackendClient"; +import { AccessInputMetadata } from "../../../Contracts/DataModels"; +import { parseConnectionString } from "../Helpers/ConnectionStringParser"; import { isResourceTokenConnectionString } from "../Helpers/ResourceTokenUtils"; +import { isDirectConnectionStringLoginApi } from "../HostedUtils"; interface Props { connectionString: string; @@ -14,39 +17,16 @@ interface Props { setEncryptedToken: (token: string) => void; setConnectionString: (connectionString: string) => void; setAuthType: (authType: AuthType) => void; + setAccountMetadata: (metadata: AccessInputMetadata) => void; } -export const fetchEncryptedToken = async (connectionString: string): Promise => { - const headers = new Headers(); - headers.append(HttpHeaders.connectionString, connectionString); - const url = configContext.PORTAL_BACKEND_ENDPOINT + "/api/connectionstring/token/generatetoken"; - const response = await fetch(url, { headers, method: "POST" }); - if (!response.ok) { - throw response; - } - - const encryptedTokenResponse: string = await response.json(); - return decodeURIComponent(encryptedTokenResponse); -}; - -export const isAccountRestrictedForConnectionStringLogin = async (connectionString: string): Promise => { - const headers = new Headers(); - headers.append(HttpHeaders.connectionString, connectionString); - const url = configContext.PORTAL_BACKEND_ENDPOINT + "/api/guest/accountrestrictions/checkconnectionstringlogin"; - const response = await fetch(url, { headers, method: "POST" }); - if (!response.ok) { - throw response; - } - - return (await response.text()).toLowerCase() === "true"; -}; - export const ConnectExplorer: React.FunctionComponent = ({ setEncryptedToken, login, setAuthType, connectionString, setConnectionString, + setAccountMetadata, }: Props) => { const [isFormVisible, { setTrue: showForm }] = useBoolean(false); const [errorMessage, setErrorMessage] = React.useState(""); @@ -67,10 +47,15 @@ export const ConnectExplorer: React.FunctionComponent = ({ event.preventDefault(); setErrorMessage(""); - if (await isAccountRestrictedForConnectionStringLogin(connectionString)) { - setErrorMessage( - "This account has been blocked from connection-string login. Please go to cosmos.azure.com/aad for AAD based login.", - ); + try { + if (await isAccountRestrictedForConnectionStringLogin(connectionString)) { + setErrorMessage( + "This account has been blocked from connection-string login. Please go to cosmos.azure.com/aad for AAD based login.", + ); + return; + } + } catch (error) { + setErrorMessage(getErrorMessage(error)); return; } @@ -79,6 +64,15 @@ export const ConnectExplorer: React.FunctionComponent = ({ return; } + const metadata = parseConnectionString(connectionString); + if (metadata && isDirectConnectionStringLoginApi(metadata.apiKind)) { + // SQL, Table, and Gremlin sign data-plane requests client-side with the account key, so + // we skip the Portal Backend proxy and use the metadata parsed from the connection string. + setAccountMetadata(metadata); + setAuthType(AuthType.ConnectionString); + return; + } + const encryptedToken = await fetchEncryptedToken(connectionString); setEncryptedToken(encryptedToken); setAuthType(AuthType.ConnectionString); diff --git a/src/Platform/Hosted/Helpers/ConnectionStringParser.test.ts b/src/Platform/Hosted/Helpers/ConnectionStringParser.test.ts index 93d76855c..823ebb115 100644 --- a/src/Platform/Hosted/Helpers/ConnectionStringParser.test.ts +++ b/src/Platform/Hosted/Helpers/ConnectionStringParser.test.ts @@ -12,6 +12,18 @@ describe("ConnectionStringParser", () => { expect(metadata.accountName).toBe(mockAccountName); expect(metadata.apiKind).toBe(DataModels.ApiKind.SQL); + expect(metadata.documentEndpoint).toBe(`https://${mockAccountName}.documents.azure.com:443/`); + expect(metadata.apiEndpoint).toBeUndefined(); + }); + + it("should keep the document endpoint given by the connection string", () => { + // The endpoint is taken from the connection string rather than rebuilt from the account name, so a + // string that omits the port keeps it omitted. + const metadata = parseConnectionString( + `AccountEndpoint=https://${mockAccountName}.documents.azure.com/;AccountKey=${mockMasterKey};`, + ); + + expect(metadata.documentEndpoint).toBe(`https://${mockAccountName}.documents.azure.com/`); }); it("should parse a valid mongo account connection string", () => { @@ -39,6 +51,8 @@ describe("ConnectionStringParser", () => { expect(metadata.accountName).toBe(mockAccountName); expect(metadata.apiKind).toBe(DataModels.ApiKind.Graph); + expect(metadata.documentEndpoint).toBe(`https://${mockAccountName}.documents.azure.com:443/`); + expect(metadata.apiEndpoint).toBe(`${mockAccountName}.gremlin.cosmos.azure.com:443`); }); it("should parse a valid table account connection string", () => { @@ -48,6 +62,20 @@ describe("ConnectionStringParser", () => { expect(metadata.accountName).toBe(mockAccountName); expect(metadata.apiKind).toBe(DataModels.ApiKind.Table); + // Table data operations go through the document endpoint, which is constructed from the account name. + expect(metadata.documentEndpoint).toBe(`https://${mockAccountName}.documents.azure.com:443/`); + expect(metadata.apiEndpoint).toBeUndefined(); + }); + + it("should parse a valid table account connection string using the cosmos.azure.com zone", () => { + const metadata = parseConnectionString( + `DefaultEndpointsProtocol=https;AccountName=${mockAccountName};AccountKey=${mockMasterKey};TableEndpoint=https://${mockAccountName}.table.cosmos.azure.com:443/;`, + ); + + expect(metadata.accountName).toBe(mockAccountName); + expect(metadata.apiKind).toBe(DataModels.ApiKind.Table); + expect(metadata.documentEndpoint).toBe(`https://${mockAccountName}.documents.azure.com:443/`); + expect(metadata.apiEndpoint).toBeUndefined(); }); it("should parse a valid cassandra account connection string", () => { diff --git a/src/Platform/Hosted/Helpers/ConnectionStringParser.ts b/src/Platform/Hosted/Helpers/ConnectionStringParser.ts index 6c8db5f96..44be7e82a 100644 --- a/src/Platform/Hosted/Helpers/ConnectionStringParser.ts +++ b/src/Platform/Hosted/Helpers/ConnectionStringParser.ts @@ -1,6 +1,13 @@ import * as Constants from "../../../Common/Constants"; import { AccessInputMetadata, ApiKind } from "../../../Contracts/DataModels"; +// Cosmos DB DNS zones used to construct endpoints client-side. These mirror what the Portal Backend's +// accessinputmetadata API constructs from the account name when the connection string does not already +// contain the endpoint. +const DocumentEndpointZone = "documents.azure.com"; +const GremlinEndpointZone = "gremlin.cosmos.azure.com"; +const DnsPort = "443"; + export function parseConnectionString(connectionString: string): AccessInputMetadata { if (connectionString) { try { @@ -11,6 +18,9 @@ export function parseConnectionString(connectionString: string): AccessInputMeta if (RegExp(Constants.EndpointsRegex.sql).test(connectionStringPart)) { accessInput.accountName = connectionStringPart.match(Constants.EndpointsRegex.sql)[1]; accessInput.apiKind = ApiKind.SQL; + // SQL and Gremlin connection strings carry the account's document endpoint, so take it as + // given instead of rebuilding it from the account name. + accessInput.documentEndpoint = connectionStringPart.substring(connectionStringPart.indexOf("=") + 1); } else if (RegExp(Constants.EndpointsRegex.mongo).test(connectionStringPart)) { const matches: string[] = connectionStringPart.match(Constants.EndpointsRegex.mongo); accessInput.accountName = matches && matches.length > 1 && matches[2]; @@ -38,6 +48,17 @@ export function parseConnectionString(connectionString: string): AccessInputMeta return undefined; } + // Table connection strings only carry the table endpoint, so the document endpoint that data plane + // operations go through has to be derived from the account name. Gremlin accounts additionally + // need the Gremlin endpoint, which is never part of the connection string. + if (accessInput.accountName) { + if (accessInput.apiKind === ApiKind.Table) { + accessInput.documentEndpoint = `https://${accessInput.accountName}.${DocumentEndpointZone}:${DnsPort}/`; + } else if (accessInput.apiKind === ApiKind.Graph) { + accessInput.apiEndpoint = `${accessInput.accountName}.${GremlinEndpointZone}:${DnsPort}`; + } + } + return accessInput; } catch (error) { return undefined; diff --git a/src/Platform/Hosted/HostedUtils.test.ts b/src/Platform/Hosted/HostedUtils.test.ts index 1b8ddd666..d199aba0f 100644 --- a/src/Platform/Hosted/HostedUtils.test.ts +++ b/src/Platform/Hosted/HostedUtils.test.ts @@ -1,5 +1,9 @@ -import { AccessInputMetadata } from "../../Contracts/DataModels"; -import { getDatabaseAccountPropertiesFromMetadata } from "./HostedUtils"; +import { AccessInputMetadata, ApiKind } from "../../Contracts/DataModels"; +import { + extractMasterKeyFromDirectLoginConnectionString, + getDatabaseAccountPropertiesFromMetadata, + isDirectConnectionStringLoginApi, +} from "./HostedUtils"; describe("getDatabaseAccountPropertiesFromMetadata", () => { it("should only return an object with the mongoEndpoint key if the apiKind is mongoCompute (5)", () => { @@ -30,3 +34,58 @@ describe("getDatabaseAccountPropertiesFromMetadata", () => { }); }); }); + +describe("extractMasterKeyFromDirectLoginConnectionString", () => { + const mockAccountName = "Test"; + const mockKey = "abc123+/=someKey=="; + + it("extracts the account key from a SQL connection string", () => { + expect( + extractMasterKeyFromDirectLoginConnectionString( + `AccountEndpoint=https://${mockAccountName}.documents.azure.com:443/;AccountKey=${mockKey};`, + ), + ).toBe(mockKey); + }); + + it("extracts the account key from a Table connection string", () => { + expect( + extractMasterKeyFromDirectLoginConnectionString( + `DefaultEndpointsProtocol=https;AccountName=${mockAccountName};AccountKey=${mockKey};TableEndpoint=https://${mockAccountName}.table.cosmosdb.azure.com:443/;`, + ), + ).toBe(mockKey); + }); + + it("extracts the account key from a Gremlin connection string", () => { + expect( + extractMasterKeyFromDirectLoginConnectionString( + `AccountEndpoint=https://${mockAccountName}.documents.azure.com:443/;AccountKey=${mockKey};ApiKind=Gremlin;`, + ), + ).toBe(mockKey); + }); + + it("returns undefined when there is no account key", () => { + expect( + extractMasterKeyFromDirectLoginConnectionString( + `AccountEndpoint=https://${mockAccountName}.documents.azure.com:443/;`, + ), + ).toBeUndefined(); + }); + + it("returns undefined for an empty connection string", () => { + expect(extractMasterKeyFromDirectLoginConnectionString("")).toBeUndefined(); + }); +}); + +describe("isDirectConnectionStringLoginApi", () => { + it("returns true for SQL, Table, and Graph", () => { + expect(isDirectConnectionStringLoginApi(ApiKind.SQL)).toBe(true); + expect(isDirectConnectionStringLoginApi(ApiKind.Table)).toBe(true); + expect(isDirectConnectionStringLoginApi(ApiKind.Graph)).toBe(true); + }); + + it("returns false for Mongo and Cassandra, which require the Portal Backend proxy", () => { + expect(isDirectConnectionStringLoginApi(ApiKind.MongoDB)).toBe(false); + expect(isDirectConnectionStringLoginApi(ApiKind.MongoDBCompute)).toBe(false); + expect(isDirectConnectionStringLoginApi(ApiKind.Cassandra)).toBe(false); + }); +}); diff --git a/src/Platform/Hosted/HostedUtils.ts b/src/Platform/Hosted/HostedUtils.ts index f39e318b1..32cc2e452 100644 --- a/src/Platform/Hosted/HostedUtils.ts +++ b/src/Platform/Hosted/HostedUtils.ts @@ -40,8 +40,14 @@ export function getDatabaseAccountKindFromExperience(apiExperience: typeof userC return AccountKind.GlobalDocumentDB; } -export function extractMasterKeyfromConnectionString(connectionString: string): string | undefined { - // Only Gremlin uses the actual master key for connection to cosmos - const matchedParts = connectionString.match("AccountKey=(.*);ApiKind=Gremlin;$"); +// Returns the master key carried by SQL, Table, and Gremlin connection strings. +export function extractMasterKeyFromDirectLoginConnectionString(connectionString: string): string | undefined { + const matchedParts = connectionString?.match(/AccountKey=([^;]*)/); return (matchedParts && matchedParts.length > 1 && matchedParts[1]) || undefined; } + +// SQL, Table, and Gremlin can sign data-plane requests client-side with the account key, so they do +// not need the Portal Backend proxy for connection-string login. Mongo and Cassandra still require the proxy. +export function isDirectConnectionStringLoginApi(apiKind: ApiKind): boolean { + return apiKind === ApiKind.SQL || apiKind === ApiKind.Table || apiKind === ApiKind.Graph; +} diff --git a/src/Platform/Hosted/extractFeatures.ts b/src/Platform/Hosted/extractFeatures.ts index ee1855818..d8ccecae0 100644 --- a/src/Platform/Hosted/extractFeatures.ts +++ b/src/Platform/Hosted/extractFeatures.ts @@ -35,7 +35,9 @@ export type Features = { readonly disableConnectionStringLogin: boolean; readonly enableContainerCopy: boolean; readonly enableCloudShell: boolean; + readonly enableCosmosDBShell: boolean; readonly enableRestoreContainer: boolean; // only for Fabric + readonly mongoDisableNativeAuth: boolean; // can be set via both flight and feature flag autoscaleDefault: boolean; @@ -103,6 +105,8 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear enableContainerCopy: "true" === get("enablecontainercopy"), enableRestoreContainer: "true" === get("enablerestorecontainer"), enableCloudShell: true, + enableCosmosDBShell: "true" === get("enablecosmosdbshell"), + mongoDisableNativeAuth: "true" === get("mongodisablenativeauth"), }; } diff --git a/src/Terminal/JupyterLabAppFactory.ts b/src/Terminal/JupyterLabAppFactory.ts deleted file mode 100644 index 1550e7b40..000000000 --- a/src/Terminal/JupyterLabAppFactory.ts +++ /dev/null @@ -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 { - const configurationSettings: Partial = 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; - } -} diff --git a/src/Terminal/NotebookAppContracts.d.ts b/src/Terminal/NotebookAppContracts.d.ts deleted file mode 100644 index 4b33b5332..000000000 --- a/src/Terminal/NotebookAppContracts.d.ts +++ /dev/null @@ -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, -} diff --git a/src/Terminal/README.md b/src/Terminal/README.md deleted file mode 100644 index 1fcd38287..000000000 --- a/src/Terminal/README.md +++ /dev/null @@ -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. diff --git a/src/Terminal/TerminalProps.ts b/src/Terminal/TerminalProps.ts deleted file mode 100644 index e4445c970..000000000 --- a/src/Terminal/TerminalProps.ts +++ /dev/null @@ -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; -} diff --git a/src/Terminal/index.css b/src/Terminal/index.css deleted file mode 100644 index eb47fb254..000000000 --- a/src/Terminal/index.css +++ /dev/null @@ -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%; -} diff --git a/src/Terminal/index.html b/src/Terminal/index.html deleted file mode 100644 index 750050168..000000000 --- a/src/Terminal/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - Notebook - - - - - diff --git a/src/Terminal/index.ts b/src/Terminal/index.ts deleted file mode 100644 index dda1400ed..000000000 --- a/src/Terminal/index.ts +++ /dev/null @@ -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 = { - 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 => { - // 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 => { - postRobot.on( - "props", - { - window: window.parent, - domain: window.location.origin, - }, - async (event) => { - // Typescript definition for event is wrong. So read props by casting to - // 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); diff --git a/src/UserContext.ts b/src/UserContext.ts index 5c83341fd..036ed322e 100644 --- a/src/UserContext.ts +++ b/src/UserContext.ts @@ -42,9 +42,37 @@ export interface PostgresConnectionStrParams { isFreeTier: boolean; } +export type VCoreMongoAuthMode = "NativeAuth" | "MicrosoftEntraID"; + +export interface VCoreMongoAuthConfig { + allowedModes?: VCoreMongoAuthMode[]; +} + export interface VCoreMongoConnectionParams { adminLogin: string; connectionString: string; + authConfig?: VCoreMongoAuthConfig; + isNativeAuthDisabled?: boolean; +} + +export const VCoreMongoNativeAuthLearnMoreUrl = "https://go.microsoft.com/fwlink/?linkid=2340100"; + +export const VCoreMongoNativeAuthDisabledMessage = + "Native DocumentDB authentication is disabled on this cluster. You can use MongoDB Shell with Entra ID authentication outside of the Azure portal."; + +export function isVCoreMongoNativeAuthDisabled(): boolean { + if (userContext.features?.mongoDisableNativeAuth) { + return true; + } + const params = userContext.vcoreMongoConnectionParams; + if (!params) { + return false; + } + if (params.isNativeAuthDisabled) { + return true; + } + const allowedModes = params.authConfig?.allowedModes || []; + return allowedModes.length > 0 && !allowedModes.includes("NativeAuth"); } export interface FabricArtifactInfo { diff --git a/src/Utils/AuthorizationUtils.test.ts b/src/Utils/AuthorizationUtils.test.ts index 6eb5f761f..fd361ac22 100644 --- a/src/Utils/AuthorizationUtils.test.ts +++ b/src/Utils/AuthorizationUtils.test.ts @@ -17,6 +17,7 @@ describe("AuthorizationUtils", () => { updateUserContext({ features: { enableContainerCopy: false, + enableCosmosDBShell: false, enableAadDataPlane: enabled, canExceedMaximumValue: false, cosmosdb: false, @@ -48,6 +49,7 @@ describe("AuthorizationUtils", () => { partitionKeyDefault2: false, notebooksDownBanner: false, enableRestoreContainer: false, + mongoDisableNativeAuth: false, }, }); }; diff --git a/src/Utils/AuthorizationUtils.ts b/src/Utils/AuthorizationUtils.ts index 92b6b4a53..0938c6f33 100644 --- a/src/Utils/AuthorizationUtils.ts +++ b/src/Utils/AuthorizationUtils.ts @@ -236,3 +236,21 @@ export function useDataplaneRbacAuthorization(userContext: UserContext): boolean export function isDataplaneRbacEnabledForProxyApi(userContext: UserContext): boolean { return useDataplaneRbacAuthorization(userContext) && hasProxyServer(userContext.apiType); } + +/** + * Determines whether Cloud Shell connections should authenticate with an Entra ID + * token (COSMOSDB_SHELL_TOKEN) instead of the account master key. + * + * Returns true when data-plane RBAC authorization is in effect, or when the account + * has local (key) auth disabled — in the latter case a master key would be rejected + * by the service, so we must fall back to Entra ID even if the RBAC toggle is off. + * + * Unlike {@link isDataplaneRbacEnabledForProxyApi}, this does not require a proxy + * server, so it works for the SQL/CosmosDB API which connects directly to the + * data plane. + */ +export function isCloudShellEntraAuthEnabled(userContext: UserContext): boolean { + return ( + useDataplaneRbacAuthorization(userContext) || userContext.databaseAccount?.properties?.disableLocalAuth === true + ); +} diff --git a/src/Utils/GitHubUtils.test.ts b/src/Utils/GitHubUtils.test.ts deleted file mode 100644 index 33578bd6b..000000000 --- a/src/Utils/GitHubUtils.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -import * as GitHubUtils from "./GitHubUtils"; - -const owner = "owner-1"; -const repo = "repo-1"; -const branch = "branch/name.1-2"; -const path = "folder name/file name1:2.ipynb"; - -describe("GitHubUtils", () => { - it("fromRepoUri parses github repo url correctly", () => { - const repoInfo = GitHubUtils.fromRepoUri(`https://github.com/${owner}/${repo}/tree/${branch}`); - expect(repoInfo).toEqual({ - owner, - repo, - branch, - }); - }); - - it("toContentUri generates github uris correctly", () => { - const uri = GitHubUtils.toContentUri(owner, repo, branch, path); - expect(uri).toBe(`github://${owner}/${repo}/${path}?ref=${branch}`); - }); - - it("fromContentUri parses the github uris correctly", () => { - const contentInfo = GitHubUtils.fromContentUri(`github://${owner}/${repo}/${path}?ref=${branch}`); - expect(contentInfo).toEqual({ - owner, - repo, - branch, - path, - }); - }); -}); diff --git a/src/Utils/GitHubUtils.ts b/src/Utils/GitHubUtils.ts deleted file mode 100644 index 9d8f55818..000000000 --- a/src/Utils/GitHubUtils.ts +++ /dev/null @@ -1,79 +0,0 @@ -// https://github.com///tree/ - -import { JunoEndpoints } from "Common/Constants"; -import { configContext } from "ConfigContext"; -import { userContext } from "UserContext"; - -// The url when users visit a repo/branch on github.com -export const RepoUriPattern = /https:\/\/github.com\/([^/]*)\/([^/]*)\/tree\/([^?]*)/; - -// github:////?ref= -// Custom scheme for github content -export const ContentUriPattern = /github:\/\/([^/]*)\/([^/]*)\/([^?]*)\?ref=(.*)/; - -// https://github.com///blob// -// We need to support this until we move to newer scheme for quickstarts -export const LegacyContentUriPattern = /https:\/\/github.com\/([^/]*)\/([^/]*)\/blob\/([^/]*)\/([^?]*)/; - -export function toRepoFullName(owner: string, repo: string): string { - return `${owner}/${repo}`; -} - -export function fromRepoUri(repoUri: string): undefined | { owner: string; repo: string; branch: string } { - const matches = repoUri.match(RepoUriPattern); - if (matches && matches.length > 3) { - return { - owner: matches[1], - repo: matches[2], - branch: matches[3], - }; - } - - return undefined; -} - -export function fromContentUri( - contentUri: string, -): undefined | { owner: string; repo: string; branch: string; path: string } { - let matches = contentUri.match(ContentUriPattern); - if (matches && matches.length > 4) { - return { - owner: matches[1], - repo: matches[2], - branch: matches[4], - path: matches[3], - }; - } - - matches = contentUri.match(LegacyContentUriPattern); - if (matches && matches.length > 4) { - return { - owner: matches[1], - repo: matches[2], - branch: matches[3], - path: matches[4], - }; - } - - return undefined; -} - -export function toContentUri(owner: string, repo: string, branch: string, path: string): string { - return `github://${owner}/${repo}/${path}?ref=${branch}`; -} - -export function toRawContentUri(owner: string, repo: string, branch: string, path: string): string { - return `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${path}`; -} - -export function GetGithubClientId(): string { - const junoEndpoint = userContext.features.junoEndpoint ?? configContext.JUNO_ENDPOINT; - if ( - junoEndpoint === JunoEndpoints.Test || - junoEndpoint === JunoEndpoints.Test2 || - junoEndpoint === JunoEndpoints.Test3 - ) { - return configContext.GITHUB_TEST_ENV_CLIENT_ID; - } - return configContext.GITHUB_CLIENT_ID; -} diff --git a/src/Utils/JunoUtils.test.ts b/src/Utils/JunoUtils.test.ts deleted file mode 100644 index b08e0b168..000000000 --- a/src/Utils/JunoUtils.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { RepoListItem } from "../Explorer/Controls/GitHub/GitHubReposComponent"; -import { IPinnedRepo } from "../Juno/JunoClient"; -import * as JunoUtils from "./JunoUtils"; -import { IGitHubRepo } from "../GitHub/GitHubClient"; - -const gitHubRepo: IGitHubRepo = { - name: "repo-name", - owner: "owner", - private: false, -}; - -const repoListItem: RepoListItem = { - key: "key", - repo: { - name: "repo-name", - owner: "owner", - private: false, - }, - branches: [ - { - name: "branch-name", - }, - ], -}; - -const pinnedRepo: IPinnedRepo = { - name: "repo-name", - owner: "owner", - private: false, - branches: [ - { - name: "branch-name", - }, - ], -}; - -describe("JunoUtils", () => { - it("toPinnedRepo converts RepoListItem to IPinnedRepo", () => { - expect(JunoUtils.toPinnedRepo(repoListItem)).toEqual(pinnedRepo); - }); - - it("toGitHubRepo converts IPinnedRepo to IGitHubRepo", () => { - expect(JunoUtils.toGitHubRepo(pinnedRepo)).toEqual(gitHubRepo); - }); -}); diff --git a/src/Utils/JunoUtils.ts b/src/Utils/JunoUtils.ts deleted file mode 100644 index cd6006a0f..000000000 --- a/src/Utils/JunoUtils.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { RepoListItem } from "../Explorer/Controls/GitHub/GitHubReposComponent"; -import { IGitHubRepo } from "../GitHub/GitHubClient"; -import { IPinnedRepo } from "../Juno/JunoClient"; - -export function toPinnedRepo(item: RepoListItem): IPinnedRepo { - return { - owner: item.repo.owner, - name: item.repo.name, - private: item.repo.private, - branches: item.branches.map((element) => ({ name: element.name })), - }; -} - -export function toGitHubRepo(pinnedRepo: IPinnedRepo): IGitHubRepo { - return { - owner: pinnedRepo.owner, - name: pinnedRepo.name, - private: pinnedRepo.private, - }; -} diff --git a/src/Utils/NotebookConfigurationUtils.ts b/src/Utils/NotebookConfigurationUtils.ts deleted file mode 100644 index 31bfccfdd..000000000 --- a/src/Utils/NotebookConfigurationUtils.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { getErrorMessage } from "../Common/ErrorHandlingUtils"; -import * as Logger from "../Common/Logger"; -import * as DataModels from "../Contracts/DataModels"; - -interface KernelConnectionMetadata { - name: string; - configurationEndpoints: DataModels.NotebookConfigurationEndpoints; - notebookConnectionInfo: DataModels.NotebookWorkspaceConnectionInfo; -} - -export const _configureServiceEndpoints = async (kernelMetadata: KernelConnectionMetadata): Promise => { - if (!kernelMetadata) { - // should never get into this state - Logger.logWarning("kernel metadata is null or undefined", "NotebookConfigurationUtils/configureServiceEndpoints"); - return; - } - - const notebookConnectionInfo = kernelMetadata.notebookConnectionInfo; - const configurationEndpoints = kernelMetadata.configurationEndpoints; - if (notebookConnectionInfo && configurationEndpoints) { - try { - const headers: HeadersInit = { "Content-Type": "application/json" }; - if (notebookConnectionInfo.authToken) { - headers["Authorization"] = `token ${notebookConnectionInfo.authToken}`; - } - const response = await fetch(`${notebookConnectionInfo.notebookServerEndpoint}/api/configureEndpoints`, { - method: "POST", - headers, - body: JSON.stringify(configurationEndpoints), - }); - if (!response.ok) { - const responseMessage = await response.json(); - Logger.logError( - getErrorMessage(responseMessage), - "NotebookConfigurationUtils/configureServiceEndpoints", - response.status, - ); - } - } catch (error) { - Logger.logError(getErrorMessage(error), "NotebookConfigurationUtils/configureServiceEndpoints"); - } - } -}; - -export const configureServiceEndpoints = async ( - notebookPath: string, - notebookConnectionInfo: DataModels.NotebookWorkspaceConnectionInfo, - kernelName: string, - clusterConnectionInfo: DataModels.SparkClusterConnectionInfo, -): Promise => { - if (!notebookPath || !notebookConnectionInfo || !kernelName) { - Logger.logError( - "Invalid or missing notebook connection info/path", - "NotebookConfigurationUtils/configureServiceEndpoints", - ); - return Promise.reject("Invalid or missing notebook connection info"); - } - - if (!clusterConnectionInfo || !clusterConnectionInfo.endpoints || clusterConnectionInfo.endpoints.length === 0) { - Logger.logError( - "Invalid or missing cluster connection info/endpoints", - "NotebookConfigurationUtils/configureServiceEndpoints", - ); - return Promise.reject("Invalid or missing cluster connection info"); - } - - const notebookEndpointInfo: DataModels.NotebookConfigurationEndpointInfo[] = clusterConnectionInfo.endpoints.map( - (clusterEndpoint) => ({ - type: clusterEndpoint.kind.toLowerCase(), - endpoint: clusterEndpoint && clusterEndpoint.endpoint, - username: clusterConnectionInfo.userName, - password: clusterConnectionInfo.password, - token: "", // TODO. This was arcadiaToken() when our synapse/spark integration comes back - }), - ); - const configurationEndpoints: DataModels.NotebookConfigurationEndpoints = { - path: notebookPath, - endpoints: notebookEndpointInfo, - }; - const kernelMetadata: KernelConnectionMetadata = { - configurationEndpoints, - notebookConnectionInfo, - name: kernelName, - }; - - return await _configureServiceEndpoints(kernelMetadata); -}; diff --git a/src/connectToGitHub.html b/src/connectToGitHub.html deleted file mode 100644 index 50b1832d7..000000000 --- a/src/connectToGitHub.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - Connect to GitHub - - - - Connecting to GitHub... - - diff --git a/src/hooks/useKnockoutExplorer.ts b/src/hooks/useKnockoutExplorer.ts index d4a4f9555..8b8c63a3a 100644 --- a/src/hooks/useKnockoutExplorer.ts +++ b/src/hooks/useKnockoutExplorer.ts @@ -83,12 +83,6 @@ export function useKnockoutExplorer(platform: Platform): Explorer { useEffect(() => { const effect = async () => { if (platform) { - //Updating phoenix feature flags for MPAC based of config context - if (configContext.isPhoenixEnabled === true) { - userContext.features.phoenixNotebooks = true; - userContext.features.phoenixFeatures = true; - } - let explorer: Explorer; try { if (platform === Platform.Hosted) { @@ -467,13 +461,24 @@ function configureHostedWithConnectionString(config: ConnectionString): Explorer properties: getDatabaseAccountPropertiesFromMetadata(config.encryptedTokenMetadata), tags: {}, }; - updateUserContext({ - // For legacy reasons lots of code expects a connection string login to look and act like an encrypted token login - authType: AuthType.EncryptedToken, - accessToken: encodeURIComponent(config.encryptedToken), - databaseAccount, - masterKey: config.masterKey, - }); + if (config.masterKey && !config.encryptedToken) { + // Direct client-side signing path (SQL, Table, Gremlin). Requests are signed locally with the + // account key via the Cosmos client's tokenProvider, so no Portal Backend proxy or encrypted token + // is required. + updateUserContext({ + authType: AuthType.ConnectionString, + databaseAccount, + masterKey: config.masterKey, + }); + } else { + // Legacy encrypted-token proxy path (Mongo, Cassandra). + updateUserContext({ + authType: AuthType.EncryptedToken, + accessToken: encodeURIComponent(config.encryptedToken), + databaseAccount, + masterKey: config.masterKey, + }); + } const explorer = new Explorer(); return explorer; } @@ -999,15 +1004,6 @@ function updateContextsFromPortalMessage(inputs: DataExplorerInputsFrame) { if (inputs.flights.indexOf(Flights.PKPartitionKeyTest) !== -1) { userContext.features.partitionKeyDefault2 = true; } - if (inputs.flights.indexOf(Flights.PhoenixNotebooks) !== -1) { - userContext.features.phoenixNotebooks = true; - } - if (inputs.flights.indexOf(Flights.PhoenixFeatures) !== -1) { - userContext.features.phoenixFeatures = true; - } - if (inputs.flights.indexOf(Flights.NotebooksDownBanner) !== -1) { - userContext.features.notebooksDownBanner = true; - } } // Handle initial theme from portal diff --git a/src/hooks/useNotebookSnapshotStore.ts b/src/hooks/useNotebookSnapshotStore.ts deleted file mode 100644 index 841f6b5d1..000000000 --- a/src/hooks/useNotebookSnapshotStore.ts +++ /dev/null @@ -1,15 +0,0 @@ -import create, { UseStore } from "zustand"; - -export interface NotebookSnapshotHooks { - snapshot?: string; - error?: string; - setSnapshot: (imageSrc: string) => void; - setError: (error: string) => void; -} - -export const useNotebookSnapshotStore: UseStore = create((set) => ({ - snapshot: undefined, - error: undefined, - setSnapshot: (imageSrc: string) => set((state) => ({ ...state, snapshot: imageSrc })), - setError: (error: string) => set((state) => ({ ...state, error })), -})); diff --git a/src/hooks/usePortalAccessToken.tsx b/src/hooks/usePortalAccessToken.tsx deleted file mode 100644 index bf59dc63f..000000000 --- a/src/hooks/usePortalAccessToken.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { useEffect, useState } from "react"; -import { HttpHeaders } from "../Common/Constants"; -import { configContext } from "../ConfigContext"; -import { AccessInputMetadata } from "../Contracts/DataModels"; - -export async function fetchAccessData(portalToken: string): Promise { - const headers = new Headers(); - // Portal encrypted token API quirk: The token header must be URL encoded - headers.append(HttpHeaders.guestAccessToken, encodeURIComponent(portalToken)); - const url: string = `${configContext.PORTAL_BACKEND_ENDPOINT}/api/connectionstring/runtimeproxy/accessinputmetadata`; - const options = { - method: "GET", - headers: headers, - }; - - return fetch(url, options) - .then((response) => response.json()) - .catch((error) => console.error(error)); -} - -export function useTokenMetadata(token: string): AccessInputMetadata | undefined { - const [state, setState] = useState(); - - useEffect(() => { - if (token) { - fetchAccessData(token).then((response) => setState(response)); - } - }, [token]); - return state; -} diff --git a/src/hooks/useTabs.ts b/src/hooks/useTabs.ts index 3540ae67e..4444f5c8c 100644 --- a/src/hooks/useTabs.ts +++ b/src/hooks/useTabs.ts @@ -9,8 +9,6 @@ import { } from "Shared/AppStatePersistenceUtility"; import create, { UseStore } from "zustand"; import * as ViewModels from "../Contracts/ViewModels"; -import { CollectionTabKind } from "../Contracts/ViewModels"; -import NotebookTabV2 from "../Explorer/Tabs/NotebookV2Tab"; import TabsBase from "../Explorer/Tabs/TabsBase"; export interface TabsState { @@ -28,7 +26,6 @@ export interface TabsState { refreshActiveTab: (comparator: (tab: TabsBase) => boolean) => void; closeTabsByComparator: (comparator: (tab: TabsBase) => boolean) => void; closeTab: (tab: TabsBase) => void; - closeAllNotebookTabs: (hardClose: boolean) => void; openAndActivateReactTab: (tabKind: ReactTabKind) => void; closeReactTab: (tabKind: ReactTabKind) => void; setIsTabExecuting: (state: boolean) => void; @@ -133,33 +130,6 @@ export const useTabs: UseStore = create((set, get) => ({ get().persistTabsState(); }, - closeAllNotebookTabs: (hardClose): void => { - const isNotebook = (tabKind: CollectionTabKind): boolean => { - if ( - tabKind === CollectionTabKind.Notebook || - tabKind === CollectionTabKind.NotebookV2 || - tabKind === CollectionTabKind.SchemaAnalyzer || - tabKind === CollectionTabKind.Terminal - ) { - return true; - } - return false; - }; - - const tabList = get().openedTabs; - if (tabList && tabList.length > 0) { - tabList.forEach((tab: NotebookTabV2) => { - const tabKind: CollectionTabKind = tab.tabKind; - if (tabKind && isNotebook(tabKind)) { - tab.onCloseTabButtonClick(hardClose); - } - }); - - if (get().openedTabs.length === 0 && !isFabricMirrored()) { - set({ activeTab: undefined, activeReactTab: undefined }); - } - } - }, openAndActivateReactTab: (tabKind: ReactTabKind) => { if (get().openedReactTabs.indexOf(tabKind) === -1) { set((state) => ({ diff --git a/src/hooks/useTerminal.ts b/src/hooks/useTerminal.ts index f727d2d79..efafe5b7a 100644 --- a/src/hooks/useTerminal.ts +++ b/src/hooks/useTerminal.ts @@ -1,26 +1,20 @@ -import postRobot from "post-robot"; import create, { UseStore } from "zustand"; interface TerminalState { - terminalWindow: Window; - setTerminal: (terminalWindow: Window) => void; + socket: WebSocket | undefined; + setSocket: (socket: WebSocket | undefined) => void; sendMessage: (message: string) => void; } export const useTerminal: UseStore = create((set, get) => ({ - terminalWindow: undefined, - setTerminal: (terminalWindow: Window) => { - set({ terminalWindow }); + socket: undefined, + setSocket: (socket: WebSocket | undefined) => { + set({ socket }); }, sendMessage: (message: string) => { - const terminalWindow = get().terminalWindow; - postRobot.send( - terminalWindow, - "sendMessage", - { type: "stdin", content: [message] }, - { - domain: window.location.origin, - }, - ); + const socket = get().socket; + if (socket && socket.readyState === WebSocket.OPEN) { + socket.send(message + "\r"); + } }, })); diff --git a/test/NoSqlTestSetup.ts b/test/NoSqlTestSetup.ts deleted file mode 100644 index ea457fd55..000000000 --- a/test/NoSqlTestSetup.ts +++ /dev/null @@ -1,71 +0,0 @@ -export function getNoSqlRbacToken(): string | undefined { - let nosqlRbacToken: string | undefined; - const shardIndex = process.env.PLAYWRIGHT_SHARD_INDEX ?? ""; - switch (parseInt(shardIndex)) { - case 1: - nosqlRbacToken = process.env.NOSQL_TESTACCOUNT_1_TOKEN; - break; - case 2: - nosqlRbacToken = process.env.NOSQL_TESTACCOUNT_2_TOKEN; - break; - case 3: - nosqlRbacToken = process.env.NOSQL_TESTACCOUNT_3_TOKEN; - break; - case 4: - nosqlRbacToken = process.env.NOSQL_TESTACCOUNT_4_TOKEN; - break; - case 5: - nosqlRbacToken = process.env.NOSQL_TESTACCOUNT_5_TOKEN; - break; - case 6: - nosqlRbacToken = process.env.NOSQL_TESTACCOUNT_6_TOKEN; - break; - case 7: - nosqlRbacToken = process.env.NOSQL_TESTACCOUNT_7_TOKEN; - break; - case 8: - nosqlRbacToken = process.env.NOSQL_TESTACCOUNT_8_TOKEN; - break; - case 9: - nosqlRbacToken = process.env.NOSQL_TESTACCOUNT_9_TOKEN; - break; - case 10: - nosqlRbacToken = process.env.NOSQL_TESTACCOUNT_10_TOKEN; - break; - case 11: - nosqlRbacToken = process.env.NOSQL_TESTACCOUNT_11_TOKEN; - break; - case 12: - nosqlRbacToken = process.env.NOSQL_TESTACCOUNT_12_TOKEN; - break; - case 13: - nosqlRbacToken = process.env.NOSQL_TESTACCOUNT_13_TOKEN; - break; - case 14: - nosqlRbacToken = process.env.NOSQL_TESTACCOUNT_14_TOKEN; - break; - case 15: - nosqlRbacToken = process.env.NOSQL_TESTACCOUNT_15_TOKEN; - break; - case 16: - nosqlRbacToken = process.env.NOSQL_TESTACCOUNT_16_TOKEN; - break; - case 17: - nosqlRbacToken = process.env.NOSQL_TESTACCOUNT_17_TOKEN; - break; - case 18: - nosqlRbacToken = process.env.NOSQL_TESTACCOUNT_18_TOKEN; - break; - case 19: - nosqlRbacToken = process.env.NOSQL_TESTACCOUNT_19_TOKEN; - break; - case 20: - nosqlRbacToken = process.env.NOSQL_TESTACCOUNT_20_TOKEN; - break; - } - - if (!nosqlRbacToken) { - console.warn(`No NoSQL RBAC token found for shard index ${shardIndex}`); - } - return nosqlRbacToken; -} diff --git a/test/fx.ts b/test/fx.ts index 7c8dcbd3f..a3cad859a 100644 --- a/test/fx.ts +++ b/test/fx.ts @@ -1,9 +1,15 @@ import { DefaultAzureCredential } from "@azure/identity"; import { Frame, Locator, Page, expect } from "@playwright/test"; -import crypto from "crypto"; -import { getNoSqlRbacToken } from "./NoSqlTestSetup"; +import crypto, { webcrypto } from "crypto"; import { TestContainerContext } from "./testData"; +// The @azure/cosmos client signs requests with globalThis.crypto (Web Crypto API). +// In Node.js >= 19 it's already available; only assign the polyfill for older versions. +// This lives in fx.ts (imported by every spec) so the polyfill always runs. +if (!globalThis.crypto) { + Object.defineProperty(globalThis, "crypto", { value: webcrypto, writable: true, configurable: true }); +} + const RETRY_COUNT = 3; export interface TestNameOptions { @@ -42,32 +48,51 @@ export enum TestAccount { SQL = "SQL", SQLReadOnly = "SQLReadOnly", SQLContainerCopyOnly = "SQLContainerCopyOnly", + SQLConnectionString = "SQLConnectionString", + TableConnectionString = "TableConnectionString", + GremlinConnectionString = "GremlinConnectionString", +} + +export enum TestAuthType { + EntraID = "EntraID", + ConnectionString = "ConnectionString", } export function getDefaultAccountName(accountType: TestAccount): string { + const accountNamePrefix = process.env.DE_ACCOUNT_PREFIX; + if (!accountNamePrefix) { + throw new Error("DE_ACCOUNT_PREFIX is not set"); + } + switch (accountType) { case TestAccount.Tables: - return "github-e2etests-tables"; + return `${accountNamePrefix}-de-test-table-1`; case TestAccount.Cassandra: - return "github-e2etests-cassandra"; + return `${accountNamePrefix}-de-test-cassandra-1`; case TestAccount.Gremlin: - return "github-e2etests-gremlin"; + return `${accountNamePrefix}-de-test-gremlin-1`; case TestAccount.Mongo: - return "github-e2etests-mongo"; + return `${accountNamePrefix}-de-test-mongo-1`; case TestAccount.MongoReadonly: - return "github-e2etests-mongo-readonly"; + return `${accountNamePrefix}-de-test-mongo-readonly`; case TestAccount.Mongo32: - return "github-e2etests-mongo32"; + return `${accountNamePrefix}-de-test-mongo32-1`; case TestAccount.SQLReadOnly: - return "github-e2etests-sql-readonly"; + return `${accountNamePrefix}-de-test-sql-readonly`; case TestAccount.SQLContainerCopyOnly: - return "github-e2etests-sql-containercopyonly"; + return `${accountNamePrefix}-de-test-sql-containercopy`; + case TestAccount.SQLConnectionString: + return `${accountNamePrefix}-de-test-sql-connstring-1`; + case TestAccount.TableConnectionString: + return `${accountNamePrefix}-de-test-table-connstring-1`; + case TestAccount.GremlinConnectionString: + return `${accountNamePrefix}-de-test-gremlin-connstring-1`; case TestAccount.SQL: { const shardIndex = process.env.PLAYWRIGHT_SHARD_INDEX ?? ""; if (!shardIndex) { throw new Error("PLAYWRIGHT_SHARD_INDEX is not set"); } - return "github-e2etests-sql-" + shardIndex; + return `${accountNamePrefix}-de-test-sql-${shardIndex}`; } default: throw new Error(`No default account name defined for account type ${accountType}`); @@ -75,7 +100,7 @@ export function getDefaultAccountName(accountType: TestAccount): string { } export const resourceGroupName = process.env.DE_TEST_RESOURCE_GROUP ?? "de-e2e-tests"; -export const subscriptionId = process.env.DE_TEST_SUBSCRIPTION_ID ?? "69e02f2d-f059-4409-9eac-97e8a276ae2c"; +export const subscriptionId = process.env.DE_TEST_SUBSCRIPTION_ID ?? process.env.AZURE_SUBSCRIPTION_ID ?? ""; export const TEST_AUTOSCALE_THROUGHPUT_RU = 1000; export const TEST_MANUAL_THROUGHPUT_RU = 800; export const TEST_AUTOSCALE_MAX_THROUGHPUT_RU_2K = 2000; @@ -92,7 +117,32 @@ function tryGetStandardName(accountType: TestAccount) { } } -export function getAccountName(accountType: TestAccount) { +// Maps a base API account type to its dedicated connection string (account key) account. +const connectionStringAccountTypes: Partial> = { + [TestAccount.SQL]: TestAccount.SQLConnectionString, + [TestAccount.Tables]: TestAccount.TableConnectionString, + [TestAccount.Gremlin]: TestAccount.GremlinConnectionString, +}; + +export function getAccountName(accountType: TestAccount, authType: TestAuthType = TestAuthType.EntraID): string { + // Connection string (account key) login uses dedicated *-connstring accounts that are only + // provisioned in CI (resolved via DE_ACCOUNT_PREFIX). Local runs use DE_TEST_ACCOUNT_PREFIX and + // typically don't have those accounts, so they fall back to the standard API account for the same + // API (which also has key auth enabled). + if (authType === TestAuthType.ConnectionString) { + const connectionStringType = connectionStringAccountTypes[accountType]; + if (!connectionStringType) { + throw new Error(`No connection string account defined for account type ${accountType}`); + } + const override = process.env[`DE_TEST_ACCOUNT_NAME_${connectionStringType.toLocaleUpperCase()}`]; + if (override) { + return override; + } + if (!process.env.DE_TEST_ACCOUNT_PREFIX) { + return getAccountName(connectionStringType); + } + } + return ( process.env[`DE_TEST_ACCOUNT_NAME_${accountType.toLocaleUpperCase()}`] ?? tryGetStandardName(accountType) ?? @@ -117,14 +167,16 @@ export async function getTestExplorerUrl(accountType: TestAccount, options?: Tes params.set("subscriptionId", subscriptionId); params.set("token", token); + const tenantId = process.env.AZURE_TENANT_ID; + if (tenantId) { + params.set("tenantId", tenantId); + } + // There seem to be occasional CORS issues with calling the copilot APIs (/api/tokens/sampledataconnection/v2, for example) // For now, since we don't test copilot, we can disable the copilot APIs by setting the feature flag to false. params.set("feature.enableCopilot", "false"); - const nosqlRbacToken = getNoSqlRbacToken(); - if (!nosqlRbacToken) { - throw new Error("No NOSQL RBAC token found."); - } + const nosqlRbacToken = process.env.NOSQL_TESTACCOUNT_TOKEN; const nosqlReadOnlyRbacToken = process.env.NOSQL_READONLY_TESTACCOUNT_TOKEN; const nosqlContainerCopyRbacToken = process.env.NOSQL_CONTAINERCOPY_TESTACCOUNT_TOKEN; @@ -204,6 +256,13 @@ export async function getTestExplorerUrl(accountType: TestAccount, options?: Tes params.set("enableaaddataplane", "true"); } break; + + case TestAccount.SQLConnectionString: + case TestAccount.TableConnectionString: + case TestAccount.GremlinConnectionString: + // Connection string (account key) login navigates directly to hostedExplorer.html and doesn't + // use this iframe test-explorer URL or any RBAC/AAD data-plane token. + break; } if (iframeSrc) { @@ -411,6 +470,7 @@ export enum CommandBarButton { ExecuteQuery = "Execute Query", UploadItem = "Upload Item", NewDocument = "New Document", + View = "View", } /** Helper class that provides locator methods for DataExplorer components, on top of a Frame */ diff --git a/test/gremlin/connectionStringLogin.spec.ts b/test/gremlin/connectionStringLogin.spec.ts new file mode 100644 index 000000000..50289d867 --- /dev/null +++ b/test/gremlin/connectionStringLogin.spec.ts @@ -0,0 +1,84 @@ +import { expect, test } from "@playwright/test"; + +import { CosmosDBManagementClient } from "@azure/arm-cosmosdb"; +import { CosmosClient, Database } from "@azure/cosmos"; +import { + DataExplorer, + Editor, + ONE_MINUTE_MS, + TestAccount, + TestAuthType, + generateUniqueName, + getAccountName, + getAzureCLICredentials, + resourceGroupName, + subscriptionId, +} from "../fx"; + +const databaseId = generateUniqueName("db"); +const graphId = "testgraph"; +const vertexId = "testvertex"; + +test.describe("Gremlin account using connection string login", () => { + let database: Database = null!; + + test.beforeAll("Seed Test Database", async () => { + const credentials = getAzureCLICredentials(); + const armClient = new CosmosDBManagementClient(credentials, subscriptionId); + const accountName = getAccountName(TestAccount.Gremlin, TestAuthType.ConnectionString); + const account = await armClient.databaseAccounts.get(resourceGroupName, accountName); + const keys = await armClient.databaseAccounts.listKeys(resourceGroupName, accountName); + + // Gremlin graphs are stored as documents, so seed a vertex via the SQL client using Cosmos' internal graph format. + const client = new CosmosClient({ endpoint: account.documentEndpoint!, key: keys.primaryMasterKey }); + database = (await client.databases.createIfNotExists({ id: databaseId })).database; + const { container } = await database.containers.createIfNotExists({ + id: graphId, + partitionKey: { paths: ["/pk"] }, + }); + await container.items.upsert({ id: vertexId, label: "person", pk: "pk1" }); + }); + + test.afterAll("Delete Test Database", async () => { + await database?.delete(); + }); + + test("reads a vertex after connection string login", async ({ page }) => { + const credentials = getAzureCLICredentials(); + const armClient = new CosmosDBManagementClient(credentials, subscriptionId); + const accountName = getAccountName(TestAccount.Gremlin, TestAuthType.ConnectionString); + const account = await armClient.databaseAccounts.get(resourceGroupName, accountName); + const keys = await armClient.databaseAccounts.listKeys(resourceGroupName, accountName); + + // Gremlin signs data-plane requests client-side with the account key, so no encrypted token is issued. + const connectionString = `AccountEndpoint=${account.documentEndpoint};AccountKey=${keys.primaryMasterKey};ApiKind=Gremlin;`; + + await page.goto("https://localhost:1234/hostedExplorer.html"); + const switchConnectionLink = page.getByTestId("Link:SwitchConnectionType"); + await switchConnectionLink.waitFor(); + await switchConnectionLink.click(); + await page.getByPlaceholder("Please enter a connection string").fill(connectionString); + await page.getByRole("button", { name: "Connect" }).click(); + + const explorer = await DataExplorer.waitForExplorer(page); + const graphNode = await explorer.waitForContainerNode(databaseId, graphId); + await graphNode.expand(); + + // Open the Graph node to load the graph explorer, then run the default query to read the seeded vertex. + const graphDataNode = await explorer.waitForNode(`${databaseId}/${graphId}/Graph`); + await graphDataNode.element.click(); + + await explorer.frame.getByRole("button", { name: "Execute Gremlin Query" }).click(); + + // Results open in the Graph view; switch to the JSON view to read the vertex document. + const jsonResultsTab = explorer.frame.getByRole("tab", { name: "JSON" }); + await jsonResultsTab.waitFor({ timeout: ONE_MINUTE_MS }); + await jsonResultsTab.click(); + + const graphJsonEditor = new Editor( + explorer.frame, + explorer.frame.locator(".graphJsonEditor").getByTestId("EditorReact/Host/Loaded"), + ); + await expect.poll(async () => await graphJsonEditor.text(), { timeout: ONE_MINUTE_MS }).toContain(vertexId); + }); +}); diff --git a/test/mongo/pagination.spec.ts b/test/mongo/pagination.spec.ts index 548d49184..2717336a0 100644 --- a/test/mongo/pagination.spec.ts +++ b/test/mongo/pagination.spec.ts @@ -1,6 +1,6 @@ import { expect, test } from "@playwright/test"; import { setupCORSBypass } from "../CORSBypass"; -import { DataExplorer, QueryTab, TestAccount, CommandBarButton, Editor } from "../fx"; +import { CommandBarButton, DataExplorer, Editor, QueryTab, TestAccount } from "../fx"; import { serializeMongoToJson } from "../testData"; const databaseId = "test-e2etests-mongo-pagination"; diff --git a/test/sql/connectionStringLogin.spec.ts b/test/sql/connectionStringLogin.spec.ts new file mode 100644 index 000000000..8359e6dee --- /dev/null +++ b/test/sql/connectionStringLogin.spec.ts @@ -0,0 +1,118 @@ +import { Page, expect, test } from "@playwright/test"; + +import { CosmosDBManagementClient } from "@azure/arm-cosmosdb"; +import { CosmosClient, Database } from "@azure/cosmos"; +import { + DataExplorer, + ONE_MINUTE_MS, + TestAccount, + TestAuthType, + generateUniqueName, + getAccountName, + getAzureCLICredentials, + resourceGroupName, + subscriptionId, +} from "../fx"; + +const databaseId = generateUniqueName("db"); +const containerId = "testcontainer"; +const documentId = "testdoc1"; + +async function loginWithConnectionString(page: Page, connectionString: string): Promise { + await page.goto("https://localhost:1234/hostedExplorer.html"); + const switchConnectionLink = page.getByTestId("Link:SwitchConnectionType"); + await switchConnectionLink.waitFor(); + await switchConnectionLink.click(); + await page.getByPlaceholder("Please enter a connection string").fill(connectionString); + await page.getByRole("button", { name: "Connect" }).click(); +} + +test.describe("SQL account using connection string login", () => { + let database: Database = null!; + let documentEndpoint: string = null!; + // SQL signs data-plane requests client-side with the account key, so no encrypted token is issued. + let connectionString: string = null!; + + test.beforeAll("Seed Test Database", async () => { + const credentials = getAzureCLICredentials(); + const armClient = new CosmosDBManagementClient(credentials, subscriptionId); + const accountName = getAccountName(TestAccount.SQL, TestAuthType.ConnectionString); + const account = await armClient.databaseAccounts.get(resourceGroupName, accountName); + const keys = await armClient.databaseAccounts.listKeys(resourceGroupName, accountName); + documentEndpoint = account.documentEndpoint!; + connectionString = `AccountEndpoint=${documentEndpoint};AccountKey=${keys.primaryMasterKey};`; + + const client = new CosmosClient({ endpoint: documentEndpoint, key: keys.primaryMasterKey }); + database = (await client.databases.createIfNotExists({ id: databaseId })).database; + const { container } = await database.containers.createIfNotExists({ + id: containerId, + partitionKey: { paths: ["/id"] }, + }); + await container.items.upsert({ id: documentId }); + }); + + test.afterAll("Delete Test Database", async () => { + await database?.delete(); + }); + + test("reads a document after connection string login", async ({ page }) => { + await loginWithConnectionString(page, connectionString); + + const explorer = await DataExplorer.waitForExplorer(page); + const collectionNode = await explorer.waitForContainerNode(databaseId, containerId); + await expect(collectionNode.element).toBeAttached(); + await collectionNode.expand(); + + // Open the Items node to load the Documents tab and read the seeded document through the data plane. + const itemsNode = await explorer.waitForContainerItemsNode(databaseId, containerId); + await itemsNode.element.click(); + + const documentsTab = explorer.documentsTab("tab0"); + await documentsTab.documentsFilter.waitFor(); + await documentsTab.documentsListPane.waitFor(); + await expect(documentsTab.resultsEditor.locator).toBeAttached({ timeout: ONE_MINUTE_MS }); + + const documentRow = documentsTab.documentsListPane.getByText(documentId, { exact: true }).nth(0); + await documentRow.waitFor(); + await documentRow.click(); + await expect(documentsTab.resultsEditor.locator).toBeAttached({ timeout: ONE_MINUTE_MS }); + + const resultText = await documentsTab.resultsEditor.text(); + expect(resultText).not.toBeNull(); + const resultData = JSON.parse(resultText!); + expect(resultData?.id).toEqual(documentId); + }); + + test("does not call the Portal Backend during login", async ({ page }) => { + // SQL derives the account metadata from the connection string and signs data-plane requests with the + // account key, so neither the encrypted token nor the access metadata endpoint should be hit. + const portalBackendCalls: string[] = []; + page.on("request", (request) => { + if (request.url().includes("/api/connectionstring/")) { + portalBackendCalls.push(request.url()); + } + }); + + await loginWithConnectionString(page, connectionString); + + const explorer = await DataExplorer.waitForExplorer(page); + const collectionNode = await explorer.waitForContainerNode(databaseId, containerId); + await expect(collectionNode.element).toBeAttached(); + + expect(portalBackendCalls).toEqual([]); + }); + + test("opens Data Explorer when the connection string has the wrong account key", async ({ page }) => { + // A well-formed but incorrect base64 account key. The login is accepted + // without checking the key against the account, so the user gets into Data Explorer either way and + // only the data-plane requests made from inside the explorer are rejected. + const wrongKey = "A".repeat(86) + "=="; + await loginWithConnectionString(page, `AccountEndpoint=${documentEndpoint};AccountKey=${wrongKey};`); + + await DataExplorer.waitForExplorer(page); + + // The connect form is replaced by the explorer rather than staying up with a login error. + await expect(page.locator("#connectExplorer")).toHaveCount(0); + await expect(page.locator(".errorDetails")).toHaveCount(0); + }); +}); diff --git a/test/sql/query.spec.ts b/test/sql/query.spec.ts index 6368c4327..c002a5d02 100644 --- a/test/sql/query.spec.ts +++ b/test/sql/query.spec.ts @@ -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: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/); +}); diff --git a/test/sql/resourceToken.spec.ts b/test/sql/resourceToken.spec.ts index 5f8a700f8..157f773e9 100644 --- a/test/sql/resourceToken.spec.ts +++ b/test/sql/resourceToken.spec.ts @@ -11,10 +11,9 @@ import { resourceGroupName, subscriptionId, } from "../fx"; -import { getNoSqlRbacToken } from "../NoSqlTestSetup"; test("SQL account using Resource token", async ({ page }) => { - const nosqlAccountRbacToken = getNoSqlRbacToken() ?? ""; + const nosqlAccountRbacToken = process.env.NOSQL_TESTACCOUNT_TOKEN ?? ""; test.skip(nosqlAccountRbacToken.length > 0, "Resource tokens not supported when using data plane RBAC."); const credentials = getAzureCLICredentials(); diff --git a/test/tables/connectionStringLogin.spec.ts b/test/tables/connectionStringLogin.spec.ts new file mode 100644 index 000000000..02db382a2 --- /dev/null +++ b/test/tables/connectionStringLogin.spec.ts @@ -0,0 +1,82 @@ +import { expect, test } from "@playwright/test"; + +import { CosmosDBManagementClient } from "@azure/arm-cosmosdb"; +import { Container, CosmosClient } from "@azure/cosmos"; +import { + DataExplorer, + ONE_MINUTE_MS, + TestAccount, + TestAuthType, + generateUniqueName, + getAccountName, + getAzureCLICredentials, + resourceGroupName, + subscriptionId, +} from "../fx"; + +// Table API accounts store tables in a fixed "TablesDB" database, with each table as a container. +const databaseId = "TablesDB"; +const tableId = generateUniqueName("table"); +const partitionKey = "testpartition"; +const rowKey = "testrow"; + +test.describe("Tables account using connection string login", () => { + let container: Container = null!; + + test.beforeAll("Seed Test Table", async () => { + const credentials = getAzureCLICredentials(); + const armClient = new CosmosDBManagementClient(credentials, subscriptionId); + const accountName = getAccountName(TestAccount.Tables, TestAuthType.ConnectionString); + const account = await armClient.databaseAccounts.get(resourceGroupName, accountName); + const keys = await armClient.databaseAccounts.listKeys(resourceGroupName, accountName); + + const client = new CosmosClient({ endpoint: account.documentEndpoint!, key: keys.primaryMasterKey }); + const { database } = await client.databases.createIfNotExists({ id: databaseId }); + container = ( + await database.containers.createIfNotExists({ + id: tableId, + partitionKey: { paths: ["/'$pk'"] }, + }) + ).container; + await container.items.upsert({ $pk: partitionKey, id: rowKey, $id: rowKey }); + }); + + test.afterAll("Delete Test Table", async () => { + // Only remove the table we created; the fixed "TablesDB" database is shared by every table in the + // account, so deleting it would destroy unrelated tables. + await container?.delete(); + }); + + test("reads an entity after connection string login", async ({ page }) => { + const credentials = getAzureCLICredentials(); + const armClient = new CosmosDBManagementClient(credentials, subscriptionId); + const accountName = getAccountName(TestAccount.Tables, TestAuthType.ConnectionString); + const { connectionStrings = [] } = await armClient.databaseAccounts.listConnectionStrings( + resourceGroupName, + accountName, + ); + + // Table accounts sign data-plane requests client-side with the account key, so no encrypted token is issued. + const connectionString = connectionStrings.find((cs) => cs.type === "Table")?.connectionString; + + await page.goto("https://localhost:1234/hostedExplorer.html"); + const switchConnectionLink = page.getByTestId("Link:SwitchConnectionType"); + await switchConnectionLink.waitFor(); + await switchConnectionLink.click(); + await page.getByPlaceholder("Please enter a connection string").fill(connectionString!); + await page.getByRole("button", { name: "Connect" }).click(); + + const explorer = await DataExplorer.waitForExplorer(page); + const tableNode = await explorer.waitForContainerNode(databaseId, tableId); + await tableNode.expand(); + + // Open the Entities node to load the table entities grid and read the seeded entity through the data plane. + const entitiesNode = await explorer.waitForNode(`${databaseId}/${tableId}/Entities`); + await entitiesNode.element.click(); + + const entitiesGrid = explorer.frame.locator("#storageTable"); + await expect(entitiesGrid).toBeVisible({ timeout: ONE_MINUTE_MS }); + await expect(entitiesGrid.getByText(rowKey, { exact: true }).first()).toBeVisible({ timeout: ONE_MINUTE_MS }); + await expect(entitiesGrid.getByText(partitionKey, { exact: true }).first()).toBeVisible(); + }); +}); diff --git a/test/testData.ts b/test/testData.ts index f9b493b70..57fbfe5a7 100644 --- a/test/testData.ts +++ b/test/testData.ts @@ -9,7 +9,6 @@ import { JSONObject, } from "@azure/cosmos"; import { Buffer } from "node:buffer"; -import { webcrypto } from "node:crypto"; import { generateUniqueName, getAccountName, @@ -18,13 +17,6 @@ import { subscriptionId, TestAccount, } from "./fx"; -import { getNoSqlRbacToken } from "./NoSqlTestSetup"; - -// In Node.js >= 19, globalThis.crypto is already available as a read-only getter. -// Only assign the polyfill for older versions. -if (!globalThis.crypto) { - Object.defineProperty(globalThis, "crypto", { value: webcrypto, writable: true, configurable: true }); -} export interface TestItem { id: string; @@ -135,7 +127,7 @@ async function createCosmosClientForSQLAccount( const rbacToken = accountType === TestAccount.SQL - ? getNoSqlRbacToken() + ? process.env.NOSQL_TESTACCOUNT_TOKEN : accountType === TestAccount.SQLContainerCopyOnly ? process.env.NOSQL_CONTAINERCOPY_TESTACCOUNT_TOKEN : ""; diff --git a/test/testExplorer/TestExplorer.ts b/test/testExplorer/TestExplorer.ts index ce65d5f86..28f763a27 100644 --- a/test/testExplorer/TestExplorer.ts +++ b/test/testExplorer/TestExplorer.ts @@ -3,7 +3,6 @@ import "../../less/hostedexplorer.less"; import { DataExplorerInputsFrame } from "../../src/Contracts/ViewModels"; import { updateUserContext } from "../../src/UserContext"; import { get, listKeys } from "../../src/Utils/arm/generatedClients/cosmos/databaseAccounts"; -import { getNoSqlRbacToken } from "../NoSqlTestSetup"; const urlSearchParams = new URLSearchParams(window.location.search); const resourceGroup = urlSearchParams.get("resourceGroup") || process.env.RESOURCE_GROUP || ""; @@ -16,7 +15,7 @@ const enablecontainercopy = urlSearchParams.get("enablecontainercopy"); const nosqlRbacToken = urlSearchParams.get("nosqlRbacToken") || - (enablecontainercopy ? process.env.NOSQL_CONTAINERCOPY_TESTACCOUNT_TOKEN : getNoSqlRbacToken()) || + (enablecontainercopy ? process.env.NOSQL_CONTAINERCOPY_TESTACCOUNT_TOKEN : process.env.NOSQL_TESTACCOUNT_TOKEN) || ""; const nosqlReadOnlyRbacToken = @@ -30,6 +29,7 @@ const mongoRbacToken = urlSearchParams.get("mongoRbacToken") || process.env.MONG const mongo32RbacToken = urlSearchParams.get("mongo32RbacToken") || process.env.MONGO32_TESTACCOUNT_TOKEN || ""; const mongoReadOnlyRbacToken = urlSearchParams.get("mongoReadOnlyRbacToken") || process.env.MONGO_READONLY_TESTACCOUNT_TOKEN || ""; +const tenantId = urlSearchParams.get("tenantId") || process.env.AZURE_TENANT_ID || ""; const initTestExplorer = async (): Promise => { updateUserContext({ @@ -51,7 +51,7 @@ const initTestExplorer = async (): Promise => { case "gremlin": rbacToken = gremlinRbacToken; break; - case "tables": + case "table": rbacToken = tableRbacToken; break; case "cassandra": @@ -90,6 +90,7 @@ const initTestExplorer = async (): Promise => { resourceGroup, authorizationToken: `Bearer ${authToken}`, aadToken: rbacToken, + tenantId, features: {}, containerCopyEnabled: enablecontainercopy === "true", hasWriteAccess: true, diff --git a/tsconfig.strict.json b/tsconfig.strict.json index 230517803..1a2ae5ece 100644 --- a/tsconfig.strict.json +++ b/tsconfig.strict.json @@ -25,6 +25,7 @@ "./src/Common/ObjectCache.ts", "./src/Common/OfferUtility.test.ts", "./src/Common/OfferUtility.ts", + "./src/Common/PortalBackendClient.ts", "./src/Common/Splitter.ts", "./src/Common/ThemeUtility.ts", "./src/Common/UrlUtility.ts", @@ -36,7 +37,6 @@ "./src/Contracts/SelfServeContracts.ts", "./src/Contracts/SubscriptionType.ts", "./src/Contracts/Versions.ts", - "./src/Explorer/Controls/GitHub/GitHubStyleConstants.ts", "./src/Explorer/Controls/SmartUi/InputUtils.ts", "./src/Explorer/Graph/GraphExplorerComponent/ArraysByKeyCache.test.ts", "./src/Explorer/Graph/GraphExplorerComponent/ArraysByKeyCache.ts", @@ -44,11 +44,6 @@ "./src/Explorer/Graph/GraphExplorerComponent/GraphData.ts", "./src/Explorer/LazyMonaco.ts", "./src/Explorer/Menus/NotificationConsole/ConsoleData.tsx", - "./src/Explorer/Notebook/FileSystemUtil.ts", - "./src/Explorer/Notebook/NotebookComponent/ContentProviders/InMemoryContentProviderUtils.ts", - "./src/Explorer/Notebook/NotebookComponent/loadTransform.ts", - "./src/Explorer/Notebook/NotebookContentItem.ts", - "./src/Explorer/Notebook/SchemaAnalyzer/SchemaAnalyzerSplashScreen.tsx", "./src/Explorer/OpenFullScreen.test.tsx", "./src/Explorer/OpenFullScreen.tsx", "./src/Explorer/Panes/PanelContainerComponent.test.tsx", @@ -67,7 +62,6 @@ "./src/Explorer/Tables/QueryBuilder/DateTimeUtilities.test.ts", "./src/Explorer/Tables/QueryBuilder/DateTimeUtilities.ts", "./src/Explorer/Tree/AccessibleVerticalList.ts", - "./src/GitHub/GitHubConnector.ts", "./src/HostedExplorerChildFrame.ts", "./src/Platform/Hosted/Components/MeControl.test.tsx", "./src/Platform/Hosted/Components/MeControl.tsx", @@ -100,8 +94,6 @@ "./src/Utils/CapabilityUtils.ts", "./src/Utils/CloudUtils.ts", "./src/Utils/EndpointUtils.ts", - "./src/Utils/GitHubUtils.test.ts", - "./src/Utils/GitHubUtils.ts", "./src/Utils/MessageValidation.test.ts", "./src/Utils/MessageValidation.ts", "./src/Utils/NotificationConsoleUtils.ts", @@ -114,8 +106,6 @@ "./src/hooks/useConfig.ts", "./src/hooks/useDirectories.tsx", "./src/hooks/useGraphPhoto.tsx", - "./src/hooks/useNotebookSnapshotStore.ts", - "./src/hooks/usePortalAccessToken.tsx", "./src/hooks/useNotificationConsole.ts", "./src/hooks/useObservable.ts", "./src/hooks/useSidePanel.ts", @@ -127,7 +117,6 @@ "./src/Platform/Hosted/Components/SwitchSubscription.tsx" ], "include": [ - "src/CellOutputViewer/transforms/**/*", "src/Common/Tooltip/**/*", "src/Controls/**/*", "src/Definitions/**/*", @@ -138,14 +127,12 @@ "src/Explorer/Controls/ResizeSensorReactComponent/**/*", "src/Explorer/Graph/GraphExplorerComponent/__mocks__/**/*", "src/Explorer/Menus/NavBar/**/*", - "src/Explorer/Notebook/NotebookComponent/__mocks__/**/*", "src/Explorer/Panes/RightPaneForm/**/*", "src/Libs/**/*", "src/Localization/**/*", "src/Platform/Emulator/**/*", "src/SelfServe/Documentation/**/*", "src/Shared/Telemetry/**/*", - "src/Terminal/**/*", "src/Utils/arm/**/*" ] } \ No newline at end of file diff --git a/utils/cleanupDBs.js b/utils/cleanupDBs.js index 0c19973a3..233743c0f 100644 --- a/utils/cleanupDBs.js +++ b/utils/cleanupDBs.js @@ -3,7 +3,7 @@ const { CosmosDBManagementClient } = require("@azure/arm-cosmosdb"); const ms = require("ms"); const subscriptionId = process.env["AZURE_SUBSCRIPTION_ID"]; -const resourceGroupName = "de-e2e-tests"; +const resourceGroupName = process.env["E2ETESTS_RESOURCEGROUP_NAME"]; const thirtyMinutesAgo = new Date(Date.now() - 1000 * 60 * 30).getTime(); diff --git a/webpack.config.js b/webpack.config.js index 19f90200a..8da4a5a5e 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -4,8 +4,6 @@ require("dotenv/config"); const path = require("path"); const MonacoWebpackPlugin = require("monaco-editor-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin"); -const InlineChunkHtmlPlugin = require("react-dev-utils/InlineChunkHtmlPlugin"); -const HTMLInlineCSSWebpackPlugin = require("html-inline-css-webpack-plugin").default; const { EnvironmentPlugin } = require("webpack"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const CopyWebpackPlugin = require("copy-webpack-plugin"); @@ -111,10 +109,7 @@ module.exports = function (_env = {}, argv = {}) { index: "./src/Index.tsx", quickstart: "./src/quickstart.ts", hostedExplorer: "./src/HostedExplorer.tsx", - terminal: "./src/Terminal/index.ts", - cellOutputViewer: "./src/CellOutputViewer/CellOutputViewer.tsx", selfServe: "./src/SelfServe/SelfServe.tsx", - connectToGitHub: "./src/GitHub/GitHubConnector.ts", redirectBridge: "./src/redirectBridge.ts", ...(mode !== "production" && { testExplorer: "./test/testExplorer/TestExplorer.ts" }), ...(mode !== "production" && { @@ -128,11 +123,6 @@ module.exports = function (_env = {}, argv = {}) { template: "src/explorer.html", chunks: ["main"], }), - new HtmlWebpackPlugin({ - filename: "terminal.html", - template: "src/Terminal/index.html", - chunks: ["terminal"], - }), new HtmlWebpackPlugin({ filename: "quickstart.html", template: "src/quickstart.html", @@ -148,16 +138,6 @@ module.exports = function (_env = {}, argv = {}) { template: "src/hostedExplorer.html", chunks: ["hostedExplorer"], }), - new HtmlWebpackPlugin({ - filename: "cellOutputViewer.html", - template: "src/CellOutputViewer/cellOutputViewer.html", - chunks: ["cellOutputViewer"], - }), - new HtmlWebpackPlugin({ - filename: "connectToGitHub.html", - template: "src/connectToGitHub.html", - chunks: ["connectToGitHub"], - }), new HtmlWebpackPlugin({ filename: "selfServe.html", template: "src/SelfServe/selfServe.html", @@ -202,10 +182,6 @@ module.exports = function (_env = {}, argv = {}) { filename: "[name].[contenthash].css", }), ...htmlWebpackPlugins, - new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/cellOutputViewer/]), - new HTMLInlineCSSWebpackPlugin({ - filter: (fileName) => fileName.includes("cellOutputViewer"), - }), new MonacoWebpackPlugin(), new CopyWebpackPlugin({ patterns: [