diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..65096ce0b --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +# NOTE: Prettier reads EditorConfig settings, so be careful adjusting settings here and assuming they'll only affect your editor ;). + +# top-most EditorConfig file +root = true + +[*.yml] +indent_size = 2 + +[*.{js,jsx,ts,tsx}] +indent_size = 2 \ No newline at end of file diff --git a/.eslintignore b/.eslintignore index c188701b9..2eeecaed5 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,5 @@ +playwright.config.ts + **/node_modules/ src/**/__mocks__/**/* dist/ @@ -89,10 +91,7 @@ src/Explorer/Tables/TableEntityProcessor.ts src/Explorer/Tables/Utilities.ts src/Explorer/Tabs/ConflictsTab.ts src/Explorer/Tabs/DatabaseSettingsTab.ts -src/Explorer/Tabs/DocumentsTab.test.ts -src/Explorer/Tabs/DocumentsTab.ts src/Explorer/Tabs/GraphTab.ts -src/Explorer/Tabs/MongoDocumentsTab.ts src/Explorer/Tabs/NotebookV2Tab.ts src/Explorer/Tabs/ScriptTabBase.ts src/Explorer/Tabs/TabComponents.ts @@ -128,7 +127,7 @@ src/Explorer/Controls/InputTypeahead/InputTypeaheadComponent.tsx src/Explorer/Controls/Notebook/NotebookTerminalComponent.test.tsx src/Explorer/Controls/Notebook/NotebookTerminalComponent.tsx src/Explorer/Controls/NotebookViewer/NotebookViewerComponent.tsx -src/Explorer/Controls/TreeComponent/TreeComponent.tsx +src/Explorer/Controls/TreeComponent/LegacyTreeComponent.tsx src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.test.tsx src/Explorer/Graph/GraphExplorerComponent/NodePropertiesComponent.tsx src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.test.tsx @@ -145,4 +144,5 @@ src/Explorer/Notebook/temp/inputs/connected-editors/codemirror.tsx src/Explorer/Tree/ResourceTreeAdapter.tsx __mocks__/monaco-editor.ts src/Explorer/Tree/ResourceTree.tsx +src/Utils/EndpointUtils.ts src/Utils/PriorityBasedExecutionUtils.ts \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a53b92645..15c555e31 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,6 +8,9 @@ on: pull_request: branches: - master +permissions: + id-token: write + contents: read jobs: codemetrics: runs-on: ubuntu-latest @@ -101,72 +104,6 @@ jobs: run: az storage blob upload -c '$web' -f ./preview/config.json --account-name cosmosexplorerpreview --name "${{github.event.pull_request.head.sha || github.sha}}/config.json" --account-key="${PREVIEW_STORAGE_KEY}" --overwrite true env: PREVIEW_STORAGE_KEY: ${{ secrets.PREVIEW_STORAGE_KEY }} - endtoendemulator: - name: "End To End Emulator Tests" - # Temporarily disabled. This test needs to be rewritten in playwright - if: false - runs-on: windows-latest - steps: - - uses: actions/checkout@v4 - - name: Use Node.js 18.x - uses: actions/setup-node@v4 - with: - node-version: 18.x - - uses: southpolesteve/cosmos-emulator-github-action@v1 - - name: End to End Tests - run: | - npm ci - npm start & - npm run wait-for-server - npx jest -c ./jest.config.e2e.js --detectOpenHandles test/sql/container.spec.ts - shell: bash - env: - DATA_EXPLORER_ENDPOINT: "https://localhost:1234/explorer.html?platform=Emulator" - PLATFORM: "Emulator" - NODE_TLS_REJECT_UNAUTHORIZED: 0 - - uses: actions/upload-artifact@v3 - if: failure() - with: - name: screenshots - path: failed-* - endtoend: - name: "E2E" - runs-on: ubuntu-latest - env: - NODE_TLS_REJECT_UNAUTHORIZED: 0 - NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET }} - strategy: - fail-fast: false - matrix: - test-file: - - ./test/cassandra/container.spec.ts - - ./test/graph/container.spec.ts - - ./test/sql/container.spec.ts - - ./test/mongo/container.spec.ts - - ./test/mongo/container32.spec.ts - - ./test/selfServe/selfServeExample.spec.ts - # - ./test/notebooks/upload.spec.ts // TEMP disabled since notebooks service is off - - ./test/sql/resourceToken.spec.ts - - ./test/tables/container.spec.ts - steps: - - uses: actions/checkout@v4 - - name: Use Node.js 18.x - uses: actions/setup-node@v4 - with: - node-version: 18.x - - run: npm ci - - run: npm start & - - run: npm run wait-for-server - - name: ${{ matrix['test-file'] }} - run: | - # Run tests up to three times - for i in $(seq 1 3); do npx jest -c ./jest.config.playwright.js ${{ matrix['test-file'] }} && s=0 && break || s=$? && sleep 1; done; (exit $s) - shell: bash - - uses: actions/upload-artifact@v3 - if: failure() - with: - name: screenshots - path: screenshots/ nuget: name: Publish Nuget if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/') @@ -216,3 +153,70 @@ jobs: name: packages with: path: "*.nupkg" + + playwright-tests: + name: "Run Playwright Tests (Shard ${{ matrix.shardIndex }} of ${{ matrix.shardTotal }})" + runs-on: ubuntu-latest + env: + NODE_TLS_REJECT_UNAUTHORIZED: 0 + AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + strategy: + fail-fast: false + matrix: + shardIndex: [1, 2, 3, 4, 5, 6, 7, 8] + shardTotal: [8] + steps: + - uses: actions/checkout@v4 + - name: "Az CLI login" + uses: azure/login@v1 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + - name: Use Node.js 18.x + uses: actions/setup-node@v4 + with: + node-version: 18.x + - run: npm ci + - run: npx playwright install --with-deps + - name: Run test shard ${{ matrix['shardIndex'] }} of ${{ matrix['shardTotal']}} + run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} + - name: Upload blob report to GitHub Actions Artifacts + if: ${{ !cancelled() }} + uses: actions/upload-artifact@v4 + with: + name: blob-report-${{ matrix.shardIndex }} + path: blob-report + retention-days: 1 + + merge-playwright-reports: + name: "Merge Playwright Reports" + # Merge reports after playwright-tests, even if some shards have failed + if: ${{ !cancelled() }} + needs: [playwright-tests] + + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 18 + - name: Install dependencies + run: npm ci + + - name: Download blob reports from GitHub Actions Artifacts + uses: actions/download-artifact@v4 + with: + path: all-blob-reports + pattern: blob-report-* + merge-multiple: true + + - name: Merge into HTML Report + run: npx playwright merge-reports --reporter html ./all-blob-reports + + - name: Upload HTML report + uses: actions/upload-artifact@v4 + with: + name: html-report--attempt-${{ github.run_attempt }} + path: playwright-report + retention-days: 14 \ No newline at end of file diff --git a/.github/workflows/cleanup.yml b/.github/workflows/cleanup.yml index 229477f0b..6698951ae 100644 --- a/.github/workflows/cleanup.yml +++ b/.github/workflows/cleanup.yml @@ -9,6 +9,10 @@ on: # Once every hour - cron: "0 15 * * *" +permissions: + id-token: write + contents: read + # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: # This workflow contains a single job called "build" @@ -16,10 +20,17 @@ jobs: name: "Cleanup Test Database Accounts" runs-on: ubuntu-latest env: - NOTEBOOKS_TEST_RUNNER_CLIENT_ID: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_ID }} - NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET }} + AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} steps: - uses: actions/checkout@v2 + + - name: "Az CLI login" + uses: azure/login@v1 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + - name: Use Node.js 18.x uses: actions/setup-node@v1 with: diff --git a/.gitignore b/.gitignore index be016240b..3617cf905 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,8 @@ Contracts/* .env failure.png screenshots/* -GettingStarted-ignore*.ipynb \ No newline at end of file +GettingStarted-ignore*.ipynb +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/.vscode/settings.json b/.vscode/settings.json index d66e1654c..a57d40961 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -20,8 +20,8 @@ "typescript.tsdk": "node_modules/typescript/lib", "editor.formatOnSave": true, "editor.codeActionsOnSave": { - "source.fixAll.eslint": true, - "source.organizeImports": true + "source.fixAll.eslint": "explicit", + "source.organizeImports": "explicit" }, "typescript.preferences.importModuleSpecifier": "non-relative", "editor.defaultFormatter": "esbenp.prettier-vscode" diff --git a/configs/mpac.json b/configs/mpac.json index cd8b27e03..0a8e7eaba 100644 --- a/configs/mpac.json +++ b/configs/mpac.json @@ -1,5 +1,5 @@ { - "JUNO_ENDPOINT": "https://tools-staging.cosmos.azure.com", - "isTerminalEnabled" : true, - "isPhoenixEnabled" : true -} \ No newline at end of file + "JUNO_ENDPOINT": "https://tools.cosmos.azure.com", + "isTerminalEnabled": true, + "isPhoenixEnabled": true +} diff --git a/images/Home_16.svg b/images/Home_16.svg new file mode 100644 index 000000000..80facc866 --- /dev/null +++ b/images/Home_16.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/jest-playwright.config.js b/jest-playwright.config.js deleted file mode 100644 index d66e8f221..000000000 --- a/jest-playwright.config.js +++ /dev/null @@ -1,13 +0,0 @@ -const isCI = require("is-ci"); - -module.exports = { - exitOnPageError: false, - launchOptions: { - headless: isCI, - slowMo: 10, - timeout: 60000, - }, - contextOptions: { - ignoreHTTPSErrors: true, - }, -}; diff --git a/jest.config.js b/jest.config.js index c00efdac6..1c1f48e6d 100644 --- a/jest.config.js +++ b/jest.config.js @@ -31,7 +31,7 @@ module.exports = { coveragePathIgnorePatterns: ["/node_modules/"], // A list of reporter names that Jest uses when writing coverage reports - coverageReporters: ["json", "text", "cobertura"], + coverageReporters: ["json", "text", "cobertura", "lcov"], // An object that configures minimum threshold enforcement for coverage results coverageThreshold: { @@ -76,6 +76,10 @@ module.exports = { "^dnd-core$": "dnd-core/dist/cjs", "^react-dnd$": "react-dnd/dist/cjs", "^react-dnd-html5-backend$": "react-dnd-html5-backend/dist/cjs", + "d3-force": "/node_modules/d3-force/dist/d3-force.min.js", + "d3-quadtree": "/node_modules/d3-quadtree/dist/d3-quadtree.min.js", + "d3-scale-chromatic": "/node_modules/d3-scale-chromatic/dist/d3-scale-chromatic.min.js", + "d3-zoom": "/node_modules/d3-zoom/dist/d3-zoom.min.js", }, // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader @@ -130,7 +134,6 @@ module.exports = { // The test environment that will be used for testing // testEnvironment: "jest-environment-jsdom", - modulePaths: ["node_modules", "/src"], // Options that will be passed to the testEnvironment diff --git a/jest.config.playwright.js b/jest.config.playwright.js deleted file mode 100644 index c452a5368..000000000 --- a/jest.config.playwright.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - preset: "jest-playwright-preset", - testMatch: ["/test/**/*.spec.[jt]s?(x)"], - setupFiles: ["dotenv/config"], - testEnvironment: "./test/playwrightEnv.js", - setupFilesAfterEnv: ["expect-playwright"], -}; diff --git a/less/Common/Constants.less b/less/Common/Constants.less index 6f034c018..d79c3e4e0 100644 --- a/less/Common/Constants.less +++ b/less/Common/Constants.less @@ -130,6 +130,7 @@ @ActiveTabWidth: 141px; @TabsHeight: 30px; @TabsWidth: 140px; +@ContentWrapper: 111px; @StatusIconContainerSize: 18px; @LoadingErrorIconSize: 14px; @ErrorIconContainer: 16px; @@ -147,6 +148,7 @@ // CommandBar @CommandBarButtonHeight: 40px; +@FabricCommandBarButtonHeight: 34px; /********************************************************************************** Portal Consts @@ -162,9 +164,10 @@ /**********************************************************************************/ @FabricFont: "Segoe UI", "Segoe UI Web (West European)", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif; +@FabricToolbarIconColor: "brightness(0) saturate(100%) invert(50%) sepia(17%) saturate(1459%) hue-rotate(81deg) brightness(99%) contrast(94%)"; @FabricBoxBorderRadius: 8px; -@FabricBoxBorderShadow: 0 0 2px rgba(0, 0, 0, 0.12), 0 2px 4px rgba(0, 0, 0, 0.14); +@FabricBoxBorderShadow: rgba(0, 0, 0, 0.133) 0px 1.6px 3.6px 0px, rgba(0, 0, 0, 0.11) 0px 0.3px 0.9px 0px; @FabricBoxMargin: 4px 3px 4px 3px; @FabricAccentMediumHigh: #0c695a; @@ -333,4 +336,11 @@ width: 0; height: 0; border-color: @InfoPointerColor transparent; +} +/********************************************************************************************************* + Screen Reader Only +**********************************************************************************************************/ +.screenReaderOnly { + position: absolute; + left: -9999px; } \ No newline at end of file diff --git a/less/documentDB.less b/less/documentDB.less index bdefc2dbd..bf5f87e3e 100644 --- a/less/documentDB.less +++ b/less/documentDB.less @@ -2264,38 +2264,49 @@ a:link { width: 82px; } -.tabdocuments .scrollable { - height: 100%; - overflow-y: auto; - overflow-x: hidden; - padding-left: 5px; - padding-right: 5px; - width: 100%; -} +// .tabdocuments .scrollable { +// height: 100%; +// overflow-y: auto; +// overflow-x: hidden; +// padding-left: 5px; +// padding-right: 5px; +// width: 100%; +// } -.tabdocuments > .tabdocumentsGridElement { - width: 50%; -} +// .tabdocuments > .tabdocumentsGridElement { +// width: 50%; +// } -.tabdocuments > .evenlySpacedHeader { - width: 30%; -} +// .tabdocuments > .evenlySpacedHeader { +// width: 30%; +// } -.tabdocuments.scrollable:focus, -.tabdocuments.scrollable:active { - outline: 1px dotted; -} +// .tabdocuments.scrollable:focus, +// .tabdocuments.scrollable:active { +// outline: 1px dotted; +// } -.tabdocuments .scrollable table td { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} +// .tabdocuments .scrollable table td { +// white-space: nowrap; +// overflow: hidden; +// text-overflow: ellipsis; +// } .mongoDocumentEditor .monaco-editor.vs .redsquiggly { display: none !important; } +.monaco-editor .quick-input-list-label { + /* Restore some of Monaco's default styles that are clobbered by our global styles */ + padding: 0; + line-height: 22px; +} + +.monaco-editor .quick-input-list .highlight { + /* Padding in highlighted text within the quick input list breaks the flow of the text */ + padding: 0; +} + td a { color: #393939; } @@ -2305,10 +2316,9 @@ td a:hover { } .loadMore { + display: block; width: 100%; - padding-left: 30%; - padding-top: 2px; - cursor: pointer; + text-align: center; } .loadMore > a:focus { @@ -2356,9 +2366,9 @@ a:link { .tabsManagerContainer { height: 100%; flex-grow: 1; - overflow: hidden; + display: flex; + flex-direction: column; min-height: 300px; - overflow-y: scroll; } .tabs { @@ -2547,10 +2557,12 @@ a:link { } .filterdivs { - padding-top: 15px; - height: 45px; - margin-bottom: 8px; + margin: 10px 0px; white-space: nowrap; + input { + line-height: 14px; // To correct vertical position of the down arrow of the input + outline: none; // Remove the dotted border on focus, because fluent has its own focus style (underlined) + } } .editFilterContainer { @@ -2567,6 +2579,18 @@ a:link { cursor: pointer; } +.documentsTab { + .documentsTable { + .documentsTableCell { + border-left: 1px solid @BaseMedium; + height: 100%; + } + .documentsTableHeader { + border-bottom: 1px solid @BaseMedium; + } + } +} + .querydropdown { border: 1px solid @BaseMedium; font-style: normal; @@ -2647,7 +2671,7 @@ a:link { width: @ActiveTabWidth; } -.nav-tabs > li.active > .tabNavContentContainer > .tab_Content > .tabNavText { +.nav-tabs > li.active > .tabNavContentContainer > .tab_Content > .contentWrapper > .tabNavText { font-weight: bolder; border-bottom: 2px solid rgba(0, 120, 212, 1); } @@ -2683,67 +2707,71 @@ a:link { width: @TabsWidth; border-right: @ButtonBorderWidth solid @BaseMedium; white-space: nowrap; + .contentWrapper { + .flex-display(); + width: @ContentWrapper; - .statusIconContainer { - width: @StatusIconContainerSize; - height: @StatusIconContainerSize; - margin-left: @SmallSpace; - display: inline-flex; + .statusIconContainer { + width: @StatusIconContainerSize; + height: @StatusIconContainerSize; + margin-left: @SmallSpace; + display: inline-flex; - .errorIconContainer { - width: @ErrorIconContainer; - height: @ErrorIconContainer; - margin-top: 1px; + .errorIconContainer { + width: @ErrorIconContainer; + height: @ErrorIconContainer; + margin-top: 1px; - .errorIcon { - width: @ErrorIconWidth; + .errorIcon { + width: @ErrorIconWidth; + height: @LoadingErrorIconSize; + background-image: url(../images/error_no_outline.svg); + background-repeat: no-repeat; + background-position: center; + background-size: 3px; + display: block; + margin: 1px 0px 0px 6px; + } + } + + .errorIconContainer.actionsEnabled { + &:hover { + .hover(); + } + + &:focus { + .focus(); + } + + &:active { + .active(); + } + } + + .errorIconContainer[tabindex]:active { + outline: none; + } + + .loadingIcon { + width: @LoadingErrorIconSize; height: @LoadingErrorIconSize; - background-image: url(../images/error_no_outline.svg); - background-repeat: no-repeat; - background-position: center; - background-size: 3px; - display: block; - margin: 1px 0px 0px 6px; + margin: 0px 0px @SmallSpace @SmallSpace; } } - .errorIconContainer.actionsEnabled { - &:hover { - .hover(); - } - - &:focus { - .focus(); - } - - &:active { - .active(); - } + .tabNavText { + margin-left: @SmallSpace; + margin-right: 2px; + color: @BaseDark; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + flex-grow: 1; } - - .errorIconContainer[tabindex]:active { - outline: none; - } - - .loadingIcon { - width: @LoadingErrorIconSize; - height: @LoadingErrorIconSize; - margin: 0px 0px @SmallSpace @SmallSpace; - } - } - - .tabNavText { - margin-left: @SmallSpace; - margin-right: 2px; - color: @BaseDark; - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - flex-grow: 1; } .tabIconSection { - width: 30px; + width: 29px; position: relative; padding-top: 2px; @@ -2897,9 +2925,21 @@ a:link { padding-left: 8px; } + .settingsSectionInlineCheckbox { + display: flex; + flex-direction: row-reverse; + justify-content: flex-end; + align-items: center; + gap: 5px; + } + .settingsSectionLabel { margin-bottom: @DefaultSpace; margin-right: 5px; + + .panelInfoIcon { + margin-left: 5px; + } } .pageOptionsPart { diff --git a/less/documentDBFabric.less b/less/documentDBFabric.less index acff5e129..ea4001780 100644 --- a/less/documentDBFabric.less +++ b/less/documentDBFabric.less @@ -25,33 +25,38 @@ a:focus { } .resourceTreeAndTabs { - border-radius: @FabricBoxBorderRadius; + border-radius: 0px; box-shadow: @FabricBoxBorderShadow; margin: @FabricBoxMargin; - margin-top: 4px; + margin-top: 0px; + margin-bottom: 0px; background-color: #ffffff; } .tabsManagerContainer { - background-color: #fafafa + background-color: #ffffff } .nav-tabs-margin { padding-top: 8px; - background-color: #fafafa + background-color: #ffffff } .commandBarContainer { background-color: #ffffff; - border-bottom: none; - border-radius: @FabricBoxBorderRadius; + border-radius: @FabricBoxBorderRadius @FabricBoxBorderRadius 0px 0px; box-shadow: @FabricBoxBorderShadow; margin: @FabricBoxMargin; + margin-top: 0px; + margin-bottom: 0px; padding-top: 2px; + padding: 0px; + height: 40px; } .dividerContainer { padding: @SmallSpace 0px @SmallSpace 0px; + height: @FabricCommandBarButtonHeight; .flex-display(); span { @@ -70,7 +75,7 @@ a:focus { border-bottom: 2px solid @FabricAccentMedium; } -.nav-tabs>li.active>.tabNavContentContainer>.tab_Content>.tabNavText { +.nav-tabs>li.active>.tabNavContentContainer>.tab_Content>.contentWrapper>.tabNavText { border-bottom: 0px none transparent; } @@ -88,9 +93,11 @@ a:focus { width: calc(@TabsWidth - (@SmallSpace * 2)); padding-bottom: @SmallSpace; - .statusIconContainer { - margin-left: 0px; - } + .contentWrapper { + .statusIconContainer { + margin-left: 0px; + } + } .tabIconSection { .cancelButton { @@ -158,9 +165,10 @@ a:focus { .dataExplorerErrorConsoleContainer { - border-radius: @FabricBoxBorderRadius; + border-radius: 0px 0px @FabricBoxBorderRadius @FabricBoxBorderRadius; box-shadow: @FabricBoxBorderShadow; margin: @FabricBoxMargin; + margin-top: 0px; width: auto; align-self: auto; } diff --git a/package-lock.json b/package-lock.json index fd4ba95b8..5a872d3fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,10 +10,10 @@ "hasInstallScript": true, "dependencies": { "@azure/arm-cosmosdb": "9.1.0", - "@azure/cosmos": "4.0.0", + "@azure/cosmos": "4.0.1-beta.3", "@azure/cosmos-language-service": "0.0.5", - "@azure/identity": "1.2.1", - "@azure/ms-rest-nodeauth": "3.0.7", + "@azure/identity": "1.5.2", + "@azure/ms-rest-nodeauth": "3.1.1", "@azure/msal-browser": "2.14.2", "@babel/plugin-proposal-class-properties": "7.12.1", "@babel/plugin-proposal-decorators": "7.12.12", @@ -51,6 +51,8 @@ "@types/lodash": "4.14.171", "@types/mkdirp": "1.0.1", "@types/node-fetch": "2.5.7", + "@uiw/react-split": "5.9.3", + "@xmldom/xmldom": "0.7.13", "applicationinsights": "1.8.0", "bootstrap": "3.4.1", "canvas": "file:./canvas", @@ -59,7 +61,7 @@ "copy-webpack-plugin": "11.0.0", "crossroads": "0.12.2", "css-element-queries": "1.1.1", - "d3": "6.1.1", + "d3": "7.8.5", "datatables.net-colreorder-dt": "1.7.0", "datatables.net-dt": "1.13.8", "date-fns": "1.29.0", @@ -74,12 +76,14 @@ "i18next-browser-languagedetector": "6.0.1", "i18next-http-backend": "1.0.23", "iframe-resizer-react": "1.1.0", + "immer": "9.0.6", "immutable": "4.0.0-rc.12", "is-ci": "2.0.0", "jquery": "3.7.1", "jquery-typeahead": "2.11.1", "jquery-ui-dist": "1.13.2", "knockout": "3.5.1", + "loader-utils": "2.0.3", "mkdirp": "1.0.4", "monaco-editor": "0.44.0", "ms": "2.1.3", @@ -99,14 +103,17 @@ "react-redux": "7.1.3", "react-splitter-layout": "4.0.0", "react-string-format": "1.0.1", + "react-window": "1.8.10", "react-youtube": "9.0.1", "reflect-metadata": "0.1.13", "rx-jupyter": "5.5.12", "sanitize-html": "2.3.3", + "shell-quote": "1.7.3", "styled-components": "5.0.1", "swr": "0.4.0", "terser-webpack-plugin": "5.3.9", - "underscore": "1.9.1", + "tinykeys": "2.1.0", + "underscore": "1.12.1", "utility-types": "3.10.0", "zustand": "3.5.0" }, @@ -115,6 +122,7 @@ "@babel/preset-env": "7.9.0", "@babel/preset-react": "7.9.4", "@babel/preset-typescript": "7.9.0", + "@playwright/test": "1.44.0", "@testing-library/react": "11.2.3", "@types/applicationinsights-js": "1.0.7", "@types/codemirror": "0.0.56", @@ -123,8 +131,8 @@ "@types/datatables.net": "1.10.28", "@types/datatables.net-colreorder": "1.4.5", "@types/dom-to-image": "2.6.2", - "@types/enzyme": "3.10.7", - "@types/enzyme-adapter-react-16": "1.0.6", + "@types/enzyme": "3.10.12", + "@types/enzyme-adapter-react-16": "1.0.9", "@types/hasher": "0.0.31", "@types/jest": "26.0.20", "@types/jquery": "3.5.29", @@ -136,6 +144,7 @@ "@types/react-notification-system": "0.2.39", "@types/react-redux": "7.1.7", "@types/react-splitter-layout": "3.0.1", + "@types/react-window": "1.8.8", "@types/sanitize-html": "1.27.2", "@types/sinon": "2.3.3", "@types/styled-components": "5.1.1", @@ -151,14 +160,13 @@ "create-file-webpack": "1.0.2", "css-loader": "6.8.1", "enzyme": "3.11.0", - "enzyme-adapter-react-16": "1.15.5", - "enzyme-to-json": "3.6.1", + "enzyme-adapter-react-16": "1.15.8", + "enzyme-to-json": "3.6.2", "eslint": "8.50.0", "eslint-cli": "1.1.1", "eslint-plugin-no-null": "1.0.2", "eslint-plugin-prefer-arrow": "1.2.3", "eslint-plugin-react-hooks": "4.6.0", - "expect-playwright": "0.3.3", "fast-glob": "3.2.5", "fs-extra": "7.0.0", "html-inline-css-webpack-plugin": "1.11.2", @@ -167,7 +175,6 @@ "html-webpack-plugin": "5.5.3", "jest": "26.6.3", "jest-canvas-mock": "2.3.1", - "jest-playwright-preset": "1.5.1", "jest-react-hooks-shallow": "1.5.1", "jest-trx-results-processor": "0.0.7", "less": "3.8.1", @@ -175,25 +182,24 @@ "less-vars-loader": "1.1.0", "mini-css-extract-plugin": "2.1.0", "monaco-editor-webpack-plugin": "7.1.0", - "node-fetch": "2.6.1", - "playwright": "1.13.0", + "node-fetch": "2.6.7", "prettier": "3.0.3", "process": "0.11.10", "querystring-es3": "0.2.1", "raw-loader": "0.5.1", - "react-dev-utils": "11.0.4", + "react-dev-utils": "12.0.1", "rimraf": "3.0.0", "sinon": "3.2.1", "style-loader": "0.23.0", "ts-loader": "9.2.4", - "typedoc": "0.21.5", + "typedoc": "0.22.15", "typescript": "4.3.5", "url-loader": "4.1.1", "wait-on": "4.0.2", "webpack": "5.88.2", "webpack-bundle-analyzer": "4.9.1", "webpack-cli": "5.1.4", - "webpack-dev-server": "4.15.1" + "webpack-dev-server": "4.15.2" } }, "canvas": { @@ -248,14 +254,6 @@ "tslib": "^1.10.0" } }, - "node_modules/@azure/core-asynciterator-polyfill": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@azure/core-asynciterator-polyfill/-/core-asynciterator-polyfill-1.0.2.tgz", - "integrity": "sha512-3rkP4LnnlWawl0LZptJOdXNrT/fHp2eQMadoasa6afspXdpGrtPZuAQc2PD0cpgyuoXtUWyC3tv7xfntjGS5Dw==", - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/@azure/core-auth": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.5.0.tgz", @@ -274,66 +272,35 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, - "node_modules/@azure/core-http": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@azure/core-http/-/core-http-1.2.6.tgz", - "integrity": "sha512-odtH7UMKtekc5YQ86xg9GlVHNXR6pq2JgJ5FBo7/jbOjNGdBqcrIVrZx2bevXVJz/uUTSx6vUf62gzTXTfqYSQ==", + "node_modules/@azure/core-client": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.9.2.tgz", + "integrity": "sha512-kRdry/rav3fUKHl/aDLd/pDLcB+4pOFwPPTVEExuMyaI5r+JBbMWqRbCY1pn5BniDaU3lRxO9eaQ1AmSMehl/w==", "dependencies": { - "@azure/abort-controller": "^1.0.0", - "@azure/core-asynciterator-polyfill": "^1.0.0", - "@azure/core-auth": "^1.3.0", - "@azure/core-tracing": "1.0.0-preview.11", + "@azure/abort-controller": "^2.0.0", + "@azure/core-auth": "^1.4.0", + "@azure/core-rest-pipeline": "^1.9.1", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.6.1", "@azure/logger": "^1.0.0", - "@types/node-fetch": "^2.5.0", - "@types/tunnel": "^0.0.1", - "form-data": "^3.0.0", - "node-fetch": "^2.6.0", - "process": "^0.11.10", - "tough-cookie": "^4.0.0", - "tslib": "^2.2.0", - "tunnel": "^0.0.6", - "uuid": "^8.3.0", - "xml2js": "^0.4.19" + "tslib": "^2.6.2" }, "engines": { - "node": ">=8.0.0" + "node": ">=18.0.0" } }, - "node_modules/@azure/core-http/node_modules/@azure/core-tracing": { - "version": "1.0.0-preview.11", - "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.0-preview.11.tgz", - "integrity": "sha512-frF0pJc9HTmKncVokhBxCqipjbql02DThQ1ZJ9wLi7SDMLdPAFyDI5xZNzX5guLz+/DtPkY+SGK2li9FIXqshQ==", + "node_modules/@azure/core-client/node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", "dependencies": { - "@opencensus/web-types": "0.0.7", - "@opentelemetry/api": "1.0.0-rc.0", - "tslib": "^2.0.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">=8.0.0" + "node": ">=18.0.0" } }, - "node_modules/@azure/core-http/node_modules/@opentelemetry/api": { - "version": "1.0.0-rc.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.0.0-rc.0.tgz", - "integrity": "sha512-iXKByCMfrlO5S6Oh97BuM56tM2cIBB0XsL/vWF/AtJrJEKx4MC/Xdu0xDsGXMGcNWpqF7ujMsjjnp0+UHBwnDQ==", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@azure/core-http/node_modules/form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@azure/core-http/node_modules/tslib": { + "node_modules/@azure/core-client/node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" @@ -396,9 +363,9 @@ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/@azure/cosmos": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@azure/cosmos/-/cosmos-4.0.0.tgz", - "integrity": "sha512-/Z27p1+FTkmjmm8jk90zi/HrczPHw2t8WecFnsnTe4xGocWl0Z4clP0YlLUTJPhRLWYa5upwD9rMvKJkS1f1kg==", + "version": "4.0.1-beta.3", + "resolved": "https://registry.npmjs.org/@azure/cosmos/-/cosmos-4.0.1-beta.3.tgz", + "integrity": "sha512-CpRGt+S5jnvtGUi4TmlS79YvxpbNc8/5/QHgIvvQ9D2ZFUqO0MjbMCU3lVZV2NAJT02BsbLfRAFe+FPn8nMRQw==", "dependencies": { "@azure/abort-controller": "^1.0.0", "@azure/core-auth": "^1.3.0", @@ -408,14 +375,14 @@ "fast-json-stable-stringify": "^2.1.0", "jsbi": "^3.1.3", "node-abort-controller": "^3.0.0", - "priorityqueuejs": "^1.0.0", + "priorityqueuejs": "^2.0.0", "semaphore": "^1.0.5", "tslib": "^2.2.0", "universal-user-agent": "^6.0.0", "uuid": "^8.3.0" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/@azure/cosmos-language-service": { @@ -454,42 +421,44 @@ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/@azure/identity": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-1.2.1.tgz", - "integrity": "sha512-vCzV4Xg5hWJ2e4Et0waOmIEgYHsqtGF06kklnqblZg0hKDLKxTAX5FzKYuDMk1CctY2UdEmWFcA2li2uOXOLXQ==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-1.5.2.tgz", + "integrity": "sha512-vqyeRbd2i0h9F4mqW5JbkP1xfabqKQ21l/81osKhpOQ2LtwaJW6nw4+0PsVYnxcbPHFCIZt6EWAk74a3OGYZJA==", "dependencies": { - "@azure/core-http": "^1.2.0", - "@azure/core-tracing": "1.0.0-preview.9", + "@azure/core-auth": "^1.3.0", + "@azure/core-client": "^1.0.0", + "@azure/core-rest-pipeline": "^1.1.0", + "@azure/core-tracing": "1.0.0-preview.12", "@azure/logger": "^1.0.0", - "@azure/msal-node": "1.0.0-beta.1", - "@opentelemetry/api": "^0.10.2", + "@azure/msal-node": "1.0.0-beta.6", + "@types/stoppable": "^1.1.0", "axios": "^0.21.1", "events": "^3.0.0", "jws": "^4.0.0", "msal": "^1.0.2", "open": "^7.0.0", "qs": "^6.7.0", + "stoppable": "^1.1.0", "tslib": "^2.0.0", "uuid": "^8.3.0" }, "engines": { - "node": ">=8.0.0" + "node": ">=12.0.0" }, "optionalDependencies": { - "keytar": "^5.4.0" + "keytar": "^7.3.0" } }, "node_modules/@azure/identity/node_modules/@azure/core-tracing": { - "version": "1.0.0-preview.9", - "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.0-preview.9.tgz", - "integrity": "sha512-zczolCLJ5QG42AEPQ+Qg9SRYNUyB+yZ5dzof4YEc+dyWczO9G2sBqbAjLB7IqrsdHN2apkiB2oXeDKCsq48jug==", + "version": "1.0.0-preview.12", + "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.0-preview.12.tgz", + "integrity": "sha512-nvo2Wc4EKZGN6eFu9n3U7OXmASmL8VxoPIH7xaD6OlQqi44bouF0YIi9ID5rEsKLiAU59IYx6M297nqWVMWPDg==", "dependencies": { - "@opencensus/web-types": "0.0.7", - "@opentelemetry/api": "^0.10.2", - "tslib": "^2.0.0" + "@opentelemetry/api": "^1.0.0", + "tslib": "^2.2.0" }, "engines": { - "node": ">=8.0.0" + "node": ">=12.0.0" } }, "node_modules/@azure/identity/node_modules/tslib": { @@ -596,13 +565,13 @@ } }, "node_modules/@azure/ms-rest-nodeauth": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@azure/ms-rest-nodeauth/-/ms-rest-nodeauth-3.0.7.tgz", - "integrity": "sha512-7Q1MyMB+eqUQy8JO+virSIzAjqR2UbKXE/YQZe+53gC8yakm8WOQ5OzGfPP+eyHqeRs6bQESyw2IC5feLWlT2A==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@azure/ms-rest-nodeauth/-/ms-rest-nodeauth-3.1.1.tgz", + "integrity": "sha512-UA/8dgLy3+ZiwJjAZHxL4MUB14fFQPkaAOZ94jsTW/Z6WmoOeny2+cLk0+dyIX/iH6qSrEWKwbStEeB970B9pA==", "dependencies": { "@azure/ms-rest-azure-env": "^2.0.0", "@azure/ms-rest-js": "^2.0.4", - "adal-node": "^0.1.28" + "adal-node": "^0.2.2" } }, "node_modules/@azure/msal-browser": { @@ -629,61 +598,17 @@ } }, "node_modules/@azure/msal-node": { - "version": "1.0.0-beta.1", - "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-1.0.0-beta.1.tgz", - "integrity": "sha512-dO/bgVScpl5loZfsfhHXmFLTNoDxGvUiZIsJCe1+HpHyFWXwGsBZ71P5ixbxRhhf/bPpZS3X+/rm1Fq2uUucJw==", + "version": "1.0.0-beta.6", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-1.0.0-beta.6.tgz", + "integrity": "sha512-ZQI11Uz1j0HJohb9JZLRD8z0moVcPks1AFW4Q/Gcl67+QvH4aKEJti7fjCcipEEZYb/qzLSO8U6IZgPYytsiJQ==", "deprecated": "A newer major version of this library is available. Please upgrade to the latest available version.", "dependencies": { - "@azure/msal-common": "^1.7.2", - "axios": "^0.19.2", + "@azure/msal-common": "^4.0.0", + "axios": "^0.21.1", "jsonwebtoken": "^8.5.1", "uuid": "^8.3.0" } }, - "node_modules/@azure/msal-node/node_modules/@azure/msal-common": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-1.7.2.tgz", - "integrity": "sha512-3/voCdFKONENX+5tMrNOBSrVJb6NbE7YB8vc4FZ/4ZbjpK7GVtq9Bu1MW+HZhrmsUzSF/joHx0ZIJDYIequ/jg==", - "dependencies": { - "debug": "^4.1.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@azure/msal-node/node_modules/axios": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", - "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", - "deprecated": "Critical security vulnerability fixed in v0.21.1. For more information, see https://github.com/axios/axios/pull/3410", - "dependencies": { - "follow-redirects": "1.5.10" - } - }, - "node_modules/@azure/msal-node/node_modules/follow-redirects": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", - "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", - "dependencies": { - "debug": "=3.1.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/@azure/msal-node/node_modules/follow-redirects/node_modules/debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/@azure/msal-node/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, "node_modules/@babel/code-frame": { "version": "7.23.4", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.4.tgz", @@ -7066,17 +6991,6 @@ "node": ">=10" } }, - "node_modules/@mapbox/node-pre-gyp/node_modules/detect-libc": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", - "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, "node_modules/@mapbox/node-pre-gyp/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -10179,29 +10093,10 @@ "@octokit/openapi-types": "^19.0.2" } }, - "node_modules/@opencensus/web-types": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/@opencensus/web-types/-/web-types-0.0.7.tgz", - "integrity": "sha512-xB+w7ZDAu3YBzqH44rCmG9/RlrOmFuDPt/bpf17eJr8eZSrLt7nc7LnWdxM9Mmoj/YKMHpxRg28txu3TcpiL+g==", - "engines": { - "node": ">=6.0" - } - }, "node_modules/@opentelemetry/api": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-0.10.2.tgz", - "integrity": "sha512-GtpMGd6vkzDMYcpu2t9LlhEgMy/SzBwRnz48EejlRArYqZzqSzAsKmegUK7zHgl+EOIaK9mKHhnRaQu3qw20cA==", - "dependencies": { - "@opentelemetry/context-base": "^0.10.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@opentelemetry/context-base": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/context-base/-/context-base-0.10.2.tgz", - "integrity": "sha512-hZNKjKOYsckoOEgBziGMnBcX0M7EtstnCmwz5jZUOUYwlZ+/xxX6z3jPu1XVO2Jivk0eLfuP9GP+vFD49CMetw==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.8.0.tgz", + "integrity": "sha512-I/s6F7yKUDdtMsoBWXJe8Qz40Tui5vsuKCWJEWVL+5q9sSWRzzx6v2KeNsOBEwd94j0eWkpWCH4yB6rZg9Mf0w==", "engines": { "node": ">=8.0.0" } @@ -10313,6 +10208,53 @@ "@phosphor/virtualdom": "^1.2.0" } }, + "node_modules/@playwright/test": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.44.0.tgz", + "integrity": "sha512-rNX5lbNidamSUorBhB4XZ9SQTjAqfe5M+p37Z8ic0jPFBMo5iCtQz1kRWkEMg+rYOKSlVycpQmpqjSFq7LXOfg==", + "dev": true, + "dependencies": { + "playwright": "1.44.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@playwright/test/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/@playwright/test/node_modules/playwright": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.44.0.tgz", + "integrity": "sha512-F9b3GUCLQ3Nffrfb6dunPOkE5Mh68tR7zN32L4jCk4FjQamgesGay7/dAAe1WaMEGV04DkdJfcJzjoCKygUaRQ==", + "dev": true, + "dependencies": { + "playwright-core": "1.44.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, "node_modules/@polka/url": { "version": "1.0.0-next.23", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.23.tgz", @@ -11909,6 +11851,7 @@ "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", "integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==", "dev": true, + "peer": true, "dependencies": { "@hapi/hoek": "^9.0.0" } @@ -11917,13 +11860,15 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", - "dev": true + "dev": true, + "peer": true }, "node_modules/@sideway/pinpoint": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", - "dev": true + "dev": true, + "peer": true }, "node_modules/@sinclair/typebox": { "version": "0.27.8", @@ -12587,9 +12532,9 @@ "integrity": "sha512-PD+wqNhrjWFjAlSVd18jvChZvOXB2SOwAILBmuYev5zswBats5qmzs/QFoooLKd2omj9BT05a8MeSeRmXLGY+Q==" }, "node_modules/@types/enzyme": { - "version": "3.10.7", - "resolved": "https://registry.npmjs.org/@types/enzyme/-/enzyme-3.10.7.tgz", - "integrity": "sha512-J+0wduPGAkzOvW7sr6hshGv1gBI3WXLRTczkRKzVPxLP3xAkYxZmvvagSBPw8Z452fZ8TGUxCmAXcb44yLQksw==", + "version": "3.10.12", + "resolved": "https://registry.npmjs.org/@types/enzyme/-/enzyme-3.10.12.tgz", + "integrity": "sha512-xryQlOEIe1TduDWAOphR0ihfebKFSWOXpIsk+70JskCfRfW+xALdnJ0r1ZOTo85F9Qsjk6vtlU7edTYHbls9tA==", "dev": true, "dependencies": { "@types/cheerio": "*", @@ -12597,9 +12542,9 @@ } }, "node_modules/@types/enzyme-adapter-react-16": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.0.6.tgz", - "integrity": "sha512-VonDkZ15jzqDWL8mPFIQnnLtjwebuL9YnDkqeCDYnB4IVgwUm0mwKkqhrxLL6mb05xm7qqa3IE95m8CZE9imCg==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.0.9.tgz", + "integrity": "sha512-z24MMxGtUL8HhXdye3tWzjp+19QTsABqLaX2oOZpxMPHRJgLfahQmOeTTrEBQd9ogW20+UmPBXD9j+XOasFHvw==", "dev": true, "dependencies": { "@types/enzyme": "*" @@ -12834,6 +12779,12 @@ "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", "devOptional": true }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "dev": true + }, "node_modules/@types/post-robot": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/@types/post-robot/-/post-robot-10.0.1.tgz", @@ -13526,6 +13477,15 @@ "@types/react": "*" } }, + "node_modules/@types/react-window": { + "version": "1.8.8", + "resolved": "https://registry.npmjs.org/@types/react-window/-/react-window-1.8.8.tgz", + "integrity": "sha512-8Ls660bHR1AUA2kuRvVG9D/4XpRC6wjAaPT9dil7Ckc76eP9TKWZwwmgfq8Q1LANX3QNDnoU4Zp48A3w+zK69Q==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", @@ -13613,6 +13573,14 @@ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "devOptional": true }, + "node_modules/@types/stoppable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@types/stoppable/-/stoppable-1.1.3.tgz", + "integrity": "sha512-7wGKIBJGE4ZxFjk9NkjAxZMLlIXroETqP1FJCdoSvKmEznwmBxQFmTB1dsCkAvVcNemuSZM5qkkd9HE/NL2JTw==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/styled-components": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.1.tgz", @@ -13639,14 +13607,6 @@ "@types/jest": "*" } }, - "node_modules/@types/tunnel": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/@types/tunnel/-/tunnel-0.0.1.tgz", - "integrity": "sha512-AOqu6bQu5MSWwYvehMXLukFHnupHrpZ8nvgae5Ggie9UwzDR1CCwoXgSSWNZJuyOlCdfdsWMA5F2LlmvyoTv8A==", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/underscore": { "version": "1.7.36", "resolved": "https://registry.npmjs.org/@types/underscore/-/underscore-1.7.36.tgz", @@ -13668,15 +13628,6 @@ "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==" }, - "node_modules/@types/wait-on": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/@types/wait-on/-/wait-on-5.3.4.tgz", - "integrity": "sha512-EBsPjFMrFlMbbUFf9D1Fp+PAB2TwmUn7a3YtHyD9RLuTIk1jDd8SxXVAoez2Ciy+8Jsceo2MYEYZzJ/DvorOKw==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/ws": { "version": "8.5.10", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", @@ -13699,16 +13650,6 @@ "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==" }, - "node_modules/@types/yauzl": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", - "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", - "dev": true, - "optional": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/youtube-player": { "version": "5.5.6", "resolved": "https://registry.npmjs.org/@types/youtube-player/-/youtube-player-5.5.6.tgz", @@ -14003,6 +13944,18 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@uiw/react-split": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/@uiw/react-split/-/react-split-5.9.3.tgz", + "integrity": "sha512-HgwETU+kRhzZAp+YiE4Yu8bNJm3jxxnGgGPfkadUl8jA1wsMD3aMMskuhQF5akiUUUadiLUvAc8e1RH9Y/SKDw==", + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, "node_modules/@ungap/url-search-params": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/@ungap/url-search-params/-/url-search-params-0.2.2.tgz", @@ -14183,6 +14136,14 @@ } } }, + "node_modules/@xmldom/xmldom": { + "version": "0.7.13", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.13.tgz", + "integrity": "sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g==", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -14300,29 +14261,39 @@ } }, "node_modules/adal-node": { - "version": "0.1.28", - "resolved": "https://registry.npmjs.org/adal-node/-/adal-node-0.1.28.tgz", - "integrity": "sha512-98nQ5MQSyJR0ZY/R0Mue/cv4OkebRyKz4hS40GdkZU42Bq49ldHeup7UeAo/0vROMB57CX2et6IF0U/Pe1rY3A==", + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/adal-node/-/adal-node-0.2.4.tgz", + "integrity": "sha512-zIcvbwQFKMUtKxxj8YMHeTT1o/TPXfVNsTXVgXD8sxwV6h4AFQgK77dRciGhuEF9/Sdm3UQPJVPc/6XxrccSeA==", "deprecated": "This package is no longer supported. Please migrate to @azure/msal-node.", "dependencies": { - "@types/node": "^8.0.47", - "async": ">=0.6.0", + "@xmldom/xmldom": "^0.8.3", + "async": "^2.6.3", + "axios": "^0.21.1", "date-utils": "*", "jws": "3.x.x", - "request": ">= 2.52.0", "underscore": ">= 1.3.1", "uuid": "^3.1.0", - "xmldom": ">= 0.1.x", "xpath.js": "~1.1.0" }, "engines": { "node": ">= 0.6.15" } }, - "node_modules/adal-node/node_modules/@types/node": { - "version": "8.10.66", - "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.66.tgz", - "integrity": "sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw==" + "node_modules/adal-node/node_modules/@xmldom/xmldom": { + "version": "0.8.10", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", + "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/adal-node/node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dependencies": { + "lodash": "^4.17.14" + } }, "node_modules/adal-node/node_modules/jwa": { "version": "1.4.1", @@ -14372,19 +14343,6 @@ "node": ">= 6.0.0" } }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/airbnb-prop-types": { "version": "2.16.0", "resolved": "https://registry.npmjs.org/airbnb-prop-types/-/airbnb-prop-types-2.16.0.tgz", @@ -14611,18 +14569,6 @@ "dev": true, "peer": true }, - "node_modules/append-transform": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", - "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", - "dev": true, - "dependencies": { - "default-require-extensions": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/applicationinsights": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/applicationinsights/-/applicationinsights-1.8.0.tgz", @@ -14638,23 +14584,9 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "optional": true - }, - "node_modules/archy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", - "dev": true - }, - "node_modules/are-we-there-yet": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", - "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==", + "dev": true, "optional": true, - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } + "peer": true }, "node_modules/argparse": { "version": "2.0.1", @@ -14698,12 +14630,15 @@ } }, "node_modules/array-buffer-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", - "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", "dependencies": { - "call-bind": "^1.0.2", - "is-array-buffer": "^3.0.1" + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -14791,15 +14726,19 @@ } }, "node_modules/array.prototype.find": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.2.2.tgz", - "integrity": "sha512-DRumkfW97iZGOfn+lIXbkVrXL04sfYKX+EfOodo8XboR5sxPDVvOjZTF/rysusa9lmhmSOeD6Vp6RKQP+eP4Tg==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.2.3.tgz", + "integrity": "sha512-fO/ORdOELvjbbeIfZfzrXFMhYHGofRGqd+am9zm3tZ4GlJINj/pA2eITyfd65Vg6+ZbHd/Cys7stpoRSWtQFdA==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -14870,16 +14809,17 @@ } }, "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz", - "integrity": "sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1", - "is-array-buffer": "^3.0.2", + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", "is-shared-array-buffer": "^1.0.2" }, "engines": { @@ -14939,7 +14879,9 @@ "node_modules/async": { "version": "3.2.5", "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", + "dev": true, + "peer": true }, "node_modules/async-hook-jl": { "version": "1.7.6", @@ -15002,9 +14944,12 @@ } }, "node_modules/available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, "engines": { "node": ">= 0.4" }, @@ -15083,6 +15028,32 @@ "webpack": ">=2" } }, + "node_modules/babel-loader/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/babel-loader/node_modules/loader-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", + "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/babel-loader/node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -15694,15 +15665,6 @@ "ieee754": "^1.1.4" } }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true, - "engines": { - "node": "*" - } - }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -15741,65 +15703,19 @@ "node": ">=0.10.0" } }, - "node_modules/caching-transform": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", - "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", - "dev": true, - "dependencies": { - "hasha": "^5.0.0", - "make-dir": "^3.0.0", - "package-hash": "^4.0.0", - "write-file-atomic": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/caching-transform/node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/caching-transform/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/caching-transform/node_modules/write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, "node_modules/call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -16232,15 +16148,6 @@ "node": ">=0.10.0" } }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/clean-webpack-plugin": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/clean-webpack-plugin/-/clean-webpack-plugin-4.0.0.tgz", @@ -16418,7 +16325,9 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==", + "dev": true, "optional": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -16695,7 +16604,9 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", - "optional": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/content-disposition": { "version": "0.5.4", @@ -16969,7 +16880,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "devOptional": true + "dev": true }, "node_modules/cosmiconfig": { "version": "5.2.1", @@ -17303,54 +17214,44 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" }, - "node_modules/cwd": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/cwd/-/cwd-0.10.0.tgz", - "integrity": "sha512-YGZxdTTL9lmLkCUTpg4j0zQ7IhRB5ZmqNBbGCl3Tg6MP/d5/6sY7L5mmTjzbc6JKgVZYiqTQTNhPFsbXNGlRaA==", - "dev": true, + "node_modules/d3": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.8.5.tgz", + "integrity": "sha512-JgoahDG51ncUfJu6wX/1vWQEqOflgXyl4MaHqlcSruTez7yhaRKR9i8VjjcQGeS2en/jnFivXuaIMnseMMt0XA==", "dependencies": { - "find-pkg": "^0.1.2", - "fs-exists-sync": "^0.1.0" + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" }, "engines": { - "node": ">=0.8" - } - }, - "node_modules/d3": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/d3/-/d3-6.1.1.tgz", - "integrity": "sha512-bJYW9wlS2uvP2EoMkcPptrUzLMHQKCbiSW+/la8iGSLZgs4KbI/f3Fch4RtnUA9PA+/nPlwyFYzTwDjX80Of8w==", - "dependencies": { - "d3-array": "2", - "d3-axis": "2", - "d3-brush": "2", - "d3-chord": "2", - "d3-color": "2", - "d3-contour": "2", - "d3-delaunay": "5", - "d3-dispatch": "2", - "d3-drag": "2", - "d3-dsv": "2", - "d3-ease": "2", - "d3-fetch": "2", - "d3-force": "2", - "d3-format": "2", - "d3-geo": "2", - "d3-hierarchy": "2", - "d3-interpolate": "2", - "d3-path": "2", - "d3-polygon": "2", - "d3-quadtree": "2", - "d3-random": "2", - "d3-scale": "3", - "d3-scale-chromatic": "2", - "d3-selection": "2", - "d3-shape": "2", - "d3-time": "2", - "d3-time-format": "3", - "d3-timer": "2", - "d3-transition": "2", - "d3-zoom": "2" + "node": ">=12" } }, "node_modules/d3-array": { @@ -17362,9 +17263,12 @@ } }, "node_modules/d3-axis": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-2.1.0.tgz", - "integrity": "sha512-z/G2TQMyuf0X3qP+Mh+2PimoJD41VOCjViJzT0BHeL/+JQAofkiWZbWxlwFGb1N8EN+Cl/CW+MUKbVzr1689Cw==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "engines": { + "node": ">=12" + } }, "node_modules/d3-brush": { "version": "2.1.0", @@ -17379,11 +17283,14 @@ } }, "node_modules/d3-chord": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-2.0.0.tgz", - "integrity": "sha512-D5PZb7EDsRNdGU4SsjQyKhja8Zgu+SHZfUSO5Ls8Wsn+jsAKUUGkcshLxMg9HDFxG3KqavGWaWkJ8EpU8ojuig==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", "dependencies": { - "d3-path": "1 - 2" + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" } }, "node_modules/d3-collection": { @@ -17397,19 +17304,36 @@ "integrity": "sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ==" }, "node_modules/d3-contour": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-2.0.0.tgz", - "integrity": "sha512-9unAtvIaNk06UwqBmvsdHX7CZ+NPDZnn8TtNH1myW93pWJkhsV25JcgnYAu0Ck5Veb1DHiCv++Ic5uvJ+h50JA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", "dependencies": { - "d3-array": "2" + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour/node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" } }, "node_modules/d3-delaunay": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-5.3.0.tgz", - "integrity": "sha512-amALSrOllWVLaHTnDLHwMIiz0d1bBu9gZXd1FiLfXf8sHcX9jrcj81TVZOqD4UX7MgBZZ07c8GxzEgBpJqc74w==", + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", "dependencies": { - "delaunator": "4" + "delaunator": "5" + }, + "engines": { + "node": ">=12" } }, "node_modules/d3-dispatch": { @@ -17427,24 +17351,46 @@ } }, "node_modules/d3-dsv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-2.0.0.tgz", - "integrity": "sha512-E+Pn8UJYx9mViuIUkoc93gJGGYut6mSDKy2+XaPwccwkRGlR+LO97L2VCCRjQivTwLHkSnAJG7yo00BWY6QM+w==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", "dependencies": { - "commander": "2", - "iconv-lite": "0.4", + "commander": "7", + "iconv-lite": "0.6", "rw": "1" }, "bin": { - "csv2json": "bin/dsv2json", - "csv2tsv": "bin/dsv2dsv", - "dsv2dsv": "bin/dsv2dsv", - "dsv2json": "bin/dsv2json", - "json2csv": "bin/json2dsv", - "json2dsv": "bin/json2dsv", - "json2tsv": "bin/json2dsv", - "tsv2csv": "bin/dsv2dsv", - "tsv2json": "bin/dsv2json" + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/d3-dsv/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, "node_modules/d3-ease": { @@ -17453,21 +17399,27 @@ "integrity": "sha512-68/n9JWarxXkOWMshcT5IcjbB+agblQUaIsbnXmrzejn2O82n3p2A9R2zEB9HIEFWKFwPAEDDN8gR0VdSAyyAQ==" }, "node_modules/d3-fetch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-2.0.0.tgz", - "integrity": "sha512-TkYv/hjXgCryBeNKiclrwqZH7Nb+GaOwo3Neg24ZVWA3MKB+Rd+BY84Nh6tmNEMcjUik1CSUWjXYndmeO6F7sw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", "dependencies": { - "d3-dsv": "1 - 2" + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" } }, "node_modules/d3-force": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-2.1.1.tgz", - "integrity": "sha512-nAuHEzBqMvpFVMf9OX75d00OxvOXdxY+xECIXjW6Gv8BRrXu6gAWbv/9XKrvfJ5i5DCokDW7RYE50LRoK092ew==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", "dependencies": { - "d3-dispatch": "1 - 2", - "d3-quadtree": "1 - 2", - "d3-timer": "1 - 2" + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" } }, "node_modules/d3-format": { @@ -17476,11 +17428,14 @@ "integrity": "sha512-Ab3S6XuE/Q+flY96HXT0jOXcM4EAClYFnRGY5zsjRGNy6qCYrQsMffs7cV5Q9xejb35zxW5hf/guKw34kvIKsA==" }, "node_modules/d3-geo": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-2.0.2.tgz", - "integrity": "sha512-8pM1WGMLGFuhq9S+FpPURxic+gKzjluCD/CHTuUF3mXMeiCo0i6R0tO1s4+GArRFde96SLcW/kOFRjoAosPsFA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", "dependencies": { - "d3-array": "^2.5.0" + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" } }, "node_modules/d3-glyphedge": { @@ -17494,9 +17449,12 @@ "integrity": "sha512-KS3fUT2ReD4RlGCjvCEm1RgMtp2NFZumdMu4DBzQK8AZv3fXRM6Xm8I4fSU07UXvH4xxg03NwWKWdvxfS/yc4w==" }, "node_modules/d3-hierarchy": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-2.0.0.tgz", - "integrity": "sha512-SwIdqM3HxQX2214EG9GTjgmCc/mbSx4mQBn+DuEETubhOw6/U3fmnji4uCVrmzOydMHSO1nZle5gh6HB/wdOzw==" + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "engines": { + "node": ">=12" + } }, "node_modules/d3-interpolate": { "version": "2.0.1", @@ -17507,9 +17465,12 @@ } }, "node_modules/d3-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-2.0.0.tgz", - "integrity": "sha512-ZwZQxKhBnv9yHaiWd6ZU4x5BtCQ7pXszEV9CU6kRgwIQVQGLMv1oiL4M+MK/n79sYzsj+gcgpPQSctJUsLN7fA==" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "engines": { + "node": ">=12" + } }, "node_modules/d3-path-arrows": { "version": "0.4.0", @@ -17531,19 +17492,28 @@ "integrity": "sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg==" }, "node_modules/d3-polygon": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-2.0.0.tgz", - "integrity": "sha512-MsexrCK38cTGermELs0cO1d79DcTsQRN7IWMJKczD/2kBjzNXxLUWP33qRF6VDpiLV/4EI4r6Gs0DAWQkE8pSQ==" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "engines": { + "node": ">=12" + } }, "node_modules/d3-quadtree": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-2.0.0.tgz", - "integrity": "sha512-b0Ed2t1UUalJpc3qXzKi+cPGxeXRr4KU9YSlocN74aTzp6R/Ud43t79yLLqxHRWZfsvWXmbDWPpoENK1K539xw==" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "engines": { + "node": ">=12" + } }, "node_modules/d3-random": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-2.2.2.tgz", - "integrity": "sha512-0D9P8TRj6qDAtHhRQn6EfdOtHMfsUWanl3yb/84C4DqpZ+VsgfI5iTVRNRbELCfNvRfpMr8OrqqUTQ6ANGCijw==" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "engines": { + "node": ">=12" + } }, "node_modules/d3-sankey-circular": { "version": "0.25.0", @@ -17573,12 +17543,15 @@ } }, "node_modules/d3-scale-chromatic": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-2.0.0.tgz", - "integrity": "sha512-LLqy7dJSL8yDy7NRmf6xSlsFZ6zYvJ4BcWFE4zBrOPnQERv9zj24ohnXKRbyi9YHnYV+HN1oEO3iFK971/gkzA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", "dependencies": { - "d3-color": "1 - 2", - "d3-interpolate": "1 - 2" + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" } }, "node_modules/d3-selection": { @@ -17641,23 +17614,181 @@ "integrity": "sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg==" }, "node_modules/d3-zoom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-2.0.0.tgz", - "integrity": "sha512-fFg7aoaEm9/jf+qfstak0IYpnesZLiMX6GZvXtUSdv8RH2o4E2qeelgdU09eKS6wGuiGMfcnMI0nTIqWzRHGpw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", "dependencies": { - "d3-dispatch": "1 - 2", - "d3-drag": "2", - "d3-interpolate": "1 - 2", - "d3-selection": "2", - "d3-transition": "2" + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "engines": { + "node": ">=12" } }, "node_modules/d3/node_modules/d3-shape": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-2.1.0.tgz", - "integrity": "sha512-PnjUqfM2PpskbSLTJvAzp2Wv4CZsnAgTfcVRTwW03QR3MkXF8Uo7B1y/lWkAsmbKwuecto++4NlsYcvYpXpTHA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", "dependencies": { - "d3-path": "1 - 2" + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" } }, "node_modules/dashdash": { @@ -17704,6 +17835,54 @@ "webidl-conversions": "^4.0.2" } }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/datatables.net": { "version": "1.13.8", "resolved": "https://registry.npmjs.org/datatables.net/-/datatables.net-1.13.8.tgz", @@ -17810,7 +17989,9 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "mimic-response": "^2.0.0" }, @@ -17818,12 +17999,6 @@ "node": ">=8" } }, - "node_modules/dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", - "dev": true - }, "node_modules/deep-diff": { "version": "0.3.8", "resolved": "https://registry.npmjs.org/deep-diff/-/deep-diff-0.3.8.tgz", @@ -17926,21 +18101,6 @@ "node": ">=10.17.0" } }, - "node_modules/default-require-extensions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz", - "integrity": "sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==", - "dev": true, - "dependencies": { - "strip-bom": "^4.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/defaults": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", @@ -17965,16 +18125,19 @@ } }, "node_modules/define-data-property": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", - "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dependencies": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/define-lazy-prop": { @@ -18077,9 +18240,12 @@ } }, "node_modules/delaunator": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-4.0.1.tgz", - "integrity": "sha512-WNPWi1IRKZfCt/qIDMfERkDp93+iZEmOxN2yy4Jg+Xhv8SLk2UTqqbe1sfiipn0and9QrE914/ihdx82Y/Giag==" + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "dependencies": { + "robust-predicates": "^3.0.2" + } }, "node_modules/delayed-stream": { "version": "1.0.0", @@ -18099,7 +18265,9 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", - "optional": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/denodeify": { "version": "1.2.1", @@ -18153,15 +18321,12 @@ } }, "node_modules/detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", "optional": true, - "bin": { - "detect-libc": "bin/detect-libc.js" - }, "engines": { - "node": ">=0.10" + "node": ">=8" } }, "node_modules/detect-newline": { @@ -18599,20 +18764,20 @@ } }, "node_modules/enzyme-adapter-react-16": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.15.5.tgz", - "integrity": "sha512-33yUJGT1nHFQlbVI5qdo5Pfqvu/h4qPwi1o0a6ZZsjpiqq92a3HjynDhwd1IeED+Su60HDWV8mxJqkTnLYdGkw==", + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.15.8.tgz", + "integrity": "sha512-uYGC31eGZBp5nGsr4nKhZKvxGQjyHGjS06BJsUlWgE29/hvnpgCsT1BJvnnyny7N3GIIVyxZ4O9GChr6hy2WQA==", "dev": true, "dependencies": { - "enzyme-adapter-utils": "^1.13.1", - "enzyme-shallow-equal": "^1.0.4", - "has": "^1.0.3", - "object.assign": "^4.1.0", - "object.values": "^1.1.1", - "prop-types": "^15.7.2", + "enzyme-adapter-utils": "^1.14.2", + "enzyme-shallow-equal": "^1.0.7", + "hasown": "^2.0.0", + "object.assign": "^4.1.5", + "object.values": "^1.1.7", + "prop-types": "^15.8.1", "react-is": "^16.13.1", "react-test-renderer": "^16.0.0-0", - "semver": "^5.7.0" + "semver": "^5.7.2" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -18630,18 +18795,18 @@ "dev": true }, "node_modules/enzyme-adapter-utils": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/enzyme-adapter-utils/-/enzyme-adapter-utils-1.14.1.tgz", - "integrity": "sha512-JZgMPF1QOI7IzBj24EZoDpaeG/p8Os7WeBZWTJydpsH7JRStc7jYbHE4CmNQaLqazaGFyLM8ALWA3IIZvxW3PQ==", + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/enzyme-adapter-utils/-/enzyme-adapter-utils-1.14.2.tgz", + "integrity": "sha512-1ZC++RlsYRaiOWE5NRaF5OgsMt7F5rn/VuaJIgc7eW/fmgg8eS1/Ut7EugSPPi7VMdWMLcymRnMF+mJUJ4B8KA==", "dev": true, "dependencies": { "airbnb-prop-types": "^2.16.0", - "function.prototype.name": "^1.1.5", - "has": "^1.0.3", - "object.assign": "^4.1.4", - "object.fromentries": "^2.0.5", + "function.prototype.name": "^1.1.6", + "hasown": "^2.0.0", + "object.assign": "^4.1.5", + "object.fromentries": "^2.0.7", "prop-types": "^15.8.1", - "semver": "^5.7.1" + "semver": "^6.3.1" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -18650,13 +18815,22 @@ "react": "0.13.x || 0.14.x || ^15.0.0-0 || ^16.0.0-0" } }, + "node_modules/enzyme-adapter-utils/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/enzyme-shallow-equal": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/enzyme-shallow-equal/-/enzyme-shallow-equal-1.0.5.tgz", - "integrity": "sha512-i6cwm7hN630JXenxxJFBKzgLC3hMTafFQXflvzHgPmDhOBhxUWDe8AeRv1qp2/uWJ2Y8z5yLWMzmAfkTOiOCZg==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/enzyme-shallow-equal/-/enzyme-shallow-equal-1.0.7.tgz", + "integrity": "sha512-/um0GFqUXnpM9SvKtje+9Tjoz3f1fpBC3eXRFrNs8kpYn69JljciYP7KZTqM/YQbUY9KUjvKB4jo/q+L6WGGvg==", "dev": true, "dependencies": { - "has": "^1.0.3", + "hasown": "^2.0.0", "object-is": "^1.1.5" }, "funding": { @@ -18664,13 +18838,13 @@ } }, "node_modules/enzyme-to-json": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/enzyme-to-json/-/enzyme-to-json-3.6.1.tgz", - "integrity": "sha512-15tXuONeq5ORoZjV/bUo2gbtZrN2IH+Z6DvL35QmZyKHgbY1ahn6wcnLd9Xv9OjiwbAXiiP8MRZwbZrCv1wYNg==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/enzyme-to-json/-/enzyme-to-json-3.6.2.tgz", + "integrity": "sha512-Ynm6Z6R6iwQ0g2g1YToz6DWhxVnt8Dy1ijR2zynRKxTyBGA8rCDXU3rs2Qc4OKvUvc2Qoe1bcFK6bnPs20TrTg==", "dev": true, "dependencies": { "@types/cheerio": "^0.22.22", - "lodash": "^4.17.15", + "lodash": "^4.17.21", "react-is": "^16.12.0" }, "engines": { @@ -18732,49 +18906,56 @@ } }, "node_modules/es-abstract": { - "version": "1.22.3", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz", - "integrity": "sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==", + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "arraybuffer.prototype.slice": "^1.0.2", - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.5", - "es-set-tostringtag": "^2.0.1", + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", "es-to-primitive": "^1.2.1", "function.prototype.name": "^1.1.6", - "get-intrinsic": "^1.2.2", - "get-symbol-description": "^1.0.0", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", "globalthis": "^1.0.3", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "has-proto": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", "has-symbols": "^1.0.3", - "hasown": "^2.0.0", - "internal-slot": "^1.0.5", - "is-array-buffer": "^3.0.2", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", "is-callable": "^1.2.7", - "is-negative-zero": "^2.0.2", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", + "is-shared-array-buffer": "^1.0.3", "is-string": "^1.0.7", - "is-typed-array": "^1.1.12", + "is-typed-array": "^1.1.13", "is-weakref": "^1.0.2", "object-inspect": "^1.13.1", "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.1", - "safe-array-concat": "^1.0.1", - "safe-regex-test": "^1.0.0", - "string.prototype.trim": "^1.2.8", - "string.prototype.trimend": "^1.0.7", - "string.prototype.trimstart": "^1.0.7", - "typed-array-buffer": "^1.0.0", - "typed-array-byte-length": "^1.0.0", - "typed-array-byte-offset": "^1.0.0", - "typed-array-length": "^1.0.4", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.13" + "which-typed-array": "^1.1.15" }, "engines": { "node": ">= 0.4" @@ -18788,6 +18969,25 @@ "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==" }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-iterator-helpers": { "version": "1.0.15", "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.15.tgz", @@ -18814,14 +19014,25 @@ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz", "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==" }, - "node_modules/es-set-tostringtag": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz", - "integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==", + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", "dependencies": { - "get-intrinsic": "^1.2.2", - "has-tostringtag": "^1.0.0", - "hasown": "^2.0.0" + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" }, "engines": { "node": ">= 0.4" @@ -18851,12 +19062,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es6-error": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", - "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", - "dev": true - }, "node_modules/es6-templates": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/es6-templates/-/es6-templates-0.2.3.tgz", @@ -19763,15 +19968,6 @@ "node": ">= 10.14.2" } }, - "node_modules/expect-playwright": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/expect-playwright/-/expect-playwright-0.3.3.tgz", - "integrity": "sha512-uoeyx2D5LawJdziMdweOp6cnZzFOOPT9VvPG6gOh6YC7N9pU0k2KpVlRiz/Vc/fFBiGUNNeJq2Aq+9GJ65Nfrw==", - "dev": true, - "peerDependencies": { - "playwright-core": "^1.9.1" - } - }, "node_modules/expect/node_modules/@jest/types": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", @@ -20034,26 +20230,6 @@ "node": ">=0.10.0" } }, - "node_modules/extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" - }, - "engines": { - "node": ">= 10.17.0" - }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" - } - }, "node_modules/extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", @@ -20242,15 +20418,6 @@ "bser": "2.1.1" } }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "dev": true, - "dependencies": { - "pend": "~1.2.0" - } - }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -20274,9 +20441,9 @@ "optional": true }, "node_modules/filesize": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/filesize/-/filesize-6.1.0.tgz", - "integrity": "sha512-LpCHtPQ3sFx67z+uh2HnSyWSLLu5Jxo21795uRDuar/EOuYWXib5EmPaGIBuSnRqH2IODiKA2k5re/K9OnN/Yg==", + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-8.0.7.tgz", + "integrity": "sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ==", "dev": true, "engines": { "node": ">= 0.4.0" @@ -20369,124 +20536,6 @@ "node": ">=6" } }, - "node_modules/find-file-up": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/find-file-up/-/find-file-up-0.1.3.tgz", - "integrity": "sha512-mBxmNbVyjg1LQIIpgO8hN+ybWBgDQK8qjht+EbrTCGmmPV/sc7RF1i9stPTD6bpvXZywBdrwRYxhSdJv867L6A==", - "dev": true, - "dependencies": { - "fs-exists-sync": "^0.1.0", - "resolve-dir": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/find-pkg": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/find-pkg/-/find-pkg-0.1.2.tgz", - "integrity": "sha512-0rnQWcFwZr7eO0513HahrWafsc3CTFioEB7DRiEYCUM/70QXSY8f3mCST17HXLcPvEhzH/Ty/Bxd72ZZsr/yvw==", - "dev": true, - "dependencies": { - "find-file-up": "^0.1.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/find-process": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/find-process/-/find-process-1.4.7.tgz", - "integrity": "sha512-/U4CYp1214Xrp3u3Fqr9yNynUrr5Le4y0SsJh2lMDDSbpwYSz3M2SMWQC+wqcx79cN8PQtHQIL8KnuY9M66fdg==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "commander": "^5.1.0", - "debug": "^4.1.1" - }, - "bin": { - "find-process": "bin/find-process.js" - } - }, - "node_modules/find-process/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/find-process/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/find-process/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/find-process/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/find-process/node_modules/commander": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", - "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/find-process/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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-process/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/find-up": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", @@ -20652,19 +20701,6 @@ "node": ">=0.10.0" } }, - "node_modules/foreground-child": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", - "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -20674,22 +20710,200 @@ } }, "node_modules/fork-ts-checker-webpack-plugin": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-4.1.6.tgz", - "integrity": "sha512-DUxuQaKoqfNne8iikd14SAkh5uw4+8vNifp6gmA73yYNS6ywLIWSLD/n/mBzHQRpW3J7rbATEakmiA8JvkTyZw==", + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.3.tgz", + "integrity": "sha512-SbH/l9ikmMWycd5puHJKTkZJKddF4iRLyW3DeZ08HTI7NGyLS38MXd/KGgeWumQO7YNQbW2u/NtPT2YowbPaGQ==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.5.5", - "chalk": "^2.4.1", - "micromatch": "^3.1.10", + "@babel/code-frame": "^7.8.3", + "@types/json-schema": "^7.0.5", + "chalk": "^4.1.0", + "chokidar": "^3.4.2", + "cosmiconfig": "^6.0.0", + "deepmerge": "^4.2.2", + "fs-extra": "^9.0.0", + "glob": "^7.1.6", + "memfs": "^3.1.2", "minimatch": "^3.0.4", - "semver": "^5.6.0", - "tapable": "^1.0.0", - "worker-rpc": "^0.1.0" + "schema-utils": "2.7.0", + "semver": "^7.3.2", + "tapable": "^1.0.0" }, "engines": { - "node": ">=6.11.5", + "node": ">=10", "yarn": ">=1.0.0" + }, + "peerDependencies": { + "eslint": ">= 6", + "typescript": ">= 2.7", + "vue-template-compiler": "*", + "webpack": ">= 4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + }, + "vue-template-compiler": { + "optional": true + } + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "dev": true, + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/schema-utils": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", + "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.4", + "ajv": "^6.12.2", + "ajv-keywords": "^3.4.1" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, "node_modules/fork-ts-checker-webpack-plugin/node_modules/tapable": { @@ -20701,6 +20915,30 @@ "node": ">=6" } }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -20766,41 +21004,12 @@ "node": ">= 0.6" } }, - "node_modules/fromentries": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", - "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "optional": true }, - "node_modules/fs-exists-sync": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz", - "integrity": "sha512-cR/vflFyPZtrN6b38ZyWxpWdhlXrzZEBawlpBQMq7033xVY7/kg0GDMBK5jg8lDYQckdJ5x/YC88lM3C7VMsLg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/fs-extra": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.0.tgz", @@ -20936,43 +21145,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gauge": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg==", - "optional": true, - "dependencies": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "node_modules/gauge/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gauge/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "optional": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -20990,15 +21162,19 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dependencies": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", "hasown": "^2.0.0" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -21028,12 +21204,13 @@ } }, "node_modules/get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" }, "engines": { "node": ">= 0.4" @@ -21324,16 +21501,18 @@ "integrity": "sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw==" }, "node_modules/gzip-size": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.1.1.tgz", - "integrity": "sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", + "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", "dev": true, "dependencies": { - "duplexer": "^0.1.1", - "pify": "^4.0.1" + "duplexer": "^0.1.2" }, "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/handle-thing": { @@ -21342,36 +21521,6 @@ "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", "dev": true }, - "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" - } - }, - "node_modules/handlebars/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -21419,20 +21568,20 @@ } }, "node_modules/has-property-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", - "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dependencies": { - "get-intrinsic": "^1.2.2" + "es-define-property": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", "engines": { "node": ">= 0.4" }, @@ -21452,11 +21601,11 @@ } }, "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dependencies": { - "has-symbols": "^1.0.2" + "has-symbols": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -21469,7 +21618,9 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", - "optional": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/has-value": { "version": "1.0.0", @@ -21507,31 +21658,6 @@ "node": ">=0.10.0" } }, - "node_modules/hasha": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", - "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", - "dev": true, - "dependencies": { - "is-stream": "^2.0.0", - "type-fest": "^0.8.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/hasha/node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/hasher": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/hasher/-/hasher-1.2.0.tgz", @@ -21541,9 +21667,9 @@ } }, "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dependencies": { "function-bind": "^1.1.2" }, @@ -21646,18 +21772,6 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, - "node_modules/homedir-polyfill": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", - "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", - "dev": true, - "dependencies": { - "parse-passwd": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/hosted-git-info": { "version": "2.8.9", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", @@ -21758,6 +21872,32 @@ "html-loader": "^0.5.1" } }, + "node_modules/html-loader/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/html-loader/node_modules/loader-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", + "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/html-minifier": { "version": "3.5.21", "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.21.tgz", @@ -22237,6 +22377,14 @@ "node-fetch": "2.6.1" } }, + "node_modules/i18next-http-backend/node_modules/node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "engines": { + "node": "4.x || >=6.0.0" + } + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -22332,10 +22480,9 @@ } }, "node_modules/immer": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/immer/-/immer-8.0.1.tgz", - "integrity": "sha512-aqXhGP7//Gui2+UrEtvxZxSquQVXTpZ7KDxfCcKAF3Vysvw0CViVaW9RZ1j1xlIYqaaaipBoqdqeibkc18PNvA==", - "dev": true, + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.6.tgz", + "integrity": "sha512-G95ivKpy+EvVAnAab4fVa4YGYn24J1SpEktnJX7JJ45Bd7xqME/SCplFzYFmTbrkwZbQ4xJK1xMTUYBkN6pWsQ==", "funding": { "type": "opencollective", "url": "https://opencollective.com/immer" @@ -22475,11 +22622,11 @@ "devOptional": true }, "node_modules/internal-slot": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz", - "integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", "dependencies": { - "get-intrinsic": "^1.2.2", + "es-errors": "^1.3.0", "hasown": "^2.0.0", "side-channel": "^1.0.4" }, @@ -22565,13 +22712,15 @@ } }, "node_modules/is-array-buffer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", - "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", "dependencies": { "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "is-typed-array": "^1.1.10" + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -22683,6 +22832,20 @@ "node": ">= 0.4" } }, + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "dependencies": { + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-date-object": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", @@ -22787,7 +22950,9 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "number-is-nan": "^1.0.0" }, @@ -22856,9 +23021,9 @@ } }, "node_modules/is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", "engines": { "node": ">= 0.4" }, @@ -22995,11 +23160,14 @@ } }, "node_modules/is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", "dependencies": { - "call-bind": "^1.0.2" + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -23052,11 +23220,11 @@ } }, "node_modules/is-typed-array": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", - "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", "dependencies": { - "which-typed-array": "^1.1.11" + "which-typed-array": "^1.1.14" }, "engines": { "node": ">= 0.4" @@ -23187,18 +23355,6 @@ "node": ">=6" } }, - "node_modules/istanbul-lib-hook": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", - "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", - "dev": true, - "dependencies": { - "append-transform": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/istanbul-lib-instrument": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz", @@ -23224,44 +23380,6 @@ "semver": "bin/semver.js" } }, - "node_modules/istanbul-lib-processinfo": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", - "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", - "dev": true, - "dependencies": { - "archy": "^1.0.0", - "cross-spawn": "^7.0.3", - "istanbul-lib-coverage": "^3.2.0", - "p-map": "^3.0.0", - "rimraf": "^3.0.0", - "uuid": "^8.3.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-processinfo/node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-processinfo/node_modules/p-map": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", - "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", - "dev": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/istanbul-lib-report": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", @@ -23556,217 +23674,6 @@ "node": ">=8" } }, - "node_modules/jest-circus": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-26.6.3.tgz", - "integrity": "sha512-ACrpWZGcQMpbv13XbzRzpytEJlilP/Su0JtNCi5r/xLpOUhnaIJr8leYYpLEMgPFURZISEHrnnpmB54Q/UziPw==", - "dev": true, - "dependencies": { - "@babel/traverse": "^7.1.0", - "@jest/environment": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/babel__traverse": "^7.0.4", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^0.7.0", - "expect": "^26.6.2", - "is-generator-fn": "^2.0.0", - "jest-each": "^26.6.2", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-runner": "^26.6.3", - "jest-runtime": "^26.6.3", - "jest-snapshot": "^26.6.2", - "jest-util": "^26.6.2", - "pretty-format": "^26.6.2", - "stack-utils": "^2.0.2", - "throat": "^5.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-circus/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-circus/node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/jest-circus/node_modules/@types/yargs": { - "version": "15.0.19", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.19.tgz", - "integrity": "sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-circus/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-circus/node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-circus/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-circus/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-circus/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-circus/node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-circus/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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-circus/node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/jest-circus/node_modules/jest-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz", - "integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "is-ci": "^2.0.0", - "micromatch": "^4.0.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-circus/node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/jest-circus/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-circus/node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, "node_modules/jest-cli": { "version": "26.6.3", "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-26.6.3.tgz", @@ -26108,38 +26015,6 @@ "node": ">=8" } }, - "node_modules/jest-playwright-preset": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/jest-playwright-preset/-/jest-playwright-preset-1.5.1.tgz", - "integrity": "sha512-zsFAe61V72vSLkd1fCcf7YbHmbdAB82SLBdUuCUF43aODIojshQEDF88KdWL9P+4JQ+DvEABT+6sFX4sY0rR2w==", - "dev": true, - "dependencies": { - "expect-playwright": "^0.3.3", - "jest-circus": "^26.6.3", - "jest-environment-node": "^26.6.2", - "jest-process-manager": "^0.2.9", - "jest-runner": "^26.6.3", - "nyc": "^15.1.0", - "playwright-core": ">=1.2.0", - "rimraf": "^3.0.2", - "uuid": "^8.3.2" - } - }, - "node_modules/jest-playwright-preset/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/jest-pnp-resolver": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", @@ -26156,113 +26031,6 @@ } } }, - "node_modules/jest-process-manager": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/jest-process-manager/-/jest-process-manager-0.2.9.tgz", - "integrity": "sha512-IKVdOSz1NLwKg9HTeyEDn63waMvKK6wcS+tCarGquNIktlXt4zAW2cfJ9vAA/xBcidWYKOPXHvy1l1N8qfw3Ww==", - "dev": true, - "dependencies": { - "@types/wait-on": "^5.2.0", - "chalk": "^4.1.0", - "cwd": "^0.10.0", - "exit": "^0.1.2", - "find-process": "^1.4.4", - "prompts": "^2.4.0", - "signal-exit": "^3.0.3", - "spawnd": "^4.4.0", - "tree-kill": "^1.2.2", - "wait-on": "^5.2.1" - } - }, - "node_modules/jest-process-manager/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-process-manager/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-process-manager/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-process-manager/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-process-manager/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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-process-manager/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-process-manager/node_modules/wait-on": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-5.3.0.tgz", - "integrity": "sha512-DwrHrnTK+/0QFaB9a8Ol5Lna3k7WvUR4jzSKmz0YaPBpuN2sACyiPVKVfj6ejnjcajAcvn3wlbTyMIn9AZouOg==", - "dev": true, - "dependencies": { - "axios": "^0.21.1", - "joi": "^17.3.0", - "lodash": "^4.17.21", - "minimist": "^1.2.5", - "rxjs": "^6.6.3" - }, - "bin": { - "wait-on": "bin/wait-on" - }, - "engines": { - "node": ">=8.9.0" - } - }, "node_modules/jest-react-hooks-shallow": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/jest-react-hooks-shallow/-/jest-react-hooks-shallow-1.5.1.tgz", @@ -28199,6 +27967,7 @@ "resolved": "https://registry.npmjs.org/joi/-/joi-17.11.0.tgz", "integrity": "sha512-NgB+lZLNoqISVy1rZocE9PZI36bL/77ie924Ri43yEvi9GUUMPeyVIr8KdFTMUlby1p0PBYMk9spIxEUQYqrJQ==", "dev": true, + "peer": true, "dependencies": { "@hapi/hoek": "^9.0.0", "@hapi/topo": "^5.0.0", @@ -28207,12 +27976,6 @@ "@sideway/pinpoint": "^2.0.0" } }, - "node_modules/jpeg-js": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz", - "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==", - "dev": true - }, "node_modules/jquery": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", @@ -28790,9 +28553,9 @@ } }, "node_modules/jsonc-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", "dev": true }, "node_modules/jsonfile": { @@ -28919,22 +28682,16 @@ "integrity": "sha512-yQa1dz+FilQ+w3JM6GH2V/wnFeQhfbkK9stvs3UiraW3GOEO7zrOBBh0ZuHsrzeN1xx6v7P5EpA2JtOUUnfN/w==" }, "node_modules/keytar": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/keytar/-/keytar-5.6.0.tgz", - "integrity": "sha512-ueulhshHSGoryfRXaIvTj0BV1yB0KddBGhGoqCxSN9LR1Ks1GKuuCdVhF+2/YOs5fMl6MlTI9On1a4DHDXoTow==", + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/keytar/-/keytar-7.9.0.tgz", + "integrity": "sha512-VPD8mtVtm5JNtA2AErl6Chp06JBfy7diFQ7TQQhdpWOl6MrCRB+eRbvAZUsbGQS9kiMq0coJsy0W0vHpDCkWsQ==", "hasInstallScript": true, "optional": true, "dependencies": { - "nan": "2.14.1", - "prebuild-install": "5.3.3" + "node-addon-api": "^4.3.0", + "prebuild-install": "^7.0.1" } }, - "node_modules/keytar/node_modules/nan": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz", - "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==", - "optional": true - }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -29208,29 +28965,16 @@ } }, "node_modules/loader-utils": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", - "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", - "dev": true, + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.3.tgz", + "integrity": "sha512-THWqIsn8QRnvLl0shHYVBN9syumU8pYWEHPTmkiVGd+7K5eFNVSY6AJhRvgGF70gg1Dz+l/k8WicvFCxdEs60A==", "dependencies": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", - "json5": "^1.0.1" + "json5": "^2.1.2" }, "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/loader-utils/node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" + "node": ">=8.9.0" } }, "node_modules/locate-path": { @@ -29612,15 +29356,15 @@ } }, "node_modules/marked": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/marked/-/marked-2.1.3.tgz", - "integrity": "sha512-/Q+7MGzaETqifOMWYEA7HVMaZb4XbcRfaOzcSsHZEith83KGlvaSG33u0SKu89Mj5h+T8V2hM+8O45Qc5XTgwA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", "dev": true, "bin": { - "marked": "bin/marked" + "marked": "bin/marked.js" }, "engines": { - "node": ">= 10" + "node": ">= 12" } }, "node_modules/martinez-polygon-clipping": { @@ -31326,12 +31070,6 @@ "node": ">=12" } }, - "node_modules/microevent.ts": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/microevent.ts/-/microevent.ts-0.1.1.tgz", - "integrity": "sha512-jo1OfR4TaEwd5HOrt5+tAZ9mqT4jmpNAusXtyfNzqVm9uiSYFZlKM1wYL4oU7azZW/PxQW53wM0S6OR1JHNa2g==", - "dev": true - }, "node_modules/micromatch": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", @@ -31407,7 +31145,9 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "dev": true, "optional": true, + "peer": true, "engines": { "node": ">=8" }, @@ -31822,19 +31562,61 @@ } }, "node_modules/node-abi": { - "version": "2.30.1", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.30.1.tgz", - "integrity": "sha512-/2D0wOQPgaUWzVSVgRMx+trKJRC2UG4SUc4oCJoXx9Uxjtp0Vy3/kt7zcbxHF8+Z/pK3UloLWzBISg72brfy1w==", + "version": "3.60.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.60.0.tgz", + "integrity": "sha512-zcGgwoXbzw9NczqbGzAWL/ToDYAxv1V8gL1D67ClbdkIfeeDBbY0GelZtC25ayLvVjr2q2cloHeQV1R0QAWqRQ==", "optional": true, "dependencies": { - "semver": "^5.4.1" + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" } }, + "node_modules/node-abi/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-abi/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "optional": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-abi/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + }, "node_modules/node-abort-controller": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==" }, + "node_modules/node-addon-api": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", + "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==", + "optional": true + }, "node_modules/node-dir": { "version": "0.1.17", "resolved": "https://registry.npmjs.org/node-dir/-/node-dir-0.1.17.tgz", @@ -31849,11 +31631,22 @@ } }, "node_modules/node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, "engines": { "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } } }, "node_modules/node-forge": { @@ -31921,18 +31714,6 @@ "dev": true, "optional": true }, - "node_modules/node-preload": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", - "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", - "dev": true, - "dependencies": { - "process-on-spawn": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/node-releases": { "version": "2.0.13", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", @@ -31952,12 +31733,6 @@ "url": "https://github.com/sponsors/antelle" } }, - "node_modules/noop-logger": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz", - "integrity": "sha512-6kM8CLXvuW5crTxsAtva2YLrRrDaiTIkIePWs9moLHqbFWT94WpNFjwS/5dfLfECg5i/lkmw3aoqVidxt23TEQ==", - "optional": true - }, "node_modules/nopt": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", @@ -32011,18 +31786,6 @@ "node": ">=8" } }, - "node_modules/npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", - "optional": true, - "dependencies": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, "node_modules/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -32045,7 +31808,9 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==", + "dev": true, "optional": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -32063,205 +31828,6 @@ "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz", "integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==" }, - "node_modules/nyc": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", - "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", - "dev": true, - "dependencies": { - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "caching-transform": "^4.0.0", - "convert-source-map": "^1.7.0", - "decamelize": "^1.2.0", - "find-cache-dir": "^3.2.0", - "find-up": "^4.1.0", - "foreground-child": "^2.0.0", - "get-package-type": "^0.1.0", - "glob": "^7.1.6", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-hook": "^3.0.0", - "istanbul-lib-instrument": "^4.0.0", - "istanbul-lib-processinfo": "^2.0.2", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.2", - "make-dir": "^3.0.0", - "node-preload": "^0.2.1", - "p-map": "^3.0.0", - "process-on-spawn": "^1.0.0", - "resolve-from": "^5.0.0", - "rimraf": "^3.0.0", - "signal-exit": "^3.0.2", - "spawn-wrap": "^2.0.0", - "test-exclude": "^6.0.0", - "yargs": "^15.0.2" - }, - "bin": { - "nyc": "bin/nyc.js" - }, - "engines": { - "node": ">=8.9" - } - }, - "node_modules/nyc/node_modules/find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", - "dev": true, - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/avajs/find-cache-dir?sponsor=1" - } - }, - "node_modules/nyc/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/istanbul-lib-instrument": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", - "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", - "dev": true, - "dependencies": { - "@babel/core": "^7.7.5", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/nyc/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/p-map": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", - "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", - "dev": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/nyc/node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", @@ -32378,12 +31944,12 @@ } }, "node_modules/object.assign": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", "has-symbols": "^1.0.3", "object-keys": "^1.1.1" }, @@ -32670,15 +32236,6 @@ "node": ">=8" } }, - "node_modules/os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/os-name": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/os-name/-/os-name-3.1.0.tgz", @@ -32780,21 +32337,6 @@ "node": ">=6" } }, - "node_modules/package-hash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", - "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.15", - "hasha": "^5.0.0", - "lodash.flattendeep": "^4.4.0", - "release-zalgo": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/param-case": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", @@ -32846,15 +32388,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/parse-passwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", - "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/parse-srcset": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz", @@ -33190,12 +32723,6 @@ "node": ">=8" } }, - "node_modules/pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true - }, "node_modules/performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -33275,39 +32802,10 @@ "node": ">=8" } }, - "node_modules/playwright": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.13.0.tgz", - "integrity": "sha512-GA5OyEeKx1v/pRcANmYncCT67Y7Y4N5zLRU5E690dn/Id10sooR5hQZmCDYsjXlutZb/1q0R3sITALnvhEjCjg==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "commander": "^6.1.0", - "debug": "^4.1.1", - "extract-zip": "^2.0.1", - "https-proxy-agent": "^5.0.0", - "jpeg-js": "^0.4.2", - "mime": "^2.4.6", - "pngjs": "^5.0.0", - "progress": "^2.0.3", - "proper-lockfile": "^4.1.1", - "proxy-from-env": "^1.1.0", - "rimraf": "^3.0.2", - "stack-utils": "^2.0.3", - "ws": "^7.4.6", - "yazl": "^2.5.1" - }, - "bin": { - "playwright": "lib/cli/cli.js" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/playwright-core": { - "version": "1.40.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.40.0.tgz", - "integrity": "sha512-fvKewVJpGeca8t0ipM56jkVSU6Eo0RmFvQ/MaCQNDYm+sdvKkMBBWTE1FdeMqIdumRaXXjZChWHvIzCGM/tA/Q==", + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.44.0.tgz", + "integrity": "sha512-ZTbkNpFfYcGWohvTTl+xewITm7EOuqIqex0c7dNZ+aXsbrLj0qI8XlGKfPpipjm0Wny/4Lt4CJsWJk1stVS5qQ==", "dev": true, "bin": { "playwright-core": "cli.js" @@ -33316,42 +32814,6 @@ "node": ">=16" } }, - "node_modules/playwright/node_modules/commander": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", - "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/playwright/node_modules/mime": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "dev": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/playwright/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/plotly.js-cartesian-dist-min": { "version": "1.52.3", "resolved": "https://registry.npmjs.org/plotly.js-cartesian-dist-min/-/plotly.js-cartesian-dist-min-1.52.3.tgz", @@ -33367,15 +32829,6 @@ "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==" }, - "node_modules/pngjs": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", - "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", - "dev": true, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/polygon-offset": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/polygon-offset/-/polygon-offset-0.3.1.tgz", @@ -33402,6 +32855,14 @@ "node": ">=0.10.0" } }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/post-robot": { "version": "10.0.42", "resolved": "https://registry.npmjs.org/post-robot/-/post-robot-10.0.42.tgz", @@ -33519,44 +32980,81 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, "node_modules/prebuild-install": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.3.tgz", - "integrity": "sha512-GV+nsUXuPW2p8Zy7SarF/2W/oiK8bFQgJcncoJ0d7kRpekEA0ftChjfEaF9/Y+QJEc/wFR7RAEa8lYByuUIe2g==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.2.tgz", + "integrity": "sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==", "optional": true, "dependencies": { - "detect-libc": "^1.0.3", + "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", - "minimist": "^1.2.0", - "mkdirp": "^0.5.1", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", "napi-build-utils": "^1.0.1", - "node-abi": "^2.7.0", - "noop-logger": "^0.1.1", - "npmlog": "^4.0.1", + "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", - "simple-get": "^3.0.3", + "simple-get": "^4.0.0", "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0", - "which-pm-runs": "^1.0.0" + "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" }, "engines": { - "node": ">=6" + "node": ">=10" } }, - "node_modules/prebuild-install/node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "node_modules/prebuild-install/node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", "optional": true, "dependencies": { - "minimist": "^1.2.6" + "mimic-response": "^3.1.0" }, - "bin": { - "mkdirp": "bin/cmd.js" + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/prebuild-install/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/prebuild-install/node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true, + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" } }, "node_modules/prelude-ls": { @@ -33707,9 +33205,9 @@ } }, "node_modules/priorityqueuejs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/priorityqueuejs/-/priorityqueuejs-1.0.0.tgz", - "integrity": "sha512-lg++21mreCEOuGWTbO5DnJKAdxfjrdN0S9ysoW9SzdSJvbkWpkaDdpG/cdsPCsEnoLUwmd9m3WcZhngW7yKA2g==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/priorityqueuejs/-/priorityqueuejs-2.0.0.tgz", + "integrity": "sha512-19BMarhgpq3x4ccvVi8k2QpJZcymo/iFUcrhPd4V96kYGovOdTsWwy7fxChYi4QY+m2EnGBWSX9Buakz+tWNQQ==" }, "node_modules/prismjs": { "version": "1.29.0", @@ -33740,28 +33238,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "devOptional": true - }, - "node_modules/process-on-spawn": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", - "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", - "dev": true, - "dependencies": { - "fromentries": "^1.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } + "dev": true }, "node_modules/promise": { "version": "7.3.1", @@ -33811,26 +33288,6 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, - "node_modules/proper-lockfile": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", - "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.4", - "retry": "^0.12.0", - "signal-exit": "^3.0.2" - } - }, - "node_modules/proper-lockfile/node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, "node_modules/property-information": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz", @@ -33865,12 +33322,6 @@ "node": ">= 0.10" } }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true - }, "node_modules/prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", @@ -34147,106 +33598,110 @@ } }, "node_modules/react-dev-utils": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-11.0.4.tgz", - "integrity": "sha512-dx0LvIGHcOPtKbeiSUM4jqpBl3TcY7CDjZdfOIcKeznE7BWr9dg0iPG90G5yfVQ+p/rGNMXdbfStvzQZEVEi4A==", + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz", + "integrity": "sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ==", "dev": true, "dependencies": { - "@babel/code-frame": "7.10.4", - "address": "1.1.2", - "browserslist": "4.14.2", - "chalk": "2.4.2", - "cross-spawn": "7.0.3", - "detect-port-alt": "1.1.6", - "escape-string-regexp": "2.0.0", - "filesize": "6.1.0", - "find-up": "4.1.0", - "fork-ts-checker-webpack-plugin": "4.1.6", - "global-modules": "2.0.0", - "globby": "11.0.1", - "gzip-size": "5.1.1", - "immer": "8.0.1", - "is-root": "2.1.0", - "loader-utils": "2.0.0", - "open": "^7.0.2", - "pkg-up": "3.1.0", - "prompts": "2.4.0", - "react-error-overlay": "^6.0.9", - "recursive-readdir": "2.2.2", - "shell-quote": "1.7.2", - "strip-ansi": "6.0.0", - "text-table": "0.2.0" + "@babel/code-frame": "^7.16.0", + "address": "^1.1.2", + "browserslist": "^4.18.1", + "chalk": "^4.1.2", + "cross-spawn": "^7.0.3", + "detect-port-alt": "^1.1.6", + "escape-string-regexp": "^4.0.0", + "filesize": "^8.0.6", + "find-up": "^5.0.0", + "fork-ts-checker-webpack-plugin": "^6.5.0", + "global-modules": "^2.0.0", + "globby": "^11.0.4", + "gzip-size": "^6.0.0", + "immer": "^9.0.7", + "is-root": "^2.1.0", + "loader-utils": "^3.2.0", + "open": "^8.4.0", + "pkg-up": "^3.1.0", + "prompts": "^2.4.2", + "react-error-overlay": "^6.0.11", + "recursive-readdir": "^2.2.2", + "shell-quote": "^1.7.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/react-dev-utils/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/react-dev-utils/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { "node": ">=10" - } - }, - "node_modules/react-dev-utils/node_modules/@babel/code-frame": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", - "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.10.4" - } - }, - "node_modules/react-dev-utils/node_modules/browserslist": { - "version": "4.14.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.2.tgz", - "integrity": "sha512-HI4lPveGKUR0x2StIz+2FXfDk9SfVMrxn6PLh1JeGUwcuoDkdKZebWiyLRJ68iIPDpMI4JLVDf7S7XzslgWOhw==", - "dev": true, - "dependencies": { - "caniuse-lite": "^1.0.30001125", - "electron-to-chromium": "^1.3.564", - "escalade": "^3.0.2", - "node-releases": "^1.1.61" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" }, "funding": { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/react-dev-utils/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/react-dev-utils/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "node_modules/react-dev-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/react-dev-utils/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "dependencies": { - "locate-path": "^5.0.0", + "locate-path": "^6.0.0", "path-exists": "^4.0.0" }, - "engines": { - "node": ">=8" - } - }, - "node_modules/react-dev-utils/node_modules/globby": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz", - "integrity": "sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ==", - "dev": true, - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", - "slash": "^3.0.0" - }, "engines": { "node": ">=10" }, @@ -34254,48 +33709,94 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/react-dev-utils/node_modules/loader-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", - "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "node_modules/react-dev-utils/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==", "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, "engines": { - "node": ">=8.9.0" + "node": ">=8" + } + }, + "node_modules/react-dev-utils/node_modules/immer": { + "version": "9.0.21", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", + "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/react-dev-utils/node_modules/loader-utils": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.1.tgz", + "integrity": "sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw==", + "dev": true, + "engines": { + "node": ">= 12.13.0" } }, "node_modules/react-dev-utils/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "dependencies": { - "p-locate": "^4.1.0" + "p-locate": "^5.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/react-dev-utils/node_modules/node-releases": { - "version": "1.1.77", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.77.tgz", - "integrity": "sha512-rB1DUFUNAN4Gn9keO2K1efO35IDK7yKHCdCaIMvFO7yUYmmZYeDjnGKle26G4rwj+LKRQpjyUUvMkPglwGCYNQ==", - "dev": true - }, - "node_modules/react-dev-utils/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "node_modules/react-dev-utils/node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", "dev": true, "dependencies": { - "p-limit": "^2.2.0" + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/react-dev-utils/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/react-dev-utils/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/react-dev-utils/node_modules/path-exists": { @@ -34307,35 +33808,13 @@ "node": ">=8" } }, - "node_modules/react-dev-utils/node_modules/prompts": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.0.tgz", - "integrity": "sha512-awZAKrk3vN6CroQukBL+R9051a4R3zCZBlJm/HBfrSZ8iTpYix3VX1vU4mveiLpiwmOJT4wokTF9m6HUk4KqWQ==", + "node_modules/react-dev-utils/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/react-dev-utils/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/react-dev-utils/node_modules/strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.0" + "has-flag": "^4.0.0" }, "engines": { "node": ">=8" @@ -34753,6 +34232,22 @@ "react-dom": ">=16.6.0" } }, + "node_modules/react-window": { + "version": "1.8.10", + "resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.10.tgz", + "integrity": "sha512-Y0Cx+dnU6NLa5/EvoHukUD0BklJ8qITCtVEPY1C/nL8wwoZ0b5aEw8Ff1dOVHw7fCzMt55XfJDd8S8W8LCaUCg==", + "dependencies": { + "@babel/runtime": "^7.0.0", + "memoize-one": ">=3.1.1 <6" + }, + "engines": { + "node": ">8.0.0" + }, + "peerDependencies": { + "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-youtube": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/react-youtube/-/react-youtube-9.0.1.tgz", @@ -34877,7 +34372,7 @@ "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "devOptional": true, + "dev": true, "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -34892,7 +34387,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "devOptional": true + "dev": true }, "node_modules/readdirp": { "version": "3.6.0", @@ -35121,13 +34616,14 @@ } }, "node_modules/regexp.prototype.flags": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", - "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "set-function-name": "^2.0.0" + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" }, "engines": { "node": ">= 0.4" @@ -35188,18 +34684,6 @@ "node": ">= 0.10" } }, - "node_modules/release-zalgo": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", - "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", - "dev": true, - "dependencies": { - "es6-error": "^4.0.1" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/remark-parse": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-5.0.0.tgz", @@ -35535,80 +35019,6 @@ "node": ">=8" } }, - "node_modules/resolve-dir": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-0.1.1.tgz", - "integrity": "sha512-QxMPqI6le2u0dCLyiGzgy92kjkkL6zO0XyvHzjdTNH3zM6e5Hz3BwG6+aEyNgiQ5Xz6PwTwgQEj3U50dByPKIA==", - "dev": true, - "dependencies": { - "expand-tilde": "^1.2.2", - "global-modules": "^0.2.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-dir/node_modules/expand-tilde": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-1.2.2.tgz", - "integrity": "sha512-rtmc+cjLZqnu9dSYosX9EWmSJhTwpACgJQTfj4hgg2JjOD/6SIQalZrt4a3aQeh++oNxkazcaxrhPUj6+g5G/Q==", - "dev": true, - "dependencies": { - "os-homedir": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-dir/node_modules/global-modules": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-0.2.3.tgz", - "integrity": "sha512-JeXuCbvYzYXcwE6acL9V2bAOeSIGl4dD+iwLY9iUx2VBJJ80R18HCn+JCwHM9Oegdfya3lEkGCdaRkSyc10hDA==", - "dev": true, - "dependencies": { - "global-prefix": "^0.1.4", - "is-windows": "^0.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-dir/node_modules/global-prefix": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-0.1.5.tgz", - "integrity": "sha512-gOPiyxcD9dJGCEArAhF4Hd0BAqvAe/JzERP7tYumE4yIkmIedPUVXcJFWbV3/p/ovIIvKjkrTk+f1UVkq7vvbw==", - "dev": true, - "dependencies": { - "homedir-polyfill": "^1.0.0", - "ini": "^1.3.4", - "is-windows": "^0.2.0", - "which": "^1.2.12" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-dir/node_modules/is-windows": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-0.2.0.tgz", - "integrity": "sha512-n67eJYmXbniZB7RF4I/FTjK1s6RPOCTxhYrVYLRaCt3lF0mpWZPKr3T2LSZAqyjQsxR2qMmGYXXzK0YWwcPM1Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-dir/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -35674,6 +35084,11 @@ "rimraf": "bin.js" } }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==" + }, "node_modules/rst-selector-parser": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz", @@ -35753,12 +35168,12 @@ } }, "node_modules/safe-array-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz", - "integrity": "sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1", + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", "has-symbols": "^1.0.3", "isarray": "^2.0.5" }, @@ -35802,14 +35217,17 @@ } }, "node_modules/safe-regex-test": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", - "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", "is-regex": "^1.1.4" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -36289,14 +35707,16 @@ "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" }, "node_modules/set-function-length": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", - "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "dependencies": { - "define-data-property": "^1.1.1", - "get-intrinsic": "^1.2.1", + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -36409,10 +35829,9 @@ } }, "node_modules/shell-quote": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz", - "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==", - "dev": true + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz", + "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==" }, "node_modules/shellwords": { "version": "0.1.1", @@ -36420,9 +35839,9 @@ "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==" }, "node_modules/shiki": { - "version": "0.9.15", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.9.15.tgz", - "integrity": "sha512-/Y0z9IzhJ8nD9nbceORCqu6NgT9X6I8Fk8c3SICHI5NbZRLdZYFaB233gwct9sU0vvSypyaL/qaKvzyQGJBZSw==", + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.10.1.tgz", + "integrity": "sha512-VsY7QJVzU51j5o1+DguUd+6vmCmZ5v/6gYu4vyYAhzjuNQU6P/vmSy4uQaOhvje031qQMiW0d2BwgMH52vqMng==", "dev": true, "dependencies": { "jsonc-parser": "^3.0.0", @@ -36482,7 +35901,9 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "decompress-response": "^4.2.0", "once": "^1.3.1", @@ -36768,59 +36189,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/spawn-wrap": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", - "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", - "dev": true, - "dependencies": { - "foreground-child": "^2.0.0", - "is-windows": "^1.0.2", - "make-dir": "^3.0.0", - "rimraf": "^3.0.0", - "signal-exit": "^3.0.2", - "which": "^2.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/spawn-wrap/node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/spawn-wrap/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/spawnd": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/spawnd/-/spawnd-4.4.0.tgz", - "integrity": "sha512-jLPOfB6QOEgMOQY15Z6+lwZEhH3F5ncXxIaZ7WHPIapwNNLyjrs61okj3VJ3K6tmP5TZ6cO0VAu9rEY4MD4YQg==", - "dev": true, - "dependencies": { - "exit": "^0.1.2", - "signal-exit": "^3.0.2", - "tree-kill": "^1.2.2", - "wait-port": "^0.2.7" - } - }, "node_modules/spdx-correct": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", @@ -37051,6 +36419,15 @@ "node": ">=0.10.0" } }, + "node_modules/stoppable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", + "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==", + "engines": { + "node": ">=4", + "npm": ">=6" + } + }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -37083,7 +36460,9 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -37097,7 +36476,9 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true, "optional": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -37106,7 +36487,9 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "ansi-regex": "^2.0.0" }, @@ -37134,13 +36517,14 @@ } }, "node_modules/string.prototype.trim": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", - "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -37150,26 +36534,29 @@ } }, "node_modules/string.prototype.trimend": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz", - "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/string.prototype.trimstart": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz", - "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -37254,6 +36641,32 @@ "node": ">= 0.12.0" } }, + "node_modules/style-loader/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/style-loader/node_modules/loader-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", + "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/style-loader/node_modules/schema-utils": { "version": "0.4.7", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz", @@ -37786,6 +37199,11 @@ "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==" }, + "node_modules/tinykeys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tinykeys/-/tinykeys-2.1.0.tgz", + "integrity": "sha512-/MESnqBD1xItZJn5oGQ4OsNORQgJfPP96XSGoyu4eLpwpL0ifO0SYR5OD76u0YMhMXsqkb0UqvI9+yXTh4xv8Q==" + }, "node_modules/tinyqueue": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-1.2.3.tgz", @@ -37885,6 +37303,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", + "devOptional": true, "dependencies": { "psl": "^1.1.33", "punycode": "^2.1.1", @@ -37899,6 +37318,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "devOptional": true, "engines": { "node": ">= 4.0.0" } @@ -37908,15 +37328,6 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, - "node_modules/tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "dev": true, - "bin": { - "tree-kill": "cli.js" - } - }, "node_modules/trim": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", @@ -38222,27 +37633,28 @@ } }, "node_modules/typed-array-buffer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", - "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1", - "is-typed-array": "^1.1.10" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" } }, "node_modules/typed-array-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", - "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", "dependencies": { - "call-bind": "^1.0.2", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "has-proto": "^1.0.1", - "is-typed-array": "^1.1.10" + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" @@ -38252,15 +37664,16 @@ } }, "node_modules/typed-array-byte-offset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", - "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "has-proto": "^1.0.1", - "is-typed-array": "^1.1.10" + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" @@ -38270,13 +37683,19 @@ } }, "node_modules/typed-array-length": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", - "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", "dependencies": { - "call-bind": "^1.0.2", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "is-typed-array": "^1.1.9" + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -38297,19 +37716,16 @@ } }, "node_modules/typedoc": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.21.5.tgz", - "integrity": "sha512-uRDRmYheE5Iju9Zz0X50pTASTpBorIHFt02F5Y8Dt4eBt55h3mwk1CBSY2+EfwBxY16N4Xm7f8KXhnfFZ0AmBw==", + "version": "0.22.15", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.22.15.tgz", + "integrity": "sha512-CMd1lrqQbFvbx6S9G6fL4HKp3GoIuhujJReWqlIvSb2T26vGai+8Os3Mde7Pn832pXYemd9BMuuYWhFpL5st0Q==", "dev": true, "dependencies": { - "glob": "^7.1.7", - "handlebars": "^4.7.7", + "glob": "^7.2.0", "lunr": "^2.3.9", - "marked": "^2.1.1", - "minimatch": "^3.0.0", - "progress": "^2.0.3", - "shiki": "^0.9.3", - "typedoc-default-themes": "^0.12.10" + "marked": "^4.0.12", + "minimatch": "^5.0.1", + "shiki": "^0.10.1" }, "bin": { "typedoc": "bin/typedoc" @@ -38318,16 +37734,28 @@ "node": ">= 12.10.0" }, "peerDependencies": { - "typescript": "4.0.x || 4.1.x || 4.2.x || 4.3.x" + "typescript": "4.0.x || 4.1.x || 4.2.x || 4.3.x || 4.4.x || 4.5.x || 4.6.x" } }, - "node_modules/typedoc-default-themes": { - "version": "0.12.10", - "resolved": "https://registry.npmjs.org/typedoc-default-themes/-/typedoc-default-themes-0.12.10.tgz", - "integrity": "sha512-fIS001cAYHkyQPidWXmHuhs8usjP5XVJjWB8oZGqkTowZaz3v7g3KDZeeqE82FBrmkAnIBOY3jgy7lnPnqATbA==", + "node_modules/typedoc/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/typedoc/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, "engines": { - "node": ">= 8" + "node": ">=10" } }, "node_modules/typescript": { @@ -38437,9 +37865,9 @@ } }, "node_modules/underscore": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", - "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==" + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz", + "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==" }, "node_modules/unherit": { "version": "1.1.3", @@ -39091,29 +38519,6 @@ "node": ">=8.9.0" } }, - "node_modules/wait-port": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/wait-port/-/wait-port-0.2.14.tgz", - "integrity": "sha512-kIzjWcr6ykl7WFbZd0TMae8xovwqcqbx6FM9l+7agOgUByhzdjfzZBPK2CPufldTOMxbUivss//Sh9MFawmPRQ==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2", - "commander": "^3.0.2", - "debug": "^4.1.1" - }, - "bin": { - "wait-port": "bin/wait-port.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wait-port/node_modules/commander": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz", - "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==", - "dev": true - }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", @@ -39264,21 +38669,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/webpack-bundle-analyzer/node_modules/gzip-size": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", - "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", - "dev": true, - "dependencies": { - "duplexer": "^0.1.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/webpack-cli": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", @@ -39355,9 +38745,9 @@ } }, "node_modules/webpack-dev-middleware": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", - "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", + "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", "dev": true, "dependencies": { "colorette": "^2.0.10", @@ -39431,9 +38821,9 @@ } }, "node_modules/webpack-dev-server": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz", - "integrity": "sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==", + "version": "4.15.2", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz", + "integrity": "sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==", "dev": true, "dependencies": { "@types/bonjour": "^3.5.9", @@ -39464,7 +38854,7 @@ "serve-index": "^1.9.1", "sockjs": "^0.3.24", "spdy": "^4.0.2", - "webpack-dev-middleware": "^5.3.1", + "webpack-dev-middleware": "^5.3.4", "ws": "^8.13.0" }, "bin": { @@ -39784,25 +39174,16 @@ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==" }, - "node_modules/which-pm-runs": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.1.0.tgz", - "integrity": "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==", - "optional": true, - "engines": { - "node": ">=4" - } - }, "node_modules/which-typed-array": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz", - "integrity": "sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -39815,7 +39196,9 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "string-width": "^1.0.2 || 2 || 3 || 4" } @@ -39948,21 +39331,6 @@ "node": ">=0.10.0" } }, - "node_modules/wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true - }, - "node_modules/worker-rpc": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/worker-rpc/-/worker-rpc-0.1.1.tgz", - "integrity": "sha512-P1WjMrUB3qgJNI9jfmpZ/htmBEjFh//6l/5y8SD9hg1Ef5zTTVVoRjTrTEzPrNBQvmhMxkoTsjOXN10GWU7aCg==", - "dev": true, - "dependencies": { - "microevent.ts": "~0.1.1" - } - }, "node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -40108,26 +39476,6 @@ "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==" }, - "node_modules/xml2js": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", - "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/xml2js/node_modules/xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "engines": { - "node": ">=4.0" - } - }, "node_modules/xmlbuilder": { "version": "8.2.2", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-8.2.2.tgz", @@ -40143,14 +39491,6 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "devOptional": true }, - "node_modules/xmldom": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.6.0.tgz", - "integrity": "sha512-iAcin401y58LckRZ0TkI4k0VSM1Qg0KGSc3i8rU+xrxe19A/BN1zHyVSJY7uoutVlaTSzYyk/v5AmkewAP7jtg==", - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/xpath.js": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/xpath.js/-/xpath.js-1.1.0.tgz", @@ -40318,25 +39658,6 @@ "node": ">=8" } }, - "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "dev": true, - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, - "node_modules/yazl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.5.1.tgz", - "integrity": "sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==", - "dev": true, - "dependencies": { - "buffer-crc32": "~0.2.3" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 3f4252d56..abec04d59 100644 --- a/package.json +++ b/package.json @@ -5,10 +5,10 @@ "main": "index.js", "dependencies": { "@azure/arm-cosmosdb": "9.1.0", - "@azure/cosmos": "4.0.0", + "@azure/cosmos": "4.0.1-beta.3", "@azure/cosmos-language-service": "0.0.5", - "@azure/identity": "1.2.1", - "@azure/ms-rest-nodeauth": "3.0.7", + "@azure/identity": "1.5.2", + "@azure/ms-rest-nodeauth": "3.1.1", "@azure/msal-browser": "2.14.2", "@babel/plugin-proposal-class-properties": "7.12.1", "@babel/plugin-proposal-decorators": "7.12.12", @@ -46,6 +46,8 @@ "@types/lodash": "4.14.171", "@types/mkdirp": "1.0.1", "@types/node-fetch": "2.5.7", + "@uiw/react-split": "5.9.3", + "@xmldom/xmldom": "0.7.13", "applicationinsights": "1.8.0", "bootstrap": "3.4.1", "canvas": "file:./canvas", @@ -54,7 +56,7 @@ "copy-webpack-plugin": "11.0.0", "crossroads": "0.12.2", "css-element-queries": "1.1.1", - "d3": "6.1.1", + "d3": "7.8.5", "datatables.net-colreorder-dt": "1.7.0", "datatables.net-dt": "1.13.8", "date-fns": "1.29.0", @@ -69,17 +71,19 @@ "i18next-browser-languagedetector": "6.0.1", "i18next-http-backend": "1.0.23", "iframe-resizer-react": "1.1.0", + "immer": "9.0.6", "immutable": "4.0.0-rc.12", "is-ci": "2.0.0", "jquery": "3.7.1", "jquery-typeahead": "2.11.1", "jquery-ui-dist": "1.13.2", "knockout": "3.5.1", + "loader-utils": "2.0.3", "mkdirp": "1.0.4", "monaco-editor": "0.44.0", "ms": "2.1.3", - "patch-package": "8.0.0", "p-retry": "4.6.2", + "patch-package": "8.0.0", "plotly.js-cartesian-dist-min": "1.52.3", "post-robot": "10.0.42", "q": "1.5.1", @@ -95,13 +99,16 @@ "react-splitter-layout": "4.0.0", "react-string-format": "1.0.1", "react-youtube": "9.0.1", + "react-window": "1.8.10", "reflect-metadata": "0.1.13", "rx-jupyter": "5.5.12", "sanitize-html": "2.3.3", + "shell-quote": "1.7.3", "styled-components": "5.0.1", "swr": "0.4.0", "terser-webpack-plugin": "5.3.9", - "underscore": "1.9.1", + "tinykeys": "2.1.0", + "underscore": "1.12.1", "utility-types": "3.10.0", "zustand": "3.5.0" }, @@ -110,6 +117,7 @@ "@babel/preset-env": "7.9.0", "@babel/preset-react": "7.9.4", "@babel/preset-typescript": "7.9.0", + "@playwright/test": "1.44.0", "@testing-library/react": "11.2.3", "@types/applicationinsights-js": "1.0.7", "@types/codemirror": "0.0.56", @@ -118,8 +126,8 @@ "@types/datatables.net": "1.10.28", "@types/datatables.net-colreorder": "1.4.5", "@types/dom-to-image": "2.6.2", - "@types/enzyme": "3.10.7", - "@types/enzyme-adapter-react-16": "1.0.6", + "@types/enzyme": "3.10.12", + "@types/enzyme-adapter-react-16": "1.0.9", "@types/hasher": "0.0.31", "@types/jest": "26.0.20", "@types/jquery": "3.5.29", @@ -131,6 +139,7 @@ "@types/react-notification-system": "0.2.39", "@types/react-redux": "7.1.7", "@types/react-splitter-layout": "3.0.1", + "@types/react-window": "1.8.8", "@types/sanitize-html": "1.27.2", "@types/sinon": "2.3.3", "@types/styled-components": "5.1.1", @@ -146,14 +155,13 @@ "create-file-webpack": "1.0.2", "css-loader": "6.8.1", "enzyme": "3.11.0", - "enzyme-adapter-react-16": "1.15.5", - "enzyme-to-json": "3.6.1", + "enzyme-adapter-react-16": "1.15.8", + "enzyme-to-json": "3.6.2", "eslint": "8.50.0", "eslint-cli": "1.1.1", "eslint-plugin-no-null": "1.0.2", "eslint-plugin-prefer-arrow": "1.2.3", "eslint-plugin-react-hooks": "4.6.0", - "expect-playwright": "0.3.3", "fast-glob": "3.2.5", "fs-extra": "7.0.0", "html-inline-css-webpack-plugin": "1.11.2", @@ -162,7 +170,6 @@ "html-webpack-plugin": "5.5.3", "jest": "26.6.3", "jest-canvas-mock": "2.3.1", - "jest-playwright-preset": "1.5.1", "jest-react-hooks-shallow": "1.5.1", "jest-trx-results-processor": "0.0.7", "less": "3.8.1", @@ -170,25 +177,24 @@ "less-vars-loader": "1.1.0", "mini-css-extract-plugin": "2.1.0", "monaco-editor-webpack-plugin": "7.1.0", - "node-fetch": "2.6.1", - "playwright": "1.13.0", + "node-fetch": "2.6.7", "prettier": "3.0.3", "process": "0.11.10", "querystring-es3": "0.2.1", "raw-loader": "0.5.1", - "react-dev-utils": "11.0.4", + "react-dev-utils": "12.0.1", "rimraf": "3.0.0", "sinon": "3.2.1", "style-loader": "0.23.0", "ts-loader": "9.2.4", - "typedoc": "0.21.5", + "typedoc": "0.22.15", "typescript": "4.3.5", "url-loader": "4.1.1", "wait-on": "4.0.2", "webpack": "5.88.2", "webpack-bundle-analyzer": "4.9.1", "webpack-cli": "5.1.4", - "webpack-dev-server": "4.15.1" + "webpack-dev-server": "4.15.2" }, "scripts": { "postinstall": "patch-package", @@ -203,6 +209,7 @@ "test": "rimraf coverage && jest", "test:debug": "jest --runInBand", "test:e2e": "jest -c ./jest.config.playwright.js --detectOpenHandles", + "test:file": "jest --coverage=false", "watch": "npm run start", "wait-for-server": "wait-on -t 240000 -i 5000 -v https-get://0.0.0.0:1234/", "build:ase": "gulp build:ase", @@ -238,4 +245,4 @@ "printWidth": 120, "endOfLine": "auto" } -} \ No newline at end of file +} diff --git a/patches/@uiw+react-split+5.9.3.patch b/patches/@uiw+react-split+5.9.3.patch new file mode 100644 index 000000000..3e5307463 --- /dev/null +++ b/patches/@uiw+react-split+5.9.3.patch @@ -0,0 +1,11 @@ +diff --git a/node_modules/@uiw/react-split/cjs/index.d.ts b/node_modules/@uiw/react-split/cjs/index.d.ts +index 644bcc3..f794760 100644 +--- a/node_modules/@uiw/react-split/cjs/index.d.ts ++++ b/node_modules/@uiw/react-split/cjs/index.d.ts +@@ -56,5 +56,5 @@ export default class Split extends React.Component { + onMouseDown(paneNumber: number, env: React.MouseEvent): void; + onDragging(env: Event): void; + onDragEnd(): void; +- render(): import("react/jsx-runtime").JSX.Element; ++ render(): JSX.Element; + } diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 000000000..074e7e5f5 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,53 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: 'test', + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 3 : 0, + workers: process.env.CI ? 1 : undefined, + reporter: process.env.CI ? 'blob' : 'html', + timeout: 10 * 60 * 1000, + use: { + actionTimeout: 5 * 60 * 1000, + trace: 'off', + video: 'off', + screenshot: 'on', + testIdAttribute: 'data-test', + contextOptions: { + ignoreHTTPSErrors: true, + }, + }, + + expect: { + timeout: 5 * 60 * 1000, + }, + + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + ], + + webServer: { + command: 'npm run start', + url: 'https://127.0.0.1:1234/_ready', + timeout: 120 * 1000, + ignoreHTTPSErrors: true, + reuseExistingServer: !process.env.CI, + }, +}); diff --git a/src/Common/Constants.ts b/src/Common/Constants.ts index 4d86f5ff7..cb4c8f007 100644 --- a/src/Common/Constants.ts +++ b/src/Common/Constants.ts @@ -88,6 +88,12 @@ export class CapabilityNames { public static readonly EnableStorageAnalytics: string = "EnableStorageAnalytics"; public static readonly EnableMongo: string = "EnableMongo"; public static readonly EnableServerless: string = "EnableServerless"; + public static readonly EnableNoSQLVectorSearch: string = "EnableNoSQLVectorSearch"; +} + +export enum CapacityMode { + Provisioned = "Provisioned", + Serverless = "Serverless", } // flight names returned from the portal are always lowercase @@ -124,7 +130,37 @@ export enum MongoBackendEndpointType { remote, } -// TODO: 435619 Add default endpoints per cloud and use regional only when available +export class BackendApi { + public static readonly GenerateToken: string = "GenerateToken"; + public static readonly PortalSettings: string = "PortalSettings"; + public static readonly AccountRestrictions: string = "AccountRestrictions"; +} + +export class PortalBackendEndpoints { + public static readonly Development: string = "https://localhost:7235"; + public static readonly Mpac: string = "https://cdb-ms-mpac-pbe.cosmos.azure.com"; + public static readonly Prod: string = "https://cdb-ms-prod-pbe.cosmos.azure.com"; + public static readonly Fairfax: string = "https://cdb-ff-prod-pbe.cosmos.azure.us"; + public static readonly Mooncake: string = "https://cdb-mc-prod-pbe.cosmos.azure.cn"; +} + +export class MongoProxyEndpoints { + public static readonly Local: string = "https://localhost:7238"; + public static readonly Mpac: string = "https://cdb-ms-mpac-mp.cosmos.azure.com"; + public static readonly Prod: string = "https://cdb-ms-prod-mp.cosmos.azure.com"; + public static readonly Fairfax: string = "https://cdb-ff-prod-mp.cosmos.azure.us"; + public static readonly Mooncake: string = "https://cdb-mc-prod-mp.cosmos.azure.cn"; +} + +export class CassandraProxyEndpoints { + public static readonly Development: string = "https://localhost:7240"; + public static readonly Mpac: string = "https://cdb-ms-mpac-cp.cosmos.azure.com"; + public static readonly Prod: string = "https://cdb-ms-prod-cp.cosmos.azure.com"; + public static readonly Fairfax: string = "https://cdb-ff-prod-cp.cosmos.azure.us"; + public static readonly Mooncake: string = "https://cdb-mc-prod-cp.cosmos.azure.cn"; +} + +//TODO: Remove this when new backend is migrated over export class CassandraBackend { public static readonly createOrDeleteApi: string = "api/cassandra/createordelete"; public static readonly guestCreateOrDeleteApi: string = "api/guest/cassandra/createordelete"; @@ -136,6 +172,17 @@ export class CassandraBackend { public static readonly guestSchemaApi: string = "api/guest/cassandra/schema"; } +export class CassandraProxyAPIs { + public static readonly createOrDeleteApi: string = "api/cassandra/createordelete"; + public static readonly connectionStringCreateOrDeleteApi: string = "api/connectionstring/cassandra/createordelete"; + public static readonly queryApi: string = "api/cassandra"; + public static readonly connectionStringQueryApi: string = "api/connectionstring/cassandra"; + public static readonly keysApi: string = "api/cassandra/keys"; + public static readonly connectionStringKeysApi: string = "api/connectionstring/cassandra/keys"; + public static readonly schemaApi: string = "api/cassandra/schema"; + public static readonly connectionStringSchemaApi: string = "api/connectionstring/cassandra/schema"; +} + export class Queries { public static CustomPageOption: string = "custom"; public static UnlimitedPageOption: string = "unlimited"; @@ -209,6 +256,11 @@ export class HttpHeaders { public static partitionKey: string = "x-ms-documentdb-partitionkey"; public static migrateOfferToManualThroughput: string = "x-ms-cosmos-migrate-offer-to-manual-throughput"; public static migrateOfferToAutopilot: string = "x-ms-cosmos-migrate-offer-to-autopilot"; + public static xAPIKey: string = "X-API-Key"; +} + +export class ContentType { + public static applicationJson: string = "application/json"; } export class ApiType { diff --git a/src/Common/CosmosClient.ts b/src/Common/CosmosClient.ts index 34cba6a28..c24680f4b 100644 --- a/src/Common/CosmosClient.ts +++ b/src/Common/CosmosClient.ts @@ -1,7 +1,6 @@ import * as Cosmos from "@azure/cosmos"; -import { sendCachedDataMessage } from "Common/MessageHandler"; import { getAuthorizationTokenUsingResourceTokens } from "Common/getAuthorizationTokenUsingResourceTokens"; -import { AuthorizationToken, MessageTypes } from "Contracts/MessageTypes"; +import { AuthorizationToken } from "Contracts/FabricMessageTypes"; import { checkDatabaseResourceTokensValidity } from "Platform/Fabric/FabricUtil"; import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility"; import { AuthType } from "../AuthType"; @@ -40,9 +39,10 @@ export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => { case Cosmos.ResourceType.item: case Cosmos.ResourceType.pkranges: // User resource tokens + // TODO userContext.fabricContext.databaseConnectionInfo can be undefined headers[HttpHeaders.msDate] = new Date().toUTCString(); - const resourceTokens = userContext.fabricDatabaseConnectionInfo.resourceTokens; - checkDatabaseResourceTokensValidity(userContext.fabricDatabaseConnectionInfo.resourceTokensTimestamp); + const resourceTokens = userContext.fabricContext.databaseConnectionInfo.resourceTokens; + checkDatabaseResourceTokensValidity(userContext.fabricContext.databaseConnectionInfo.resourceTokensTimestamp); return getAuthorizationTokenUsingResourceTokens(resourceTokens, requestInfo.path, requestInfo.resourceId); case Cosmos.ResourceType.none: @@ -50,13 +50,23 @@ export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => { case Cosmos.ResourceType.offer: case Cosmos.ResourceType.user: case Cosmos.ResourceType.permission: - // User master tokens - const authorizationToken = await sendCachedDataMessage(MessageTypes.GetAuthorizationToken, [ - requestInfo, - ]); - console.log("Response from Fabric: ", authorizationToken); - headers[HttpHeaders.msDate] = authorizationToken.XDate; - return decodeURIComponent(authorizationToken.PrimaryReadWriteToken); + // For now, these operations aren't used, so fetching the authorization token is commented out. + // This provider must return a real token to pass validation by the client, so we return the cached resource token + // (which is a valid token, but won't work for these operations). + const resourceTokens2 = userContext.fabricContext.databaseConnectionInfo.resourceTokens; + return getAuthorizationTokenUsingResourceTokens(resourceTokens2, requestInfo.path, requestInfo.resourceId); + + /* ************** TODO: Uncomment this code if we need to support these operations ************** + // User master tokens + const authorizationToken = await sendCachedDataMessage( + FabricMessageTypes.GetAuthorizationToken, + [requestInfo], + userContext.fabricContext.connectionId, + ); + console.log("Response from Fabric: ", authorizationToken); + headers[HttpHeaders.msDate] = authorizationToken.XDate; + return decodeURIComponent(authorizationToken.PrimaryReadWriteToken); + ***********************************************************************************************/ } } diff --git a/src/Common/DocumentUtility.ts b/src/Common/DocumentUtility.ts index 99cdefc5a..322d883b0 100644 --- a/src/Common/DocumentUtility.ts +++ b/src/Common/DocumentUtility.ts @@ -1,9 +1,9 @@ import { userContext } from "../UserContext"; -export const getEntityName = (): string => { +export const getEntityName = (multiple?: boolean): string => { if (userContext.apiType === "Mongo") { - return "document"; + return multiple ? "documents" : "document"; } - return "item"; + return multiple ? "items" : "item"; }; diff --git a/src/Common/IteratorUtilities.ts b/src/Common/IteratorUtilities.ts index f85ad7fb2..6283488b8 100644 --- a/src/Common/IteratorUtilities.ts +++ b/src/Common/IteratorUtilities.ts @@ -1,3 +1,4 @@ +import { QueryOperationOptions } from "@azure/cosmos"; import { QueryResults } from "../Contracts/ViewModels"; interface QueryResponse { @@ -10,13 +11,17 @@ interface QueryResponse { } export interface MinimalQueryIterator { - fetchNext: () => Promise; + fetchNext: (queryOperationOptions?: QueryOperationOptions) => Promise; } // Pick, "fetchNext">; -export function nextPage(documentsIterator: MinimalQueryIterator, firstItemIndex: number): Promise { - return documentsIterator.fetchNext().then((response) => { +export function nextPage( + documentsIterator: MinimalQueryIterator, + firstItemIndex: number, + queryOperationOptions?: QueryOperationOptions, +): Promise { + return documentsIterator.fetchNext(queryOperationOptions).then((response) => { const documents = response.resources; // eslint-disable-next-line @typescript-eslint/no-explicit-any const headers = (response as any).headers || {}; // TODO this is a private key. Remove any diff --git a/src/Common/MessageHandler.ts b/src/Common/MessageHandler.ts index e32b31d6c..2a986f835 100644 --- a/src/Common/MessageHandler.ts +++ b/src/Common/MessageHandler.ts @@ -1,5 +1,7 @@ +import { FabricMessageTypes } from "Contracts/FabricMessageTypes"; import Q from "q"; import * as _ from "underscore"; +import * as Logger from "../Common/Logger"; import { MessageTypes } from "../Contracts/ExplorerContracts"; import { getDataExplorerWindow } from "../Utils/WindowUtils"; import * as Constants from "./Constants"; @@ -27,15 +29,24 @@ export function handleCachedDataMessage(message: any): void { runGarbageCollector(); } +/** + * + * @param messageType + * @param params + * @param scope Use this string to identify request Useful to distinguish response from different senders + * @param timeoutInMs + * @returns + */ export function sendCachedDataMessage( - messageType: MessageTypes, + messageType: MessageTypes | FabricMessageTypes, params: Object[], + scope?: string, timeoutInMs?: number, ): Q.Promise { let cachedDataPromise: CachedDataPromise = { deferred: Q.defer(), startTime: new Date(), - id: _.uniqueId(), + id: _.uniqueId(scope), }; RequestMap[cachedDataPromise.id] = cachedDataPromise; sendMessage({ type: messageType, params: params, id: cachedDataPromise.id }); @@ -47,6 +58,10 @@ export function sendCachedDataMessage( ); } +/** + * + * @param data Overwrite the data property of the message + */ export function sendMessage(data: any): void { _sendMessage({ signature: "pcIframe", @@ -82,10 +97,18 @@ const _sendMessage = (message: any): void => { const portalChildWindow = getDataExplorerWindow(window) || window; if (portalChildWindow === window) { // Current window is a child of portal, send message to portal window - portalChildWindow.parent.postMessage(message, portalChildWindow.document.referrer || "*"); + if (portalChildWindow.document.referrer) { + portalChildWindow.parent.postMessage(message, portalChildWindow.document.referrer); + } else { + Logger.logError("Iframe failed to send message to portal", "MessageHandler"); + } } else { // Current window is not a child of portal, send message to the child window instead (which is data explorer) - portalChildWindow.postMessage(message, portalChildWindow.location.origin || "*"); + if (portalChildWindow.location.origin) { + portalChildWindow.postMessage(message, portalChildWindow.location.origin); + } else { + Logger.logError("Iframe failed to send message to data explorer", "MessageHandler"); + } } } }; diff --git a/src/Common/MongoProxyClient.ts b/src/Common/MongoProxyClient.ts index ea4103ff6..d9aa0fb4c 100644 --- a/src/Common/MongoProxyClient.ts +++ b/src/Common/MongoProxyClient.ts @@ -1,6 +1,10 @@ import { Constants as CosmosSDKConstants } from "@azure/cosmos"; +import { + allowedMongoProxyEndpoints, + allowedMongoProxyEndpoints_ToBeDeprecated, + validateEndpoint, +} from "Utils/EndpointUtils"; import queryString from "querystring"; -import { allowedMongoProxyEndpoints, validateEndpoint } from "Utils/EndpointValidation"; import { AuthType } from "../AuthType"; import { configContext } from "../ConfigContext"; import * as DataModels from "../Contracts/DataModels"; @@ -10,7 +14,7 @@ import DocumentId from "../Explorer/Tree/DocumentId"; import { hasFlag } from "../Platform/Hosted/extractFeatures"; import { userContext } from "../UserContext"; import { logConsoleError } from "../Utils/NotificationConsoleUtils"; -import { ApiType, HttpHeaders, HttpStatusCodes } from "./Constants"; +import { ApiType, ContentType, HttpHeaders, HttpStatusCodes, MongoProxyEndpoints } from "./Constants"; import { MinimalQueryIterator } from "./IteratorUtilities"; import { sendMessage } from "./MessageHandler"; @@ -62,6 +66,73 @@ export function queryDocuments( isResourceList: boolean, query: string, continuationToken?: string, +): Promise { + if (!useMongoProxyEndpoint("resourcelist") || !useMongoProxyEndpoint("queryDocuments")) { + return queryDocuments_ToBeDeprecated(databaseId, collection, isResourceList, query, continuationToken); + } + + const { databaseAccount } = userContext; + const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint; + const params = { + databaseID: databaseId, + collectionID: collection.id(), + resourceUrl: `${resourceEndpoint}dbs/${databaseId}/colls/${collection.id()}/docs/`, + resourceID: collection.rid, + resourceType: "docs", + subscriptionID: userContext.subscriptionId, + resourceGroup: userContext.resourceGroup, + databaseAccountName: databaseAccount.name, + partitionKey: + collection && collection.partitionKey && !collection.partitionKey.systemKey + ? collection.partitionKeyProperties?.[0] + : "", + query, + }; + + const endpoint = getFeatureEndpointOrDefault("resourcelist") || ""; + + const headers = { + ...defaultHeaders, + ...authHeaders(), + [CosmosSDKConstants.HttpHeaders.IsQuery]: "true", + [CosmosSDKConstants.HttpHeaders.PopulateQueryMetrics]: "true", + [CosmosSDKConstants.HttpHeaders.EnableScanInQuery]: "true", + [CosmosSDKConstants.HttpHeaders.EnableCrossPartitionQuery]: "true", + [CosmosSDKConstants.HttpHeaders.ParallelizeCrossPartitionQuery]: "true", + [HttpHeaders.contentType]: "application/query+json", + }; + + if (continuationToken) { + headers[CosmosSDKConstants.HttpHeaders.Continuation] = continuationToken; + } + + const path = isResourceList ? "/resourcelist" : "/queryDocuments"; + + return window + .fetch(`${endpoint}${path}`, { + method: "POST", + body: JSON.stringify(params), + headers, + }) + .then(async (response) => { + if (response.ok) { + return { + continuationToken: response.headers.get(CosmosSDKConstants.HttpHeaders.Continuation), + documents: (await response.json()).Documents as DataModels.DocumentId[], + headers: response.headers, + }; + } + await errorHandling(response, "querying documents", params); + return undefined; + }); +} + +function queryDocuments_ToBeDeprecated( + databaseId: string, + collection: Collection, + isResourceList: boolean, + query: string, + continuationToken?: string, ): Promise { const { databaseAccount } = userContext; const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint; @@ -122,6 +193,54 @@ export function readDocument( databaseId: string, collection: Collection, documentId: DocumentId, +): Promise { + if (!useMongoProxyEndpoint("readDocument")) { + return readDocument_ToBeDeprecated(databaseId, collection, documentId); + } + const { databaseAccount } = userContext; + const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint; + const idComponents = documentId.self.split("/"); + const path = idComponents.slice(0, 4).join("/"); + const rid = encodeURIComponent(idComponents[5]); + const params = { + databaseID: databaseId, + collectionID: collection.id(), + resourceUrl: `${resourceEndpoint}${path}/${rid}`, + resourceID: rid, + resourceType: "docs", + subscriptionID: userContext.subscriptionId, + resourceGroup: userContext.resourceGroup, + databaseAccountName: databaseAccount.name, + partitionKey: + documentId && documentId.partitionKey && !documentId.partitionKey.systemKey + ? documentId.partitionKeyProperties?.[0] + : "", + }; + + const endpoint = getFeatureEndpointOrDefault("readDocument"); + + return window + .fetch(endpoint, { + method: "POST", + body: JSON.stringify(params), + headers: { + ...defaultHeaders, + ...authHeaders(), + [HttpHeaders.contentType]: ContentType.applicationJson, + }, + }) + .then(async (response) => { + if (response.ok) { + return response.json(); + } + return await errorHandling(response, "reading document", params); + }); +} + +export function readDocument_ToBeDeprecated( + databaseId: string, + collection: Collection, + documentId: DocumentId, ): Promise { const { databaseAccount } = userContext; const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint; @@ -169,6 +288,51 @@ export function createDocument( collection: Collection, partitionKeyProperty: string, documentContent: unknown, +): Promise { + if (!useMongoProxyEndpoint("createDocument")) { + return createDocument_ToBeDeprecated(databaseId, collection, partitionKeyProperty, documentContent); + } + const { databaseAccount } = userContext; + const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint; + const params = { + databaseID: databaseId, + collectionID: collection.id(), + resourceUrl: `${resourceEndpoint}dbs/${databaseId}/colls/${collection.id()}/docs/`, + resourceID: collection.rid, + resourceType: "docs", + subscriptionID: userContext.subscriptionId, + resourceGroup: userContext.resourceGroup, + databaseAccountName: databaseAccount.name, + partitionKey: + collection && collection.partitionKey && !collection.partitionKey.systemKey ? partitionKeyProperty : "", + documentContent: JSON.stringify(documentContent), + }; + + const endpoint = getFeatureEndpointOrDefault("createDocument"); + + return window + .fetch(`${endpoint}/createDocument`, { + method: "POST", + body: JSON.stringify(params), + headers: { + ...defaultHeaders, + ...authHeaders(), + [HttpHeaders.contentType]: ContentType.applicationJson, + }, + }) + .then(async (response) => { + if (response.ok) { + return response.json(); + } + return await errorHandling(response, "creating document", params); + }); +} + +export function createDocument_ToBeDeprecated( + databaseId: string, + collection: Collection, + partitionKeyProperty: string, + documentContent: unknown, ): Promise { const { databaseAccount } = userContext; const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint; @@ -208,6 +372,56 @@ export function updateDocument( collection: Collection, documentId: DocumentId, documentContent: string, +): Promise { + if (!useMongoProxyEndpoint("updateDocument")) { + return updateDocument_ToBeDeprecated(databaseId, collection, documentId, documentContent); + } + const { databaseAccount } = userContext; + const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint; + const idComponents = documentId.self.split("/"); + const path = idComponents.slice(0, 5).join("/"); + const rid = encodeURIComponent(idComponents[5]); + const params = { + databaseID: databaseId, + collectionID: collection.id(), + resourceUrl: `${resourceEndpoint}${path}/${rid}`, + resourceID: rid, + resourceType: "docs", + subscriptionID: userContext.subscriptionId, + resourceGroup: userContext.resourceGroup, + databaseAccountName: databaseAccount.name, + partitionKey: + documentId && documentId.partitionKey && !documentId.partitionKey.systemKey + ? documentId.partitionKeyProperties?.[0] + : "", + documentContent, + }; + const endpoint = getFeatureEndpointOrDefault("updateDocument"); + + return window + .fetch(endpoint, { + method: "PUT", + body: JSON.stringify(params), + headers: { + ...defaultHeaders, + ...authHeaders(), + [HttpHeaders.contentType]: ContentType.applicationJson, + [CosmosSDKConstants.HttpHeaders.PartitionKey]: JSON.stringify(documentId.partitionKeyHeader()), + }, + }) + .then(async (response) => { + if (response.ok) { + return response.json(); + } + return await errorHandling(response, "updating document", params); + }); +} + +export function updateDocument_ToBeDeprecated( + databaseId: string, + collection: Collection, + documentId: DocumentId, + documentContent: string, ): Promise { const { databaseAccount } = userContext; const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint; @@ -237,7 +451,7 @@ export function updateDocument( headers: { ...defaultHeaders, ...authHeaders(), - [HttpHeaders.contentType]: "application/json", + [HttpHeaders.contentType]: ContentType.applicationJson, [CosmosSDKConstants.HttpHeaders.PartitionKey]: JSON.stringify(documentId.partitionKeyHeader()), }, }) @@ -250,6 +464,53 @@ export function updateDocument( } export function deleteDocument(databaseId: string, collection: Collection, documentId: DocumentId): Promise { + if (!useMongoProxyEndpoint("deleteDocument")) { + return deleteDocument_ToBeDeprecated(databaseId, collection, documentId); + } + const { databaseAccount } = userContext; + const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint; + const idComponents = documentId.self.split("/"); + const path = idComponents.slice(0, 5).join("/"); + const rid = encodeURIComponent(idComponents[5]); + const params = { + databaseID: databaseId, + collectionID: collection.id(), + resourceUrl: `${resourceEndpoint}${path}/${rid}`, + resourceID: rid, + resourceType: "docs", + subscriptionID: userContext.subscriptionId, + resourceGroup: userContext.resourceGroup, + databaseAccountName: databaseAccount.name, + partitionKey: + documentId && documentId.partitionKey && !documentId.partitionKey.systemKey + ? documentId.partitionKeyProperties?.[0] + : "", + }; + const endpoint = getFeatureEndpointOrDefault("deleteDocument"); + + return window + .fetch(endpoint, { + method: "DELETE", + body: JSON.stringify(params), + headers: { + ...defaultHeaders, + ...authHeaders(), + [HttpHeaders.contentType]: ContentType.applicationJson, + }, + }) + .then(async (response) => { + if (response.ok) { + return undefined; + } + return await errorHandling(response, "deleting document", params); + }); +} + +export function deleteDocument_ToBeDeprecated( + databaseId: string, + collection: Collection, + documentId: DocumentId, +): Promise { const { databaseAccount } = userContext; const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint; const idComponents = documentId.self.split("/"); @@ -277,7 +538,7 @@ export function deleteDocument(databaseId: string, collection: Collection, docum headers: { ...defaultHeaders, ...authHeaders(), - [HttpHeaders.contentType]: "application/json", + [HttpHeaders.contentType]: ContentType.applicationJson, [CosmosSDKConstants.HttpHeaders.PartitionKey]: JSON.stringify(documentId.partitionKeyHeader()), }, }) @@ -291,6 +552,52 @@ export function deleteDocument(databaseId: string, collection: Collection, docum export function createMongoCollectionWithProxy( params: DataModels.CreateCollectionParams, +): Promise { + if (!useMongoProxyEndpoint("createCollectionWithProxy")) { + return createMongoCollectionWithProxy_ToBeDeprecated(params); + } + const { databaseAccount } = userContext; + const shardKey: string = params.partitionKey?.paths[0]; + + const createCollectionParams = { + databaseID: params.databaseId, + collectionID: params.collectionId, + resourceUrl: databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint, + resourceID: "", + resourceType: "colls", + subscriptionID: userContext.subscriptionId, + resourceGroup: userContext.resourceGroup, + databaseAccountName: databaseAccount.name, + partitionKey: shardKey, + isAutoscale: !!params.autoPilotMaxThroughput, + hasSharedThroughput: params.databaseLevelThroughput, + offerThroughput: params.autoPilotMaxThroughput || params.offerThroughput, + createDatabase: params.createNewDatabase, + isSharded: !!shardKey, + }; + + const endpoint = getFeatureEndpointOrDefault("createCollectionWithProxy"); + + return window + .fetch(`${endpoint}/createCollection`, { + method: "POST", + body: JSON.stringify(createCollectionParams), + headers: { + ...defaultHeaders, + ...authHeaders(), + [HttpHeaders.contentType]: ContentType.applicationJson, + }, + }) + .then(async (response) => { + if (response.ok) { + return response.json(); + } + return await errorHandling(response, "creating collection", createCollectionParams); + }); +} + +export function createMongoCollectionWithProxy_ToBeDeprecated( + params: DataModels.CreateCollectionParams, ): Promise { const { databaseAccount } = userContext; const shardKey: string = params.partitionKey?.paths[0]; @@ -334,13 +641,20 @@ export function createMongoCollectionWithProxy( return await errorHandling(response, "creating collection", mongoParams); }); } - export function getFeatureEndpointOrDefault(feature: string): string { - const endpoint = - hasFlag(userContext.features.mongoProxyAPIs, feature) && - validateEndpoint(userContext.features.mongoProxyEndpoint, allowedMongoProxyEndpoints) - ? userContext.features.mongoProxyEndpoint - : configContext.MONGO_BACKEND_ENDPOINT || configContext.BACKEND_ENDPOINT; + let endpoint; + if (useMongoProxyEndpoint(feature)) { + endpoint = configContext.MONGO_PROXY_ENDPOINT; + } else { + endpoint = + hasFlag(userContext.features.mongoProxyAPIs, feature) && + validateEndpoint(userContext.features.mongoProxyEndpoint, [ + ...allowedMongoProxyEndpoints, + ...allowedMongoProxyEndpoints_ToBeDeprecated, + ]) + ? userContext.features.mongoProxyEndpoint + : configContext.MONGO_BACKEND_ENDPOINT || configContext.BACKEND_ENDPOINT; + } return getEndpoint(endpoint); } @@ -349,11 +663,37 @@ export function getEndpoint(endpoint: string): string { let url = endpoint + "/api/mongo/explorer"; if (userContext.authType === AuthType.EncryptedToken) { - url = url.replace("api/mongo", "api/guest/mongo"); + if (endpoint === configContext.MONGO_PROXY_ENDPOINT) { + url = url.replace("api/mongo", "api/connectionstring/mongo"); + } else { + url = url.replace("api/mongo", "api/guest/mongo"); + } } return url; } +export function useMongoProxyEndpoint(api: string): boolean { + const activeMongoProxyEndpoints: string[] = [ + MongoProxyEndpoints.Local, + MongoProxyEndpoints.Mpac, + MongoProxyEndpoints.Prod, + MongoProxyEndpoints.Fairfax, + ]; + let canAccessMongoProxy: boolean = userContext.databaseAccount.properties.publicNetworkAccess === "Enabled"; + if ( + configContext.MONGO_PROXY_ENDPOINT !== MongoProxyEndpoints.Local && + userContext.databaseAccount.properties.ipRules?.length > 0 + ) { + canAccessMongoProxy = canAccessMongoProxy && configContext.MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED; + } + + return ( + canAccessMongoProxy && + configContext.NEW_MONGO_APIS?.includes(api) && + activeMongoProxyEndpoints.includes(configContext.MONGO_PROXY_ENDPOINT) + ); +} + // TODO: This function throws most of the time except on Forbidden which is a bit strange // It causes problems for TypeScript understanding the types async function errorHandling(response: Response, action: string, params: unknown): Promise { diff --git a/src/Common/QueriesClient.ts b/src/Common/QueriesClient.ts index a4834b128..b56b78bb8 100644 --- a/src/Common/QueriesClient.ts +++ b/src/Common/QueriesClient.ts @@ -3,8 +3,7 @@ import * as _ from "underscore"; import * as DataModels from "../Contracts/DataModels"; import * as ViewModels from "../Contracts/ViewModels"; import Explorer from "../Explorer/Explorer"; -import DocumentsTab from "../Explorer/Tabs/DocumentsTab"; -import DocumentId from "../Explorer/Tree/DocumentId"; +import DocumentId, { IDocumentIdContainer } from "../Explorer/Tree/DocumentId"; import { useDatabases } from "../Explorer/useDatabases"; import { userContext } from "../UserContext"; import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils"; @@ -162,10 +161,10 @@ export class QueriesClient { { partitionKey: QueriesClient.PartitionKey, partitionKeyProperties: ["id"], - } as DocumentsTab, + } as IDocumentIdContainer, query, [query.queryName], - ); // TODO: Remove DocumentId's dependency on DocumentsTab + ); const options: any = { partitionKey: query.resourceId }; return deleteDocument(queriesCollection, documentId) .then( diff --git a/src/Common/ResourceTreeContainer.tsx b/src/Common/ResourceTreeContainer.tsx index e3968c112..0259c42e2 100644 --- a/src/Common/ResourceTreeContainer.tsx +++ b/src/Common/ResourceTreeContainer.tsx @@ -1,14 +1,10 @@ -import { ResourceTree } from "Explorer/Tree/ResourceTree"; import React, { FunctionComponent, MutableRefObject, useEffect, useRef } from "react"; import arrowLeftImg from "../../images/imgarrowlefticon.svg"; import refreshImg from "../../images/refresh-cosmos.svg"; -import { AuthType } from "../AuthType"; import Explorer from "../Explorer/Explorer"; -import { ResourceTokenTree } from "../Explorer/Tree/ResourceTokenTree"; -import { ResourceTree2 } from "../Explorer/Tree2/ResourceTree"; +import { ResourceTree } from "../Explorer/Tree/ResourceTree"; import { userContext } from "../UserContext"; import { getApiShortDisplayName } from "../Utils/APITypeUtils"; -import { Platform, configContext } from "./../ConfigContext"; import { NormalizedEventKey } from "./Constants"; export interface ResourceTreeContainerProps { @@ -74,12 +70,8 @@ export const ResourceTreeContainer: FunctionComponent - {userContext.authType === AuthType.ResourceToken ? ( - - ) : userContext.features.enableKoResourceTree ? ( + {userContext.features.enableKoResourceTree ? (
- ) : configContext.platform === Platform.Fabric ? ( - ) : ( )} diff --git a/src/Common/TableEntity.tsx b/src/Common/TableEntity.tsx index f3d0b5244..eece32ecb 100644 --- a/src/Common/TableEntity.tsx +++ b/src/Common/TableEntity.tsx @@ -142,7 +142,7 @@ export const TableEntity: FunctionComponent = ({ editEntity = ({ delete entity => { + let dataTransferJobs: DataTransferJobGetResults[] = []; + let dataTransferFeeds: DataTransferJobFeedResults = await listByDatabaseAccount( + subscriptionId, + resourceGroup, + accountName, + ); + dataTransferJobs = [...dataTransferJobs, ...(dataTransferFeeds?.value || [])]; + while (dataTransferFeeds?.nextLink) { + const nextResponse = await window.fetch(dataTransferFeeds.nextLink, { + headers: { + Authorization: userContext.authorizationToken, + }, + }); + if (nextResponse.ok) { + dataTransferFeeds = await nextResponse.json(); + dataTransferJobs = [...dataTransferJobs, ...(dataTransferFeeds?.value || [])]; + } else { + break; + } + } + return dataTransferJobs; +}; + +export const initiateDataTransfer = async (params: DataTransferParams): Promise => { + const { + jobName, + apiType, + subscriptionId, + resourceGroupName, + accountName, + sourceDatabaseName, + sourceCollectionName, + targetDatabaseName, + targetCollectionName, + } = params; + const sourcePayload = createPayload(apiType, sourceDatabaseName, sourceCollectionName); + const targetPayload = createPayload(apiType, targetDatabaseName, targetCollectionName); + const body: CreateJobRequest = { + properties: { + source: sourcePayload, + destination: targetPayload, + }, + }; + return create(subscriptionId, resourceGroupName, accountName, jobName, body); +}; + +export const pollDataTransferJob = async ( + jobName: string, + subscriptionId: string, + resourceGroupName: string, + accountName: string, +): Promise => { + const currentPollingJobs = useDataTransferJobs.getState().pollingDataTransferJobs; + if (currentPollingJobs.has(jobName)) { + return; + } + let clearMessage = NotificationConsoleUtils.logConsoleProgress(`Data transfer job ${jobName} in progress`); + return await promiseRetry( + () => pollDataTransferJobOperation(jobName, subscriptionId, resourceGroupName, accountName, clearMessage), + { + retries: 500, + maxTimeout: 5000, + onFailedAttempt: (error: FailedAttemptError) => { + clearMessage(); + clearMessage = NotificationConsoleUtils.logConsoleProgress(error.message); + }, + }, + ); +}; + +const pollDataTransferJobOperation = async ( + jobName: string, + subscriptionId: string, + resourceGroupName: string, + accountName: string, + clearMessage?: () => void, +): Promise => { + if (!userContext.authorizationToken) { + throw new Error("No authority token provided"); + } + + addToPolling(jobName); + + const body: DataTransferJobGetResults = await get(subscriptionId, resourceGroupName, accountName, jobName); + const status = body?.properties?.status; + + updateDataTransferJob(body); + + if (status === "Cancelled") { + removeFromPolling(jobName); + clearMessage && clearMessage(); + const cancelMessage = `Data transfer job ${jobName} cancelled`; + NotificationConsoleUtils.logConsoleError(cancelMessage); + throw new AbortError(cancelMessage); + } + if (status === "Failed" || status === "Faulted") { + removeFromPolling(jobName); + const errorMessage = body?.properties?.error + ? JSON.stringify(body?.properties?.error) + : "Operation could not be completed"; + const error = new Error(errorMessage); + clearMessage && clearMessage(); + NotificationConsoleUtils.logConsoleError(`Data transfer job ${jobName} failed: ${errorMessage}`); + throw new AbortError(error); + } + if (status === "Completed") { + removeFromPolling(jobName); + clearMessage && clearMessage(); + NotificationConsoleUtils.logConsoleInfo(`Data transfer job ${jobName} completed`); + return body; + } + const processedCount = body.properties.processedCount; + const totalCount = body.properties.totalCount; + const retryMessage = `Data transfer job ${jobName} in progress, total count: ${totalCount}, processed count: ${processedCount}`; + throw new Error(retryMessage); +}; + +export const cancelDataTransferJob = async ( + subscriptionId: string, + resourceGroupName: string, + accountName: string, + jobName: string, +): Promise => { + const cancelResult: DataTransferJobGetResults = await cancel(subscriptionId, resourceGroupName, accountName, jobName); + updateDataTransferJob(cancelResult); + removeFromPolling(cancelResult?.properties?.jobName); +}; + +const createPayload = ( + apiType: ApiType, + databaseName: string, + containerName: string, +): + | CosmosSqlDataTransferDataSourceSink + | CosmosMongoDataTransferDataSourceSink + | CosmosCassandraDataTransferDataSourceSink => { + switch (apiType) { + case "SQL": + return { + component: "CosmosDBSql", + databaseName: databaseName, + containerName: containerName, + } as CosmosSqlDataTransferDataSourceSink; + case "Mongo": + return { + component: "CosmosDBMongo", + databaseName: databaseName, + collectionName: containerName, + } as CosmosMongoDataTransferDataSourceSink; + case "Cassandra": + return { + component: "CosmosDBCassandra", + keyspaceName: databaseName, + tableName: containerName, + }; + default: + throw new Error(`Unsupported API type for data transfer: ${apiType}`); + } +}; diff --git a/src/Common/dataAccess/deleteDocument.ts b/src/Common/dataAccess/deleteDocument.ts index 5caef9e0e..f20dc9cc8 100644 --- a/src/Common/dataAccess/deleteDocument.ts +++ b/src/Common/dataAccess/deleteDocument.ts @@ -1,3 +1,4 @@ +import { BulkOperationType, OperationInput } from "@azure/cosmos"; import { CollectionBase } from "../../Contracts/ViewModels"; import DocumentId from "../../Explorer/Tree/DocumentId"; import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; @@ -24,3 +25,58 @@ export const deleteDocument = async (collection: CollectionBase, documentId: Doc clearMessage(); } }; + +/** + * Bulk delete documents + * @param collection + * @param documentId + * @returns array of ids that were successfully deleted + */ +export const deleteDocuments = async (collection: CollectionBase, documentIds: DocumentId[]): Promise => { + const nbDocuments = documentIds.length; + const clearMessage = logConsoleProgress(`Deleting ${documentIds.length} ${getEntityName(true)}`); + try { + const v2Container = await client().database(collection.databaseId).container(collection.id()); + + // Bulk can only delete 100 documents at a time + const BULK_DELETE_LIMIT = 100; + const promiseArray = []; + + while (documentIds.length > 0) { + const documentIdsChunk = documentIds.splice(0, BULK_DELETE_LIMIT); + const operations: OperationInput[] = documentIdsChunk.map((documentId) => ({ + id: documentId.id(), + // bulk delete: if not partition key is specified, do not pass empty array, but undefined + partitionKey: + documentId.partitionKeyValue && + Array.isArray(documentId.partitionKeyValue) && + documentId.partitionKeyValue.length === 0 + ? undefined + : documentId.partitionKeyValue, + operationType: BulkOperationType.Delete, + })); + + const promise = v2Container.items.bulk(operations).then((bulkResult) => { + return documentIdsChunk.filter((_, index) => bulkResult[index].statusCode === 204); + }); + promiseArray.push(promise); + } + + const allResult = await Promise.all(promiseArray); + const flatAllResult = Array.prototype.concat.apply([], allResult); + logConsoleInfo( + `Successfully deleted ${getEntityName(flatAllResult.length > 1)}: ${flatAllResult.length} out of ${nbDocuments}`, + ); + // TODO: handle case result.length != nbDocuments + return flatAllResult; + } catch (error) { + handleError( + error, + "DeleteDocuments", + `Error while deleting ${documentIds.length} ${getEntityName(documentIds.length > 1)}`, + ); + throw error; + } finally { + clearMessage(); + } +}; diff --git a/src/Common/dataAccess/queryDocuments.ts b/src/Common/dataAccess/queryDocuments.ts index 0b8ebd29d..223fe987d 100644 --- a/src/Common/dataAccess/queryDocuments.ts +++ b/src/Common/dataAccess/queryDocuments.ts @@ -1,4 +1,5 @@ import { FeedOptions, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos"; +import { isVectorSearchEnabled } from "Utils/CapabilityUtils"; import { LocalStorageUtility, StorageKey } from "../../Shared/StorageUtility"; import { Queries } from "../Constants"; import { client } from "../CosmosClient"; @@ -26,5 +27,6 @@ export const getCommonQueryOptions = (options: FeedOptions): FeedOptions => { (storedItemPerPageSetting !== undefined && storedItemPerPageSetting) || Queries.itemsPerPage; options.maxDegreeOfParallelism = LocalStorageUtility.getEntryNumber(StorageKey.MaxDegreeOfParellism); + options.disableNonStreamingOrderByQuery = !isVectorSearchEnabled(); return options; }; diff --git a/src/Common/dataAccess/queryDocumentsPage.ts b/src/Common/dataAccess/queryDocumentsPage.ts index 556ed290c..17e84ba28 100644 --- a/src/Common/dataAccess/queryDocumentsPage.ts +++ b/src/Common/dataAccess/queryDocumentsPage.ts @@ -1,3 +1,4 @@ +import { QueryOperationOptions } from "@azure/cosmos"; import { QueryResults } from "../../Contracts/ViewModels"; import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { getEntityName } from "../DocumentUtility"; @@ -8,12 +9,13 @@ export const queryDocumentsPage = async ( resourceName: string, documentsIterator: MinimalQueryIterator, firstItemIndex: number, + queryOperationOptions?: QueryOperationOptions, ): Promise => { const entityName = getEntityName(); const clearMessage = logConsoleProgress(`Querying ${entityName} for container ${resourceName}`); try { - const result: QueryResults = await nextPage(documentsIterator, firstItemIndex); + const result: QueryResults = await nextPage(documentsIterator, firstItemIndex, queryOperationOptions); const itemCount = (result.documents && result.documents.length) || 0; logConsoleInfo(`Successfully fetched ${itemCount} ${entityName} for container ${resourceName}`); return result; diff --git a/src/Common/dataAccess/readCollection.ts b/src/Common/dataAccess/readCollection.ts index 3943b762d..a873aab37 100644 --- a/src/Common/dataAccess/readCollection.ts +++ b/src/Common/dataAccess/readCollection.ts @@ -2,7 +2,6 @@ import { CosmosClient } from "@azure/cosmos"; import { sampleDataClient } from "Common/SampleDataClient"; import { userContext } from "UserContext"; import * as DataModels from "../../Contracts/DataModels"; -import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { client } from "../CosmosClient"; import { handleError } from "../ErrorHandlingUtils"; @@ -31,7 +30,6 @@ export async function readCollectionInternal( collectionId: string, ): Promise { let collection: DataModels.Collection; - const clearMessage = logConsoleProgress(`Querying container ${collectionId}`); try { const response = await cosmosClient.database(databaseId).container(collectionId).read(); collection = response.resource as DataModels.Collection; @@ -39,6 +37,5 @@ export async function readCollectionInternal( handleError(error, "ReadCollection", `Error while querying container ${collectionId}`); throw error; } - clearMessage(); return collection; } diff --git a/src/Common/dataAccess/readCollections.ts b/src/Common/dataAccess/readCollections.ts index ec875b570..4098b3fe8 100644 --- a/src/Common/dataAccess/readCollections.ts +++ b/src/Common/dataAccess/readCollections.ts @@ -18,13 +18,13 @@ export async function readCollections(databaseId: string): Promise[] = []; - for (const collectionResourceId in userContext.fabricDatabaseConnectionInfo.resourceTokens) { + for (const collectionResourceId in userContext.fabricContext.databaseConnectionInfo.resourceTokens) { // Dictionary key looks like this: dbs/SampleDB/colls/Container const resourceIdObj = collectionResourceId.split("/"); const tokenDatabaseId = resourceIdObj[1]; diff --git a/src/Common/dataAccess/readDatabases.ts b/src/Common/dataAccess/readDatabases.ts index ad4102d9b..9cc0b0641 100644 --- a/src/Common/dataAccess/readDatabases.ts +++ b/src/Common/dataAccess/readDatabases.ts @@ -14,8 +14,8 @@ export async function readDatabases(): Promise { let databases: DataModels.Database[]; const clearMessage = logConsoleProgress(`Querying databases`); - if (configContext.platform === Platform.Fabric && userContext.fabricDatabaseConnectionInfo?.resourceTokens) { - const tokensData = userContext.fabricDatabaseConnectionInfo; + if (configContext.platform === Platform.Fabric && userContext.fabricContext?.databaseConnectionInfo.resourceTokens) { + const tokensData = userContext.fabricContext.databaseConnectionInfo; const databaseIdsSet = new Set(); // databaseId diff --git a/src/ConfigContext.ts b/src/ConfigContext.ts index 1679a3657..8db033788 100644 --- a/src/ConfigContext.ts +++ b/src/ConfigContext.ts @@ -1,17 +1,25 @@ -import { JunoEndpoints } from "Common/Constants"; +import { + BackendApi, + CassandraProxyEndpoints, + JunoEndpoints, + MongoProxyEndpoints, + PortalBackendEndpoints, +} from "Common/Constants"; import { allowedAadEndpoints, allowedArcadiaEndpoints, + allowedCassandraProxyEndpoints, allowedEmulatorEndpoints, allowedGraphEndpoints, allowedHostedExplorerEndpoints, allowedJunoOrigins, allowedMongoBackendEndpoints, + allowedMongoProxyEndpoints, allowedMsalRedirectEndpoints, defaultAllowedArmEndpoints, defaultAllowedBackendEndpoints, validateEndpoint, -} from "Utils/EndpointValidation"; +} from "Utils/EndpointUtils"; export enum Platform { Portal = "Portal", @@ -34,10 +42,22 @@ export interface ConfigContext { ARM_API_VERSION: string; GRAPH_ENDPOINT: string; GRAPH_API_VERSION: string; + // This is the endpoint to get offering Ids to be used to fetch prices. Refer to this doc: https://learn.microsoft.com/en-us/rest/api/marketplacecatalog/dataplane/skus/list?view=rest-marketplacecatalog-dataplane-2023-05-01-preview&tabs=HTTP + CATALOG_ENDPOINT: string; + CATALOG_API_VERSION: string; + CATALOG_API_KEY: string; ARCADIA_ENDPOINT: string; ARCADIA_LIVY_ENDPOINT_DNS_ZONE: string; BACKEND_ENDPOINT?: string; + PORTAL_BACKEND_ENDPOINT?: string; + NEW_BACKEND_APIS?: BackendApi[]; MONGO_BACKEND_ENDPOINT?: string; + MONGO_PROXY_ENDPOINT?: string; + MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED?: boolean; + NEW_MONGO_APIS?: string[]; + CASSANDRA_PROXY_ENDPOINT?: string; + CASSANDRA_PROXY_OUTBOUND_IPS_ALLOWLISTED: boolean; + NEW_CASSANDRA_APIS?: string[]; PROXY_PATH?: string; JUNO_ENDPOINT: string; GITHUB_CLIENT_ID: string; @@ -67,6 +87,7 @@ let configContext: Readonly = { `^https:\\/\\/.*\\.analysis-df\\.net$`, `^https:\\/\\/.*\\.analysis-df\\.windows\\.net$`, `^https:\\/\\/.*\\.azure-test\\.net$`, + `^https:\\/\\/cosmos-explorer-preview\\.azurewebsites\\.net`, ], // Webpack injects this at build time gitSha: process.env.GIT_SHA, hostedExplorerURL: "https://cosmos.azure.com/", @@ -76,12 +97,31 @@ let configContext: Readonly = { ARM_API_VERSION: "2016-06-01", GRAPH_ENDPOINT: "https://graph.microsoft.com", GRAPH_API_VERSION: "1.6", + CATALOG_ENDPOINT: "https://catalogapi.azure.com/", + CATALOG_API_VERSION: "2023-05-01-preview", + 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, BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com", + PORTAL_BACKEND_ENDPOINT: PortalBackendEndpoints.Prod, + MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod, + NEW_MONGO_APIS: [ + // "resourcelist", + // "queryDocuments", + // "createDocument", + // "readDocument", + // "updateDocument", + // "deleteDocument", + // "createCollectionWithProxy", + // "legacyMongoShell", + ], + MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED: false, + CASSANDRA_PROXY_ENDPOINT: CassandraProxyEndpoints.Prod, + NEW_CASSANDRA_APIS: ["postQuery", "createOrDelete", "getKeys", "getSchema"], + CASSANDRA_PROXY_OUTBOUND_IPS_ALLOWLISTED: false, isTerminalEnabled: false, isPhoenixEnabled: false, }; @@ -127,10 +167,18 @@ export function updateConfigContext(newContext: Partial): void { delete newContext.BACKEND_ENDPOINT; } + if (!validateEndpoint(newContext.MONGO_PROXY_ENDPOINT, allowedMongoProxyEndpoints)) { + delete newContext.MONGO_PROXY_ENDPOINT; + } + if (!validateEndpoint(newContext.MONGO_BACKEND_ENDPOINT, allowedMongoBackendEndpoints)) { delete newContext.MONGO_BACKEND_ENDPOINT; } + if (!validateEndpoint(newContext.CASSANDRA_PROXY_ENDPOINT, allowedCassandraProxyEndpoints)) { + delete newContext.CASSANDRA_PROXY_ENDPOINT; + } + if (!validateEndpoint(newContext.JUNO_ENDPOINT, allowedJunoOrigins)) { delete newContext.JUNO_ENDPOINT; } @@ -148,12 +196,12 @@ export function updateConfigContext(newContext: Partial): void { // Injected for local development. These will be removed in the production bundle by webpack if (process.env.NODE_ENV === "development") { - const port: string = process.env.PORT || "1234"; updateConfigContext({ - BACKEND_ENDPOINT: "https://localhost:" + port, - MONGO_BACKEND_ENDPOINT: "https://localhost:" + port, PROXY_PATH: "/proxy", EMULATOR_ENDPOINT: "https://localhost:8081", + PORTAL_BACKEND_ENDPOINT: PortalBackendEndpoints.Mpac, + MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Mpac, + CASSANDRA_PROXY_ENDPOINT: CassandraProxyEndpoints.Mpac, }); } diff --git a/src/Contracts/DataExplorerMessagesContract.ts b/src/Contracts/DataExplorerMessagesContract.ts new file mode 100644 index 000000000..a38940120 --- /dev/null +++ b/src/Contracts/DataExplorerMessagesContract.ts @@ -0,0 +1,27 @@ +import { FabricMessageTypes } from "./FabricMessageTypes"; + +// This is the current version of these messages +export const DATA_EXPLORER_RPC_VERSION = "3"; + +// Data Explorer to Fabric +export type DataExploreMessageV3 = + | { + type: FabricMessageTypes.Ready; + id: string; + params: [string]; // version + } + | { + type: FabricMessageTypes.GetAuthorizationToken; + id: string; + params: GetCosmosTokenMessageOptions[]; + } + | { + type: FabricMessageTypes.GetAllResourceTokens; + id: string; + }; + +export type GetCosmosTokenMessageOptions = { + verb: "connect" | "delete" | "get" | "head" | "options" | "patch" | "post" | "put" | "trace"; + resourceType: "" | "dbs" | "colls" | "docs" | "sprocs" | "pkranges"; + resourceId: string; +}; diff --git a/src/Contracts/DataModels.ts b/src/Contracts/DataModels.ts index ac7b9499e..3f7e3a7db 100644 --- a/src/Contracts/DataModels.ts +++ b/src/Contracts/DataModels.ts @@ -1,4 +1,4 @@ -import { ConnectionStatusType, ContainerStatusType } from "../Common/Constants"; +import { CapacityMode, ConnectionStatusType, ContainerStatusType } from "../Common/Constants"; export interface ArmEntity { id: string; @@ -35,6 +35,7 @@ export interface DatabaseAccountExtendedProperties { ipRules?: IpRule[]; privateEndpointConnections?: unknown[]; capacity?: { totalThroughputLimit: number }; + capacityMode?: CapacityMode; locations?: DatabaseAccountResponseLocation[]; postgresqlEndpoint?: string; publicNetworkAccess?: string; @@ -157,8 +158,10 @@ export interface Collection extends Resource { changeFeedPolicy?: ChangeFeedPolicy; analyticalStorageTtl?: number; geospatialConfig?: GeospatialConfig; + vectorEmbeddingPolicy?: VectorEmbeddingPolicy; schema?: ISchema; requestSchema?: () => void; + computedProperties?: ComputedProperties; } export interface CollectionsWithPagination { @@ -193,10 +196,23 @@ export interface IndexingPolicy { indexingMode: "consistent" | "lazy" | "none"; includedPaths: any; excludedPaths: any; - compositeIndexes?: any; - spatialIndexes?: any; + compositeIndexes?: any[]; + spatialIndexes?: any[]; + vectorIndexes?: VectorIndex[]; } +export interface VectorIndex { + path: string; + type: "flat" | "diskANN" | "quantizedFlat"; +} + +export interface ComputedProperty { + name: string; + query: string; +} + +export type ComputedProperties = ComputedProperty[]; + export interface PartitionKey { paths: string[]; kind: "Hash" | "Range" | "MultiHash"; @@ -325,6 +341,18 @@ export interface CreateCollectionParams { partitionKey?: PartitionKey; uniqueKeyPolicy?: UniqueKeyPolicy; createMongoWildcardIndex?: boolean; + vectorEmbeddingPolicy?: VectorEmbeddingPolicy; +} + +export interface VectorEmbeddingPolicy { + vectorEmbeddings: VectorEmbedding[]; +} + +export interface VectorEmbedding { + dataType: "float16" | "float32" | "uint8" | "int8"; + dimensions: number; + distanceFunction: "euclidean" | "cosine" | "dotproduct"; + path: string; } export interface ReadDatabaseOfferParams { diff --git a/src/Contracts/ExplorerContracts.ts b/src/Contracts/ExplorerContracts.ts index 42e5d3dc0..6e49b0555 100644 --- a/src/Contracts/ExplorerContracts.ts +++ b/src/Contracts/ExplorerContracts.ts @@ -1,6 +1,6 @@ -import { MessageTypes } from "Contracts/MessageTypes"; import * as ActionContracts from "./ActionContracts"; import * as Diagnostics from "./Diagnostics"; +import { MessageTypes } from "./MessageTypes"; import * as Versions from "./Versions"; export { ActionContracts, Diagnostics, MessageTypes, Versions }; diff --git a/src/Contracts/FabricMessageTypes.ts b/src/Contracts/FabricMessageTypes.ts new file mode 100644 index 000000000..aa374472d --- /dev/null +++ b/src/Contracts/FabricMessageTypes.ts @@ -0,0 +1,13 @@ +/** + * Data Explorer -> Fabric communication. + */ +export enum FabricMessageTypes { + GetAuthorizationToken = "GetAuthorizationToken", + GetAllResourceTokens = "GetAllResourceTokens", + Ready = "Ready", +} + +export interface AuthorizationToken { + XDate: string; + PrimaryReadWriteToken: string; +} diff --git a/src/Contracts/FabricContract.ts b/src/Contracts/FabricMessagesContract.ts similarity index 50% rename from src/Contracts/FabricContract.ts rename to src/Contracts/FabricMessagesContract.ts index f591be5df..dcf5a5a50 100644 --- a/src/Contracts/FabricContract.ts +++ b/src/Contracts/FabricMessagesContract.ts @@ -1,6 +1,12 @@ -import { AuthorizationToken, MessageTypes } from "./MessageTypes"; +import { AuthorizationToken } from "Contracts/FabricMessageTypes"; -export type FabricMessage = +// This is the version of these messages +export const FABRIC_RPC_VERSION = "2"; + +// Fabric to Data Explorer + +// TODO Deprecated. Remove this section once DE is updated +export type FabricMessageV1 = | { type: "newContainer"; databaseName: string; @@ -26,38 +32,53 @@ export type FabricMessage = | { type: "allResourceTokens"; message: { + id: string; + error: string | undefined; endpoint: string | undefined; databaseId: string | undefined; resourceTokens: unknown | undefined; resourceTokensTimestamp: number | undefined; }; }; +// ----------------------------- -export type DataExploreMessage = - | "ready" +export type FabricMessageV2 = | { - type: MessageTypes.TelemetryInfo; - data: { - action: "LoadDatabases"; - actionModifier: "success" | "start"; - defaultExperience: "SQL"; + type: "newContainer"; + databaseName: string; + } + | { + type: "initialize"; + version: string; + id: string; + message: { + connectionId: string; + isVisible: boolean; }; } | { - type: MessageTypes.GetAuthorizationToken; - id: string; - params: GetCosmosTokenMessageOptions[]; + type: "authorizationToken"; + message: { + id: string; + error: string | undefined; + data: AuthorizationToken | undefined; + }; } | { - type: MessageTypes.GetAllResourceTokens; + type: "allResourceTokens_v2"; + message: { + id: string; + error: string | undefined; + data: FabricDatabaseConnectionInfo | undefined; + }; + } + | { + type: "explorerVisible"; + message: { + visible: boolean; + }; }; -export type GetCosmosTokenMessageOptions = { - verb: "connect" | "delete" | "get" | "head" | "options" | "patch" | "post" | "put" | "trace"; - resourceType: "" | "dbs" | "colls" | "docs" | "sprocs" | "pkranges"; - resourceId: string; -}; - export type CosmosDBTokenResponse = { token: string; date: string; @@ -66,12 +87,9 @@ export type CosmosDBTokenResponse = { export type CosmosDBConnectionInfoResponse = { endpoint: string; databaseId: string; - resourceTokens: unknown; + resourceTokens: { [resourceId: string]: string }; }; -export interface FabricDatabaseConnectionInfo { - endpoint: string; - databaseId: string; - resourceTokens: { [resourceId: string]: string }; +export interface FabricDatabaseConnectionInfo extends CosmosDBConnectionInfoResponse { resourceTokensTimestamp: number; } diff --git a/src/Contracts/MessageTypes.ts b/src/Contracts/MessageTypes.ts index c1feec4d9..4550d2a53 100644 --- a/src/Contracts/MessageTypes.ts +++ b/src/Contracts/MessageTypes.ts @@ -1,6 +1,13 @@ /** * Messaging types used with Data Explorer <-> Portal communication, - * Hosted <-> Explorer communication and Data Explorer -> Fabric communication. + * Hosted <-> Explorer communication + * + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * WARNING: !!!!!!! YOU CAN ONLY ADD NEW TYPES TO THE END OF THIS ENUM !!!!!!! + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * + * Enum are integers, so inserting or deleting a type will break the communication. + * */ export enum MessageTypes { TelemetryInfo, @@ -37,13 +44,9 @@ export enum MessageTypes { DisplayNPSSurvey, OpenVCoreMongoNetworkingBlade, OpenVCoreMongoConnectionStringsBlade, - - // Data Explorer -> Fabric communication - GetAuthorizationToken, - GetAllResourceTokens, -} - -export interface AuthorizationToken { - XDate: string; - PrimaryReadWriteToken: string; + GetAuthorizationToken, // unused. Can be removed if the portal uses the same list of enums. + GetAllResourceTokens, // unused. Can be removed if the portal uses the same list of enums. + Ready, // unused. Can be removed if the portal uses the same list of enums. + OpenCESCVAFeedbackBlade, + ActivateTab, } diff --git a/src/Contracts/ViewModels.ts b/src/Contracts/ViewModels.ts index 7521647df..04afc10bb 100644 --- a/src/Contracts/ViewModels.ts +++ b/src/Contracts/ViewModels.ts @@ -135,6 +135,7 @@ export interface Collection extends CollectionBase { changeFeedPolicy: ko.Observable; geospatialConfig: ko.Observable; documentIds: ko.ObservableArray; + computedProperties: ko.Observable; cassandraKeys: CassandraTableKeys; cassandraSchema: CassandraTableKey[]; @@ -175,6 +176,11 @@ export interface Collection extends CollectionBase { loadTriggers(): Promise; loadOffer(): Promise; + showStoredProcedures: ko.Observable; + showTriggers: ko.Observable; + showUserDefinedFunctions: ko.Observable; + showConflicts: ko.Observable; + createStoredProcedureNode(data: StoredProcedureDefinition & Resource): StoredProcedure; createUserDefinedFunctionNode(data: UserDefinedFunctionDefinition & Resource): UserDefinedFunction; createTriggerNode(data: TriggerDefinition | SqlTriggerResource): Trigger; @@ -323,9 +329,9 @@ export enum DocumentExplorerState { noDocumentSelected, newDocumentValid, newDocumentInvalid, - exisitingDocumentNoEdits, - exisitingDocumentDirtyValid, - exisitingDocumentDirtyInvalid, + existingDocumentNoEdits, + existingDocumentDirtyValid, + existingDocumentDirtyInvalid, } export enum IndexingPolicyEditorState { @@ -338,9 +344,9 @@ export enum IndexingPolicyEditorState { export enum ScriptEditorState { newInvalid, newValid, - exisitingNoEdits, - exisitingDirtyValid, - exisitingDirtyInvalid, + existingNoEdits, + existingDirtyValid, + existingDirtyInvalid, } export enum CollectionTabKind { @@ -386,9 +392,11 @@ export interface DataExplorerInputsFrame { dnsSuffix?: string; serverId?: string; extensionEndpoint?: string; + portalBackendEndpoint?: string; + mongoProxyEndpoint?: string; + cassandraProxyEndpoint?: string; subscriptionType?: SubscriptionType; quotaId?: string; - addCollectionDefaultFlight?: string; isTryCosmosDBSubscription?: boolean; loadDatabaseAccountTimestamp?: number; sharedThroughputMinimum?: number; @@ -406,6 +414,7 @@ export interface DataExplorerInputsFrame { features?: { [key: string]: string; }; + feedbackPolicies?: any; } export interface SelfServeFrameInputs { @@ -416,6 +425,7 @@ export interface SelfServeFrameInputs { authorizationToken: string; csmEndpoint: string; flights?: readonly string[]; + catalogAPIKey: string; } export class MonacoEditorSettings { diff --git a/src/Explorer/ContextMenuButtonFactory.tsx b/src/Explorer/ContextMenuButtonFactory.tsx index d9f9f3439..90c78e365 100644 --- a/src/Explorer/ContextMenuButtonFactory.tsx +++ b/src/Explorer/ContextMenuButtonFactory.tsx @@ -1,3 +1,4 @@ +import { TreeNodeMenuItem } from "Explorer/Controls/TreeComponent/TreeNodeComponent"; import { useDatabases } from "Explorer/useDatabases"; import { Action } from "Shared/Telemetry/TelemetryConstants"; import { traceOpen } from "Shared/Telemetry/TelemetryProcessor"; @@ -19,7 +20,6 @@ import { userContext } from "../UserContext"; import { getCollectionName, getDatabaseName } from "../Utils/APITypeUtils"; import { useSidePanel } from "../hooks/useSidePanel"; import { Platform, configContext } from "./../ConfigContext"; -import { TreeNodeMenuItem } from "./Controls/TreeComponent/TreeComponent"; import Explorer from "./Explorer"; import { useNotebook } from "./Notebook/useNotebook"; import { DeleteCollectionConfirmationPane } from "./Panes/DeleteCollectionConfirmationPane/DeleteCollectionConfirmationPane"; diff --git a/src/Explorer/Controls/CollapsiblePanel/CollapsibleSectionComponent.tsx b/src/Explorer/Controls/CollapsiblePanel/CollapsibleSectionComponent.tsx index d943f7cf0..80da43414 100644 --- a/src/Explorer/Controls/CollapsiblePanel/CollapsibleSectionComponent.tsx +++ b/src/Explorer/Controls/CollapsiblePanel/CollapsibleSectionComponent.tsx @@ -1,4 +1,4 @@ -import { Icon, Label, Stack } from "@fluentui/react"; +import { DirectionalHint, Icon, Label, Stack, TooltipHost } from "@fluentui/react"; import * as React from "react"; import { NormalizedEventKey } from "../../../Common/Constants"; import { accordionStackTokens } from "../Settings/SettingsRenderUtils"; @@ -8,6 +8,7 @@ export interface CollapsibleSectionProps { isExpandedByDefault: boolean; onExpand?: () => void; children: JSX.Element; + tooltipContent?: string | JSX.Element | JSX.Element[]; } export interface CollapsibleSectionState { @@ -26,8 +27,8 @@ export class CollapsibleSectionComponent extends React.Component + {this.props.tooltipContent && ( + + + + )} {this.state.isExpanded && this.props.children} diff --git a/src/Explorer/Controls/CommandButton/CommandButtonComponent.tsx b/src/Explorer/Controls/CommandButton/CommandButtonComponent.tsx index 1e5cfc171..b6e847d54 100644 --- a/src/Explorer/Controls/CommandButton/CommandButtonComponent.tsx +++ b/src/Explorer/Controls/CommandButton/CommandButtonComponent.tsx @@ -1,6 +1,7 @@ /** * React component for Command button component. */ +import { KeyboardAction } from "KeyboardShortcuts"; import * as React from "react"; import CollapseChevronDownIcon from "../../../../images/QueryBuilder/CollapseChevronDown_16x.png"; import { KeyCodes } from "../../../Common/Constants"; @@ -30,7 +31,7 @@ export interface CommandButtonComponentProps { /** * Click handler for command button click */ - onCommandClick: (e: React.SyntheticEvent) => void; + onCommandClick?: (e: React.SyntheticEvent | KeyboardEvent) => void; /** * Label for the button @@ -107,10 +108,17 @@ export interface CommandButtonComponentProps { * Vertical bar to divide buttons */ isDivider?: boolean; + /** * Aria-label for the button */ ariaLabel: string; + + /** + * If specified, a keyboard action that should trigger this button's onCommandClick handler when activated. + * If not specified, the button will not be triggerable by keyboard shortcuts. + */ + keyboardAction?: KeyboardAction; } export class CommandButtonComponent extends React.Component { diff --git a/src/Explorer/Controls/Editor/EditorReact.tsx b/src/Explorer/Controls/Editor/EditorReact.tsx index 956253a05..f2274d7dc 100644 --- a/src/Explorer/Controls/Editor/EditorReact.tsx +++ b/src/Explorer/Controls/Editor/EditorReact.tsx @@ -20,7 +20,10 @@ export interface EditorReactProps { lineDecorationsWidth?: monaco.editor.IEditorOptions["lineDecorationsWidth"]; minimap?: monaco.editor.IEditorOptions["minimap"]; scrollBeyondLastLine?: monaco.editor.IEditorOptions["scrollBeyondLastLine"]; + fontSize?: monaco.editor.IEditorOptions["fontSize"]; monacoContainerStyles?: React.CSSProperties; + className?: string; + spinnerClassName?: string; } export class EditorReact extends React.Component { @@ -46,9 +49,25 @@ export class EditorReact extends React.Component - {!this.state.showEditor && } + {!this.state.showEditor && ( + + )}
this.setRef(elt)} /> @@ -71,9 +92,14 @@ export class EditorReact extends React.Component { + // Hooking the model's onDidChangeContent event because of some event ordering issues. + // If a single user input causes BOTH the editor content to change AND the cursor selection to change (which is likely), + // then there are some inconsistencies as to which event fires first. + // But the editor.onDidChangeModelContent event seems to always fire before the cursor selection event. + // (This is NOT true for the model's onDidChangeContent event, which sometimes fires after the cursor selection event.) + // If the cursor selection event fires first, then the calling component may re-render the component with old content, so we want to ensure the model content changed event always fires first. + this.editor.onDidChangeModelContent(() => { const queryEditorModel = this.editor.getModel(); this.props.onContentChanged(queryEditorModel.getValue()); }); @@ -98,7 +124,7 @@ export class EditorReact extends React.Component this.setState({ isMongoIndexingPolicyDiscardable }); + private onComputedPropertiesContentChange = (newComputedProperties: DataModels.ComputedProperties): void => + this.setState({ computedPropertiesContent: newComputedProperties }); + + private resetShouldDiscardComputedProperties = (): void => this.setState({ shouldDiscardComputedProperties: false }); + + private logComputedPropertiesSuccessMessage = (): void => { + if (this.props.settingsTab.onLoadStartKey) { + traceSuccess( + Action.Tab, + { + databaseName: this.collection.databaseId, + collectionName: this.collection.id(), + + dataExplorerArea: Constants.Areas.Tab, + tabTitle: this.props.settingsTab.tabTitle(), + }, + this.props.settingsTab.onLoadStartKey, + ); + this.props.settingsTab.onLoadStartKey = undefined; + } + }; + + private onComputedPropertiesDirtyChange = (isComputedPropertiesDirty: boolean): void => + this.setState({ isComputedPropertiesDirty: isComputedPropertiesDirty }); + private calculateTotalThroughputUsed = (): void => { this.totalThroughputUsed = 0; (useDatabases.getState().databases || []).forEach(async (database) => { @@ -636,7 +696,6 @@ export class SettingsComponent extends React.Component => { const newCollection: DataModels.Collection = { ...this.collection.rawDataModel }; - if (this.state.isSubSettingsSaveable || this.state.isIndexingPolicyDirty || this.state.isConflictResolutionDirty) { + if ( + this.state.isSubSettingsSaveable || + this.state.isIndexingPolicyDirty || + this.state.isConflictResolutionDirty || + this.state.isComputedPropertiesDirty + ) { let defaultTtl: number; switch (this.state.timeToLive) { case TtlType.On: @@ -825,6 +897,10 @@ export class SettingsComponent extends React.Component, }); + if (this.isVectorSearchEnabled) { + tabs.push({ + tab: SettingsV2TabTypes.ContainerVectorPolicyTab, + content: , + }); + } + if (this.shouldShowIndexingPolicyEditor) { tabs.push({ tab: SettingsV2TabTypes.IndexingPolicyTab, @@ -1091,6 +1197,20 @@ export class SettingsComponent extends React.Component, + }); + } + + if (this.shouldShowComputedPropertiesEditor) { + tabs.push({ + tab: SettingsV2TabTypes.ComputedPropertiesTab, + content: , + }); + } + const pivotProps: IPivotProps = { onLinkClick: this.onPivotChange, selectedKey: SettingsV2TabTypes[this.state.selectedTab], diff --git a/src/Explorer/Controls/Settings/SettingsRenderUtils.test.tsx b/src/Explorer/Controls/Settings/SettingsRenderUtils.test.tsx index 2e66a86f9..06a9d4a0c 100644 --- a/src/Explorer/Controls/Settings/SettingsRenderUtils.test.tsx +++ b/src/Explorer/Controls/Settings/SettingsRenderUtils.test.tsx @@ -11,7 +11,6 @@ import { getThroughputApplyLongDelayMessage, getThroughputApplyShortDelayMessage, getToolTipContainer, - indexingPolicynUnsavedWarningMessage, manualToAutoscaleDisclaimerElement, mongoIndexTransformationRefreshingMessage, mongoIndexingPolicyAADError, @@ -39,7 +38,6 @@ class SettingsRenderUtilsTestComponent extends React.Component { {manualToAutoscaleDisclaimerElement} {ttlWarning} - {indexingPolicynUnsavedWarningMessage} {updateThroughputDelayedApplyWarningMessage} {getThroughputApplyDelayedMessage(false, 1000, "RU/s", "sampleDb", "sampleCollection", 2000)} diff --git a/src/Explorer/Controls/Settings/SettingsRenderUtils.tsx b/src/Explorer/Controls/Settings/SettingsRenderUtils.tsx index fefd2f6e5..1fe4536f0 100644 --- a/src/Explorer/Controls/Settings/SettingsRenderUtils.tsx +++ b/src/Explorer/Controls/Settings/SettingsRenderUtils.tsx @@ -61,6 +61,8 @@ export interface PriceBreakdown { currencySign: string; } +export type editorType = "indexPolicy" | "computedProperties"; + export const infoAndToolTipTextStyle: ITextStyles = { root: { fontSize: 14, color: "windowtext" } }; export const noLeftPaddingCheckBoxStyle: ICheckboxStyles = { @@ -254,9 +256,10 @@ export const ttlWarning: JSX.Element = ( ); -export const indexingPolicynUnsavedWarningMessage: JSX.Element = ( +export const unsavedEditorWarningMessage = (editor: editorType): JSX.Element => ( - You have not saved the latest changes made to your indexing policy. Please click save to confirm the changes. + You have not saved the latest changes made to your{" "} + {editor === "indexPolicy" ? "indexing policy" : "computed properties"}. Please click save to confirm the changes. ); diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/ComputedPropertiesComponent.test.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/ComputedPropertiesComponent.test.tsx new file mode 100644 index 000000000..811bc17ba --- /dev/null +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/ComputedPropertiesComponent.test.tsx @@ -0,0 +1,56 @@ +import * as DataModels from "Contracts/DataModels"; +import { shallow } from "enzyme"; +import React from "react"; +import { ComputedPropertiesComponent, ComputedPropertiesComponentProps } from "./ComputedPropertiesComponent"; + +describe("ComputedPropertiesComponent", () => { + const initialComputedPropertiesContent: DataModels.ComputedProperties = [ + { + name: "prop1", + query: "query1", + }, + ]; + const baseProps: ComputedPropertiesComponentProps = { + computedPropertiesContent: initialComputedPropertiesContent, + computedPropertiesContentBaseline: initialComputedPropertiesContent, + logComputedPropertiesSuccessMessage: () => { + return; + }, + onComputedPropertiesContentChange: () => { + return; + }, + onComputedPropertiesDirtyChange: () => { + return; + }, + resetShouldDiscardComputedProperties: () => { + return; + }, + shouldDiscardComputedProperties: false, + }; + + it("renders", () => { + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); + }); + + it("computed properties are reset", () => { + const wrapper = shallow(); + + const computedPropertiesComponentInstance = wrapper.instance() as ComputedPropertiesComponent; + const resetComputedPropertiesEditorMockFn = jest.fn(); + computedPropertiesComponentInstance.resetComputedPropertiesEditor = resetComputedPropertiesEditorMockFn; + + wrapper.setProps({ shouldDiscardComputedProperties: true }); + wrapper.update(); + expect(resetComputedPropertiesEditorMockFn.mock.calls.length).toEqual(1); + }); + + it("dirty is set", () => { + let computedPropertiesComponent = new ComputedPropertiesComponent(baseProps); + expect(computedPropertiesComponent.IsComponentDirty()).toEqual(false); + + const newProps = { ...baseProps, computedPropertiesContent: undefined as DataModels.ComputedProperties }; + computedPropertiesComponent = new ComputedPropertiesComponent(newProps); + expect(computedPropertiesComponent.IsComponentDirty()).toEqual(true); + }); +}); diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/ComputedPropertiesComponent.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/ComputedPropertiesComponent.tsx new file mode 100644 index 000000000..c8650b988 --- /dev/null +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/ComputedPropertiesComponent.tsx @@ -0,0 +1,128 @@ +import { FontIcon, Link, MessageBar, MessageBarType, Stack, Text } from "@fluentui/react"; +import * as DataModels from "Contracts/DataModels"; +import { titleAndInputStackProps, unsavedEditorWarningMessage } from "Explorer/Controls/Settings/SettingsRenderUtils"; +import { isDirty } from "Explorer/Controls/Settings/SettingsUtils"; +import { loadMonaco } from "Explorer/LazyMonaco"; +import * as monaco from "monaco-editor"; +import * as React from "react"; + +export interface ComputedPropertiesComponentProps { + computedPropertiesContent: DataModels.ComputedProperties; + computedPropertiesContentBaseline: DataModels.ComputedProperties; + logComputedPropertiesSuccessMessage: () => void; + onComputedPropertiesContentChange: (newComputedProperties: DataModels.ComputedProperties) => void; + onComputedPropertiesDirtyChange: (isComputedPropertiesDirty: boolean) => void; + resetShouldDiscardComputedProperties: () => void; + shouldDiscardComputedProperties: boolean; +} + +interface ComputedPropertiesComponentState { + computedPropertiesContentIsValid: boolean; +} + +export class ComputedPropertiesComponent extends React.Component< + ComputedPropertiesComponentProps, + ComputedPropertiesComponentState +> { + private shouldCheckComponentIsDirty = true; + private computedPropertiesDiv = React.createRef(); + private computedPropertiesEditor: monaco.editor.IStandaloneCodeEditor; + + constructor(props: ComputedPropertiesComponentProps) { + super(props); + this.state = { + computedPropertiesContentIsValid: true, + }; + } + + componentDidUpdate(): void { + if (this.props.shouldDiscardComputedProperties) { + this.resetComputedPropertiesEditor(); + this.props.resetShouldDiscardComputedProperties(); + } + this.onComponentUpdate(); + } + + componentDidMount(): void { + this.resetComputedPropertiesEditor(); + this.onComponentUpdate(); + } + + public resetComputedPropertiesEditor = (): void => { + if (!this.computedPropertiesEditor) { + this.createComputedPropertiesEditor(); + } else { + const indexingPolicyEditorModel = this.computedPropertiesEditor.getModel(); + const value: string = JSON.stringify(this.props.computedPropertiesContent, undefined, 4); + indexingPolicyEditorModel.setValue(value); + } + this.onComponentUpdate(); + }; + + private onComponentUpdate = (): void => { + if (!this.shouldCheckComponentIsDirty) { + this.shouldCheckComponentIsDirty = true; + return; + } + this.props.onComputedPropertiesDirtyChange(this.IsComponentDirty()); + this.shouldCheckComponentIsDirty = false; + }; + + public IsComponentDirty = (): boolean => { + if ( + isDirty(this.props.computedPropertiesContent, this.props.computedPropertiesContentBaseline) && + this.state.computedPropertiesContentIsValid + ) { + return true; + } + + return false; + }; + + private async createComputedPropertiesEditor(): Promise { + const value: string = JSON.stringify(this.props.computedPropertiesContent, undefined, 4); + const monaco = await loadMonaco(); + this.computedPropertiesEditor = monaco.editor.create(this.computedPropertiesDiv.current, { + value: value, + language: "json", + ariaLabel: "Computed properties", + }); + if (this.computedPropertiesEditor) { + const computedPropertiesEditorModel = this.computedPropertiesEditor.getModel(); + computedPropertiesEditorModel.onDidChangeContent(this.onEditorContentChange.bind(this)); + this.props.logComputedPropertiesSuccessMessage(); + } + } + + private onEditorContentChange = (): void => { + const computedPropertiesEditorModel = this.computedPropertiesEditor.getModel(); + try { + const newComputedPropertiesContent = JSON.parse( + computedPropertiesEditorModel.getValue(), + ) as DataModels.ComputedProperties; + this.props.onComputedPropertiesContentChange(newComputedPropertiesContent); + this.setState({ computedPropertiesContentIsValid: true }); + } catch (e) { + this.setState({ computedPropertiesContentIsValid: false }); + } + }; + + public render(): JSX.Element { + return ( + + {isDirty(this.props.computedPropertiesContent, this.props.computedPropertiesContentBaseline) && ( + + {unsavedEditorWarningMessage("computedProperties")} + + )} + + + {"Learn more"} + +   about how to define computed properties and how to use them. + +
+
+ ); + } +} diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/ContainerVectorPolicyComponent.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/ContainerVectorPolicyComponent.tsx new file mode 100644 index 000000000..ca4d63a04 --- /dev/null +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/ContainerVectorPolicyComponent.tsx @@ -0,0 +1,30 @@ +import { Stack } from "@fluentui/react"; +import { VectorEmbeddingPolicy } from "Contracts/DataModels"; +import { EditorReact } from "Explorer/Controls/Editor/EditorReact"; +import { titleAndInputStackProps } from "Explorer/Controls/Settings/SettingsRenderUtils"; +import React from "react"; + +export interface ContainerVectorPolicyComponentProps { + vectorEmbeddingPolicy: VectorEmbeddingPolicy; +} + +export const ContainerVectorPolicyComponent: React.FC = ({ + vectorEmbeddingPolicy, +}) => { + return ( + + + + ); +}; diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyComponent.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyComponent.tsx index 1f216b241..bc5de93a4 100644 --- a/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyComponent.tsx +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyComponent.tsx @@ -3,7 +3,7 @@ import * as monaco from "monaco-editor"; import * as React from "react"; import * as DataModels from "../../../../Contracts/DataModels"; import { loadMonaco } from "../../../LazyMonaco"; -import { indexingPolicynUnsavedWarningMessage, titleAndInputStackProps } from "../SettingsRenderUtils"; +import { titleAndInputStackProps, unsavedEditorWarningMessage } from "../SettingsRenderUtils"; import { isDirty, isIndexTransforming } from "../SettingsUtils"; import { IndexingPolicyRefreshComponent } from "./IndexingPolicyRefresh/IndexingPolicyRefreshComponent"; @@ -16,6 +16,7 @@ export interface IndexingPolicyComponentProps { logIndexingPolicySuccessMessage: () => void; indexTransformationProgress: number; refreshIndexTransformationProgress: () => Promise; + isVectorSearchEnabled?: boolean; onIndexingPolicyDirtyChange: (isIndexingPolicyDirty: boolean) => void; } @@ -119,10 +120,15 @@ export class IndexingPolicyComponent extends React.Component< indexTransformationProgress={this.props.indexTransformationProgress} refreshIndexTransformationProgress={this.props.refreshIndexTransformationProgress} /> - {isDirty(this.props.indexingPolicyContent, this.props.indexingPolicyContentBaseline) && ( - {indexingPolicynUnsavedWarningMessage} + {this.props.isVectorSearchEnabled && ( + + Container vector policies and vector indexes are not modifiable after container creation + )} -
+ {isDirty(this.props.indexingPolicyContent, this.props.indexingPolicyContentBaseline) && ( + {unsavedEditorWarningMessage("indexPolicy")} + )} +
); } diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/MongoIndexingPolicy/MongoIndexingPolicyComponent.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/MongoIndexingPolicy/MongoIndexingPolicyComponent.tsx index c3b09286e..a55630532 100644 --- a/src/Explorer/Controls/Settings/SettingsSubComponents/MongoIndexingPolicy/MongoIndexingPolicyComponent.tsx +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/MongoIndexingPolicy/MongoIndexingPolicyComponent.tsx @@ -19,7 +19,6 @@ import { addMongoIndexStackProps, createAndAddMongoIndexStackProps, customDetailsListStyles, - indexingPolicynUnsavedWarningMessage, infoAndToolTipTextStyle, mediumWidthStackStyles, mongoCompoundIndexNotSupportedMessage, @@ -27,15 +26,16 @@ import { onRenderRow, separatorStyles, subComponentStackProps, + unsavedEditorWarningMessage, } from "../../SettingsRenderUtils"; import { AddMongoIndexProps, - getMongoIndexType, - getMongoIndexTypeText, - isIndexTransforming, MongoIndexIdField, MongoIndexTypes, MongoNotificationType, + getMongoIndexType, + getMongoIndexTypeText, + isIndexTransforming, } from "../../SettingsUtils"; import { IndexingPolicyRefreshComponent } from "../IndexingPolicyRefresh/IndexingPolicyRefreshComponent"; import { AddMongoIndexComponent } from "./AddMongoIndexComponent"; @@ -297,7 +297,7 @@ export class MongoIndexingPolicyComponent extends React.Component = ({ database, collection, explorer }) => { + const { dataTransferJobs } = useDataTransferJobs(); + const [portalDataTransferJob, setPortalDataTransferJob] = React.useState(null); + + React.useEffect(() => { + const loadDataTransferJobs = refreshDataTransferOperations; + loadDataTransferJobs(); + }, []); + + React.useEffect(() => { + const currentJob = findPortalDataTransferJob(); + setPortalDataTransferJob(currentJob); + startPollingforUpdate(currentJob); + }, [dataTransferJobs]); + + const isHierarchicalPartitionedContainer = (): boolean => collection.partitionKey?.kind === "MultiHash"; + + const getPartitionKeyValue = (): string => { + return (collection.partitionKeyProperties || []).map((property) => "/" + property).join(", "); + }; + + const partitionKeyName = "Partition key"; + const partitionKeyValue = getPartitionKeyValue(); + + const textHeadingStyle = { + root: { fontWeight: FontWeights.semibold, fontSize: 16 }, + }; + + const textSubHeadingStyle = { + root: { fontWeight: FontWeights.semibold }, + }; + + const startPollingforUpdate = (currentJob: DataTransferJobGetResults) => { + if (isCurrentJobInProgress(currentJob)) { + const jobName = currentJob?.properties?.jobName; + try { + pollDataTransferJob( + jobName, + userContext.subscriptionId, + userContext.resourceGroup, + userContext.databaseAccount.name, + ); + } catch (error) { + handleError(error, "ChangePartitionKey", `Failed to complete data transfer job ${jobName}`); + } + } + }; + + const cancelRunningDataTransferJob = async (currentJob: DataTransferJobGetResults) => { + await cancelDataTransferJob( + userContext.subscriptionId, + userContext.resourceGroup, + userContext.databaseAccount.name, + currentJob?.properties?.jobName, + ); + }; + + const isCurrentJobInProgress = (currentJob: DataTransferJobGetResults) => { + const jobStatus = currentJob?.properties?.status; + return ( + jobStatus && + jobStatus !== "Completed" && + jobStatus !== "Cancelled" && + jobStatus !== "Failed" && + jobStatus !== "Faulted" + ); + }; + + const refreshDataTransferOperations = async () => { + await refreshDataTransferJobs( + userContext.subscriptionId, + userContext.resourceGroup, + userContext.databaseAccount.name, + ); + }; + + const findPortalDataTransferJob = (): DataTransferJobGetResults => { + return dataTransferJobs.find((feed: DataTransferJobGetResults) => { + const sourceSink: CosmosSqlDataTransferDataSourceSink = feed?.properties + ?.source as CosmosSqlDataTransferDataSourceSink; + return sourceSink.databaseName === collection.databaseId && sourceSink.containerName === collection.id(); + }); + }; + + const getProgressDescription = (): string => { + const processedCount = portalDataTransferJob?.properties?.processedCount; + const totalCount = portalDataTransferJob?.properties?.totalCount; + const processedCountString = totalCount > 0 ? `(${processedCount} of ${totalCount} documents processed)` : ""; + return `${portalDataTransferJob?.properties?.status} ${processedCountString}`; + }; + + const startPartitionkeyChangeWorkflow = () => { + useSidePanel + .getState() + .openSidePanel( + "Change partition key", + , + ); + }; + + const getPercentageComplete = () => { + const jobStatus = portalDataTransferJob?.properties?.status; + const isCompleted = jobStatus === "Completed"; + if (isCompleted) { + return 1; + } + const processedCount = portalDataTransferJob?.properties?.processedCount; + const totalCount = portalDataTransferJob?.properties?.totalCount; + const isJobInProgress = isCurrentJobInProgress(portalDataTransferJob); + return isJobInProgress ? (totalCount > 0 ? processedCount / totalCount : null) : 0; + }; + + return ( + + + Change {partitionKeyName.toLowerCase()} + + + Current {partitionKeyName.toLowerCase()} + Partitioning + + + {partitionKeyValue} + {isHierarchicalPartitionedContainer() ? "Hierarchical" : "Non-hierarchical"} + + + + + To safeguard the integrity of the data being copied to the new container, ensure that no updates are made to the + source container for the entire duration of the partition key change process. + + Learn more + + + + To change the partition key, a new destination container must be created or an existing destination container + selected. Data will then be copied to the destination container. + + + {portalDataTransferJob && ( + + {partitionKeyName} change job + + + {isCurrentJobInProgress(portalDataTransferJob) && ( + cancelRunningDataTransferJob(portalDataTransferJob)} /> + )} + + + )} + + ); +}; diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputInputAutoPilotV3Component.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputInputAutoPilotV3Component.tsx index 6346e8733..664ae01c7 100644 --- a/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputInputAutoPilotV3Component.tsx +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputInputAutoPilotV3Component.tsx @@ -306,7 +306,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component< }; const costElement = (): JSX.Element => { - const prices: PriceBreakdown = getRuPriceBreakdown(throughput, serverId, numberOfRegions, isMultimaster, true); + const prices: PriceBreakdown = getRuPriceBreakdown(throughput, serverId, numberOfRegions, isMultimaster, false); return ( {newThroughput && newThroughputCostElement()} diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/__snapshots__/ThroughputInputAutoPilotV3Component.test.tsx.snap b/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/__snapshots__/ThroughputInputAutoPilotV3Component.test.tsx.snap index 6a0f9efd1..605732141 100644 --- a/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/__snapshots__/ThroughputInputAutoPilotV3Component.test.tsx.snap +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/__snapshots__/ThroughputInputAutoPilotV3Component.test.tsx.snap @@ -917,7 +917,7 @@ exports[`ThroughputInputAutoPilotV3Component spendAck checkbox visible 1`] = ` > $ - 0.012 + 0.0080 /hr $ - 0.29 + 0.19 /day $ - 8.76 + 5.84 /mo @@ -1354,7 +1354,7 @@ exports[`ThroughputInputAutoPilotV3Component throughput input visible 1`] = ` > $ - 0.012 + 0.0080 /hr $ - 0.29 + 0.19 /day $ - 8.76 + 5.84 /mo diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/__snapshots__/ComputedPropertiesComponent.test.tsx.snap b/src/Explorer/Controls/Settings/SettingsSubComponents/__snapshots__/ComputedPropertiesComponent.test.tsx.snap new file mode 100644 index 000000000..2f67d7d70 --- /dev/null +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/__snapshots__/ComputedPropertiesComponent.test.tsx.snap @@ -0,0 +1,36 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ComputedPropertiesComponent renders 1`] = ` + + + + Learn more + + + +   about how to define computed properties and how to use them. + +
+ +`; diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/__snapshots__/IndexingPolicyComponent.test.tsx.snap b/src/Explorer/Controls/Settings/SettingsSubComponents/__snapshots__/IndexingPolicyComponent.test.tsx.snap index 1f66324f5..93516dd7e 100644 --- a/src/Explorer/Controls/Settings/SettingsSubComponents/__snapshots__/IndexingPolicyComponent.test.tsx.snap +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/__snapshots__/IndexingPolicyComponent.test.tsx.snap @@ -12,7 +12,7 @@ exports[`IndexingPolicyComponent renders 1`] = ` refreshIndexTransformationProgress={[Function]} />
diff --git a/src/Explorer/Controls/Settings/SettingsUtils.tsx b/src/Explorer/Controls/Settings/SettingsUtils.tsx index a533b6446..cff7d1f74 100644 --- a/src/Explorer/Controls/Settings/SettingsUtils.tsx +++ b/src/Explorer/Controls/Settings/SettingsUtils.tsx @@ -4,7 +4,7 @@ import * as ViewModels from "../../../Contracts/ViewModels"; import { MongoIndex } from "../../../Utils/arm/generatedClients/cosmos/types"; const zeroValue = 0; -export type isDirtyTypes = boolean | string | number | DataModels.IndexingPolicy; +export type isDirtyTypes = boolean | string | number | DataModels.IndexingPolicy | DataModels.ComputedProperties; export const TtlOff = "off"; export const TtlOn = "on"; export const TtlOnNoDefault = "on-nodefault"; @@ -45,6 +45,9 @@ export enum SettingsV2TabTypes { ConflictResolutionTab, SubSettingsTab, IndexingPolicyTab, + PartitionKeyTab, + ComputedPropertiesTab, + ContainerVectorPolicyTab, } export interface IsComponentDirtyResult { @@ -146,6 +149,12 @@ export const getTabTitle = (tab: SettingsV2TabTypes): string => { return "Settings"; case SettingsV2TabTypes.IndexingPolicyTab: return "Indexing Policy"; + case SettingsV2TabTypes.PartitionKeyTab: + return "Partition Keys (preview)"; + case SettingsV2TabTypes.ComputedPropertiesTab: + return "Computed Properties"; + case SettingsV2TabTypes.ContainerVectorPolicyTab: + return "Container Vector Policy (preview)"; default: throw new Error(`Unknown tab ${tab}`); } @@ -199,3 +208,49 @@ export const getMongoIndexTypeText = (index: MongoIndexTypes): string => { export const isIndexTransforming = (indexTransformationProgress: number): boolean => // index transformation progress can be 0 indexTransformationProgress !== undefined && indexTransformationProgress !== 100; + +export const getPartitionKeyName = (apiType: string, isLowerCase?: boolean): string => { + const partitionKeyName = apiType === "Mongo" ? "Shard key" : "Partition key"; + return isLowerCase ? partitionKeyName.toLocaleLowerCase() : partitionKeyName; +}; + +export const getPartitionKeyTooltipText = (apiType: string): string => { + if (apiType === "Mongo") { + return "The shard key (field) is used to split your data across many replica sets (shards) to achieve unlimited scalability. It’s critical to choose a field that will evenly distribute your data."; + } + let tooltipText = `The ${getPartitionKeyName( + apiType, + true, + )} is used to automatically distribute data across partitions for scalability. Choose a property in your JSON document that has a wide range of values and evenly distributes request volume.`; + if (apiType === "SQL") { + tooltipText += " For small read-heavy workloads or write-heavy workloads of any size, id is often a good choice."; + } + return tooltipText; +}; + +export const getPartitionKeySubtext = (partitionKeyDefault: boolean, apiType: string): string => { + if (partitionKeyDefault && (apiType === "SQL" || apiType === "Mongo")) { + const subtext = "For small workloads, the item ID is a suitable choice for the partition key."; + return subtext; + } + return ""; +}; + +export const getPartitionKeyPlaceHolder = (apiType: string, index?: number): string => { + switch (apiType) { + case "Mongo": + return "e.g., categoryId"; + case "Gremlin": + return "e.g., /address"; + case "SQL": + return `${ + index === undefined + ? "Required - first partition key e.g., /TenantId" + : index === 0 + ? "second partition key e.g., /UserId" + : "third partition key e.g., /SessionId" + }`; + default: + return "e.g., /address/zipCode"; + } +}; diff --git a/src/Explorer/Controls/Settings/TestUtils.tsx b/src/Explorer/Controls/Settings/TestUtils.tsx index 41b11ca68..d0c794025 100644 --- a/src/Explorer/Controls/Settings/TestUtils.tsx +++ b/src/Explorer/Controls/Settings/TestUtils.tsx @@ -40,6 +40,12 @@ export const collection = { version: 2, }, partitionKeyProperties: ["partitionKey"], + computedProperties: ko.observable([ + { + name: "queryName", + query: "query", + }, + ]), readSettings: () => { return; }, diff --git a/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap b/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap index 5e905e786..561368bdd 100644 --- a/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap +++ b/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap @@ -26,10 +26,10 @@ exports[`SettingsComponent renders 1`] = ` Object { "analyticalStorageTtl": [Function], "changeFeedPolicy": [Function], + "computedProperties": [Function], "conflictResolutionPolicy": [Function], "container": Explorer { "_isInitializingNotebooks": false, - "_resetNotebookWorkspace": [Function], "isFixedCollectionWithSharedThroughputSupported": [Function], "isTabsContentExpanded": [Function], "onRefreshDatabasesKeyPress": [Function], @@ -103,10 +103,10 @@ exports[`SettingsComponent renders 1`] = ` Object { "analyticalStorageTtl": [Function], "changeFeedPolicy": [Function], + "computedProperties": [Function], "conflictResolutionPolicy": [Function], "container": Explorer { "_isInitializingNotebooks": false, - "_resetNotebookWorkspace": [Function], "isFixedCollectionWithSharedThroughputSupported": [Function], "isTabsContentExpanded": [Function], "onRefreshDatabasesKeyPress": [Function], @@ -196,6 +196,7 @@ exports[`SettingsComponent renders 1`] = ` "indexingMode": "consistent", } } + isVectorSearchEnabled={false} logIndexingPolicySuccessMessage={[Function]} onIndexingPolicyContentChange={[Function]} onIndexingPolicyDirtyChange={[Function]} @@ -204,6 +205,131 @@ exports[`SettingsComponent renders 1`] = ` shouldDiscardIndexingPolicy={false} /> + + + + + +
diff --git a/src/Explorer/Controls/Settings/__snapshots__/SettingsRenderUtils.test.tsx.snap b/src/Explorer/Controls/Settings/__snapshots__/SettingsRenderUtils.test.tsx.snap index 004862ffe..5a71353cd 100644 --- a/src/Explorer/Controls/Settings/__snapshots__/SettingsRenderUtils.test.tsx.snap +++ b/src/Explorer/Controls/Settings/__snapshots__/SettingsRenderUtils.test.tsx.snap @@ -99,18 +99,6 @@ exports[`SettingsUtils functions render 1`] = ` . - - You have not saved the latest changes made to your indexing policy. Please click save to confirm the changes. - = ({ Estimate your required RU/s with{" "} = ({ Estimate your required RU/s with  - + capacity calculator . diff --git a/src/Explorer/Controls/ThroughputInput/__snapshots__/ThroughputInput.test.tsx.snap b/src/Explorer/Controls/ThroughputInput/__snapshots__/ThroughputInput.test.tsx.snap index 391d88845..eb21939c9 100644 --- a/src/Explorer/Controls/ThroughputInput/__snapshots__/ThroughputInput.test.tsx.snap +++ b/src/Explorer/Controls/ThroughputInput/__snapshots__/ThroughputInput.test.tsx.snap @@ -733,11 +733,13 @@ exports[`ThroughputInput Pane should render Default properly 1`] = ` { - const grandChild11: TreeNode = { +const buildChildren = (): LegacyTreeNode[] => { + const grandChild11: LegacyTreeNode = { label: "ZgrandChild11", }; - const grandChild12: TreeNode = { + const grandChild12: LegacyTreeNode = { label: "AgrandChild12", }; - const child1: TreeNode = { + const child1: LegacyTreeNode = { label: "Bchild1", children: [grandChild11, grandChild12], }; - const child2: TreeNode = { + const child2: LegacyTreeNode = { label: "2child2", }; return [child1, child2]; }; -const buildChildren2 = (): TreeNode[] => { - const grandChild11: TreeNode = { +const buildChildren2 = (): LegacyTreeNode[] => { + const grandChild11: LegacyTreeNode = { label: "ZgrandChild11", }; - const grandChild12: TreeNode = { + const grandChild12: LegacyTreeNode = { label: "AgrandChild12", }; - const child1: TreeNode = { + const child1: LegacyTreeNode = { label: "aChild", }; - const child2: TreeNode = { + const child2: LegacyTreeNode = { label: "bchild", children: [grandChild11, grandChild12], }; - const child3: TreeNode = { + const child3: LegacyTreeNode = { label: "cchild", }; - const child4: TreeNode = { + const child4: LegacyTreeNode = { label: "dchild", children: [grandChild11, grandChild12], }; @@ -50,7 +50,7 @@ const buildChildren2 = (): TreeNode[] => { return [child1, child2, child3, child4]; }; -describe("TreeComponent", () => { +describe("LegacyTreeComponent", () => { it("renders a simple tree", () => { const root = { label: "root", @@ -62,14 +62,14 @@ describe("TreeComponent", () => { className: "tree", }; - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); }); }); -describe("TreeNodeComponent", () => { +describe("LegacyTreeNodeComponent", () => { it("renders a simple node (sorted children, expanded)", () => { - const node: TreeNode = { + const node: LegacyTreeNode = { label: "label", id: "id", children: buildChildren(), @@ -98,12 +98,12 @@ describe("TreeNodeComponent", () => { generation: 12, paddingLeft: 23, }; - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); }); it("renders unsorted children by default", () => { - const node: TreeNode = { + const node: LegacyTreeNode = { label: "label", children: buildChildren(), isExpanded: true, @@ -113,12 +113,12 @@ describe("TreeNodeComponent", () => { generation: 2, paddingLeft: 9, }; - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); }); it("does not render children by default", () => { - const node: TreeNode = { + const node: LegacyTreeNode = { label: "label", children: buildChildren(), isAlphaSorted: false, @@ -128,12 +128,12 @@ describe("TreeNodeComponent", () => { generation: 2, paddingLeft: 9, }; - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); }); it("renders sorted children, expanded, leaves and parents separated", () => { - const node: TreeNode = { + const node: LegacyTreeNode = { label: "label", id: "id", children: buildChildren2(), @@ -156,12 +156,12 @@ describe("TreeNodeComponent", () => { generation: 12, paddingLeft: 23, }; - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); }); it("renders loading icon", () => { - const node: TreeNode = { + const node: LegacyTreeNode = { label: "label", children: [], isExpanded: true, @@ -172,7 +172,7 @@ describe("TreeNodeComponent", () => { generation: 2, paddingLeft: 9, }; - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); }); }); diff --git a/src/Explorer/Controls/TreeComponent/TreeComponent.tsx b/src/Explorer/Controls/TreeComponent/LegacyTreeComponent.tsx similarity index 76% rename from src/Explorer/Controls/TreeComponent/TreeComponent.tsx rename to src/Explorer/Controls/TreeComponent/LegacyTreeComponent.tsx index 362390b80..854818138 100644 --- a/src/Explorer/Controls/TreeComponent/TreeComponent.tsx +++ b/src/Explorer/Controls/TreeComponent/LegacyTreeComponent.tsx @@ -12,6 +12,7 @@ import { IContextualMenuItemProps, IContextualMenuProps, } from "@fluentui/react"; +import { TreeNodeMenuItem } from "Explorer/Controls/TreeComponent/TreeNodeComponent"; import * as React from "react"; import AnimateHeight from "react-animate-height"; import LoadingIndicator_3Squares from "../../../../images/LoadingIndicator_3Squares.gif"; @@ -22,18 +23,10 @@ import { StyleConstants } from "../../../Common/StyleConstants"; import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants"; import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor"; -export interface TreeNodeMenuItem { - label: string; - onClick: () => void; - iconSrc?: string; - isDisabled?: boolean; - styleClass?: string; -} - -export interface TreeNode { +export interface LegacyTreeNode { label: string; id?: string; - children?: TreeNode[]; + children?: LegacyTreeNode[]; contextMenu?: TreeNodeMenuItem[]; iconSrc?: string; isExpanded?: boolean; @@ -50,34 +43,37 @@ export interface TreeNode { onContextMenuOpen?: () => void; } -export interface TreeComponentProps { - rootNode: TreeNode; +export interface LegacyTreeComponentProps { + rootNode: LegacyTreeNode; style?: any; className?: string; } -export class TreeComponent extends React.Component { +export class LegacyTreeComponent extends React.Component { public render(): JSX.Element { return (
- +
); } } /* Tree node is a react component */ -interface TreeNodeComponentProps { - node: TreeNode; +interface LegacyTreeNodeComponentProps { + node: LegacyTreeNode; generation: number; paddingLeft: number; } -interface TreeNodeComponentState { +interface LegacyTreeNodeComponentState { isExpanded: boolean; isMenuShowing: boolean; } -export class TreeNodeComponent extends React.Component { +export class LegacyTreeNodeComponent extends React.Component< + LegacyTreeNodeComponentProps, + LegacyTreeNodeComponentState +> { private static readonly paddingPerGenerationPx = 16; private static readonly iconOffset = 22; private static readonly transitionDurationMS = 200; @@ -85,7 +81,7 @@ export class TreeNodeComponent extends React.Component(); private isExpanded: boolean; - constructor(props: TreeNodeComponentProps) { + constructor(props: LegacyTreeNodeComponentProps) { super(props); this.isExpanded = props.node.isExpanded; this.state = { @@ -94,13 +90,13 @@ export class TreeNodeComponent extends React.Component a.label.localeCompare(b.label); + const compareFct = (a: LegacyTreeNode, b: LegacyTreeNode) => a.label.localeCompare(b.label); let unsortedChildren; if (treeNode.isLeavesParentsSeparate) { // Separate parents and leave - const parents: TreeNode[] = treeNode.children.filter((node) => node.children); - const leaves: TreeNode[] = treeNode.children.filter((node) => !node.children); + const parents: LegacyTreeNode[] = treeNode.children.filter((node) => node.children); + const leaves: LegacyTreeNode[] = treeNode.children.filter((node) => !node.children); if (treeNode.isAlphaSorted) { parents.sort(compareFct); @@ -141,18 +137,18 @@ export class TreeNodeComponent extends React.Component !!child.children); + const childrenWithSubChildren = node.children.filter((child: LegacyTreeNode) => !!child.children); if (childrenWithSubChildren.length > 0) { - additionalOffsetPx = TreeNodeComponent.iconOffset; + additionalOffsetPx = LegacyTreeNodeComponent.iconOffset; } } @@ -160,16 +156,17 @@ export class TreeNodeComponent extends React.Component) => this.onNodeClick(event, node)} onKeyPress={(event: React.KeyboardEvent) => this.onNodeKeyPress(event, node)} @@ -178,9 +175,9 @@ export class TreeNodeComponent extends React.Component
{this.renderCollapseExpandIcon(node)} {node.iconSrc && } @@ -195,10 +192,13 @@ export class TreeNodeComponent extends React.Component
{node.children && ( - +
- {TreeNodeComponent.getSortedChildren(node).map((childNode: TreeNode) => ( - ( + - previous || (child.isSelected && child.isSelected()) || TreeNodeComponent.isAnyDescendantSelected(child), + (previous: boolean, child: LegacyTreeNode) => + previous || + (child.isSelected && child.isSelected()) || + LegacyTreeNodeComponent.isAnyDescendantSelected(child), false, ) ); @@ -232,10 +234,10 @@ export class TreeNodeComponent extends React.Component { - this.contextMenuRef.current.firstChild.dispatchEvent(TreeNodeComponent.createClickEvent()); + this.contextMenuRef.current.firstChild.dispatchEvent(LegacyTreeNodeComponent.createClickEvent()); }; - private renderContextMenuButton(node: TreeNode): JSX.Element { + private renderContextMenuButton(node: LegacyTreeNode): JSX.Element { const menuItemLabel = "More"; const buttonStyles: Partial = { rootFocused: { outline: `1px dashed ${StyleConstants.FocusColor}` }, @@ -247,7 +249,7 @@ export class TreeNodeComponent extends React.Component this.setState({ isMenuShowing: false }), contextualMenuItemAs: (props: IContextualMenuItemProps) => (
e.target.dispatchEvent(TreeNodeComponent.createClickEvent())} + onContextMenu={(e) => e.target.dispatchEvent(LegacyTreeNodeComponent.createClickEvent())} > {props.item.onRenderIcon()} ; } @@ -314,12 +316,12 @@ export class TreeNodeComponent extends React.Component, node: TreeNode): void => { + private onNodeClick = (event: React.MouseEvent, node: LegacyTreeNode): void => { event.stopPropagation(); if (node.children) { const isExpanded = !this.state.isExpanded; // Prevent collapsing if node header is blank - if (!(TreeNodeComponent.isNodeHeaderBlank(node) && !isExpanded)) { + if (!(LegacyTreeNodeComponent.isNodeHeaderBlank(node) && !isExpanded)) { this.setState({ isExpanded }); } } @@ -327,14 +329,14 @@ export class TreeNodeComponent extends React.Component, node: TreeNode): void => { + private onNodeKeyPress = (event: React.KeyboardEvent, node: LegacyTreeNode): void => { if (event.charCode === Constants.KeyCodes.Space || event.charCode === Constants.KeyCodes.Enter) { event.stopPropagation(); this.props.node.onClick && this.props.node.onClick(this.state.isExpanded); } }; - private onCollapseExpandIconKeyPress = (event: React.KeyboardEvent, node: TreeNode): void => { + private onCollapseExpandIconKeyPress = (event: React.KeyboardEvent, node: LegacyTreeNode): void => { if (event.charCode === Constants.KeyCodes.Space || event.charCode === Constants.KeyCodes.Enter) { event.stopPropagation(); if (node.children) { diff --git a/src/Explorer/Controls/TreeComponent/TreeNodeComponent.test.tsx b/src/Explorer/Controls/TreeComponent/TreeNodeComponent.test.tsx new file mode 100644 index 000000000..e0cdf5700 --- /dev/null +++ b/src/Explorer/Controls/TreeComponent/TreeNodeComponent.test.tsx @@ -0,0 +1,194 @@ +import { TreeItem, TreeItemLayout, tokens } from "@fluentui/react-components"; +import PromiseSource from "Utils/PromiseSource"; +import { mount, shallow } from "enzyme"; +import React from "react"; +import { act } from "react-dom/test-utils"; +import { TreeNode, TreeNodeComponent } from "./TreeNodeComponent"; + +function generateTestNode(id: string, additionalProps?: Partial): TreeNode { + const node: TreeNode = { + id, + label: `${id}Label`, + className: `${id}Class`, + iconSrc: `${id}Icon`, + onClick: jest.fn().mockName(`${id}Click`), + ...additionalProps, + }; + return node; +} + +describe("TreeNodeComponent", () => { + it("renders a single node", () => { + const node = generateTestNode("root"); + const component = shallow(); + expect(component).toMatchSnapshot(); + + // The "click" handler is actually attached to onOpenChange, with a type of "Click". + component + .find(TreeItem) + .props() + .onOpenChange(null!, { open: true, value: "borp", target: null!, event: null!, type: "Click" }); + expect(node.onClick).toHaveBeenCalled(); + }); + + it("renders a node with a menu", () => { + const node = generateTestNode("root", { + contextMenu: [ + { + label: "enabledItem", + onClick: jest.fn().mockName("enabledItemClick"), + }, + { + label: "disabledItem", + onClick: jest.fn().mockName("disabledItemClick"), + isDisabled: true, + }, + ], + }); + const component = shallow(); + expect(component).toMatchSnapshot(); + }); + + it("renders a loading spinner if the node is loading", async () => { + const loading = new PromiseSource(); + const node = generateTestNode("root", { + onExpanded: () => loading.promise, + }); + const component = shallow(); + + act(() => { + component + .find(TreeItem) + .props() + .onOpenChange(null!, { open: true, value: "borp", target: null!, event: null!, type: "ExpandIconClick" }); + }); + + expect(component).toMatchSnapshot("loading"); + await loading.resolveAndWait(); + expect(component).toMatchSnapshot("loaded"); + }); + + it("renders single selected leaf node as selected", () => { + const node = generateTestNode("root", { + isSelected: () => true, + }); + const component = shallow(); + expect(component.find(TreeItemLayout).props().style?.backgroundColor).toStrictEqual( + tokens.colorNeutralBackground1Selected, + ); + expect(component).toMatchSnapshot(); + }); + + it("renders selected parent node as selected if no descendant nodes are selected", () => { + const node = generateTestNode("root", { + isSelected: () => true, + children: [ + generateTestNode("child1", { + children: [generateTestNode("grandchild1"), generateTestNode("grandchild2")], + }), + generateTestNode("child2"), + ], + }); + const component = shallow(); + expect(component.find(TreeItemLayout).props().style?.backgroundColor).toStrictEqual( + tokens.colorNeutralBackground1Selected, + ); + expect(component).toMatchSnapshot(); + }); + + it("renders selected parent node as unselected if any descendant node is selected", () => { + const node = generateTestNode("root", { + isSelected: () => true, + children: [ + generateTestNode("child1", { + children: [ + generateTestNode("grandchild1", { + isSelected: () => true, + }), + generateTestNode("grandchild2"), + ], + }), + generateTestNode("child2"), + ], + }); + const component = shallow(); + expect(component.find(TreeItemLayout).props().style?.backgroundColor).toBeUndefined(); + expect(component).toMatchSnapshot(); + }); + + it("renders an icon if the node has one", () => { + const node = generateTestNode("root", { + iconSrc: "the-icon.svg", + }); + const component = shallow(); + expect(component).toMatchSnapshot(); + }); + + it("renders a node as expandable if it has empty, but defined, children array", () => { + const node = generateTestNode("root", { + isLoading: true, + children: [ + generateTestNode("child1", { + children: [], + }), + generateTestNode("child2"), + ], + }); + const component = shallow(); + expect(component).toMatchSnapshot(); + }); + + it("does not render children if the node is loading", () => { + const node = generateTestNode("root", { + isLoading: true, + children: [ + generateTestNode("child1", { + children: [generateTestNode("grandchild1"), generateTestNode("grandchild2")], + }), + generateTestNode("child2"), + ], + }); + const component = shallow(); + expect(component).toMatchSnapshot(); + }); + + it("fully renders a tree", () => { + const child3Loading = new PromiseSource(); + const node = generateTestNode("root", { + isSelected: () => true, + children: [ + generateTestNode("child1", { + children: [ + generateTestNode("grandchild1", { + iconSrc: "grandchild1Icon.svg", + isSelected: () => true, + }), + generateTestNode("grandchild2"), + ], + }), + generateTestNode("child2Loading", { + isLoading: true, + children: [generateTestNode("grandchild3NotRendered")], + }), + generateTestNode("child3Expanding", { + onExpanded: () => child3Loading.promise, + }), + ], + }); + const component = mount(); + + // Find and expand the child3Expanding node + const expandingChild = component.find(TreeItem).filterWhere((n) => n.props().value === "root/child3ExpandingLabel"); + act(() => { + expandingChild.props().onOpenChange(null!, { + open: true, + value: "root/child3ExpandingLabel", + target: null!, + event: null!, + type: "Click", + }); + }); + + expect(component).toMatchSnapshot(); + }); +}); diff --git a/src/Explorer/Controls/TreeComponent/TreeNodeComponent.tsx b/src/Explorer/Controls/TreeComponent/TreeNodeComponent.tsx new file mode 100644 index 000000000..9ec2c904a --- /dev/null +++ b/src/Explorer/Controls/TreeComponent/TreeNodeComponent.tsx @@ -0,0 +1,207 @@ +import { + Button, + Menu, + MenuItem, + MenuList, + MenuOpenChangeData, + MenuOpenEvent, + MenuPopover, + MenuTrigger, + Spinner, + Tree, + TreeItem, + TreeItemLayout, + TreeOpenChangeData, + TreeOpenChangeEvent, +} from "@fluentui/react-components"; +import { MoreHorizontal20Regular } from "@fluentui/react-icons"; +import { tokens } from "@fluentui/react-theme"; +import * as React from "react"; +import { useCallback } from "react"; + +export interface TreeNodeMenuItem { + label: string; + onClick: () => void; + iconSrc?: string; + isDisabled?: boolean; + styleClass?: string; +} + +export interface TreeNode { + label: string; + id?: string; + children?: TreeNode[]; + contextMenu?: TreeNodeMenuItem[]; + iconSrc?: string; + isExpanded?: boolean; + className?: string; + isAlphaSorted?: boolean; + // data?: any; // Piece of data corresponding to this node + timestamp?: number; + isLeavesParentsSeparate?: boolean; // Display parents together first, then leaves + isLoading?: boolean; + isScrollable?: boolean; + isSelected?: () => boolean; + onClick?: () => void; // Only if a leaf, other click will expand/collapse + onExpanded?: () => Promise; + onCollapsed?: () => void; + onContextMenuOpen?: () => void; +} + +export interface TreeNodeComponentProps { + node: TreeNode; + className?: string; + treeNodeId: string; +} + +/** Function that returns true if any descendant (at any depth) of this node is selected. */ +function isAnyDescendantSelected(node: TreeNode): boolean { + return ( + node.children && + node.children.reduce( + (previous: boolean, child: TreeNode) => + previous || (child.isSelected && child.isSelected()) || isAnyDescendantSelected(child), + false, + ) + ); +} + +const getTreeIcon = (iconSrc: string): JSX.Element => ; + +export const TreeNodeComponent: React.FC = ({ + node, + treeNodeId, +}: TreeNodeComponentProps): JSX.Element => { + const [isLoading, setIsLoading] = React.useState(false); + + const getSortedChildren = (treeNode: TreeNode): TreeNode[] => { + if (!treeNode || !treeNode.children) { + return undefined; + } + + const compareFct = (a: TreeNode, b: TreeNode) => a.label.localeCompare(b.label); + + let unsortedChildren; + if (treeNode.isLeavesParentsSeparate) { + // Separate parents and leave + const parents: TreeNode[] = treeNode.children.filter((node) => node.children); + const leaves: TreeNode[] = treeNode.children.filter((node) => !node.children); + + if (treeNode.isAlphaSorted) { + parents.sort(compareFct); + leaves.sort(compareFct); + } + + unsortedChildren = parents.concat(leaves); + } else { + unsortedChildren = treeNode.isAlphaSorted ? treeNode.children.sort(compareFct) : treeNode.children; + } + + return unsortedChildren; + }; + + // A branch node is any node with a defined children array, even if the array is empty. + const isBranch = !!node.children; + + const onOpenChange = useCallback( + (_: TreeOpenChangeEvent, data: TreeOpenChangeData) => { + if (data.type === "Click" && !isBranch && node.onClick) { + node.onClick(); + } + if (!node.isExpanded && data.open && node.onExpanded) { + // Catch the transition non-expanded to expanded + setIsLoading(true); + node.onExpanded?.().then(() => setIsLoading(false)); + } else if (node.isExpanded && !data.open && node.onCollapsed) { + // Catch the transition expanded to non-expanded + node.onCollapsed?.(); + } + }, + [isBranch, node, setIsLoading], + ); + + const onMenuOpenChange = useCallback( + (e: MenuOpenEvent, data: MenuOpenChangeData) => { + if (data.open) { + node.onContextMenuOpen?.(); + } + }, + [node], + ); + + // We show a node as selected if it is selected AND no descendant is selected. + // We want to show only the deepest selected node as selected. + const isCurrentNodeSelected = node.isSelected && node.isSelected(); + const shouldShowAsSelected = isCurrentNodeSelected && !isAnyDescendantSelected(node); + + const contextMenuItems = (node.contextMenu ?? []).map((menuItem) => ( + + {menuItem.label} + + )); + + const treeItem = ( + + 0 && ( + + + + ) + } + expandIcon={isLoading ? : undefined} + iconBefore={node.iconSrc && getTreeIcon(node.iconSrc)} + style={{ + backgroundColor: shouldShowAsSelected ? tokens.colorNeutralBackground1Selected : undefined, + }} + > + {node.label} + + {!node.isLoading && node.children?.length > 0 && ( + + {getSortedChildren(node).map((childNode: TreeNode) => ( + + ))} + + )} + + ); + + if (contextMenuItems.length === 0) { + return treeItem; + } + + // For accessibility, it's highly recommended that any 'actions' also be available in the context menu. + // See https://react.fluentui.dev/?path=/docs/components-tree--default#actions + return ( + + {treeItem} + + {contextMenuItems} + + + ); +}; diff --git a/src/Explorer/Controls/TreeComponent/__snapshots__/TreeComponent.test.tsx.snap b/src/Explorer/Controls/TreeComponent/__snapshots__/LegacyTreeComponent.test.tsx.snap similarity index 91% rename from src/Explorer/Controls/TreeComponent/__snapshots__/TreeComponent.test.tsx.snap rename to src/Explorer/Controls/TreeComponent/__snapshots__/LegacyTreeComponent.test.tsx.snap index f85616372..ef0420f81 100644 --- a/src/Explorer/Controls/TreeComponent/__snapshots__/TreeComponent.test.tsx.snap +++ b/src/Explorer/Controls/TreeComponent/__snapshots__/LegacyTreeComponent.test.tsx.snap @@ -1,11 +1,11 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`TreeComponent renders a simple tree 1`] = ` +exports[`LegacyTreeComponent renders a simple tree 1`] = `
- `; -exports[`TreeNodeComponent does not render children by default 1`] = ` +exports[`LegacyTreeNodeComponent does not render children by default 1`] = `
- - `; -exports[`TreeNodeComponent renders a simple node (sorted children, expanded) 1`] = ` +exports[`LegacyTreeNodeComponent renders a simple node (sorted children, expanded) 1`] = `
- - `; -exports[`TreeNodeComponent renders loading icon 1`] = ` +exports[`LegacyTreeNodeComponent renders loading icon 1`] = `
`; -exports[`TreeNodeComponent renders sorted children, expanded, leaves and parents separated 1`] = ` +exports[`LegacyTreeNodeComponent renders sorted children, expanded, leaves and parents separated 1`] = `
- - - - `; -exports[`TreeNodeComponent renders unsorted children by default 1`] = ` +exports[`LegacyTreeNodeComponent renders unsorted children by default 1`] = `
- - + + } + style={ + Object { + "backgroundColor": undefined, + } + } + > + rootLabel + + +`; + +exports[`TreeNodeComponent fully renders a tree 1`] = ` + + +
+
, + }, + "isActionsVisible": false, + "isAsideVisible": false, + "itemType": "branch", + "layoutRef": Object { + "current":
+ + +
+ rootLabel +
+
, + }, + "open": false, + "selectionRef": Object { + "current": null, + }, + "subtreeRef": Object { + "current": null, + }, + "treeItemRef": Object { + "current": , + }, + "value": "root", + } + } + > + + } + style={ + Object { + "backgroundColor": undefined, + } + } + > +
+
+ + + + + + + +
+
+ +
+
+ rootLabel +
+
+
+ + + +
+ + +
+
, + }, + "isActionsVisible": false, + "isAsideVisible": false, + "itemType": "branch", + "layoutRef": Object { + "current":
+ + +
+ child1Label +
+
, + }, + "open": false, + "selectionRef": Object { + "current": null, + }, + "subtreeRef": Object { + "current": null, + }, + "treeItemRef": Object { + "current": , + }, + "value": "root/child1Label", + } + } + > + + } + style={ + Object { + "backgroundColor": undefined, + } + } + > +
+
+ + + + + + + +
+
+ +
+
+ child1Label +
+
+
+ + + + +
+
+
+ + +
+
, + }, + "isActionsVisible": false, + "isAsideVisible": false, + "itemType": "branch", + "layoutRef": Object { + "current":
+ + +
+ child2LoadingLabel +
+
, + }, + "open": false, + "selectionRef": Object { + "current": null, + }, + "subtreeRef": Object { + "current": null, + }, + "treeItemRef": Object { + "current": , + }, + "value": "root/child2LoadingLabel", + } + } + > + + } + style={ + Object { + "backgroundColor": undefined, + } + } + > +
+
+ + + + + + + +
+
+ +
+
+ child2LoadingLabel +
+
+
+ +
+ + + + +
+
, + }, + "isActionsVisible": false, + "isAsideVisible": false, + "itemType": "leaf", + "layoutRef": Object { + "current":
+ + +
+ child3ExpandingLabel +
+
, + }, + "open": false, + "selectionRef": Object { + "current": null, + }, + "subtreeRef": Object { + "current": null, + }, + "treeItemRef": Object { + "current":
+
+ + +
+ child3ExpandingLabel +
+
+
, + }, + "value": "root/child3ExpandingLabel", + } + } + > + + } + style={ + Object { + "backgroundColor": undefined, + } + } + > +
+
+ +
+
+ child3ExpandingLabel +
+
+
+ +
+ +
+
+ + + + +
+ + +`; + +exports[`TreeNodeComponent renders a loading spinner if the node is loading: loaded 1`] = ` + + + } + style={ + Object { + "backgroundColor": undefined, + } + } + > + rootLabel + + +`; + +exports[`TreeNodeComponent renders a loading spinner if the node is loading: loading 1`] = ` + + + } + iconBefore={ + + } + style={ + Object { + "backgroundColor": undefined, + } + } + > + rootLabel + + +`; + +exports[`TreeNodeComponent renders a node as expandable if it has empty, but defined, children array 1`] = ` + + + } + style={ + Object { + "backgroundColor": undefined, + } + } + > + rootLabel + + +`; + +exports[`TreeNodeComponent renders a node with a menu 1`] = ` + + + + + + + } + className="rootClass" + data-test="TreeNode:root" + iconBefore={ + + } + style={ + Object { + "backgroundColor": undefined, + } + } + > + rootLabel + + + + + + + enabledItem + + + disabledItem + + + + +`; + +exports[`TreeNodeComponent renders a single node 1`] = ` + + + } + style={ + Object { + "backgroundColor": undefined, + } + } + > + rootLabel + + +`; + +exports[`TreeNodeComponent renders an icon if the node has one 1`] = ` + + + } + style={ + Object { + "backgroundColor": undefined, + } + } + > + rootLabel + + +`; + +exports[`TreeNodeComponent renders selected parent node as selected if no descendant nodes are selected 1`] = ` + + + } + style={ + Object { + "backgroundColor": "var(--colorNeutralBackground1Selected)", + } + } + > + rootLabel + + + + + + +`; + +exports[`TreeNodeComponent renders selected parent node as unselected if any descendant node is selected 1`] = ` + + + } + style={ + Object { + "backgroundColor": undefined, + } + } + > + rootLabel + + + + + + +`; + +exports[`TreeNodeComponent renders single selected leaf node as selected 1`] = ` + + + } + style={ + Object { + "backgroundColor": "var(--colorNeutralBackground1Selected)", + } + } + > + rootLabel + + +`; diff --git a/src/Explorer/Controls/TreeComponent2/TreeNode2Component.tsx b/src/Explorer/Controls/TreeComponent2/TreeNode2Component.tsx deleted file mode 100644 index 14c66db12..000000000 --- a/src/Explorer/Controls/TreeComponent2/TreeNode2Component.tsx +++ /dev/null @@ -1,147 +0,0 @@ -import { - Button, - Menu, - MenuItem, - MenuList, - MenuPopover, - MenuTrigger, - Spinner, - Tree, - TreeItem, - TreeItemLayout, - TreeOpenChangeData, - TreeOpenChangeEvent, -} from "@fluentui/react-components"; -import { MoreHorizontal20Regular } from "@fluentui/react-icons"; -import { tokens } from "@fluentui/react-theme"; -import * as React from "react"; - -export interface TreeNode2MenuItem { - label: string; - onClick: () => void; - iconSrc?: string; - isDisabled?: boolean; - styleClass?: string; -} - -export interface TreeNode2 { - label: string; - id?: string; - children?: TreeNode2[]; - contextMenu?: TreeNode2MenuItem[]; - iconSrc?: string; - isExpanded?: boolean; - className?: string; - isAlphaSorted?: boolean; - // data?: any; // Piece of data corresponding to this node - timestamp?: number; - isLeavesParentsSeparate?: boolean; // Display parents together first, then leaves - isLoading?: boolean; - isScrollable?: boolean; - isSelected?: () => boolean; - onClick?: () => void; // Only if a leaf, other click will expand/collapse - onExpanded?: () => Promise; - onCollapsed?: () => void; - onContextMenuOpen?: () => void; -} - -export interface TreeNode2ComponentProps { - node: TreeNode2; - className?: string; - treeNodeId: string; -} - -const getTreeIcon = (iconSrc: string): JSX.Element => ; - -export const TreeNode2Component: React.FC = ({ - node, - treeNodeId, -}: TreeNode2ComponentProps): JSX.Element => { - const [isLoading, setIsLoading] = React.useState(false); - - const getSortedChildren = (treeNode: TreeNode2): TreeNode2[] => { - if (!treeNode || !treeNode.children) { - return undefined; - } - - const compareFct = (a: TreeNode2, b: TreeNode2) => a.label.localeCompare(b.label); - - let unsortedChildren; - if (treeNode.isLeavesParentsSeparate) { - // Separate parents and leave - const parents: TreeNode2[] = treeNode.children.filter((node) => node.children); - const leaves: TreeNode2[] = treeNode.children.filter((node) => !node.children); - - if (treeNode.isAlphaSorted) { - parents.sort(compareFct); - leaves.sort(compareFct); - } - - unsortedChildren = parents.concat(leaves); - } else { - unsortedChildren = treeNode.isAlphaSorted ? treeNode.children.sort(compareFct) : treeNode.children; - } - - return unsortedChildren; - }; - - const onOpenChange = (_: TreeOpenChangeEvent, data: TreeOpenChangeData) => { - if (!node.isExpanded && data.open && node.onExpanded) { - // Catch the transition non-expanded to expanded - setIsLoading(true); - node.onExpanded?.().then(() => setIsLoading(false)); - } else if (node.isExpanded && !data.open && node.onCollapsed) { - // Catch the transition expanded to non-expanded - node.onCollapsed?.(); - } - }; - - return ( - - - -
diff --git a/src/Explorer/Menus/CommandBar/CommandBarComponentAdapter.tsx b/src/Explorer/Menus/CommandBar/CommandBarComponentAdapter.tsx index 191a91c62..9a5f222a3 100644 --- a/src/Explorer/Menus/CommandBar/CommandBarComponentAdapter.tsx +++ b/src/Explorer/Menus/CommandBar/CommandBarComponentAdapter.tsx @@ -5,6 +5,7 @@ */ import { CommandBar as FluentCommandBar, ICommandBarItemProps } from "@fluentui/react"; import { useNotebook } from "Explorer/Notebook/useNotebook"; +import { KeyboardActionGroup, useKeyboardActionGroup } from "KeyboardShortcuts"; import { userContext } from "UserContext"; import * as React from "react"; import create, { UseStore } from "zustand"; @@ -24,17 +25,23 @@ interface Props { export interface CommandBarStore { contextButtons: CommandButtonComponentProps[]; setContextButtons: (contextButtons: CommandButtonComponentProps[]) => void; + isHidden: boolean; + setIsHidden: (isHidden: boolean) => void; } export const useCommandBar: UseStore = create((set) => ({ contextButtons: [], setContextButtons: (contextButtons: CommandButtonComponentProps[]) => set((state) => ({ ...state, contextButtons })), + isHidden: false, + setIsHidden: (isHidden: boolean) => set((state) => ({ ...state, isHidden })), })); export const CommandBar: React.FC = ({ container }: Props) => { const selectedNodeState = useSelectedNode(); const buttons = useCommandBar((state) => state.contextButtons); + const isHidden = useCommandBar((state) => state.isHidden); const backgroundColor = StyleConstants.BaseLight; + const setKeyboardHandlers = useKeyboardActionGroup(KeyboardActionGroup.COMMAND_BAR); if (userContext.apiType === "Postgres" || userContext.apiType === "VCoreMongo") { const buttons = @@ -42,7 +49,7 @@ export const CommandBar: React.FC = ({ container }: Props) => { ? CommandBarComponentButtonFactory.createPostgreButtons(container) : CommandBarComponentButtonFactory.createVCoreMongoButtons(container); return ( -
+
= ({ container }: Props) => { ? { root: { backgroundColor: "transparent", - padding: "0px 14px 0px 14px", + padding: "2px 8px 0px 8px", }, } : { @@ -100,8 +107,12 @@ export const CommandBar: React.FC = ({ container }: Props) => { }, }; + const allButtons = staticButtons.concat(contextButtons).concat(controlButtons); + const keyboardHandlers = CommandBarUtil.createKeyboardHandlers(allButtons); + setKeyboardHandlers(keyboardHandlers); + return ( -
+
{ }); }); - describe("Enable notebook button", () => { - const enableNotebookBtnLabel = "Enable Notebooks (Preview)"; - const selectedNodeState = useSelectedNode.getState(); - - beforeAll(() => { - mockExplorer = {} as Explorer; - updateUserContext({ - portalEnv: "prod", - databaseAccount: { - properties: { - capabilities: [{ name: "EnableTable" }], - }, - } as DatabaseAccount, - }); - }); - - afterEach(() => { - updateUserContext({ - portalEnv: "prod", - }); - useNotebook.getState().setIsNotebookEnabled(false); - useNotebook.getState().setIsNotebooksEnabledForAccount(false); - }); - - it("Notebooks is already enabled - button should be hidden", () => { - useNotebook.getState().setIsNotebookEnabled(true); - useNotebook.getState().setIsNotebooksEnabledForAccount(true); - - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel); - expect(enableNotebookBtn).toBeUndefined(); - }); - - it("Account is running on one of the national clouds - button should be hidden", () => { - updateUserContext({ - portalEnv: "mooncake", - }); - - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel); - expect(enableNotebookBtn).toBeUndefined(); - }); - - it("Notebooks is not enabled but is available - button should be shown and enabled", () => { - useNotebook.getState().setIsNotebooksEnabledForAccount(true); - - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel); - - //TODO: modify once notebooks are available - expect(enableNotebookBtn).toBeUndefined(); - //expect(enableNotebookBtn).toBeDefined(); - //expect(enableNotebookBtn.disabled).toBe(false); - //expect(enableNotebookBtn.tooltipText).toBe(""); - }); - - it("Notebooks is not enabled and is unavailable - button should be shown and disabled", () => { - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel); - - //TODO: modify once notebooks are available - expect(enableNotebookBtn).toBeUndefined(); - //expect(enableNotebookBtn).toBeDefined(); - //expect(enableNotebookBtn.disabled).toBe(true); - //expect(enableNotebookBtn.tooltipText).toBe( - // "Notebooks are not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks." - //); - }); - }); - - describe("Open Mongo shell button", () => { - const openMongoShellBtnLabel = "Open Mongo shell"; - const selectedNodeState = useSelectedNode.getState(); - - beforeAll(() => { - mockExplorer = {} as Explorer; - updateUserContext({ - databaseAccount: { - properties: { - capabilities: [{ name: "EnableTable" }], - }, - } as DatabaseAccount, - }); - }); - - afterAll(() => { - updateUserContext({ - apiType: "SQL", - }); - useNotebook.getState().setIsShellEnabled(false); - }); - - beforeEach(() => { - updateUserContext({ - apiType: "Mongo", - }); - useNotebook.getState().setIsShellEnabled(true); - }); - - afterEach(() => { - useNotebook.getState().setIsNotebookEnabled(false); - useNotebook.getState().setIsNotebooksEnabledForAccount(false); - }); - - it("Mongo Api not available - button should be hidden", () => { - updateUserContext({ - apiType: "SQL", - }); - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel); - expect(openMongoShellBtn).toBeUndefined(); - }); - - it("Running on a national cloud - button should be hidden", () => { - updateUserContext({ - portalEnv: "mooncake", - }); - - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel); - expect(openMongoShellBtn).toBeUndefined(); - }); - - it("Notebooks is not enabled and is unavailable - button should be hidden", () => { - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel); - expect(openMongoShellBtn).toBeUndefined(); - }); - - it("Notebooks is not enabled and is available - button should be hidden", () => { - useNotebook.getState().setIsNotebooksEnabledForAccount(true); - - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel); - expect(openMongoShellBtn).toBeUndefined(); - }); - - it("Notebooks is enabled and is unavailable - button should be shown and enabled", () => { - useNotebook.getState().setIsNotebookEnabled(true); - - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel); - expect(openMongoShellBtn).toBeDefined(); - - //TODO: modify once notebooks are available - expect(openMongoShellBtn.disabled).toBe(true); - //expect(openMongoShellBtn.disabled).toBe(false); - //expect(openMongoShellBtn.tooltipText).toBe(""); - }); - - it("Notebooks is enabled and is available - button should be shown and enabled", () => { - useNotebook.getState().setIsNotebookEnabled(true); - useNotebook.getState().setIsNotebooksEnabledForAccount(true); - - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel); - expect(openMongoShellBtn).toBeDefined(); - - //TODO: modify once notebooks are available - expect(openMongoShellBtn.disabled).toBe(true); - //expect(openMongoShellBtn.disabled).toBe(false); - //expect(openMongoShellBtn.tooltipText).toBe(""); - }); - - it("Notebooks is enabled and is available, terminal is unavailable due to ipRules - button should be hidden", () => { - useNotebook.getState().setIsNotebookEnabled(true); - useNotebook.getState().setIsNotebooksEnabledForAccount(true); - useNotebook.getState().setIsShellEnabled(false); - - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel); - expect(openMongoShellBtn).toBeUndefined(); - }); - }); - describe("Open Cassandra shell button", () => { const openCassandraShellBtnLabel = "Open Cassandra shell"; const selectedNodeState = useSelectedNode.getState(); @@ -305,42 +128,6 @@ describe("CommandBarComponentButtonFactory tests", () => { const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel); expect(openCassandraShellBtn).toBeUndefined(); }); - - it("Notebooks is not enabled and is available - button should be shown and enabled", () => { - useNotebook.getState().setIsNotebooksEnabledForAccount(true); - - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel); - expect(openCassandraShellBtn).toBeUndefined(); - }); - - it("Notebooks is enabled and is unavailable - button should be shown and enabled", () => { - useNotebook.getState().setIsNotebookEnabled(true); - - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel); - - expect(openCassandraShellBtn).toBeDefined(); - - //TODO: modify once notebooks are available - expect(openCassandraShellBtn.disabled).toBe(true); - //expect(openCassandraShellBtn.disabled).toBe(false); - //expect(openCassandraShellBtn.tooltipText).toBe(""); - }); - - it("Notebooks is enabled and is available - button should be shown and enabled", () => { - useNotebook.getState().setIsNotebookEnabled(true); - useNotebook.getState().setIsNotebooksEnabledForAccount(true); - - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel); - expect(openCassandraShellBtn).toBeDefined(); - - //TODO: modify once notebooks are available - expect(openCassandraShellBtn.disabled).toBe(true); - //expect(openCassandraShellBtn.disabled).toBe(false); - //expect(openCassandraShellBtn.tooltipText).toBe(""); - }); }); describe("Open Postgres and vCore Mongo buttons", () => { @@ -368,62 +155,6 @@ describe("CommandBarComponentButtonFactory tests", () => { }); }); - describe("GitHub buttons", () => { - const connectToGitHubBtnLabel = "Connect to GitHub"; - const manageGitHubSettingsBtnLabel = "Manage GitHub settings"; - const selectedNodeState = useSelectedNode.getState(); - - beforeAll(() => { - mockExplorer = {} as Explorer; - updateUserContext({ - databaseAccount: { - properties: { - capabilities: [{ name: "EnableTable" }], - }, - } as DatabaseAccount, - }); - - mockExplorer.notebookManager = new NotebookManager(); - mockExplorer.notebookManager.gitHubOAuthService = new GitHubOAuthService(undefined); - }); - - afterEach(() => { - jest.resetAllMocks(); - useNotebook.getState().setIsNotebookEnabled(false); - }); - - it("Notebooks is enabled and GitHubOAuthService is not logged in - connect to github button should be visible", () => { - useNotebook.getState().setIsNotebookEnabled(true); - - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const connectToGitHubBtn = buttons.find((button) => button.commandButtonLabel === connectToGitHubBtnLabel); - expect(connectToGitHubBtn).toBeDefined(); - }); - - it("Notebooks is enabled and GitHubOAuthService is logged in - manage github settings button should be visible", () => { - useNotebook.getState().setIsNotebookEnabled(true); - mockExplorer.notebookManager.gitHubOAuthService.isLoggedIn = jest.fn().mockReturnValue(true); - - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const manageGitHubSettingsBtn = buttons.find( - (button) => button.commandButtonLabel === manageGitHubSettingsBtnLabel, - ); - expect(manageGitHubSettingsBtn).toBeDefined(); - }); - - it("Notebooks is not enabled - connect to github and manage github settings buttons should be hidden", () => { - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - - const connectToGitHubBtn = buttons.find((button) => button.commandButtonLabel === connectToGitHubBtnLabel); - expect(connectToGitHubBtn).toBeUndefined(); - - const manageGitHubSettingsBtn = buttons.find( - (button) => button.commandButtonLabel === manageGitHubSettingsBtnLabel, - ); - expect(manageGitHubSettingsBtn).toBeUndefined(); - }); - }); - describe("Resource token", () => { const mockCollection = { id: ko.observable("test") } as CollectionBase; useSelectedNode.getState().setSelectedNode(mockCollection); diff --git a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx index 5e9de9cde..a1aa3e49b 100644 --- a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx +++ b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx @@ -1,3 +1,5 @@ +import { KeyboardAction } from "KeyboardShortcuts"; +import { ReactTabKind, useTabs } from "hooks/useTabs"; import * as React from "react"; import AddCollectionIcon from "../../../../images/AddCollection.svg"; import AddDatabaseIcon from "../../../../images/AddDatabase.svg"; @@ -6,13 +8,10 @@ import AddStoredProcedureIcon from "../../../../images/AddStoredProcedure.svg"; import AddTriggerIcon from "../../../../images/AddTrigger.svg"; import AddUdfIcon from "../../../../images/AddUdf.svg"; import BrowseQueriesIcon from "../../../../images/BrowseQuery.svg"; -import CosmosTerminalIcon from "../../../../images/Cosmos-Terminal.svg"; import FeedbackIcon from "../../../../images/Feedback-Command.svg"; +import HomeIcon from "../../../../images/Home_16.svg"; import HostedTerminalIcon from "../../../../images/Hosted-Terminal.svg"; import OpenQueryFromDiskIcon from "../../../../images/OpenQueryFromDisk.svg"; -import GitHubIcon from "../../../../images/github.svg"; -import NewNotebookIcon from "../../../../images/notebook/Notebook-new.svg"; -import ResetWorkspaceIcon from "../../../../images/notebook/Notebook-reset-workspace.svg"; import OpenInTabIcon from "../../../../images/open-in-tab.svg"; import SettingsIcon from "../../../../images/settings_15x15.svg"; import SynapseIcon from "../../../../images/synapse-link.svg"; @@ -20,7 +19,6 @@ import { AuthType } from "../../../AuthType"; import * as Constants from "../../../Common/Constants"; import { Platform, configContext } from "../../../ConfigContext"; import * as ViewModels from "../../../Contracts/ViewModels"; -import { JunoClient } from "../../../Juno/JunoClient"; import { userContext } from "../../../UserContext"; import { getCollectionName, getDatabaseName } from "../../../Utils/APITypeUtils"; import { isRunningOnNationalCloud } from "../../../Utils/CloudUtils"; @@ -31,7 +29,6 @@ import { useNotebook } from "../../Notebook/useNotebook"; import { OpenFullScreen } from "../../OpenFullScreen"; import { AddDatabasePanel } from "../../Panes/AddDatabasePanel/AddDatabasePanel"; import { BrowseQueriesPane } from "../../Panes/BrowseQueriesPane/BrowseQueriesPane"; -import { GitHubReposPanel } from "../../Panes/GitHubReposPanel/GitHubReposPanel"; import { LoadQueryPane } from "../../Panes/LoadQueryPane/LoadQueryPane"; import { SettingsPane } from "../../Panes/SettingsPane/SettingsPane"; import { useDatabases } from "../../useDatabases"; @@ -57,7 +54,11 @@ export function createStaticCommandBarButtons( }; if (configContext.platform !== Platform.Fabric) { + const homeBtn = createHomeButton(); + buttons.push(homeBtn); + const newCollectionBtn = createNewCollectionGroup(container); + newCollectionBtn.keyboardAction = KeyboardAction.NEW_COLLECTION; // Just for the root button, not the child version we create below. buttons.push(newCollectionBtn); if (userContext.apiType !== "Tables" && userContext.apiType !== "Cassandra") { const addSynapseLink = createOpenSynapseLinkDialogButton(container); @@ -75,57 +76,6 @@ export function createStaticCommandBarButtons( } } - if (useNotebook.getState().isNotebookEnabled) { - addDivider(); - const notebookButtons: CommandButtonComponentProps[] = []; - - const newNotebookButton = createNewNotebookButton(container); - newNotebookButton.children = [createNewNotebookButton(container), createuploadNotebookButton(container)]; - notebookButtons.push(newNotebookButton); - - if (container.notebookManager?.gitHubOAuthService) { - notebookButtons.push(createManageGitHubAccountButton(container)); - } - if (useNotebook.getState().isPhoenixFeatures && configContext.isTerminalEnabled) { - notebookButtons.push(createOpenTerminalButton(container)); - } - if (useNotebook.getState().isPhoenixNotebooks && selectedNodeState.isConnectedToContainer()) { - notebookButtons.push(createNotebookWorkspaceResetButton(container)); - } - if ( - (userContext.apiType === "Mongo" && - useNotebook.getState().isShellEnabled && - selectedNodeState.isDatabaseNodeOrNoneSelected()) || - userContext.apiType === "Cassandra" - ) { - notebookButtons.push(createDivider()); - if (userContext.apiType === "Cassandra") { - notebookButtons.push(createOpenTerminalButtonByKind(container, ViewModels.TerminalKind.Cassandra)); - } else { - notebookButtons.push(createOpenTerminalButtonByKind(container, ViewModels.TerminalKind.Mongo)); - } - } - - notebookButtons.forEach((btn) => { - if (btn.commandButtonLabel.indexOf("Cassandra") !== -1) { - if (!useNotebook.getState().isPhoenixFeatures) { - applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.cassandraShellTemporarilyDownMsg); - } - } else if (btn.commandButtonLabel.indexOf("Mongo") !== -1) { - if (!useNotebook.getState().isPhoenixFeatures) { - applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.mongoShellTemporarilyDownMsg); - } - } else if (btn.commandButtonLabel.indexOf("Open Terminal") !== -1) { - if (!useNotebook.getState().isPhoenixFeatures) { - applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.temporarilyDownMsg); - } - } else if (!useNotebook.getState().isPhoenixNotebooks) { - applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.temporarilyDownMsg); - } - buttons.push(btn); - }); - } - if (!selectedNodeState.isDatabaseNodeOrNoneSelected()) { const isQuerySupported = userContext.apiType === "SQL" || userContext.apiType === "Gremlin"; @@ -135,7 +85,7 @@ export function createStaticCommandBarButtons( buttons.push(newSqlQueryBtn); } - if (isQuerySupported && selectedNodeState.findSelectedCollection()) { + if (isQuerySupported && selectedNodeState.findSelectedCollection() && configContext.platform !== Platform.Fabric) { const openQueryBtn = createOpenQueryButton(container); openQueryBtn.children = [createOpenQueryButton(container), createOpenQueryFromDiskButton()]; buttons.push(openQueryBtn); @@ -146,6 +96,7 @@ export function createStaticCommandBarButtons( const newStoredProcedureBtn: CommandButtonComponentProps = { iconSrc: AddStoredProcedureIcon, iconAlt: label, + keyboardAction: KeyboardAction.NEW_SPROC, onCommandClick: () => { const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection(); selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection); @@ -196,18 +147,22 @@ export function createContextCommandBarButtons( } export function createControlCommandBarButtons(container: Explorer): CommandButtonComponentProps[] { - const buttons: CommandButtonComponentProps[] = [ - { - iconSrc: SettingsIcon, - iconAlt: "Settings", - onCommandClick: () => useSidePanel.getState().openSidePanel("Settings", ), - commandButtonLabel: undefined, - ariaLabel: "Settings", - tooltipText: "Settings", - hasPopup: true, - disabled: false, - }, - ]; + const buttons: CommandButtonComponentProps[] = + configContext.platform === Platform.Fabric && userContext.fabricContext?.isReadOnly + ? [] + : [ + { + iconSrc: SettingsIcon, + iconAlt: "Settings", + onCommandClick: () => + useSidePanel.getState().openSidePanel("Settings", ), + commandButtonLabel: undefined, + ariaLabel: "Settings", + tooltipText: "Settings", + hasPopup: true, + disabled: false, + }, + ]; const showOpenFullScreen = configContext.platform === Platform.Portal && !isRunningOnNationalCloud() && userContext.apiType !== "Gremlin"; @@ -231,12 +186,12 @@ export function createControlCommandBarButtons(container: Explorer): CommandButt buttons.push(fullScreenButton); } - if (configContext.platform !== Platform.Emulator) { + if (configContext.platform === Platform.Portal) { const label = "Feedback"; const feedbackButtonOptions: CommandButtonComponentProps = { iconSrc: FeedbackIcon, iconAlt: label, - onCommandClick: () => container.provideFeedbackEmail(), + onCommandClick: () => container.openCESCVAFeedbackBlade(), commandButtonLabel: undefined, ariaLabel: label, tooltipText: label, @@ -281,6 +236,18 @@ function createNewCollectionGroup(container: Explorer): CommandButtonComponentPr }; } +function createHomeButton(): CommandButtonComponentProps { + const label = "Home"; + return { + iconSrc: HomeIcon, + iconAlt: label, + onCommandClick: () => useTabs.getState().openAndActivateReactTab(ReactTabKind.Home), + commandButtonLabel: label, + hasPopup: false, + ariaLabel: label, + }; +} + function createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonComponentProps { if (configContext.platform === Platform.Emulator) { return undefined; @@ -313,6 +280,7 @@ function createNewDatabase(container: Explorer): CommandButtonComponentProps { return { iconSrc: AddDatabaseIcon, iconAlt: label, + keyboardAction: KeyboardAction.NEW_DATABASE, onCommandClick: async () => { const throughputCap = userContext.databaseAccount?.properties.capacity?.totalThroughputLimit; if (throughputCap && throughputCap !== -1) { @@ -333,6 +301,7 @@ function createNewSQLQueryButton(selectedNodeState: SelectedNodeState): CommandB id: "newQueryBtn", iconSrc: AddSqlQueryIcon, iconAlt: label, + keyboardAction: KeyboardAction.NEW_QUERY, onCommandClick: () => { const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection(); selectedCollection && selectedCollection.onNewQueryClick(selectedCollection); @@ -348,6 +317,7 @@ function createNewSQLQueryButton(selectedNodeState: SelectedNodeState): CommandB id: "newQueryBtn", iconSrc: AddSqlQueryIcon, iconAlt: label, + keyboardAction: KeyboardAction.NEW_QUERY, onCommandClick: () => { const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection(); selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection); @@ -373,6 +343,7 @@ export function createScriptCommandButtons(selectedNodeState: SelectedNodeState) const newStoredProcedureBtn: CommandButtonComponentProps = { iconSrc: AddStoredProcedureIcon, iconAlt: label, + keyboardAction: KeyboardAction.NEW_SPROC, onCommandClick: () => { const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection(); selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection); @@ -392,6 +363,7 @@ export function createScriptCommandButtons(selectedNodeState: SelectedNodeState) const newUserDefinedFunctionBtn: CommandButtonComponentProps = { iconSrc: AddUdfIcon, iconAlt: label, + keyboardAction: KeyboardAction.NEW_UDF, onCommandClick: () => { const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection(); selectedCollection && selectedCollection.onNewUserDefinedFunctionClick(selectedCollection); @@ -411,6 +383,7 @@ export function createScriptCommandButtons(selectedNodeState: SelectedNodeState) const newTriggerBtn: CommandButtonComponentProps = { iconSrc: AddTriggerIcon, iconAlt: label, + keyboardAction: KeyboardAction.NEW_TRIGGER, onCommandClick: () => { const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection(); selectedCollection && selectedCollection.onNewTriggerClick(selectedCollection); @@ -428,45 +401,12 @@ export function createScriptCommandButtons(selectedNodeState: SelectedNodeState) return buttons; } -function applyNotebooksTemporarilyDownStyle(buttonProps: CommandButtonComponentProps, tooltip: string): void { - if (!buttonProps.isDivider) { - buttonProps.disabled = true; - buttonProps.tooltipText = tooltip; - } -} - -function createNewNotebookButton(container: Explorer): CommandButtonComponentProps { - const label = "New Notebook"; - return { - id: "newNotebookBtn", - iconSrc: NewNotebookIcon, - iconAlt: label, - onCommandClick: () => container.onNewNotebookClicked(), - commandButtonLabel: label, - hasPopup: false, - disabled: useSelectedNode.getState().isQueryCopilotCollectionSelected(), - ariaLabel: label, - }; -} - -function createuploadNotebookButton(container: Explorer): CommandButtonComponentProps { - const label = "Upload to Notebook Server"; - return { - iconSrc: NewNotebookIcon, - iconAlt: label, - onCommandClick: () => container.openUploadFilePanel(), - commandButtonLabel: label, - hasPopup: false, - disabled: useSelectedNode.getState().isQueryCopilotCollectionSelected(), - ariaLabel: label, - }; -} - function createOpenQueryButton(container: Explorer): CommandButtonComponentProps { const label = "Open Query"; return { iconSrc: BrowseQueriesIcon, iconAlt: label, + keyboardAction: KeyboardAction.OPEN_QUERY, onCommandClick: () => useSidePanel.getState().openSidePanel("Open Saved Queries", ), commandButtonLabel: label, @@ -481,6 +421,7 @@ function createOpenQueryFromDiskButton(): CommandButtonComponentProps { return { iconSrc: OpenQueryFromDiskIcon, iconAlt: label, + keyboardAction: KeyboardAction.OPEN_QUERY_FROM_DISK, onCommandClick: () => useSidePanel.getState().openSidePanel("Load Query", ), commandButtonLabel: label, ariaLabel: label, @@ -489,19 +430,6 @@ function createOpenQueryFromDiskButton(): CommandButtonComponentProps { }; } -function createOpenTerminalButton(container: Explorer): CommandButtonComponentProps { - const label = "Open Terminal"; - return { - iconSrc: CosmosTerminalIcon, - iconAlt: label, - onCommandClick: () => container.openNotebookTerminal(ViewModels.TerminalKind.Default), - commandButtonLabel: label, - hasPopup: false, - disabled: useSelectedNode.getState().isQueryCopilotCollectionSelected(), - ariaLabel: label, - }; -} - function createOpenTerminalButtonByKind( container: Explorer, terminalKind: ViewModels.TerminalKind, @@ -541,45 +469,6 @@ function createOpenTerminalButtonByKind( }; } -function createNotebookWorkspaceResetButton(container: Explorer): CommandButtonComponentProps { - const label = "Reset Workspace"; - return { - iconSrc: ResetWorkspaceIcon, - iconAlt: label, - onCommandClick: () => container.resetNotebookWorkspace(), - commandButtonLabel: label, - hasPopup: false, - disabled: useSelectedNode.getState().isQueryCopilotCollectionSelected(), - ariaLabel: label, - }; -} - -function createManageGitHubAccountButton(container: Explorer): CommandButtonComponentProps { - const connectedToGitHub: boolean = container.notebookManager?.gitHubOAuthService.isLoggedIn(); - const label = connectedToGitHub ? "Manage GitHub settings" : "Connect to GitHub"; - const junoClient = new JunoClient(); - return { - iconSrc: GitHubIcon, - iconAlt: label, - onCommandClick: () => { - useSidePanel - .getState() - .openSidePanel( - label, - , - ); - }, - commandButtonLabel: label, - hasPopup: false, - disabled: useSelectedNode.getState().isQueryCopilotCollectionSelected(), - ariaLabel: label, - }; -} - function createStaticCommandBarButtonsForResourceToken( container: Explorer, selectedNodeState: SelectedNodeState, diff --git a/src/Explorer/Menus/CommandBar/CommandBarUtil.tsx b/src/Explorer/Menus/CommandBar/CommandBarUtil.tsx index 570a7ad1f..7378be453 100644 --- a/src/Explorer/Menus/CommandBar/CommandBarUtil.tsx +++ b/src/Explorer/Menus/CommandBar/CommandBarUtil.tsx @@ -7,6 +7,7 @@ import { IDropdownStyles, } from "@fluentui/react"; import { useQueryCopilot } from "hooks/useQueryCopilot"; +import { KeyboardHandlerMap } from "KeyboardShortcuts"; import * as React from "react"; import _ from "underscore"; import ChevronDownIcon from "../../../../images/Chevron_down.svg"; @@ -25,7 +26,10 @@ import { MemoryTracker } from "./MemoryTrackerComponent"; * @param btns */ export const convertButton = (btns: CommandButtonComponentProps[], backgroundColor: string): ICommandBarItemProps[] => { - const buttonHeightPx = StyleConstants.CommandBarButtonHeight; + const buttonHeightPx = + configContext.platform == Platform.Fabric + ? StyleConstants.FabricCommandBarButtonHeight + : StyleConstants.CommandBarButtonHeight; const hoverColor = configContext.platform == Platform.Fabric ? StyleConstants.FabricAccentLight : StyleConstants.AccentLight; @@ -34,7 +38,7 @@ export const convertButton = (btns: CommandButtonComponentProps[], backgroundCol if (isDisabled) { return StyleConstants.GrayScale; } - return configContext.platform == Platform.Fabric ? StyleConstants.NoColor : undefined; + return configContext.platform == Platform.Fabric ? StyleConstants.FabricToolbarIconColor : undefined; }; return btns @@ -56,21 +60,23 @@ export const convertButton = (btns: CommandButtonComponentProps[], backgroundCol imageProps: btn.iconSrc ? { src: btn.iconSrc, alt: btn.iconAlt } : undefined, iconName: btn.iconName, }, - onClick: (ev?: React.MouseEvent | React.KeyboardEvent) => { - btn.onCommandClick(ev); - let copilotEnabled = false; - if (useQueryCopilot.getState().copilotEnabled && useQueryCopilot.getState().copilotUserDBEnabled) { - copilotEnabled = useQueryCopilot.getState().copilotEnabledforExecution; - } - TelemetryProcessor.trace(Action.ClickCommandBarButton, ActionModifiers.Mark, { label, copilotEnabled }); - }, + onClick: btn.onCommandClick + ? (ev?: React.MouseEvent | React.KeyboardEvent) => { + btn.onCommandClick(ev); + let copilotEnabled = false; + if (useQueryCopilot.getState().copilotEnabled && useQueryCopilot.getState().copilotUserDBEnabled) { + copilotEnabled = useQueryCopilot.getState().copilotEnabledforExecution; + } + TelemetryProcessor.trace(Action.ClickCommandBarButton, ActionModifiers.Mark, { label, copilotEnabled }); + } + : undefined, key: `${btn.commandButtonLabel}${index}`, text: label, - "data-test": label, title: btn.tooltipText, name: label, disabled: btn.disabled, ariaLabel: btn.ariaLabel, + "data-test": `CommandBar/Button:${label}`, buttonStyles: { root: { backgroundColor: backgroundColor, @@ -93,7 +99,12 @@ export const convertButton = (btns: CommandButtonComponentProps[], backgroundCol }, width: 16, }, - label: { fontSize: StyleConstants.mediumFontSize }, + label: { + fontSize: + configContext.platform == Platform.Fabric + ? StyleConstants.DefaultFontSize + : StyleConstants.mediumFontSize, + }, rootHovered: { backgroundColor: hoverColor }, rootPressed: { backgroundColor: hoverColor }, splitButtonMenuButtonExpanded: { @@ -112,6 +123,7 @@ export const convertButton = (btns: CommandButtonComponentProps[], backgroundCol splitButtonContainer: { marginLeft: 5, marginRight: 5, + height: buttonHeightPx, }, }, className: btn.className, @@ -129,7 +141,12 @@ export const convertButton = (btns: CommandButtonComponentProps[], backgroundCol // TODO Figure out how to do it the proper way with subComponentStyles. // TODO Remove all this crazy styling once we adopt Ui-Fabric Azure themes selectors: { - ".ms-ContextualMenu-itemText": { fontSize: StyleConstants.mediumFontSize }, + ".ms-ContextualMenu-itemText": { + fontSize: + configContext.platform == Platform.Fabric + ? StyleConstants.DefaultFontSize + : StyleConstants.mediumFontSize, + }, ".ms-ContextualMenu-link:hover": { backgroundColor: hoverColor }, ".ms-ContextualMenu-icon": { width: 16, height: 16 }, }, @@ -219,3 +236,28 @@ export const createConnectionStatus = (container: Explorer, poolId: PoolIdType, onRender: () => , }; }; + +export function createKeyboardHandlers(allButtons: CommandButtonComponentProps[]): KeyboardHandlerMap { + const handlers: KeyboardHandlerMap = {}; + + function createHandlers(buttons: CommandButtonComponentProps[]) { + buttons.forEach((button) => { + if (!button.disabled && button.keyboardAction) { + handlers[button.keyboardAction] = (e) => { + button.onCommandClick(e); + + // If the handler is bound, it means the button is visible and enabled, so we should prevent the default action + return true; + }; + } + + if (button.children && button.children.length > 0) { + createHandlers(button.children); + } + }); + } + + createHandlers(allButtons); + + return handlers; +} diff --git a/src/Explorer/Menus/NotificationConsole/NotificationConsoleComponent.tsx b/src/Explorer/Menus/NotificationConsole/NotificationConsoleComponent.tsx index b66d344cc..7cefa9ac2 100644 --- a/src/Explorer/Menus/NotificationConsole/NotificationConsoleComponent.tsx +++ b/src/Explorer/Menus/NotificationConsole/NotificationConsoleComponent.tsx @@ -162,6 +162,7 @@ export class NotificationConsoleComponent extends React.Component< role="button" onKeyDown={(event: React.KeyboardEvent) => this.onClearNotificationsKeyPress(event)} tabIndex={0} + style={{ border: "1px solid black", borderRadius: "2px" }} > clear notifications image Clear Notifications diff --git a/src/Explorer/Menus/NotificationConsole/__snapshots__/NotificationConsoleComponent.test.tsx.snap b/src/Explorer/Menus/NotificationConsole/__snapshots__/NotificationConsoleComponent.test.tsx.snap index 8b8b2bdeb..f702107b8 100644 --- a/src/Explorer/Menus/NotificationConsole/__snapshots__/NotificationConsoleComponent.test.tsx.snap +++ b/src/Explorer/Menus/NotificationConsole/__snapshots__/NotificationConsoleComponent.test.tsx.snap @@ -146,6 +146,12 @@ exports[`NotificationConsoleComponent renders the console 1`] = ` onClick={[Function]} onKeyDown={[Function]} role="button" + style={ + Object { + "border": "1px solid black", + "borderRadius": "2px", + } + } tabIndex={0} > { - if (!action.collectionResourceId && collections.length === 0) { - subscription.dispose(); - openCollectionTab(action, databases, ++i); - return; - } - - for (let j = 0; j < collections.length; j++) { - const collection: ViewModels.Collection = collections[j]; - if (!!action.collectionResourceId && collection.id() !== action.collectionResourceId) { - continue; - } - - // select the collection - collection.expandCollection(); - - if ( - action.tabKind === ActionContracts.TabKind.SQLDocuments || - action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.SQLDocuments] - ) { - collection.onDocumentDBDocumentsClick(); - break; - } - - if ( - action.tabKind === ActionContracts.TabKind.MongoDocuments || - action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.MongoDocuments] - ) { - collection.onMongoDBDocumentsClick(); - break; - } - - if ( - action.tabKind === ActionContracts.TabKind.SchemaAnalyzer || - action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.SchemaAnalyzer] - ) { - collection.onSchemaAnalyzerClick(); - break; - } - - if ( - action.tabKind === ActionContracts.TabKind.TableEntities || - action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.TableEntities] - ) { - collection.onTableEntitiesClick(); - break; - } - - if ( - action.tabKind === ActionContracts.TabKind.Graph || - action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.Graph] - ) { - collection.onGraphDocumentsClick(); - break; - } - - if ( - action.tabKind === ActionContracts.TabKind.SQLQuery || - action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.SQLQuery] - ) { - collection.onNewQueryClick( - collection, - undefined, - generateQueryText(action as ActionContracts.OpenQueryTab, collection.partitionKeyProperties), - ); - break; - } - - if ( - action.tabKind === ActionContracts.TabKind.ScaleSettings || - action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.ScaleSettings] - ) { - collection.onSettingsClick(); - break; - } - } - subscription.dispose(); + //if databases are not yet loaded, wait until loaded + if (!databases || databases.length === 0) { + const databaseActionHandler = (databases: ViewModels.Database[]) => { + databasesUnsubscription(); + openCollectionTab(action, databases, 0); + return; }; + const databasesUnsubscription = useDatabases.subscribe(databaseActionHandler, (state) => state.databases); + } else { + for (let i = initialDatabaseIndex; i < databases.length; i++) { + const database: ViewModels.Database = databases[i]; + if (!!action.databaseResourceId && database.id() !== action.databaseResourceId) { + continue; + } - const subscription = database.collections.subscribe((collections) => collectionActionHandler(collections)); - if (database.collections && database.collections() && database.collections().length) { - collectionActionHandler(database.collections()); + //expand database first if not expanded to load the collections + if (!database.isDatabaseExpanded?.()) { + database.expandDatabase?.(); + } + + const collectionActionHandler = (collections: ViewModels.Collection[]) => { + if (!action.collectionResourceId && collections.length === 0) { + subscription.dispose(); + openCollectionTab(action, databases, ++i); + return; + } + + for (let j = 0; j < collections.length; j++) { + const collection: ViewModels.Collection = collections[j]; + if (!!action.collectionResourceId && collection.id() !== action.collectionResourceId) { + continue; + } + + // select the collection + collection.expandCollection(); + + if ( + action.tabKind === ActionContracts.TabKind.SQLDocuments || + action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.SQLDocuments] + ) { + collection.onDocumentDBDocumentsClick(); + break; + } + + if ( + action.tabKind === ActionContracts.TabKind.MongoDocuments || + action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.MongoDocuments] + ) { + collection.onMongoDBDocumentsClick(); + break; + } + + if ( + action.tabKind === ActionContracts.TabKind.SchemaAnalyzer || + action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.SchemaAnalyzer] + ) { + collection.onSchemaAnalyzerClick(); + break; + } + + if ( + action.tabKind === ActionContracts.TabKind.TableEntities || + action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.TableEntities] + ) { + collection.onTableEntitiesClick(); + break; + } + + if ( + action.tabKind === ActionContracts.TabKind.Graph || + action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.Graph] + ) { + collection.onGraphDocumentsClick(); + break; + } + + if ( + action.tabKind === ActionContracts.TabKind.SQLQuery || + action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.SQLQuery] + ) { + collection.onNewQueryClick( + collection, + undefined, + generateQueryText(action as ActionContracts.OpenQueryTab, collection.partitionKeyProperties), + ); + break; + } + + if ( + action.tabKind === ActionContracts.TabKind.ScaleSettings || + action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.ScaleSettings] + ) { + collection.onSettingsClick(); + break; + } + } + subscription.dispose(); + }; + + const subscription = database.collections.subscribe((collections) => collectionActionHandler(collections)); + if (database.collections && database.collections() && database.collections().length) { + collectionActionHandler(database.collections()); + } + + break; } - - break; } } @@ -154,7 +170,7 @@ function openPane(action: ActionContracts.OpenPane, explorer: Explorer) { action.paneKind === ActionContracts.PaneKind.GlobalSettings || action.paneKind === ActionContracts.PaneKind[ActionContracts.PaneKind.GlobalSettings] ) { - useSidePanel.getState().openSidePanel("Settings", ); + useSidePanel.getState().openSidePanel("Settings", ); } } diff --git a/src/Explorer/Panes/AddCollectionPanel.tsx b/src/Explorer/Panes/AddCollectionPanel.tsx index 2a99a56a7..a3a01d5fb 100644 --- a/src/Explorer/Panes/AddCollectionPanel.tsx +++ b/src/Explorer/Panes/AddCollectionPanel.tsx @@ -21,6 +21,7 @@ import { getErrorMessage, getErrorStack } from "Common/ErrorHandlingUtils"; import { configContext, Platform } from "ConfigContext"; import * as DataModels from "Contracts/DataModels"; import { SubscriptionType } from "Contracts/SubscriptionType"; +import { AddVectorEmbeddingPolicyForm } from "Explorer/Panes/VectorSearchPanel/AddVectorEmbeddingPolicyForm"; import { useSidePanel } from "hooks/useSidePanel"; import { useTeachingBubble } from "hooks/useTeachingBubble"; import React from "react"; @@ -29,7 +30,7 @@ import { Action } from "Shared/Telemetry/TelemetryConstants"; import * as TelemetryProcessor from "Shared/Telemetry/TelemetryProcessor"; import { userContext } from "UserContext"; import { getCollectionName } from "Utils/APITypeUtils"; -import { isCapabilityEnabled, isServerlessAccount } from "Utils/CapabilityUtils"; +import { isCapabilityEnabled, isServerlessAccount, isVectorSearchEnabled } from "Utils/CapabilityUtils"; import { getUpsellMessage } from "Utils/PricingUtils"; import { CollapsibleSectionComponent } from "../Controls/CollapsiblePanel/CollapsibleSectionComponent"; import { ThroughputInput } from "../Controls/ThroughputInput/ThroughputInput"; @@ -81,6 +82,10 @@ export const AllPropertiesIndexed: DataModels.IndexingPolicy = { excludedPaths: [], }; +export const DefaultVectorEmbeddingPolicy: DataModels.VectorEmbeddingPolicy = { + vectorEmbeddings: [], +}; + export interface AddCollectionPanelState { createNewDatabase: boolean; newDatabaseId: string; @@ -101,6 +106,9 @@ export interface AddCollectionPanelState { isExecuting: boolean; isThroughputCapExceeded: boolean; teachingBubbleStep: number; + vectorIndexingPolicy: DataModels.VectorIndex[]; + vectorEmbeddingPolicy: DataModels.VectorEmbedding[]; + vectorPolicyValidated: boolean; } export class AddCollectionPanel extends React.Component { @@ -136,6 +144,9 @@ export class AddCollectionPanel extends React.Component +
{this.state.errorMessage && ( - - {this.getPartitionKeySubtext()} - + {this.getPartitionKeySubtext()} @@ -863,17 +878,44 @@ export class AddCollectionPanel extends React.Component )} - + {this.shouldShowVectorSearchParameters() && ( + + { + this.scrollToSection("collapsibleVectorPolicySectionContent"); + }} + tooltipContent={this.getContainerVectorPolicyTooltipContent()} + > + + + { + this.setState({ vectorEmbeddingPolicy, vectorIndexingPolicy, vectorPolicyValidated }); + }} + /> + + + + + )} {userContext.apiType !== "Tables" && ( { TelemetryProcessor.traceOpen(Action.ExpandAddCollectionPaneAdvancedSection); - this.scrollToAdvancedSection(); + this.scrollToSection("collapsibleAdvancedSectionContent"); }} > - + {isCapabilityEnabled("EnableMongo") && !isCapabilityEnabled("EnableMongo16MBDocumentSupport") && ( @@ -924,10 +966,9 @@ export class AddCollectionPanel extends React.Component - To ensure compatibility with - older SDKs, the created container will use a legacy partitioning scheme that supports partition - key values of size only up to 101 bytes. If this is enabled, you will not be able to use - hierarchical partition keys.{" "} + To ensure compatibility with older SDKs, the + created container will use a legacy partitioning scheme that supports partition key values of size + only up to 101 bytes. If this is enabled, you will not be able to use hierarchical partition keys.{" "} Learn more @@ -1070,6 +1111,18 @@ export class AddCollectionPanel extends React.Component + Describe any properties in your data that contain vectors, so that they can be made available for similarity + queries.{" "} + + Learn more + + + ); + } + private shouldShowCollectionThroughputInput(): boolean { if (isServerlessAccount()) { return false; @@ -1209,6 +1274,10 @@ export class AddCollectionPanel extends React.Component = ({ }, subscriptionType: SubscriptionType[subscriptionType], subscriptionQuotaId: userContext.quotaId, - defaultsCheck: { - flight: userContext.addCollectionFlight, - }, dataExplorerArea: Constants.Areas.ContextualPane, }; @@ -75,7 +72,6 @@ export const AddDatabasePanel: FunctionComponent = ({ subscriptionQuotaId: userContext.quotaId, defaultsCheck: { throughput, - flight: userContext.addCollectionFlight, }, dataExplorerArea: Constants.Areas.ContextualPane, }; diff --git a/src/Explorer/Panes/CassandraAddCollectionPane/CassandraAddCollectionPane.tsx b/src/Explorer/Panes/CassandraAddCollectionPane/CassandraAddCollectionPane.tsx index 0dd145af9..9332652e9 100644 --- a/src/Explorer/Panes/CassandraAddCollectionPane/CassandraAddCollectionPane.tsx +++ b/src/Explorer/Panes/CassandraAddCollectionPane/CassandraAddCollectionPane.tsx @@ -59,7 +59,6 @@ export const CassandraAddCollectionPane: FunctionComponent Enter CQL command to create the table.{" "} - + Learn More @@ -293,8 +292,8 @@ export const CassandraAddCollectionPane: FunctionComponent Promise; +} + +export const ChangePartitionKeyPane: React.FC = ({ + sourceDatabase, + sourceCollection, + explorer, + onClose, +}) => { + const [targetCollectionId, setTargetCollectionId] = React.useState(); + const [createNewContainer, setCreateNewContainer] = React.useState(true); + const [formError, setFormError] = React.useState(); + const [isExecuting, setIsExecuting] = React.useState(false); + const [subPartitionKeys, setSubPartitionKeys] = React.useState([]); + const [partitionKey, setPartitionKey] = React.useState(); + + const getCollectionOptions = (): IDropdownOption[] => { + return sourceDatabase + .collections() + .filter((collection) => collection.id !== sourceCollection.id) + .map((collection) => ({ + key: collection.id(), + text: collection.id(), + })); + }; + + const submit = async () => { + if (!validateInputs()) { + return; + } + setIsExecuting(true); + try { + createNewContainer && (await createContainer()); + await createDataTransferJob(); + await onClose(); + } catch (error) { + handleError(error, "ChangePartitionKey", "Failed to start data transfer job"); + } + setIsExecuting(false); + useSidePanel.getState().closeSidePanel(); + }; + + const validateInputs = (): boolean => { + if (!createNewContainer && !targetCollectionId) { + setFormError("Choose an existing container"); + return false; + } + return true; + }; + + const createDataTransferJob = async () => { + const jobName = `Portal_${targetCollectionId}_${Math.floor(Date.now() / 1000)}`; + const dataTransferParams: DataTransferParams = { + jobName, + apiType: userContext.apiType, + subscriptionId: userContext.subscriptionId, + resourceGroupName: userContext.resourceGroup, + accountName: userContext.databaseAccount.name, + sourceDatabaseName: sourceDatabase.id(), + sourceCollectionName: sourceCollection.id(), + targetDatabaseName: sourceDatabase.id(), + targetCollectionName: targetCollectionId, + }; + await initiateDataTransfer(dataTransferParams); + }; + + const createContainer = async () => { + const partitionKeyString = partitionKey.trim(); + const partitionKeyData: DataModels.PartitionKey = partitionKeyString + ? { + paths: [partitionKeyString, ...(subPartitionKeys.length > 0 ? subPartitionKeys : [])], + kind: subPartitionKeys.length > 0 ? "MultiHash" : "Hash", + version: 2, + } + : undefined; + + const createCollectionParams: DataModels.CreateCollectionParams = { + createNewDatabase: false, + collectionId: targetCollectionId, + databaseId: sourceDatabase.id(), + databaseLevelThroughput: isSelectedDatabaseSharedThroughput(), + offerThroughput: sourceCollection.offer()?.manualThroughput, + autoPilotMaxThroughput: sourceCollection.offer()?.autoscaleMaxThroughput, + partitionKey: partitionKeyData, + }; + await createCollection(createCollectionParams); + await explorer.refreshAllDatabases(); + }; + + const isSelectedDatabaseSharedThroughput = (): boolean => { + const selectedDatabase = useDatabases + .getState() + .databases?.find((database) => database.id() === sourceDatabase.id()); + return !!selectedDatabase?.offer(); + }; + + return ( + + + + When changing a container’s partition key, you will need to create a destination container with the correct + partition key. You may also select an existing destination container.  + + Learn more + + + + + + + Database id + + + + + + + + +
+ setCreateNewContainer(true)} + /> + New container + + setCreateNewContainer(false)} + /> + Existing container +
+
+ {createNewContainer ? ( + + All configurations except for unique keys will be copied from the source container + + + + + {`${getCollectionName()} id`} + + + + + + ) => setTargetCollectionId(event.target.value)} + /> + + + + + + {getPartitionKeyName(userContext.apiType)} + + + + + + + + {getPartitionKeySubtext(userContext.features.partitionKeyDefault, userContext.apiType)} + + + ) => { + if (!partitionKey && !event.target.value.startsWith("/")) { + setPartitionKey("/" + event.target.value); + } else { + setPartitionKey(event.target.value); + } + }} + /> + {subPartitionKeys.map((subPartitionKey: string, index: number) => { + return ( + +
+ 0 ? 1 : 0} + className="panelTextField" + autoComplete="off" + placeholder={getPartitionKeyPlaceHolder(userContext.apiType, index)} + aria-label={getPartitionKeyName(userContext.apiType)} + pattern={".*"} + title={""} + value={subPartitionKey} + onChange={(event: React.ChangeEvent) => { + const keys = [...subPartitionKeys]; + if (!keys[index] && !event.target.value.startsWith("/")) { + keys[index] = "/" + event.target.value.trim(); + setSubPartitionKeys(keys); + } else { + keys[index] = event.target.value.trim(); + setSubPartitionKeys(keys); + } + }} + /> + { + const keys = subPartitionKeys.filter((uniqueKey, j) => index !== j); + setSubPartitionKeys(keys); + }} + /> +
+ ); + })} + + = Constants.BackendDefaults.maxNumMultiHashPartition} + onClick={() => setSubPartitionKeys([...subPartitionKeys, ""])} + > + Add hierarchical partition key + + {subPartitionKeys.length > 0 && ( + + This feature allows you to + partition your data with up to three levels of keys for better data distribution. Requires .NET V3, + Java V4 SDK, or preview JavaScript V3 SDK.{" "} + + Learn more + + + )} + +
+
+ ) : ( + + + + + {`${getCollectionName()}`} + + + + + + + , collection: IDropdownOption) => { + setTargetCollectionId(collection.key as string); + setFormError(""); + }} + defaultSelectedKey={targetCollectionId} + responsiveMode={999} + /> + + )} +
+
+ ); +}; diff --git a/src/Explorer/Panes/DeleteCollectionConfirmationPane/__snapshots__/DeleteCollectionConfirmationPane.test.tsx.snap b/src/Explorer/Panes/DeleteCollectionConfirmationPane/__snapshots__/DeleteCollectionConfirmationPane.test.tsx.snap index e63cb45bd..66d7da606 100644 --- a/src/Explorer/Panes/DeleteCollectionConfirmationPane/__snapshots__/DeleteCollectionConfirmationPane.test.tsx.snap +++ b/src/Explorer/Panes/DeleteCollectionConfirmationPane/__snapshots__/DeleteCollectionConfirmationPane.test.tsx.snap @@ -379,6 +379,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect > + `; diff --git a/src/Explorer/Panes/DeleteDatabaseConfirmationPanel.tsx b/src/Explorer/Panes/DeleteDatabaseConfirmationPanel.tsx index 8e0f6c2a1..af291bba6 100644 --- a/src/Explorer/Panes/DeleteDatabaseConfirmationPanel.tsx +++ b/src/Explorer/Panes/DeleteDatabaseConfirmationPanel.tsx @@ -9,6 +9,7 @@ import { DefaultExperienceUtility } from "Shared/DefaultExperienceUtility"; import { Action, ActionModifiers } from "Shared/Telemetry/TelemetryConstants"; import * as TelemetryProcessor from "Shared/Telemetry/TelemetryProcessor"; import { userContext } from "UserContext"; +import { getDatabaseName } from "Utils/APITypeUtils"; import { logConsoleError } from "Utils/NotificationConsoleUtils"; import { useSidePanel } from "hooks/useSidePanel"; import { useTabs } from "hooks/useTabs"; @@ -37,11 +38,11 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent => { if (selectedDatabase?.id() && databaseInput !== selectedDatabase.id()) { setFormError( - `Input database name "${databaseInput}" does not match the selected database "${selectedDatabase.id()}"`, + `Input ${getDatabaseName()} name "${databaseInput}" does not match the selected ${getDatabaseName()} "${selectedDatabase.id()}"`, ); - logConsoleError(`Error while deleting collection ${selectedDatabase && selectedDatabase.id()}`); + logConsoleError(`Error while deleting ${getDatabaseName()} ${selectedDatabase && selectedDatabase.id()}`); logConsoleError( - `Input database name "${databaseInput}" does not match the selected database "${selectedDatabase.id()}"`, + `Input ${getDatabaseName()} name "${databaseInput}" does not match the selected ${getDatabaseName()} "${selectedDatabase.id()}"`, ); return; } @@ -123,17 +124,18 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent {!formError && }
* - Confirm by typing the database id + Confirm by typing the {getDatabaseName()} id { @@ -149,7 +151,7 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent - What is the reason why you are deleting this database? + What is the reason why you are deleting this {getDatabaseName()}? + `; diff --git a/src/Explorer/Panes/GitHubReposPanel/__snapshots__/GitHubReposPanel.test.tsx.snap b/src/Explorer/Panes/GitHubReposPanel/__snapshots__/GitHubReposPanel.test.tsx.snap index 4a3a8942e..19f98d5c6 100644 --- a/src/Explorer/Panes/GitHubReposPanel/__snapshots__/GitHubReposPanel.test.tsx.snap +++ b/src/Explorer/Panes/GitHubReposPanel/__snapshots__/GitHubReposPanel.test.tsx.snap @@ -18,7 +18,6 @@ exports[`GitHub Repos Panel should render Default properly 1`] = ` Object { "container": Explorer { "_isInitializingNotebooks": false, - "_resetNotebookWorkspace": [Function], "isFixedCollectionWithSharedThroughputSupported": [Function], "isTabsContentExpanded": [Function], "onRefreshDatabasesKeyPress": [Function], diff --git a/src/Explorer/Panes/PanelComponent.less b/src/Explorer/Panes/PanelComponent.less index 481c6ee64..3a0ab9b3c 100644 --- a/src/Explorer/Panes/PanelComponent.less +++ b/src/Explorer/Panes/PanelComponent.less @@ -48,6 +48,13 @@ font-size: @mediumFontSize; padding: 0 @LargeSpace 0 @SmallSpace; } + + .panelSectionSpinner { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + } } } diff --git a/src/Explorer/Panes/PanelContainerComponent.tsx b/src/Explorer/Panes/PanelContainerComponent.tsx index fa2db4f42..5795ec941 100644 --- a/src/Explorer/Panes/PanelContainerComponent.tsx +++ b/src/Explorer/Panes/PanelContainerComponent.tsx @@ -53,12 +53,13 @@ export class PanelContainerComponent extends React.Component = ( = ({ const handleOnSubmit = (event: React.FormEvent) => { event.preventDefault(); onSubmit(); + const screenReaderStatusElement = document.getElementById("screenReaderStatus"); + if (screenReaderStatusElement) { + screenReaderStatusElement.innerHTML = labelToLoadingItemName[submitButtonText] || "Loading"; + } }; return ( @@ -42,6 +47,7 @@ export const RightPaneForm: FunctionComponent = ({ /> )} + {isExecuting && } ); diff --git a/src/Explorer/Panes/RightPaneForm/__snapshots__/RightPaneForm.test.tsx.snap b/src/Explorer/Panes/RightPaneForm/__snapshots__/RightPaneForm.test.tsx.snap index bcfa8a9e6..fcc8e3678 100644 --- a/src/Explorer/Panes/RightPaneForm/__snapshots__/RightPaneForm.test.tsx.snap +++ b/src/Explorer/Panes/RightPaneForm/__snapshots__/RightPaneForm.test.tsx.snap @@ -21,6 +21,7 @@ exports[`Right Pane Form should render Default properly 1`] = ` > + `; diff --git a/src/Explorer/Panes/SettingsPane/SettingsPane.test.tsx b/src/Explorer/Panes/SettingsPane/SettingsPane.test.tsx index 4838ca858..25b0167f5 100644 --- a/src/Explorer/Panes/SettingsPane/SettingsPane.test.tsx +++ b/src/Explorer/Panes/SettingsPane/SettingsPane.test.tsx @@ -6,7 +6,7 @@ import { SettingsPane } from "./SettingsPane"; describe("Settings Pane", () => { it("should render Default properly", () => { - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); }); @@ -18,7 +18,7 @@ describe("Settings Pane", () => { }, } as DatabaseAccount, }); - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); }); }); diff --git a/src/Explorer/Panes/SettingsPane/SettingsPane.tsx b/src/Explorer/Panes/SettingsPane/SettingsPane.tsx index 4f7373fbb..320a10015 100644 --- a/src/Explorer/Panes/SettingsPane/SettingsPane.tsx +++ b/src/Explorer/Panes/SettingsPane/SettingsPane.tsx @@ -9,29 +9,50 @@ import { Toggle, } from "@fluentui/react"; import * as Constants from "Common/Constants"; +import { SplitterDirection } from "Common/Splitter"; import { InfoTooltip } from "Common/Tooltip/InfoTooltip"; import { configContext } from "ConfigContext"; -import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility"; +import { useDatabases } from "Explorer/useDatabases"; +import { + DefaultRUThreshold, + LocalStorageUtility, + StorageKey, + getDefaultQueryResultsView, + getRUThreshold, + ruThresholdEnabled as isRUThresholdEnabled, +} from "Shared/StorageUtility"; import * as StringUtility from "Shared/StringUtility"; import { userContext } from "UserContext"; import { logConsoleInfo } from "Utils/NotificationConsoleUtils"; import * as PriorityBasedExecutionUtils from "Utils/PriorityBasedExecutionUtils"; +import { useQueryCopilot } from "hooks/useQueryCopilot"; import { useSidePanel } from "hooks/useSidePanel"; import React, { FunctionComponent, useState } from "react"; +import Explorer from "../../Explorer"; import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm"; -export const SettingsPane: FunctionComponent = () => { +export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({ + explorer, +}: { + explorer: Explorer; +}): JSX.Element => { const closeSidePanel = useSidePanel((state) => state.closeSidePanel); const [isExecuting, setIsExecuting] = useState(false); + const [refreshExplorer, setRefreshExplorer] = useState(false); const [pageOption, setPageOption] = useState( LocalStorageUtility.getEntryNumber(StorageKey.ActualItemPerPage) === Constants.Queries.unlimitedItemsPerPage ? Constants.Queries.UnlimitedPageOption : Constants.Queries.CustomPageOption, ); + const [ruThresholdEnabled, setRUThresholdEnabled] = useState(isRUThresholdEnabled()); + const [ruThreshold, setRUThreshold] = useState(getRUThreshold()); const [queryTimeoutEnabled, setQueryTimeoutEnabled] = useState( LocalStorageUtility.getEntryBoolean(StorageKey.QueryTimeoutEnabled), ); const [queryTimeout, setQueryTimeout] = useState(LocalStorageUtility.getEntryNumber(StorageKey.QueryTimeout)); + const [defaultQueryResultsView, setDefaultQueryResultsView] = useState( + getDefaultQueryResultsView(), + ); const [automaticallyCancelQueryAfterTimeout, setAutomaticallyCancelQueryAfterTimeout] = useState( LocalStorageUtility.getEntryBoolean(StorageKey.AutomaticallyCancelQueryAfterTimeout), ); @@ -78,13 +99,20 @@ export const SettingsPane: FunctionComponent = () => { ? LocalStorageUtility.getEntryString(StorageKey.PriorityLevel) : Constants.PriorityLevel.Default, ); + const [copilotSampleDBEnabled, setCopilotSampleDBEnabled] = useState( + LocalStorageUtility.getEntryString(StorageKey.CopilotSampleDBEnabled) === "true", + ); const explorerVersion = configContext.gitSha; const shouldShowQueryPageOptions = userContext.apiType === "SQL"; const shouldShowGraphAutoVizOption = userContext.apiType === "Gremlin"; const shouldShowCrossPartitionOption = userContext.apiType !== "Gremlin"; const shouldShowParallelismOption = userContext.apiType !== "Gremlin"; const shouldShowPriorityLevelOption = PriorityBasedExecutionUtils.isFeatureEnabled(); - const handlerOnSubmit = () => { + const shouldShowCopilotSampleDBOption = + userContext.apiType === "SQL" && + useQueryCopilot.getState().copilotEnabled && + useDatabases.getState().sampleDataResourceTokenCollection; + const handlerOnSubmit = async () => { setIsExecuting(true); LocalStorageUtility.setEntryNumber( @@ -92,6 +120,7 @@ export const SettingsPane: FunctionComponent = () => { isCustomPageOptionSelected() ? customItemPerPage : Constants.Queries.unlimitedItemsPerPage, ); LocalStorageUtility.setEntryNumber(StorageKey.CustomItemPerPage, customItemPerPage); + LocalStorageUtility.setEntryBoolean(StorageKey.RUThresholdEnabled, ruThresholdEnabled); LocalStorageUtility.setEntryBoolean(StorageKey.QueryTimeoutEnabled, queryTimeoutEnabled); LocalStorageUtility.setEntryNumber(StorageKey.RetryAttempts, retryAttempts); LocalStorageUtility.setEntryNumber(StorageKey.RetryInterval, retryInterval); @@ -100,6 +129,8 @@ export const SettingsPane: FunctionComponent = () => { LocalStorageUtility.setEntryString(StorageKey.IsCrossPartitionQueryEnabled, crossPartitionQueryEnabled.toString()); LocalStorageUtility.setEntryNumber(StorageKey.MaxDegreeOfParellism, maxDegreeOfParallelism); LocalStorageUtility.setEntryString(StorageKey.PriorityLevel, priorityLevel.toString()); + LocalStorageUtility.setEntryString(StorageKey.CopilotSampleDBEnabled, copilotSampleDBEnabled.toString()); + LocalStorageUtility.setEntryString(StorageKey.DefaultQueryResultsView, defaultQueryResultsView); if (shouldShowGraphAutoVizOption) { LocalStorageUtility.setEntryBoolean( @@ -108,6 +139,10 @@ export const SettingsPane: FunctionComponent = () => { ); } + if (ruThresholdEnabled) { + LocalStorageUtility.setEntryNumber(StorageKey.RUThreshold, ruThreshold); + } + if (queryTimeoutEnabled) { LocalStorageUtility.setEntryNumber(StorageKey.QueryTimeout, queryTimeout); LocalStorageUtility.setEntryBoolean( @@ -139,6 +174,7 @@ export const SettingsPane: FunctionComponent = () => { logConsoleInfo( `Updated query setting to ${LocalStorageUtility.getEntryString(StorageKey.SetPartitionKeyUndefined)}`, ); + refreshExplorer && (await explorer.refreshExplorer()); closeSidePanel(); }; @@ -171,6 +207,11 @@ export const SettingsPane: FunctionComponent = () => { { key: Constants.PriorityLevel.High, text: "High" }, ]; + const defaultQueryResultsViewOptionList: IChoiceGroupOption[] = [ + { key: SplitterDirection.Vertical, text: "Vertical" }, + { key: SplitterDirection.Horizontal, text: "Horizontal" }, + ]; + const handleOnPriorityLevelOptionChange = ( ev: React.FormEvent, option: IChoiceGroupOption, @@ -182,6 +223,17 @@ export const SettingsPane: FunctionComponent = () => { setPageOption(option.key); }; + const handleOnRUThresholdToggleChange = (ev: React.MouseEvent, checked?: boolean): void => { + setRUThresholdEnabled(checked); + }; + + const handleOnRUThresholdSpinButtonChange = (ev: React.MouseEvent, newValue?: string): void => { + const ruThreshold = Number(newValue); + if (!isNaN(ruThreshold)) { + setRUThreshold(ruThreshold); + } + }; + const handleOnQueryTimeoutToggleChange = (ev: React.MouseEvent, checked?: boolean): void => { setQueryTimeoutEnabled(checked); }; @@ -197,6 +249,13 @@ export const SettingsPane: FunctionComponent = () => { } }; + const handleOnDefaultQueryResultsViewChange = ( + ev: React.MouseEvent, + option: IChoiceGroupOption, + ): void => { + setDefaultQueryResultsView(option.key as SplitterDirection); + }; + const handleOnQueryRetryAttemptsSpinButtonChange = (ev: React.MouseEvent, newValue?: string): void => { const retryAttempts = Number(newValue); if (!isNaN(retryAttempts)) { @@ -218,6 +277,12 @@ export const SettingsPane: FunctionComponent = () => { } }; + const handleSampleDatabaseChange = async (ev: React.MouseEvent, checked?: boolean): Promise => { + setCopilotSampleDBEnabled(checked); + useQueryCopilot.getState().setCopilotSampleDBEnabled(checked); + setRefreshExplorer(false); + }; + const choiceButtonStyles = { root: { clear: "both", @@ -240,7 +305,7 @@ export const SettingsPane: FunctionComponent = () => { ], }; - const queryTimeoutToggleStyles: IToggleStyles = { + const toggleStyles: IToggleStyles = { label: { fontSize: 12, fontWeight: 400, @@ -253,7 +318,7 @@ export const SettingsPane: FunctionComponent = () => { text: {}, }; - const queryTimeoutSpinButtonStyles: ISpinButtonStyles = { + const spinButtonStyles: ISpinButtonStyles = { label: { fontSize: 12, fontWeight: 400, @@ -319,48 +384,102 @@ export const SettingsPane: FunctionComponent = () => {
)} {userContext.apiType === "SQL" && ( -
-
-
- - Query Timeout - - - When a query reaches a specified time limit, a popup with an option to cancel the query will show - unless automatic cancellation has been enabled - -
-
- -
- {queryTimeoutEnabled && ( + <> +
+
+
+ + RU Threshold + + If a query exceeds a configured RU threshold, the query will be aborted. +
-
- )} + {ruThresholdEnabled && ( +
+ +
+ )} +
-
+
+
+
+ + Query Timeout + + + When a query reaches a specified time limit, a popup with an option to cancel the query will show + unless automatic cancellation has been enabled + +
+
+ +
+ {queryTimeoutEnabled && ( +
+ + +
+ )} +
+
+
+
+
+ + Default Query Results View + + Select the default view to use when displaying query results. +
+
+ +
+
+
+ )}
@@ -385,7 +504,7 @@ export const SettingsPane: FunctionComponent = () => { onIncrement={(newValue) => setRetryAttempts(parseInt(newValue) + 1 || retryAttempts)} onDecrement={(newValue) => setRetryAttempts(parseInt(newValue) - 1 || retryAttempts)} onValidate={(newValue) => setRetryAttempts(parseInt(newValue) || retryAttempts)} - styles={queryTimeoutSpinButtonStyles} + styles={spinButtonStyles} />
@@ -407,7 +526,7 @@ export const SettingsPane: FunctionComponent = () => { onIncrement={(newValue) => setRetryInterval(parseInt(newValue) + 1000 || retryInterval)} onDecrement={(newValue) => setRetryInterval(parseInt(newValue) - 1000 || retryInterval)} onValidate={(newValue) => setRetryInterval(parseInt(newValue) || retryInterval)} - styles={queryTimeoutSpinButtonStyles} + styles={spinButtonStyles} />
@@ -429,12 +548,12 @@ export const SettingsPane: FunctionComponent = () => { onIncrement={(newValue) => setMaxWaitTimeInSeconds(parseInt(newValue) + 1 || MaxWaitTimeInSeconds)} onDecrement={(newValue) => setMaxWaitTimeInSeconds(parseInt(newValue) - 1 || MaxWaitTimeInSeconds)} onValidate={(newValue) => setMaxWaitTimeInSeconds(parseInt(newValue) || MaxWaitTimeInSeconds)} - styles={queryTimeoutSpinButtonStyles} + styles={spinButtonStyles} />
-
+
Enable container pagination @@ -454,7 +573,7 @@ export const SettingsPane: FunctionComponent = () => {
{shouldShowCrossPartitionOption && (
-
+
Enable cross-partition query @@ -545,6 +664,30 @@ export const SettingsPane: FunctionComponent = () => {
)} + {shouldShowCopilotSampleDBOption && ( +
+
+
+ Enable sample database + + This is a sample database and collection with synthetic product data you can use to explore using + NoSQL queries and Query Advisor. This will appear as another database in the Data Explorer UI, and is + created by, and maintained by Microsoft at no cost to you. + +
+ + +
+
+ )}
Explorer Version
diff --git a/src/Explorer/Panes/SettingsPane/__snapshots__/SettingsPane.test.tsx.snap b/src/Explorer/Panes/SettingsPane/__snapshots__/SettingsPane.test.tsx.snap index 8898bae23..f887a53dd 100644 --- a/src/Explorer/Panes/SettingsPane/__snapshots__/SettingsPane.test.tsx.snap +++ b/src/Explorer/Panes/SettingsPane/__snapshots__/SettingsPane.test.tsx.snap @@ -97,6 +97,74 @@ exports[`Settings Pane should render Default properly 1`] = `
+
+
+
+ + RU Threshold + + + If a query exceeds a configured RU threshold, the query will be aborted. + +
+
+ +
+
+ +
+
+
@@ -137,6 +205,67 @@ exports[`Settings Pane should render Default properly 1`] = `
+
+
+
+ + Default Query Results View + + + Select the default view to use when displaying query results. + +
+
+ +
+
+
@@ -274,7 +403,7 @@ exports[`Settings Pane should render Default properly 1`] = ` className="settingsSection" >
+ `; diff --git a/src/Explorer/Panes/Tables/AddTableEntityPanel.tsx b/src/Explorer/Panes/Tables/AddTableEntityPanel.tsx index c9d9bda10..7d73ccc1f 100644 --- a/src/Explorer/Panes/Tables/AddTableEntityPanel.tsx +++ b/src/Explorer/Panes/Tables/AddTableEntityPanel.tsx @@ -1,5 +1,6 @@ import { IDropdownOption, Image, Label, Stack, Text, TextField } from "@fluentui/react"; import { useBoolean } from "@fluentui/react-hooks"; +import { logConsoleError } from "Utils/NotificationConsoleUtils"; import React, { FunctionComponent, useEffect, useState } from "react"; import * as _ from "underscore"; import AddPropertyIcon from "../../../../images/Add-property.svg"; @@ -97,9 +98,19 @@ export const AddTableEntityPanel: FunctionComponent = /* Add new entity attribute */ const onSubmit = async (): Promise => { for (let i = 0; i < entities.length; i++) { - const { property, type } = entities[i]; - if (property === "" || property === undefined) { - setFormError(`Property name cannot be empty. Please enter a property name`); + const { property, type, value } = entities[i]; + if ((property === "PartitionKey" && value === "") || (property === "RowKey" && value === "")) { + logConsoleError(`${property} cannot be empty. Please input a value for ${property}`); + setFormError(`${property} cannot be empty. Please input a value for ${property}`); + return; + } + + if ( + (property === "PartitionKey" && containsAnyWhiteSpace(value) === true) || + (property === "RowKey" && containsAnyWhiteSpace(value) === true) + ) { + logConsoleError(`${property} cannot have whitespace. Please input a value for ${property} without whitespace`); + setFormError(`${property} cannot have whitespace. Please input a value for ${property} without whitespace`); return; } @@ -107,12 +118,14 @@ export const AddTableEntityPanel: FunctionComponent = setFormError(`Property type cannot be empty. Please select a type from the dropdown for property ${property}`); return; } + + setFormError(""); } setIsExecuting(true); const entity: Entities.ITableEntity = entityFromAttributes(entities); - const newEntity: Entities.ITableEntity = await tableDataClient.createDocument(queryTablesTab.collection, entity); try { + const newEntity: Entities.ITableEntity = await tableDataClient.createDocument(queryTablesTab.collection, entity); await tableEntityListViewModel.addEntityToCache(newEntity); if (!tryInsertNewHeaders(tableEntityListViewModel, newEntity)) { tableEntityListViewModel.redrawTableThrottled(); @@ -127,6 +140,13 @@ export const AddTableEntityPanel: FunctionComponent = } }; + const containsAnyWhiteSpace = (entityValue: string) => { + if (/\s/.test(entityValue)) { + return true; + } + return false; + }; + const tryInsertNewHeaders = (viewModel: TableEntityListViewModel, newEntity: Entities.ITableEntity): boolean => { let newHeaders: string[] = []; const keys = Object.keys(newEntity); @@ -182,9 +202,14 @@ export const AddTableEntityPanel: FunctionComponent = const entityChange = (value: string | Date, indexOfInput: number, key: string): void => { const cloneEntities: EntityRowType[] = [...entities]; if (key === "property") { - cloneEntities[indexOfInput].property = value.toString(); + cloneEntities[indexOfInput].property = value.toString().trim(); } else if (key === "time") { cloneEntities[indexOfInput].entityTimeValue = value.toString(); + } else if ( + cloneEntities[indexOfInput].property === "PartitionKey" || + cloneEntities[indexOfInput].property === "RowKey" + ) { + cloneEntities[indexOfInput].value = value.toString().trim(); } else { cloneEntities[indexOfInput].value = value.toString(); } @@ -236,6 +261,7 @@ export const AddTableEntityPanel: FunctionComponent = { entityChange(newInput, selectedRow, "value"); diff --git a/src/Explorer/Panes/Tables/EditTableEntityPanel.tsx b/src/Explorer/Panes/Tables/EditTableEntityPanel.tsx index be2f982e0..e59fceee9 100644 --- a/src/Explorer/Panes/Tables/EditTableEntityPanel.tsx +++ b/src/Explorer/Panes/Tables/EditTableEntityPanel.tsx @@ -1,5 +1,6 @@ import { IDropdownOption, Image, Label, Stack, Text, TextField } from "@fluentui/react"; import { useBoolean } from "@fluentui/react-hooks"; +import { logConsoleError } from "Utils/NotificationConsoleUtils"; import React, { FunctionComponent, useEffect, useState } from "react"; import * as _ from "underscore"; import AddPropertyIcon from "../../../../images/Add-property.svg"; @@ -190,7 +191,7 @@ export const EditTableEntityPanel: FunctionComponent const onSubmit = async (): Promise => { for (let i = 0; i < entities.length; i++) { - const { property, type } = entities[i]; + const { property, type, value } = entities[i]; if (property === "" || property === undefined) { setFormError(`Property name cannot be empty. Please enter a property name`); return; @@ -200,6 +201,17 @@ export const EditTableEntityPanel: FunctionComponent setFormError(`Property type cannot be empty. Please select a type from the dropdown for property ${property}`); return; } + + if ( + (property === "PartitionKey" && value === "") || + (property === "PartitionKey" && value === undefined) || + (property === "RowKey" && value === "") || + (property === "RowKey" && value === undefined) + ) { + logConsoleError(`${property} cannot be empty. Please input a value for ${property}`); + setFormError(`${property} cannot be empty. Please input a value for ${property}`); + return; + } } setIsExecuting(true); @@ -359,7 +371,7 @@ export const EditTableEntityPanel: FunctionComponent selectedKey={entity.type} entityPropertyPlaceHolder={detailedHelp} entityValuePlaceholder={entity.entityValuePlaceholder} - entityValue={entity.value?.toString()} + entityValue={entity.value.toString()} isEntityTypeDate={entity.isEntityTypeDate} entityTimeValue={entity.entityTimeValue} isEntityValueDisable={entity.isEntityValueDisable} diff --git a/src/Explorer/Panes/Tables/TableQuerySelectPanel/__snapshots__/TableQuerySelectPanel.test.tsx.snap b/src/Explorer/Panes/Tables/TableQuerySelectPanel/__snapshots__/TableQuerySelectPanel.test.tsx.snap index ca5c6c926..c1e269786 100644 --- a/src/Explorer/Panes/Tables/TableQuerySelectPanel/__snapshots__/TableQuerySelectPanel.test.tsx.snap +++ b/src/Explorer/Panes/Tables/TableQuerySelectPanel/__snapshots__/TableQuerySelectPanel.test.tsx.snap @@ -1258,6 +1258,7 @@ exports[`Table query select Panel should render Default properly 1`] = ` > + `; diff --git a/src/Explorer/Panes/Tables/__snapshots__/AddTableEntityPanel.test.tsx.snap b/src/Explorer/Panes/Tables/__snapshots__/AddTableEntityPanel.test.tsx.snap index 80d494808..100c5a7ae 100644 --- a/src/Explorer/Panes/Tables/__snapshots__/AddTableEntityPanel.test.tsx.snap +++ b/src/Explorer/Panes/Tables/__snapshots__/AddTableEntityPanel.test.tsx.snap @@ -369,6 +369,7 @@ exports[`Excute Add Table Entity Pane should render Default properly 1`] = ` > + `; diff --git a/src/Explorer/Panes/Tables/__snapshots__/EditTableEntityPanel.test.tsx.snap b/src/Explorer/Panes/Tables/__snapshots__/EditTableEntityPanel.test.tsx.snap index da5c0c722..ffab606a2 100644 --- a/src/Explorer/Panes/Tables/__snapshots__/EditTableEntityPanel.test.tsx.snap +++ b/src/Explorer/Panes/Tables/__snapshots__/EditTableEntityPanel.test.tsx.snap @@ -375,6 +375,7 @@ exports[`Excute Edit Table Entity Pane should render Default properly 1`] = ` > + `; diff --git a/src/Explorer/Panes/UploadFilePane/UploadFilePane.tsx b/src/Explorer/Panes/UploadFilePane/UploadFilePane.tsx deleted file mode 100644 index d9b1d9792..000000000 --- a/src/Explorer/Panes/UploadFilePane/UploadFilePane.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { Upload } from "Common/Upload/Upload"; -import { useSidePanel } from "hooks/useSidePanel"; -import React, { ChangeEvent, FunctionComponent, useState } from "react"; -import { logConsoleError, logConsoleInfo, logConsoleProgress } from "Utils/NotificationConsoleUtils"; -import { NotebookContentItem } from "../../Notebook/NotebookContentItem"; -import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm"; - -export interface UploadFilePanelProps { - uploadFile: (name: string, content: string) => Promise; -} - -export const UploadFilePane: FunctionComponent = ({ uploadFile }: UploadFilePanelProps) => { - const closeSidePanel = useSidePanel((state) => state.closeSidePanel); - const extensions: string = undefined; //ex. ".ipynb" - const errorMessage = "Could not upload file"; - const inProgressMessage = "Uploading file to notebook server"; - const successMessage = "Successfully uploaded file to notebook server"; - - const [files, setFiles] = useState(); - const [formErrors, setFormErrors] = useState(""); - const [isExecuting, setIsExecuting] = useState(false); - - const submit = () => { - setFormErrors(""); - if (!files || files.length === 0) { - setFormErrors("No file specified. Please input a file."); - logConsoleError(`${errorMessage} -- No file specified. Please input a file.`); - return; - } - - const file: File = files.item(0); - - const clearMessage = logConsoleProgress(`${inProgressMessage}: ${file.name}`); - - setIsExecuting(true); - - onSubmit(files.item(0)) - .then( - () => { - logConsoleInfo(`${successMessage} ${file.name}`); - closeSidePanel(); - }, - (error: string) => { - setFormErrors(errorMessage); - logConsoleError(`${errorMessage} ${file.name}: ${error}`); - }, - ) - .finally(() => { - setIsExecuting(false); - clearMessage(); - }); - }; - - const updateSelectedFiles = (event: ChangeEvent): void => { - setFiles(event.target.files); - }; - - const onSubmit = async (file: File): Promise => { - const readFileAsText = (inputFile: File): Promise => { - const reader = new FileReader(); - return new Promise((resolve, reject) => { - reader.onerror = () => { - reader.abort(); - reject(`Problem parsing file: ${inputFile}`); - }; - reader.onload = () => { - resolve(reader.result as string); - }; - reader.readAsText(inputFile); - }); - }; - - const fileContent = await readFileAsText(file); - return uploadFile(file.name, fileContent); - }; - - const props: RightPaneFormProps = { - formError: formErrors, - isExecuting: isExecuting, - submitButtonText: "Upload", - onSubmit: submit, - }; - - return ( - -
- -
-
- ); -}; diff --git a/src/Explorer/Panes/VectorSearchPanel/AddVectorEmbeddingPolicyForm.test.tsx b/src/Explorer/Panes/VectorSearchPanel/AddVectorEmbeddingPolicyForm.test.tsx new file mode 100644 index 000000000..400725749 --- /dev/null +++ b/src/Explorer/Panes/VectorSearchPanel/AddVectorEmbeddingPolicyForm.test.tsx @@ -0,0 +1,84 @@ +import "@testing-library/jest-dom/extend-expect"; +import { RenderResult, fireEvent, render, screen, waitFor } from "@testing-library/react"; +import { VectorEmbedding, VectorIndex } from "Contracts/DataModels"; +import React from "react"; +import { AddVectorEmbeddingPolicyForm } from "./AddVectorEmbeddingPolicyForm"; + +const mockVectorEmbedding: VectorEmbedding[] = [ + { path: "/vector1", dataType: "float32", distanceFunction: "euclidean", dimensions: 0 }, +]; + +const mockVectorIndex: VectorIndex[] = [{ path: "/vector1", type: "flat" }]; + +const mockOnVectorEmbeddingChange = jest.fn(); + +describe("AddVectorEmbeddingPolicyForm", () => { + let component: RenderResult; + + beforeEach(() => { + component = render( + , + ); + }); + + test("renders correctly", () => { + expect(screen.getByText("Vector embedding 1")).toBeInTheDocument(); + expect(screen.getByPlaceholderText("/vector1")).toBeInTheDocument(); + }); + + test("calls onVectorEmbeddingChange on adding a new vector embedding", () => { + fireEvent.click(screen.getByText("Add vector embedding")); + expect(mockOnVectorEmbeddingChange).toHaveBeenCalled(); + }); + + test("calls onDelete when delete button is clicked", async () => { + const deleteButton = component.container.querySelector("#delete-vector-policy-1"); + fireEvent.click(deleteButton); + expect(mockOnVectorEmbeddingChange).toHaveBeenCalled(); + expect(screen.queryByText("Vector embedding 1")).toBeNull(); + }); + + test("calls onVectorEmbeddingPathChange on input change", () => { + fireEvent.change(screen.getByPlaceholderText("/vector1"), { target: { value: "/newPath" } }); + expect(mockOnVectorEmbeddingChange).toHaveBeenCalled(); + }); + + test("validates input correctly", async () => { + fireEvent.change(screen.getByPlaceholderText("/vector1"), { target: { value: "" } }); + await waitFor(() => expect(screen.getByText("Vector embedding path should not be empty")).toBeInTheDocument(), { + timeout: 1500, + }); + await waitFor( + () => + expect( + screen.getByText("Vector embedding dimension must be greater than 0 and less than or equal 4096"), + ).toBeInTheDocument(), + { + timeout: 1500, + }, + ); + fireEvent.change(component.container.querySelector("#vector-policy-dimension-1"), { target: { value: "4096" } }); + fireEvent.change(screen.getByPlaceholderText("/vector1"), { target: { value: "/vector1" } }); + await waitFor(() => expect(screen.queryByText("Vector embedding path should not be empty")).toBeNull(), { + timeout: 1500, + }); + await waitFor( + () => expect(screen.queryByText("Maximum allowed dimension for flat index is 505")).toBeInTheDocument(), + { + timeout: 1500, + }, + ); + }); + + test("duplicate vector path is not allowed", async () => { + fireEvent.click(screen.getByText("Add vector embedding")); + fireEvent.change(component.container.querySelector("#vector-policy-path-2"), { target: { value: "/vector1" } }); + await waitFor(() => expect(screen.queryByText("Vector embedding path is already defined")).toBeNull(), { + timeout: 1500, + }); + }); +}); diff --git a/src/Explorer/Panes/VectorSearchPanel/AddVectorEmbeddingPolicyForm.tsx b/src/Explorer/Panes/VectorSearchPanel/AddVectorEmbeddingPolicyForm.tsx new file mode 100644 index 000000000..a5012cad5 --- /dev/null +++ b/src/Explorer/Panes/VectorSearchPanel/AddVectorEmbeddingPolicyForm.tsx @@ -0,0 +1,300 @@ +import { + DefaultButton, + Dropdown, + IDropdownOption, + IStyleFunctionOrObject, + ITextFieldStyleProps, + ITextFieldStyles, + IconButton, + Label, + Stack, + TextField, +} from "@fluentui/react"; +import { VectorEmbedding, VectorIndex } from "Contracts/DataModels"; +import { CollapsibleSectionComponent } from "Explorer/Controls/CollapsiblePanel/CollapsibleSectionComponent"; +import { + getDataTypeOptions, + getDistanceFunctionOptions, + getIndexTypeOptions, +} from "Explorer/Panes/VectorSearchPanel/VectorSearchUtils"; +import React, { FunctionComponent, useState } from "react"; + +export interface IAddVectorEmbeddingPolicyFormProps { + vectorEmbedding: VectorEmbedding[]; + vectorIndex: VectorIndex[]; + onVectorEmbeddingChange: ( + vectorEmbeddings: VectorEmbedding[], + vectorIndexingPolicies: VectorIndex[], + validationPassed: boolean, + ) => void; +} + +export interface VectorEmbeddingPolicyData { + path: string; + dataType: VectorEmbedding["dataType"]; + distanceFunction: VectorEmbedding["distanceFunction"]; + dimensions: number; + indexType: VectorIndex["type"] | "none"; + pathError: string; + dimensionsError: string; +} + +type VectorEmbeddingPolicyProperty = "dataType" | "distanceFunction" | "indexType"; + +const textFieldStyles: IStyleFunctionOrObject = { + fieldGroup: { + height: 27, + }, + field: { + fontSize: 12, + padding: "0 8px", + }, +}; + +const dropdownStyles = { + title: { + height: 27, + lineHeight: "24px", + fontSize: 12, + }, + dropdown: { + height: 27, + lineHeight: "24px", + }, + dropdownItem: { + fontSize: 12, + }, +}; + +export const AddVectorEmbeddingPolicyForm: FunctionComponent = ({ + vectorEmbedding, + vectorIndex, + onVectorEmbeddingChange, +}): JSX.Element => { + const onVectorEmbeddingPathError = (path: string, index?: number): string => { + let error = ""; + if (!path) { + error = "Vector embedding path should not be empty"; + } + if ( + index >= 0 && + vectorEmbeddingPolicyData?.find( + (vectorEmbedding: VectorEmbeddingPolicyData, dataIndex: number) => + dataIndex !== index && vectorEmbedding.path === path, + ) + ) { + error = "Vector embedding path is already defined"; + } + return error; + }; + + const onVectorEmbeddingDimensionError = (dimension: number, indexType: VectorIndex["type"] | "none"): string => { + let error = ""; + if (dimension <= 0 || dimension > 4096) { + error = "Vector embedding dimension must be greater than 0 and less than or equal 4096"; + } + if (indexType === "flat" && dimension > 505) { + error = "Maximum allowed dimension for flat index is 505"; + } + return error; + }; + + const initializeData = (vectorEmbedding: VectorEmbedding[], vectorIndex: VectorIndex[]) => { + const mergedData: VectorEmbeddingPolicyData[] = []; + vectorEmbedding.forEach((embedding) => { + const matchingIndex = vectorIndex.find((index) => index.path === embedding.path); + mergedData.push({ + ...embedding, + indexType: matchingIndex?.type || "none", + pathError: onVectorEmbeddingPathError(embedding.path), + dimensionsError: onVectorEmbeddingDimensionError(embedding.dimensions, matchingIndex?.type || "none"), + }); + }); + return mergedData; + }; + + const [vectorEmbeddingPolicyData, setVectorEmbeddingPolicyData] = useState( + initializeData(vectorEmbedding, vectorIndex), + ); + + React.useEffect(() => { + propagateData(); + }, [vectorEmbeddingPolicyData]); + + const propagateData = () => { + const vectorEmbeddings: VectorEmbedding[] = vectorEmbeddingPolicyData.map((policy: VectorEmbeddingPolicyData) => ({ + dataType: policy.dataType, + dimensions: policy.dimensions, + distanceFunction: policy.distanceFunction, + path: policy.path, + })); + const vectorIndexingPolicies: VectorIndex[] = vectorEmbeddingPolicyData + .filter((policy: VectorEmbeddingPolicyData) => policy.indexType !== "none") + .map( + (policy) => + ({ + path: policy.path, + type: policy.indexType, + }) as VectorIndex, + ); + const validationPassed = vectorEmbeddingPolicyData.every( + (policy: VectorEmbeddingPolicyData) => policy.pathError === "" && policy.dimensionsError === "", + ); + onVectorEmbeddingChange(vectorEmbeddings, vectorIndexingPolicies, validationPassed); + }; + + const onVectorEmbeddingPathChange = (index: number, event: React.ChangeEvent) => { + const value = event.target.value.trim(); + const vectorEmbeddings = [...vectorEmbeddingPolicyData]; + if (!vectorEmbeddings[index]?.path && !value.startsWith("/")) { + vectorEmbeddings[index].path = "/" + value; + } else { + vectorEmbeddings[index].path = value; + } + const error = onVectorEmbeddingPathError(value, index); + vectorEmbeddings[index].pathError = error; + setVectorEmbeddingPolicyData(vectorEmbeddings); + }; + + const onVectorEmbeddingDimensionsChange = (index: number, event: React.ChangeEvent) => { + const value = parseInt(event.target.value.trim()) || 0; + const vectorEmbeddings = [...vectorEmbeddingPolicyData]; + const vectorEmbedding = vectorEmbeddings[index]; + vectorEmbeddings[index].dimensions = value; + const error = onVectorEmbeddingDimensionError(value, vectorEmbedding.indexType); + vectorEmbeddings[index].dimensionsError = error; + setVectorEmbeddingPolicyData(vectorEmbeddings); + }; + + const onVectorEmbeddingIndexTypeChange = (index: number, option: IDropdownOption): void => { + const vectorEmbeddings = [...vectorEmbeddingPolicyData]; + const vectorEmbedding = vectorEmbeddings[index]; + vectorEmbeddings[index].indexType = option.key as never; + const error = onVectorEmbeddingDimensionError(vectorEmbedding.dimensions, vectorEmbedding.indexType); + vectorEmbeddings[index].dimensionsError = error; + setVectorEmbeddingPolicyData(vectorEmbeddings); + }; + + const onVectorEmbeddingPolicyChange = ( + index: number, + option: IDropdownOption, + property: VectorEmbeddingPolicyProperty, + ): void => { + const vectorEmbeddings = [...vectorEmbeddingPolicyData]; + vectorEmbeddings[index][property] = option.key as never; + setVectorEmbeddingPolicyData(vectorEmbeddings); + }; + + const onAdd = () => { + setVectorEmbeddingPolicyData([ + ...vectorEmbeddingPolicyData, + { + path: "", + dataType: "float32", + distanceFunction: "euclidean", + dimensions: 0, + indexType: "none", + pathError: onVectorEmbeddingPathError(""), + dimensionsError: onVectorEmbeddingDimensionError(0, "none"), + }, + ]); + }; + + const onDelete = (index: number) => { + const vectorEmbeddings = vectorEmbeddingPolicyData.filter((_uniqueKey, j) => index !== j); + setVectorEmbeddingPolicyData(vectorEmbeddings); + }; + + return ( + + {vectorEmbeddingPolicyData.length > 0 && + vectorEmbeddingPolicyData.map((vectorEmbeddingPolicy: VectorEmbeddingPolicyData, index: number) => ( + + + + + + ) => onVectorEmbeddingPathChange(index, event)} + value={vectorEmbeddingPolicy.path || ""} + errorMessage={vectorEmbeddingPolicy.pathError} + /> + + + + , option: IDropdownOption) => + onVectorEmbeddingPolicyChange(index, option, "dataType") + } + > + + + + , option: IDropdownOption) => + onVectorEmbeddingPolicyChange(index, option, "distanceFunction") + } + > + + + + ) => + onVectorEmbeddingDimensionsChange(index, event) + } + errorMessage={vectorEmbeddingPolicy.dimensionsError} + /> + + + + , option: IDropdownOption) => + onVectorEmbeddingIndexTypeChange(index, option) + } + > + + + onDelete(index)} + /> + + + ))} + + Add vector embedding + + + ); +}; diff --git a/src/Explorer/Panes/VectorSearchPanel/VectorSearchUtils.ts b/src/Explorer/Panes/VectorSearchPanel/VectorSearchUtils.ts new file mode 100644 index 000000000..0f5547aa7 --- /dev/null +++ b/src/Explorer/Panes/VectorSearchPanel/VectorSearchUtils.ts @@ -0,0 +1,16 @@ +import { IDropdownOption } from "@fluentui/react"; + +const dataTypes = ["float32", "uint8", "int8"]; +const distanceFunctions = ["euclidean", "cosine", "dotproduct"]; +const indexTypes = ["none", "flat", "diskANN", "quantizedFlat"]; + +export const getDataTypeOptions = (): IDropdownOption[] => createDropdownOptionsFromLiterals(dataTypes); +export const getDistanceFunctionOptions = (): IDropdownOption[] => createDropdownOptionsFromLiterals(distanceFunctions); +export const getIndexTypeOptions = (): IDropdownOption[] => createDropdownOptionsFromLiterals(indexTypes); + +function createDropdownOptionsFromLiterals(literals: T[]): IDropdownOption[] { + return literals.map((value) => ({ + key: value, + text: value, + })); +} diff --git a/src/Explorer/Panes/__snapshots__/AddCollectionPanel.test.tsx.snap b/src/Explorer/Panes/__snapshots__/AddCollectionPanel.test.tsx.snap index 0a19a654b..93f52c6ce 100644 --- a/src/Explorer/Panes/__snapshots__/AddCollectionPanel.test.tsx.snap +++ b/src/Explorer/Panes/__snapshots__/AddCollectionPanel.test.tsx.snap @@ -3,6 +3,7 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
To ensure compatibility with older SDKs, the created container will use a legacy partitioning scheme that supports partition key values of size only up to 101 bytes. If this is enabled, you will not be able to use hierarchical partition keys. diff --git a/src/Explorer/Panes/__snapshots__/DeleteDatabaseConfirmationPanel.test.tsx.snap b/src/Explorer/Panes/__snapshots__/DeleteDatabaseConfirmationPanel.test.tsx.snap index 4ac4a9e20..c6a239ebf 100644 --- a/src/Explorer/Panes/__snapshots__/DeleteDatabaseConfirmationPanel.test.tsx.snap +++ b/src/Explorer/Panes/__snapshots__/DeleteDatabaseConfirmationPanel.test.tsx.snap @@ -361,12 +361,15 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = ` - Confirm by typing the database id + Confirm by typing the + Database + id - What is the reason why you are deleting this database? + What is the reason why you are deleting this + Database + ?