diff --git a/.github/workflows/bump-python-package.yml b/.github/workflows/bump-python-package.yml index 336e5235e418..846291828abb 100644 --- a/.github/workflows/bump-python-package.yml +++ b/.github/workflows/bump-python-package.yml @@ -17,7 +17,7 @@ on: jobs: bump-python-package: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 permissions: actions: write contents: write diff --git a/.github/workflows/cancel_duplicates.yml b/.github/workflows/cancel_duplicates.yml index 751a498ab542..a749a2add605 100644 --- a/.github/workflows/cancel_duplicates.yml +++ b/.github/workflows/cancel_duplicates.yml @@ -9,7 +9,7 @@ on: jobs: cancel-duplicate-runs: name: Cancel duplicate workflow runs - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 permissions: actions: write contents: read diff --git a/.github/workflows/check_db_migration_confict.yml b/.github/workflows/check_db_migration_confict.yml index e717f41193d0..af291becde6f 100644 --- a/.github/workflows/check_db_migration_confict.yml +++ b/.github/workflows/check_db_migration_confict.yml @@ -19,7 +19,7 @@ concurrency: jobs: check_db_migration_conflict: name: Check DB migration conflict - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 permissions: contents: read pull-requests: write diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 6d0c12239170..773e7358345f 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -12,7 +12,7 @@ permissions: jobs: dependency-review: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: "Checkout Repository" uses: actions/checkout@v4 diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index f38cd4fee4d1..c8c4756ea543 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -15,7 +15,7 @@ concurrency: jobs: setup_matrix: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 outputs: matrix_config: ${{ steps.set_matrix.outputs.matrix_config }} steps: @@ -28,7 +28,7 @@ jobs: docker-build: name: docker-build needs: setup_matrix - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 strategy: matrix: build_preset: ${{fromJson(needs.setup_matrix.outputs.matrix_config)}} diff --git a/.github/workflows/embedded-sdk-release.yml b/.github/workflows/embedded-sdk-release.yml index 323b1a9e99f1..b0c75343824d 100644 --- a/.github/workflows/embedded-sdk-release.yml +++ b/.github/workflows/embedded-sdk-release.yml @@ -8,7 +8,7 @@ on: jobs: config: - runs-on: "ubuntu-latest" + runs-on: "ubuntu-22.04" outputs: has-secrets: ${{ steps.check.outputs.has-secrets }} steps: @@ -23,7 +23,7 @@ jobs: build: needs: config if: needs.config.outputs.has-secrets - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 defaults: run: working-directory: superset-embedded-sdk diff --git a/.github/workflows/embedded-sdk-test.yml b/.github/workflows/embedded-sdk-test.yml index e3f3c1bdcac8..50058d0af75f 100644 --- a/.github/workflows/embedded-sdk-test.yml +++ b/.github/workflows/embedded-sdk-test.yml @@ -13,7 +13,7 @@ concurrency: jobs: embedded-sdk-test: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 defaults: run: working-directory: superset-embedded-sdk diff --git a/.github/workflows/ephemeral-env-pr-close.yml b/.github/workflows/ephemeral-env-pr-close.yml index e61559b05295..5fc634f6cdf7 100644 --- a/.github/workflows/ephemeral-env-pr-close.yml +++ b/.github/workflows/ephemeral-env-pr-close.yml @@ -6,7 +6,7 @@ on: jobs: config: - runs-on: "ubuntu-latest" + runs-on: "ubuntu-22.04" outputs: has-secrets: ${{ steps.check.outputs.has-secrets }} steps: @@ -22,7 +22,7 @@ jobs: needs: config if: needs.config.outputs.has-secrets name: Cleanup ephemeral envs - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 permissions: pull-requests: write steps: diff --git a/.github/workflows/ephemeral-env.yml b/.github/workflows/ephemeral-env.yml index a48f8eb6aa89..1cd80282ec24 100644 --- a/.github/workflows/ephemeral-env.yml +++ b/.github/workflows/ephemeral-env.yml @@ -6,7 +6,7 @@ on: jobs: config: - runs-on: "ubuntu-latest" + runs-on: "ubuntu-22.04" if: github.event.issue.pull_request outputs: has-secrets: ${{ steps.check.outputs.has-secrets }} @@ -26,7 +26,7 @@ jobs: needs: config if: needs.config.outputs.has-secrets name: Evaluate ephemeral env comment trigger (/testenv) - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 permissions: pull-requests: write outputs: @@ -88,7 +88,7 @@ jobs: cancel-in-progress: true needs: ephemeral-env-comment name: ephemeral-docker-build - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Get Info from comment uses: actions/github-script@v7 @@ -153,7 +153,7 @@ jobs: needs: [ephemeral-env-comment, ephemeral-docker-build] if: needs.ephemeral-env-comment.outputs.slash-command == 'up' name: Spin up an ephemeral environment - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 permissions: contents: read pull-requests: write diff --git a/.github/workflows/generate-FOSSA-report.yml b/.github/workflows/generate-FOSSA-report.yml index 352ba845d9de..807c8ea40fc2 100644 --- a/.github/workflows/generate-FOSSA-report.yml +++ b/.github/workflows/generate-FOSSA-report.yml @@ -8,7 +8,7 @@ on: jobs: config: - runs-on: "ubuntu-latest" + runs-on: "ubuntu-22.04" outputs: has-secrets: ${{ steps.check.outputs.has-secrets }} steps: @@ -24,7 +24,7 @@ jobs: needs: config if: needs.config.outputs.has-secrets name: Generate Report - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" uses: actions/checkout@v4 diff --git a/.github/workflows/github-action-validator.yml b/.github/workflows/github-action-validator.yml index 0dd50155310c..5acc5e0880e1 100644 --- a/.github/workflows/github-action-validator.yml +++ b/.github/workflows/github-action-validator.yml @@ -11,7 +11,7 @@ on: jobs: validate-all-ghas: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout Repository uses: actions/checkout@v4 diff --git a/.github/workflows/issue_creation.yml b/.github/workflows/issue_creation.yml index 2ea1de2b0a43..fc3b3e45bdf7 100644 --- a/.github/workflows/issue_creation.yml +++ b/.github/workflows/issue_creation.yml @@ -9,7 +9,7 @@ on: jobs: superbot-orglabel: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 permissions: contents: read pull-requests: write diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 15a41995d1d5..5af67547f982 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -7,7 +7,7 @@ jobs: permissions: contents: read pull-requests: write - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/labeler@v5 with: diff --git a/.github/workflows/latest-release-tag.yml b/.github/workflows/latest-release-tag.yml index bd73462e895d..659214af9e09 100644 --- a/.github/workflows/latest-release-tag.yml +++ b/.github/workflows/latest-release-tag.yml @@ -6,7 +6,7 @@ on: jobs: latest-release: name: Add/update tag to new release - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 permissions: contents: write diff --git a/.github/workflows/license-check.yml b/.github/workflows/license-check.yml index 5f5468071d74..8974c5ae43ff 100644 --- a/.github/workflows/license-check.yml +++ b/.github/workflows/license-check.yml @@ -12,7 +12,7 @@ concurrency: jobs: license_check: name: License Check - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" uses: actions/checkout@v4 diff --git a/.github/workflows/no-hold-label.yml b/.github/workflows/no-hold-label.yml index 73a3664e084f..866650ece4c1 100644 --- a/.github/workflows/no-hold-label.yml +++ b/.github/workflows/no-hold-label.yml @@ -11,7 +11,7 @@ concurrency: jobs: check-hold-label: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Check for 'hold' label uses: actions/github-script@v7 diff --git a/.github/workflows/pr-lint.yml b/.github/workflows/pr-lint.yml index e6ea96a6aea9..5ba91fee6ebf 100644 --- a/.github/workflows/pr-lint.yml +++ b/.github/workflows/pr-lint.yml @@ -10,7 +10,7 @@ on: jobs: lint-check: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 permissions: contents: read pull-requests: write diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 6ccb66df771f..af6765019250 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -15,7 +15,7 @@ concurrency: jobs: pre-commit: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" uses: actions/checkout@v4 diff --git a/.github/workflows/prefer-typescript.yml b/.github/workflows/prefer-typescript.yml index 0b34f25bae4d..4739ae8b6bf8 100644 --- a/.github/workflows/prefer-typescript.yml +++ b/.github/workflows/prefer-typescript.yml @@ -21,7 +21,7 @@ jobs: prefer_typescript: if: github.ref == 'ref/heads/master' && github.event_name == 'pull_request' name: Prefer TypeScript - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 permissions: contents: read pull-requests: write diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 43862bd50b5a..4435054a5c7a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,7 +8,7 @@ on: jobs: config: - runs-on: "ubuntu-latest" + runs-on: "ubuntu-22.04" outputs: has-secrets: ${{ steps.check.outputs.has-secrets }} steps: @@ -25,7 +25,7 @@ jobs: if: needs.config.outputs.has-secrets name: Bump version and publish package(s) - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: matrix: diff --git a/.github/workflows/superset-applitool-cypress.yml b/.github/workflows/superset-applitool-cypress.yml index 8e5aa91cdba6..72fd1a734321 100644 --- a/.github/workflows/superset-applitool-cypress.yml +++ b/.github/workflows/superset-applitool-cypress.yml @@ -6,7 +6,7 @@ on: jobs: config: - runs-on: "ubuntu-latest" + runs-on: "ubuntu-22.04" outputs: has-secrets: ${{ steps.check.outputs.has-secrets }} steps: @@ -21,7 +21,7 @@ jobs: cypress-applitools: needs: config if: needs.config.outputs.has-secrets - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: diff --git a/.github/workflows/superset-applitools-storybook.yml b/.github/workflows/superset-applitools-storybook.yml index 147d64d28641..5382120bc163 100644 --- a/.github/workflows/superset-applitools-storybook.yml +++ b/.github/workflows/superset-applitools-storybook.yml @@ -12,7 +12,7 @@ env: jobs: config: - runs-on: "ubuntu-latest" + runs-on: "ubuntu-22.04" outputs: has-secrets: ${{ steps.check.outputs.has-secrets }} steps: @@ -27,7 +27,7 @@ jobs: cron: needs: config if: needs.config.outputs.has-secrets - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: matrix: node: [18] diff --git a/.github/workflows/superset-cli.yml b/.github/workflows/superset-cli.yml index 060bae5dff37..29dd87adfe17 100644 --- a/.github/workflows/superset-cli.yml +++ b/.github/workflows/superset-cli.yml @@ -15,7 +15,7 @@ concurrency: jobs: test-load-examples: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 env: PYTHONPATH: ${{ github.workspace }} SUPERSET_CONFIG: tests.integration_tests.superset_test_config diff --git a/.github/workflows/superset-docs-deploy.yml b/.github/workflows/superset-docs-deploy.yml index 2bce8c023c6e..052eecdcab0c 100644 --- a/.github/workflows/superset-docs-deploy.yml +++ b/.github/workflows/superset-docs-deploy.yml @@ -12,7 +12,7 @@ on: jobs: config: - runs-on: "ubuntu-latest" + runs-on: "ubuntu-22.04" outputs: has-secrets: ${{ steps.check.outputs.has-secrets }} steps: @@ -28,7 +28,7 @@ jobs: needs: config if: needs.config.outputs.has-secrets name: Build & Deploy - runs-on: "ubuntu-latest" + runs-on: "ubuntu-22.04" steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" uses: actions/checkout@v4 diff --git a/.github/workflows/superset-docs-verify.yml b/.github/workflows/superset-docs-verify.yml index 9f665d8086bb..de82268e046d 100644 --- a/.github/workflows/superset-docs-verify.yml +++ b/.github/workflows/superset-docs-verify.yml @@ -14,7 +14,7 @@ concurrency: jobs: build-deploy: name: Build & Deploy - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 defaults: run: working-directory: docs diff --git a/.github/workflows/superset-e2e.yml b/.github/workflows/superset-e2e.yml index 06e4cffb0626..092864246240 100644 --- a/.github/workflows/superset-e2e.yml +++ b/.github/workflows/superset-e2e.yml @@ -28,7 +28,7 @@ concurrency: jobs: cypress-matrix: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 permissions: contents: read pull-requests: read @@ -66,20 +66,20 @@ jobs: # Conditional checkout based on context - name: Checkout for push or pull_request event if: github.event_name == 'push' || github.event_name == 'pull_request' - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: persist-credentials: false submodules: recursive - name: Checkout using ref (workflow_dispatch) if: github.event_name == 'workflow_dispatch' && github.event.inputs.ref != '' - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: persist-credentials: false ref: ${{ github.event.inputs.ref }} submodules: recursive - name: Checkout using PR ID (workflow_dispatch) if: github.event_name == 'workflow_dispatch' && github.event.inputs.pr_id != '' - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: persist-credentials: false ref: refs/pull/${{ github.event.inputs.pr_id }}/merge diff --git a/.github/workflows/superset-frontend.yml b/.github/workflows/superset-frontend.yml index 6438c2cc1d8d..b8c2b2c4e2d3 100644 --- a/.github/workflows/superset-frontend.yml +++ b/.github/workflows/superset-frontend.yml @@ -15,7 +15,7 @@ concurrency: jobs: frontend-build: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" uses: actions/checkout@v4 diff --git a/.github/workflows/superset-helm-lint.yml b/.github/workflows/superset-helm-lint.yml index 48266a3299ea..5649f491a490 100644 --- a/.github/workflows/superset-helm-lint.yml +++ b/.github/workflows/superset-helm-lint.yml @@ -13,7 +13,7 @@ concurrency: jobs: lint-test: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" uses: actions/checkout@v4 diff --git a/.github/workflows/superset-helm-release.yml b/.github/workflows/superset-helm-release.yml index 062e23758634..242820afce45 100644 --- a/.github/workflows/superset-helm-release.yml +++ b/.github/workflows/superset-helm-release.yml @@ -10,7 +10,7 @@ on: jobs: release: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 permissions: contents: write diff --git a/.github/workflows/superset-python-integrationtest.yml b/.github/workflows/superset-python-integrationtest.yml index 80b2a3b98b75..7cd135e55959 100644 --- a/.github/workflows/superset-python-integrationtest.yml +++ b/.github/workflows/superset-python-integrationtest.yml @@ -15,7 +15,7 @@ concurrency: jobs: test-mysql: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 env: PYTHONPATH: ${{ github.workspace }} SUPERSET_CONFIG: tests.integration_tests.superset_test_config @@ -74,7 +74,7 @@ jobs: token: ${{ secrets.CODECOV_TOKEN }} verbose: true test-postgres: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: matrix: python-version: ["current", "next", "previous"] @@ -136,7 +136,7 @@ jobs: verbose: true test-sqlite: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 env: PYTHONPATH: ${{ github.workspace }} SUPERSET_CONFIG: tests.integration_tests.superset_test_config diff --git a/.github/workflows/superset-python-misc.yml b/.github/workflows/superset-python-misc.yml index d58226216fcc..12417d147a50 100644 --- a/.github/workflows/superset-python-misc.yml +++ b/.github/workflows/superset-python-misc.yml @@ -16,7 +16,7 @@ concurrency: jobs: python-lint: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" uses: actions/checkout@v4 @@ -33,7 +33,7 @@ jobs: if: steps.check.outputs.python babel-extract: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" uses: actions/checkout@v4 diff --git a/.github/workflows/superset-python-presto-hive.yml b/.github/workflows/superset-python-presto-hive.yml index 6ab65430b406..d87a70964cc5 100644 --- a/.github/workflows/superset-python-presto-hive.yml +++ b/.github/workflows/superset-python-presto-hive.yml @@ -16,7 +16,7 @@ concurrency: jobs: test-postgres-presto: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 env: PYTHONPATH: ${{ github.workspace }} SUPERSET_CONFIG: tests.integration_tests.superset_test_config @@ -84,7 +84,7 @@ jobs: verbose: true test-postgres-hive: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 env: PYTHONPATH: ${{ github.workspace }} SUPERSET_CONFIG: tests.integration_tests.superset_test_config diff --git a/.github/workflows/superset-python-unittest.yml b/.github/workflows/superset-python-unittest.yml index 454ee0c61e08..0f9cfc8aa10e 100644 --- a/.github/workflows/superset-python-unittest.yml +++ b/.github/workflows/superset-python-unittest.yml @@ -16,7 +16,7 @@ concurrency: jobs: unit-tests: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: matrix: python-version: ["current", "next"] diff --git a/.github/workflows/superset-translations.yml b/.github/workflows/superset-translations.yml index 11dbebb09800..292a42afdb83 100644 --- a/.github/workflows/superset-translations.yml +++ b/.github/workflows/superset-translations.yml @@ -15,7 +15,7 @@ concurrency: jobs: frontend-check-translations: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" uses: actions/checkout@v4 @@ -46,7 +46,7 @@ jobs: npm run build-translation babel-extract: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" uses: actions/checkout@v4 diff --git a/.github/workflows/superset-websocket.yml b/.github/workflows/superset-websocket.yml index 2d55ceafa03c..f1785a39abe4 100644 --- a/.github/workflows/superset-websocket.yml +++ b/.github/workflows/superset-websocket.yml @@ -18,7 +18,7 @@ concurrency: jobs: app-checks: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" uses: actions/checkout@v4 diff --git a/.github/workflows/supersetbot.yml b/.github/workflows/supersetbot.yml index 88d4648cc78d..f7e106ed9c7a 100644 --- a/.github/workflows/supersetbot.yml +++ b/.github/workflows/supersetbot.yml @@ -15,7 +15,7 @@ on: jobs: supersetbot: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 if: > github.event_name == 'workflow_dispatch' || (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@supersetbot')) diff --git a/.github/workflows/tag-release.yml b/.github/workflows/tag-release.yml index dc0ecd7e7f66..8ee03b3d04c2 100644 --- a/.github/workflows/tag-release.yml +++ b/.github/workflows/tag-release.yml @@ -23,7 +23,7 @@ on: - 'false' jobs: config: - runs-on: "ubuntu-latest" + runs-on: "ubuntu-22.04" outputs: has-secrets: ${{ steps.check.outputs.has-secrets }} steps: @@ -39,7 +39,7 @@ jobs: needs: config if: needs.config.outputs.has-secrets name: docker-release - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 strategy: matrix: build_preset: ["dev", "lean", "py310", "websocket", "dockerize"] diff --git a/.github/workflows/tech-debt.yml b/.github/workflows/tech-debt.yml index 6f73a3a51b62..f17e220a402b 100644 --- a/.github/workflows/tech-debt.yml +++ b/.github/workflows/tech-debt.yml @@ -8,7 +8,7 @@ on: jobs: config: - runs-on: "ubuntu-latest" + runs-on: "ubuntu-22.04" outputs: has-secrets: ${{ steps.check.outputs.has-secrets }} steps: @@ -23,7 +23,7 @@ jobs: process-and-upload: needs: config if: needs.config.outputs.has-secrets - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 name: Generate Reports steps: - name: Checkout Repository diff --git a/.github/workflows/welcome-new-users.yml b/.github/workflows/welcome-new-users.yml index 0144e20892d4..2c602967770d 100644 --- a/.github/workflows/welcome-new-users.yml +++ b/.github/workflows/welcome-new-users.yml @@ -6,7 +6,7 @@ on: jobs: welcome: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 permissions: pull-requests: write diff --git a/RESOURCES/INTHEWILD.md b/RESOURCES/INTHEWILD.md index da918ef52a36..8063dc388f94 100644 --- a/RESOURCES/INTHEWILD.md +++ b/RESOURCES/INTHEWILD.md @@ -178,6 +178,7 @@ Join our growing community! - [Skyscanner](https://www.skyscanner.net/) [@cleslie, @stanhoucke] ### Others +- [10Web](https://10web.io/) - [AI inside](https://inside.ai/en/) - [Automattic](https://automattic.com/) [@Khrol, @Usiel] - [Dropbox](https://www.dropbox.com/) [@bkyryliuk] diff --git a/docs/package.json b/docs/package.json index 102cd80a77b3..4774a3ac83f7 100644 --- a/docs/package.json +++ b/docs/package.json @@ -17,7 +17,7 @@ "typecheck": "tsc" }, "dependencies": { - "@algolia/client-search": "^4.23.3", + "@algolia/client-search": "^4.24.0", "@ant-design/icons": "^5.3.7", "@docsearch/react": "^3.6.0", "@docusaurus/core": "^3.3.2", @@ -41,7 +41,7 @@ "react-dom": "^18.3.1", "react-github-btn": "^1.4.0", "react-svg-pan-zoom": "^3.12.1", - "stream": "^0.0.2", + "stream": "^0.0.3", "swagger-ui-react": "^5.17.14", "url-loader": "^4.1.1" }, @@ -49,8 +49,8 @@ "@docusaurus/module-type-aliases": "^3.4.0", "@docusaurus/tsconfig": "^3.4.0", "@types/react": "^18.3.3", - "typescript": "^5.4.5", - "webpack": "^5.91.0" + "typescript": "^5.5.2", + "webpack": "^5.92.1" }, "browserslist": { "production": [ diff --git a/docs/yarn.lock b/docs/yarn.lock index c522e53040bf..8e9c47269db2 100644 --- a/docs/yarn.lock +++ b/docs/yarn.lock @@ -41,6 +41,11 @@ resolved "https://registry.yarnpkg.com/@algolia/cache-common/-/cache-common-4.23.3.tgz#3bec79092d512a96c9bfbdeec7cff4ad36367166" integrity sha512-h9XcNI6lxYStaw32pHpB1TMm0RuxphF+Ik4o7tcQiodEdpKK+wKufY6QXtba7t3k8eseirEMVB83uFFF3Nu54A== +"@algolia/cache-common@4.24.0": + version "4.24.0" + resolved "https://registry.yarnpkg.com/@algolia/cache-common/-/cache-common-4.24.0.tgz#81a8d3a82ceb75302abb9b150a52eba9960c9744" + integrity sha512-emi+v+DmVLpMGhp0V9q9h5CdkURsNmFC+cOS6uK9ndeJm9J4TiqSvPYVu+THUP8P/S08rxf5x2P+p3CfID0Y4g== + "@algolia/cache-in-memory@4.23.3": version "4.23.3" resolved "https://registry.yarnpkg.com/@algolia/cache-in-memory/-/cache-in-memory-4.23.3.tgz#3945f87cd21ffa2bec23890c85305b6b11192423" @@ -75,6 +80,14 @@ "@algolia/requester-common" "4.23.3" "@algolia/transporter" "4.23.3" +"@algolia/client-common@4.24.0": + version "4.24.0" + resolved "https://registry.yarnpkg.com/@algolia/client-common/-/client-common-4.24.0.tgz#77c46eee42b9444a1d1c1583a83f7df4398a649d" + integrity sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA== + dependencies: + "@algolia/requester-common" "4.24.0" + "@algolia/transporter" "4.24.0" + "@algolia/client-personalization@4.23.3": version "4.23.3" resolved "https://registry.yarnpkg.com/@algolia/client-personalization/-/client-personalization-4.23.3.tgz#35fa8e5699b0295fbc400a8eb211dc711e5909db" @@ -84,7 +97,7 @@ "@algolia/requester-common" "4.23.3" "@algolia/transporter" "4.23.3" -"@algolia/client-search@4.23.3", "@algolia/client-search@^4.23.3": +"@algolia/client-search@4.23.3": version "4.23.3" resolved "https://registry.yarnpkg.com/@algolia/client-search/-/client-search-4.23.3.tgz#a3486e6af13a231ec4ab43a915a1f318787b937f" integrity sha512-P4VAKFHqU0wx9O+q29Q8YVuaowaZ5EM77rxfmGnkHUJggh28useXQdopokgwMeYw2XUht49WX5RcTQ40rZIabw== @@ -93,6 +106,15 @@ "@algolia/requester-common" "4.23.3" "@algolia/transporter" "4.23.3" +"@algolia/client-search@^4.24.0": + version "4.24.0" + resolved "https://registry.yarnpkg.com/@algolia/client-search/-/client-search-4.24.0.tgz#75e6c02d33ef3e0f34afd9962c085b856fc4a55f" + integrity sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA== + dependencies: + "@algolia/client-common" "4.24.0" + "@algolia/requester-common" "4.24.0" + "@algolia/transporter" "4.24.0" + "@algolia/events@^4.0.1": version "4.0.1" resolved "https://registry.yarnpkg.com/@algolia/events/-/events-4.0.1.tgz#fd39e7477e7bc703d7f893b556f676c032af3950" @@ -103,6 +125,11 @@ resolved "https://registry.yarnpkg.com/@algolia/logger-common/-/logger-common-4.23.3.tgz#35c6d833cbf41e853a4f36ba37c6e5864920bfe9" integrity sha512-y9kBtmJwiZ9ZZ+1Ek66P0M68mHQzKRxkW5kAAXYN/rdzgDN0d2COsViEFufxJ0pb45K4FRcfC7+33YB4BLrZ+g== +"@algolia/logger-common@4.24.0": + version "4.24.0" + resolved "https://registry.yarnpkg.com/@algolia/logger-common/-/logger-common-4.24.0.tgz#28d439976019ec0a46ba7a1a739ef493d4ef8123" + integrity sha512-LLUNjkahj9KtKYrQhFKCzMx0BY3RnNP4FEtO+sBybCjJ73E8jNdaKJ/Dd8A/VA4imVHP5tADZ8pn5B8Ga/wTMA== + "@algolia/logger-console@4.23.3": version "4.23.3" resolved "https://registry.yarnpkg.com/@algolia/logger-console/-/logger-console-4.23.3.tgz#30f916781826c4db5f51fcd9a8a264a06e136985" @@ -139,6 +166,11 @@ resolved "https://registry.yarnpkg.com/@algolia/requester-common/-/requester-common-4.23.3.tgz#7dbae896e41adfaaf1d1fa5f317f83a99afb04b3" integrity sha512-xloIdr/bedtYEGcXCiF2muajyvRhwop4cMZo+K2qzNht0CMzlRkm8YsDdj5IaBhshqfgmBb3rTg4sL4/PpvLYw== +"@algolia/requester-common@4.24.0": + version "4.24.0" + resolved "https://registry.yarnpkg.com/@algolia/requester-common/-/requester-common-4.24.0.tgz#1c60c198031f48fcdb9e34c4057a3ea987b9a436" + integrity sha512-k3CXJ2OVnvgE3HMwcojpvY6d9kgKMPRxs/kVohrwF5WMr2fnqojnycZkxPoEg+bXm8fi5BBfFmOqgYztRtHsQA== + "@algolia/requester-node-http@4.23.3": version "4.23.3" resolved "https://registry.yarnpkg.com/@algolia/requester-node-http/-/requester-node-http-4.23.3.tgz#c9f94a5cb96a15f48cea338ab6ef16bbd0ff989f" @@ -155,6 +187,15 @@ "@algolia/logger-common" "4.23.3" "@algolia/requester-common" "4.23.3" +"@algolia/transporter@4.24.0": + version "4.24.0" + resolved "https://registry.yarnpkg.com/@algolia/transporter/-/transporter-4.24.0.tgz#226bb1f8af62430374c1972b2e5c8580ab275102" + integrity sha512-86nI7w6NzWxd1Zp9q3413dRshDqAzSbsQjhcDhPIatEFiZrL1/TjnHL8S7jVKFePlIMzDsZWXAXwXzcok9c5oA== + dependencies: + "@algolia/cache-common" "4.24.0" + "@algolia/logger-common" "4.24.0" + "@algolia/requester-common" "4.24.0" + "@ampproject/remapping@^2.2.0": version "2.3.0" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" @@ -3086,10 +3127,10 @@ accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: mime-types "~2.1.34" negotiator "0.6.3" -acorn-import-assertions@^1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac" - integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA== +acorn-import-attributes@^1.9.5: + version "1.9.5" + resolved "https://registry.yarnpkg.com/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz#7eb1557b1ba05ef18b5ed0ec67591bfab04688ef" + integrity sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ== acorn-jsx@^5.0.0: version "5.3.2" @@ -3924,6 +3965,11 @@ common-path-prefix@^3.0.0: resolved "https://registry.yarnpkg.com/common-path-prefix/-/common-path-prefix-3.0.0.tgz#7d007a7e07c58c4b4d5f433131a19141b29f11e0" integrity sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w== +component-emitter@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-2.0.0.tgz#3a137dfe66fcf2efe3eab7cb7d5f51741b3620c6" + integrity sha512-4m5s3Me2xxlVKG9PkZpQqHQR7bgpnN7joDMJ4yvVkVXngjoITG76IaZmzmywSeRTeTpc6N6r3H3+KyUurV8OYw== + compressible@~2.0.16: version "2.0.18" resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" @@ -4566,11 +4612,6 @@ electron-to-chromium@^1.4.668: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.752.tgz#99227455547c8254488e3dab7d316c34a2c067b8" integrity sha512-P3QJreYI/AUTcfBVrC4zy9KvnZWekViThgQMX/VpJ+IsOBbcX5JFpORM4qWapwWQ+agb2nYAOyn/4PMXOk0m2Q== -emitter-component@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/emitter-component/-/emitter-component-1.1.2.tgz#d65af5833dc7c682fd0ade35f902d16bc4bad772" - integrity sha512-QdXO3nXOzZB4pAjM0n6ZE+R9/+kPpECA/XSELIcc54NeYVnBqIk+4DFiBgK+8QbV3mdvTG6nedl7dTYgO+5wDw== - emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" @@ -4617,10 +4658,10 @@ end-of-stream@^1.1.0, end-of-stream@^1.4.1: dependencies: once "^1.4.0" -enhanced-resolve@^5.16.0: - version "5.16.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.16.0.tgz#65ec88778083056cb32487faa9aef82ed0864787" - integrity sha512-O+QWCviPNSSLAD9Ucn8Awv+poAkqn3T1XY5/N7kR7rQO9yfSGWkYZDwpJ+iKF7B8rxaQKWngSqACpgzeapSyoA== +enhanced-resolve@^5.17.0: + version "5.17.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.17.0.tgz#d037603789dd9555b89aaec7eb78845c49089bc5" + integrity sha512-dwDPwZL0dmye8Txp2gzFmA6sxALaSvdRDjPH0viLcKrtlOL3tw62nWWweVD1SdILDTJrbrL6tdWVN58Wo6U3eA== dependencies: graceful-fs "^4.2.4" tapable "^2.2.0" @@ -9465,12 +9506,12 @@ std-env@^3.0.1: resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.7.0.tgz#c9f7386ced6ecf13360b6c6c55b8aaa4ef7481d2" integrity sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg== -stream@^0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/stream/-/stream-0.0.2.tgz#7f5363f057f6592c5595f00bc80a27f5cec1f0ef" - integrity sha512-gCq3NDI2P35B2n6t76YJuOp7d6cN/C7Rt0577l91wllh0sY9ZBuw9KaSGqH/b0hzn3CWWJbpbW0W0WvQ1H/Q7g== +stream@^0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/stream/-/stream-0.0.3.tgz#3f3934a900a561ce3e2b9ffbd2819cead32699d9" + integrity sha512-aMsbn7VKrl4A2T7QAQQbzgN7NVc70vgF5INQrBXqn4dCXN1zy3L9HGgLO5s7PExmdrzTJ8uR/27aviW8or8/+A== dependencies: - emitter-component "^1.1.1" + component-emitter "^2.0.0" string-convert@^0.2.0: version "0.2.1" @@ -9893,10 +9934,10 @@ types-ramda@^0.30.0: dependencies: ts-toolbelt "^9.6.0" -typescript@^5.4.5: - version "5.4.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.5.tgz#42ccef2c571fdbd0f6718b1d1f5e6e5ef006f611" - integrity sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ== +typescript@^5.5.2: + version "5.5.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.2.tgz#c26f023cb0054e657ce04f72583ea2d85f8d0507" + integrity sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew== undici-types@~5.26.4: version "5.26.5" @@ -10245,10 +10286,10 @@ webpack-sources@^3.2.3: resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== -webpack@^5.88.1, webpack@^5.91.0: - version "5.91.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.91.0.tgz#ffa92c1c618d18c878f06892bbdc3373c71a01d9" - integrity sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw== +webpack@^5.88.1, webpack@^5.92.1: + version "5.92.1" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.92.1.tgz#eca5c1725b9e189cffbd86e8b6c3c7400efc5788" + integrity sha512-JECQ7IwJb+7fgUFBlrJzbyu3GEuNBcdqr1LD7IbSzwkSmIevTm8PF+wej3Oxuz/JFBUZ6O1o43zsPkwm1C4TmA== dependencies: "@types/eslint-scope" "^3.7.3" "@types/estree" "^1.0.5" @@ -10256,10 +10297,10 @@ webpack@^5.88.1, webpack@^5.91.0: "@webassemblyjs/wasm-edit" "^1.12.1" "@webassemblyjs/wasm-parser" "^1.12.1" acorn "^8.7.1" - acorn-import-assertions "^1.9.0" + acorn-import-attributes "^1.9.5" browserslist "^4.21.10" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.16.0" + enhanced-resolve "^5.17.0" es-module-lexer "^1.2.1" eslint-scope "5.1.1" events "^3.2.0" diff --git a/superset-frontend/package-lock.json b/superset-frontend/package-lock.json index 03cb16269688..676b956c150d 100644 --- a/superset-frontend/package-lock.json +++ b/superset-frontend/package-lock.json @@ -229,7 +229,7 @@ "eslint": "^8.56.0", "eslint-config-airbnb": "^19.0.4", "eslint-config-prettier": "^7.2.0", - "eslint-import-resolver-typescript": "^2.5.0", + "eslint-import-resolver-typescript": "^3.6.1", "eslint-plugin-cypress": "^2.11.2", "eslint-plugin-file-progress": "^1.2.0", "eslint-plugin-import": "^2.24.2", @@ -23483,12 +23483,6 @@ "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" }, - "node_modules/@types/parse5": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-6.0.3.tgz", - "integrity": "sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==", - "license": "MIT" - }, "node_modules/@types/prettier": { "version": "2.7.3", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", @@ -32777,6 +32771,18 @@ "node": ">= 4.2.1" } }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/devtools-protocol": { "version": "0.0.1232444", "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1232444.tgz", @@ -34102,19 +34108,24 @@ "dev": true }, "node_modules/eslint-import-resolver-typescript": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-2.5.0.tgz", - "integrity": "sha512-qZ6e5CFr+I7K4VVhQu3M/9xGv9/YmwsEXrsm3nimw8vWaVHRDrQRp26BgCypTxBp3vUp4o5aVEJRiy0F2DFddQ==", + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.1.tgz", + "integrity": "sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==", "dev": true, "dependencies": { - "debug": "^4.3.1", - "glob": "^7.1.7", - "is-glob": "^4.0.1", - "resolve": "^1.20.0", - "tsconfig-paths": "^3.9.0" + "debug": "^4.3.4", + "enhanced-resolve": "^5.12.0", + "eslint-module-utils": "^2.7.4", + "fast-glob": "^3.3.1", + "get-tsconfig": "^4.5.0", + "is-core-module": "^2.11.0", + "is-glob": "^4.0.3" }, "engines": { - "node": ">=4" + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts/projects/eslint-import-resolver-ts" }, "peerDependencies": { "eslint": "*", @@ -34122,9 +34133,9 @@ } }, "node_modules/eslint-import-resolver-typescript/node_modules/debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "dev": true, "dependencies": { "ms": "2.1.2" @@ -34138,12 +34149,34 @@ } } }, + "node_modules/eslint-import-resolver-typescript/node_modules/enhanced-resolve": { + "version": "5.17.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.0.tgz", + "integrity": "sha512-dwDPwZL0dmye8Txp2gzFmA6sxALaSvdRDjPH0viLcKrtlOL3tw62nWWweVD1SdILDTJrbrL6tdWVN58Wo6U3eA==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/eslint-import-resolver-typescript/node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "node_modules/eslint-import-resolver-typescript/node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/eslint-module-utils": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", @@ -38681,11 +38714,264 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-from-parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.1.tgz", + "integrity": "sha512-Er/Iixbc7IEa7r/XLtuG52zoqn/b3Xng/w6aZQ0xGVxzhw5xUFxcRqdPzP6yFi/4HBYRaifaI5fQ1RH8n0ZeOQ==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "hastscript": "^8.0.0", + "property-information": "^6.0.0", + "vfile": "^6.0.0", + "vfile-location": "^5.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/hast-util-from-parse5/node_modules/@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==" + }, + "node_modules/hast-util-from-parse5/node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/hast-util-from-parse5/node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5/node_modules/hastscript": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-8.0.0.tgz", + "integrity": "sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw==", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5/node_modules/property-information": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", + "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/hast-util-from-parse5/node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/hast-util-from-parse5/node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5/node_modules/vfile": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.1.tgz", + "integrity": "sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5/node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-parse-selector": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.1.tgz", "integrity": "sha512-Xyh0v+nHmQvrOqop2Jqd8gOdyQtE8sIP9IQf7mlVDqp924W4w/8Liuguk2L2qei9hARnQSG2m+wAOCxM7npJVw==" }, + "node_modules/hast-util-raw": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.0.4.tgz", + "integrity": "sha512-LHE65TD2YiNsHD3YuXcKPHXPLuYh/gjp12mOfU8jxSrm1f/yJpsb0F/KKljS6U9LJoP0Ux+tCe8iJ2AsPzTdgA==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-from-parse5": "^8.0.0", + "hast-util-to-parse5": "^8.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "parse5": "^7.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/hast-util-raw/node_modules/@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==" + }, + "node_modules/hast-util-raw/node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw/node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw/node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw/node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw/node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw/node_modules/vfile": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.1.tgz", + "integrity": "sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw/node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-sanitize": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/hast-util-sanitize/-/hast-util-sanitize-5.0.1.tgz", @@ -38791,13 +39077,57 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/hast-util-to-estree/node_modules/style-to-object": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.4.4.tgz", - "integrity": "sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg==", - "dev": true, + "node_modules/hast-util-to-parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz", + "integrity": "sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==", "dependencies": { - "inline-style-parser": "0.1.1" + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-parse5/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/hast-util-to-parse5/node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/hast-util-to-parse5/node_modules/property-information": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", + "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/hast-util-to-parse5/node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, "node_modules/hast-util-whitespace": { @@ -38990,6 +39320,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/html-webpack-plugin": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.0.tgz", @@ -51380,6 +51719,221 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/mdast-util-to-hast/node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/mdast-util-to-hast/node_modules/@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==" + }, + "node_modules/mdast-util-to-hast/node_modules/micromark-util-character": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-to-hast/node_modules/micromark-util-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.0.tgz", + "integrity": "sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/mdast-util-to-hast/node_modules/micromark-util-sanitize-uri": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.0.tgz", + "integrity": "sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-to-hast/node_modules/micromark-util-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/mdast-util-to-hast/node_modules/micromark-util-types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.0.tgz", + "integrity": "sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/mdast-util-to-hast/node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast/node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast/node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast/node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast/node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast/node_modules/vfile": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.1.tgz", + "integrity": "sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast/node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/mdast-util-to-string": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-1.1.0.tgz", @@ -59040,9 +59594,9 @@ } }, "node_modules/react-markdown": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-8.0.3.tgz", - "integrity": "sha512-We36SfqaKoVNpN1QqsZwWSv/OZt5J15LNgTLWynwAN5b265hrQrsjMtlRNwUvS+YyR3yDM8HpTNc4pK9H/Gc0A==", + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-8.0.7.tgz", + "integrity": "sha512-bvWbzG4MtOU62XqBx3Xx+zB2raaFFsq4mYiAzfjXJMEz2sixgeAfraA3tvzULF02ZdOMUOKTBFFaZJDDrq+BJQ==", "dependencies": { "@types/hast": "^2.0.0", "@types/prop-types": "^15.0.0", @@ -59055,7 +59609,7 @@ "remark-parse": "^10.0.0", "remark-rehype": "^10.0.0", "space-separated-tokens": "^2.0.0", - "style-to-object": "^0.3.0", + "style-to-object": "^0.4.0", "unified": "^10.0.0", "unist-util-visit": "^4.0.0", "vfile": "^5.0.0" @@ -60210,173 +60764,71 @@ } }, "node_modules/rehype-raw": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-6.1.1.tgz", - "integrity": "sha512-d6AKtisSRtDRX4aSPsJGTfnzrX2ZkHQLE5kiUuGOeEoLpbEulFF4hj0mLPbsa+7vmguDKOVVEQdHKDSwoaIDsQ==", - "dependencies": { - "@types/hast": "^2.0.0", - "hast-util-raw": "^7.2.0", - "unified": "^10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/rehype-raw/node_modules/comma-separated-tokens": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.2.tgz", - "integrity": "sha512-G5yTt3KQN4Yn7Yk4ed73hlZ1evrFKXeUW3086p3PRFNp7m2vIjI6Pg+Kgb+oyzhd9F2qdcoj67+y3SdxL5XWsg==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/rehype-raw/node_modules/hast-to-hyperscript": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/hast-to-hyperscript/-/hast-to-hyperscript-10.0.1.tgz", - "integrity": "sha512-dhIVGoKCQVewFi+vz3Vt567E4ejMppS1haBRL6TEmeLeJVB1i/FJIIg/e6s1Bwn0g5qtYojHEKvyGA+OZuyifw==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", + "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", "dependencies": { - "@types/unist": "^2.0.0", - "comma-separated-tokens": "^2.0.0", - "property-information": "^6.0.0", - "space-separated-tokens": "^2.0.0", - "style-to-object": "^0.3.0", - "unist-util-is": "^5.0.0", - "web-namespaces": "^2.0.0" + "@types/hast": "^3.0.0", + "hast-util-raw": "^9.0.0", + "vfile": "^6.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/rehype-raw/node_modules/hast-util-from-parse5": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-7.1.0.tgz", - "integrity": "sha512-m8yhANIAccpU4K6+121KpPP55sSl9/samzQSQGpb0mTExcNh2WlvjtMwSWFhg6uqD4Rr6Nfa8N6TMypQM51rzQ==", + "node_modules/rehype-raw/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", "dependencies": { - "@types/hast": "^2.0.0", - "@types/parse5": "^6.0.0", - "@types/unist": "^2.0.0", - "hastscript": "^7.0.0", - "property-information": "^6.0.0", - "vfile": "^5.0.0", - "vfile-location": "^4.0.0", - "web-namespaces": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "@types/unist": "*" } }, - "node_modules/rehype-raw/node_modules/hast-util-parse-selector": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.0.tgz", - "integrity": "sha512-AyjlI2pTAZEOeu7GeBPZhROx0RHBnydkQIXlhnFzDi0qfXTmGUWoCYZtomHbrdrheV4VFUlPcfJ6LMF5T6sQzg==", - "dependencies": { - "@types/hast": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } + "node_modules/rehype-raw/node_modules/@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==" }, - "node_modules/rehype-raw/node_modules/hast-util-raw": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-7.2.2.tgz", - "integrity": "sha512-0x3BhhdlBcqRIKyc095lBSDvmQNMY3Eulj2PLsT5XCyKYrxssI5yr3P4Kv/PBo1s/DMkZy2voGkMXECnFCZRLQ==", + "node_modules/rehype-raw/node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", "dependencies": { - "@types/hast": "^2.0.0", - "@types/parse5": "^6.0.0", - "hast-util-from-parse5": "^7.0.0", - "hast-util-to-parse5": "^7.0.0", - "html-void-elements": "^2.0.0", - "parse5": "^6.0.0", - "unist-util-position": "^4.0.0", - "unist-util-visit": "^4.0.0", - "vfile": "^5.0.0", - "web-namespaces": "^2.0.0", - "zwitch": "^2.0.0" + "@types/unist": "^3.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/rehype-raw/node_modules/hast-util-to-parse5": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-7.0.0.tgz", - "integrity": "sha512-YHiS6aTaZ3N0Q3nxaY/Tj98D6kM8QX5Q8xqgg8G45zR7PvWnPGPP0vcKCgb/moIydEJ/QWczVrX0JODCVeoV7A==", + "node_modules/rehype-raw/node_modules/vfile": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.1.tgz", + "integrity": "sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==", "dependencies": { - "@types/hast": "^2.0.0", - "@types/parse5": "^6.0.0", - "hast-to-hyperscript": "^10.0.0", - "property-information": "^6.0.0", - "web-namespaces": "^2.0.0", - "zwitch": "^2.0.0" + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/rehype-raw/node_modules/hastscript": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-7.1.0.tgz", - "integrity": "sha512-uBjaTTLN0MkCZxY/R2fWUOcu7FRtUVzKRO5P/RAfgsu3yFiMB1JWCO4AjeVkgHxAira1f2UecHK5WfS9QurlWA==", + "node_modules/rehype-raw/node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", "dependencies": { - "@types/hast": "^2.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-parse-selector": "^3.0.0", - "property-information": "^6.0.0", - "space-separated-tokens": "^2.0.0" + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/rehype-raw/node_modules/html-void-elements": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz", - "integrity": "sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/rehype-raw/node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" - }, - "node_modules/rehype-raw/node_modules/property-information": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.1.1.tgz", - "integrity": "sha512-hrzC564QIl0r0vy4l6MvRLhafmUowhO/O3KgVSoXIbbA2Sz4j8HGpJc6T2cubRVwMwpdiG/vKGfhT4IixmKN9w==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/rehype-raw/node_modules/space-separated-tokens": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.1.tgz", - "integrity": "sha512-ekwEbFp5aqSPKaqeY1PGrlGQxPNaq+Cnx4+bE2D8sciBQrHpbwoBbawqTN2+6jPs9IdWxxiUcN0K2pkczD3zmw==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/rehype-raw/node_modules/web-namespaces": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", - "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/rehype-sanitize": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/rehype-sanitize/-/rehype-sanitize-6.0.0.tgz", @@ -63182,9 +63634,9 @@ } }, "node_modules/style-to-object": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.3.0.tgz", - "integrity": "sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA==", + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.4.4.tgz", + "integrity": "sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg==", "dependencies": { "inline-style-parser": "0.1.1" } @@ -65461,12 +65913,56 @@ } }, "node_modules/vfile-location": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-4.0.1.tgz", - "integrity": "sha512-JDxPlTbZrZCQXogGheBHjbRWjESSPEak770XwWPfw5mTc1v1nWGLB/apzZxsx8a0SJVfF8HK8ql8RD308vXRUw==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.2.tgz", + "integrity": "sha512-NXPYyxyBSH7zB5U6+3uDdd6Nybz6o6/od9rk8bp9H8GR3L+cm/fC0uUTbqBmUTnMCUDslAGBOIKNfvvb+gGlDg==", "dependencies": { - "@types/unist": "^2.0.0", - "vfile": "^5.0.0" + "@types/unist": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location/node_modules/@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==" + }, + "node_modules/vfile-location/node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location/node_modules/vfile": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.1.tgz", + "integrity": "sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location/node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" }, "funding": { "type": "opencollective", @@ -65667,6 +66163,15 @@ "defaults": "^1.0.3" } }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/web-streams-polyfill": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.2.tgz", @@ -68194,8 +68699,8 @@ "math-expression-evaluator": "^1.3.8", "pretty-ms": "^7.0.0", "react-error-boundary": "^1.2.5", - "react-markdown": "^8.0.3", - "rehype-raw": "^6.1.1", + "react-markdown": "^8.0.7", + "rehype-raw": "^7.0.0", "rehype-sanitize": "^6.0.0", "remark-gfm": "^3.0.1", "reselect": "^4.0.0", @@ -70015,7 +70520,7 @@ }, "devDependencies": { "@types/jest": "^29.5.12", - "@types/lodash": "^4.17.4", + "@types/lodash": "^4.17.6", "jest": "^29.7.0" }, "peerDependencies": { @@ -70030,9 +70535,9 @@ } }, "plugins/plugin-chart-handlebars/node_modules/@types/lodash": { - "version": "4.17.4", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.4.tgz", - "integrity": "sha512-wYCP26ZLxaT3R39kiN2+HcJ4kTd3U1waI/cY7ivWYqFP6pW3ZNpvi6Wd6PHZx7T/t8z0vlkXMg3QYLa7DZ/IJQ==", + "version": "4.17.6", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.6.tgz", + "integrity": "sha512-OpXEVoCKSS3lQqjx9GGGOapBeuW5eUboYHRlHP9urXPX25IKZ6AnP5ZRxtVf63iieUbsHxLn8NQ5Nlftc6yzAA==", "dev": true }, "plugins/plugin-chart-handlebars/node_modules/just-handlebars-helpers": { @@ -87228,8 +87733,8 @@ "math-expression-evaluator": "^1.3.8", "pretty-ms": "^7.0.0", "react-error-boundary": "^1.2.5", - "react-markdown": "^8.0.3", - "rehype-raw": "^6.1.1", + "react-markdown": "^8.0.7", + "rehype-raw": "^7.0.0", "rehype-sanitize": "^6.0.0", "remark-gfm": "^3.0.1", "reselect": "^4.0.0", @@ -88548,16 +89053,16 @@ "version": "file:plugins/plugin-chart-handlebars", "requires": { "@types/jest": "^29.5.12", - "@types/lodash": "^4.17.4", + "@types/lodash": "^4.17.6", "handlebars": "^4.7.7", "jest": "^29.7.0", "just-handlebars-helpers": "^1.0.19" }, "dependencies": { "@types/lodash": { - "version": "4.17.4", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.4.tgz", - "integrity": "sha512-wYCP26ZLxaT3R39kiN2+HcJ4kTd3U1waI/cY7ivWYqFP6pW3ZNpvi6Wd6PHZx7T/t8z0vlkXMg3QYLa7DZ/IJQ==", + "version": "4.17.6", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.6.tgz", + "integrity": "sha512-OpXEVoCKSS3lQqjx9GGGOapBeuW5eUboYHRlHP9urXPX25IKZ6AnP5ZRxtVf63iieUbsHxLn8NQ5Nlftc6yzAA==", "dev": true }, "just-handlebars-helpers": { @@ -91210,11 +91715,6 @@ "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" }, - "@types/parse5": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-6.0.3.tgz", - "integrity": "sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==" - }, "@types/prettier": { "version": "2.7.3", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", @@ -98518,6 +99018,14 @@ "debug": "^2.6.0" } }, + "devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "requires": { + "dequal": "^2.0.0" + } + }, "devtools-protocol": { "version": "0.0.1232444", "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1232444.tgz", @@ -99827,32 +100335,50 @@ } }, "eslint-import-resolver-typescript": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-2.5.0.tgz", - "integrity": "sha512-qZ6e5CFr+I7K4VVhQu3M/9xGv9/YmwsEXrsm3nimw8vWaVHRDrQRp26BgCypTxBp3vUp4o5aVEJRiy0F2DFddQ==", + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.1.tgz", + "integrity": "sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==", "dev": true, "requires": { - "debug": "^4.3.1", - "glob": "^7.1.7", - "is-glob": "^4.0.1", - "resolve": "^1.20.0", - "tsconfig-paths": "^3.9.0" + "debug": "^4.3.4", + "enhanced-resolve": "^5.12.0", + "eslint-module-utils": "^2.7.4", + "fast-glob": "^3.3.1", + "get-tsconfig": "^4.5.0", + "is-core-module": "^2.11.0", + "is-glob": "^4.0.3" }, "dependencies": { "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "dev": true, "requires": { "ms": "2.1.2" } }, + "enhanced-resolve": { + "version": "5.17.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.0.tgz", + "integrity": "sha512-dwDPwZL0dmye8Txp2gzFmA6sxALaSvdRDjPH0viLcKrtlOL3tw62nWWweVD1SdILDTJrbrL6tdWVN58Wo6U3eA==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + } + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true + }, + "tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true } } }, @@ -103036,11 +103562,200 @@ "function-bind": "^1.1.2" } }, + "hast-util-from-parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.1.tgz", + "integrity": "sha512-Er/Iixbc7IEa7r/XLtuG52zoqn/b3Xng/w6aZQ0xGVxzhw5xUFxcRqdPzP6yFi/4HBYRaifaI5fQ1RH8n0ZeOQ==", + "requires": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "hastscript": "^8.0.0", + "property-information": "^6.0.0", + "vfile": "^6.0.0", + "vfile-location": "^5.0.0", + "web-namespaces": "^2.0.0" + }, + "dependencies": { + "@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "requires": { + "@types/unist": "*" + } + }, + "@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==" + }, + "comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==" + }, + "hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "requires": { + "@types/hast": "^3.0.0" + } + }, + "hastscript": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-8.0.0.tgz", + "integrity": "sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw==", + "requires": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0" + } + }, + "property-information": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", + "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==" + }, + "space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==" + }, + "unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "requires": { + "@types/unist": "^3.0.0" + } + }, + "vfile": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.1.tgz", + "integrity": "sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==", + "requires": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + } + }, + "vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "requires": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + } + } + } + }, "hast-util-parse-selector": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.1.tgz", "integrity": "sha512-Xyh0v+nHmQvrOqop2Jqd8gOdyQtE8sIP9IQf7mlVDqp924W4w/8Liuguk2L2qei9hARnQSG2m+wAOCxM7npJVw==" }, + "hast-util-raw": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.0.4.tgz", + "integrity": "sha512-LHE65TD2YiNsHD3YuXcKPHXPLuYh/gjp12mOfU8jxSrm1f/yJpsb0F/KKljS6U9LJoP0Ux+tCe8iJ2AsPzTdgA==", + "requires": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-from-parse5": "^8.0.0", + "hast-util-to-parse5": "^8.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "parse5": "^7.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "dependencies": { + "@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "requires": { + "@types/unist": "*" + } + }, + "@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==" + }, + "unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "requires": { + "@types/unist": "^3.0.0" + } + }, + "unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "requires": { + "@types/unist": "^3.0.0" + } + }, + "unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "requires": { + "@types/unist": "^3.0.0" + } + }, + "unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "requires": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + } + }, + "unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "requires": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + } + }, + "vfile": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.1.tgz", + "integrity": "sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==", + "requires": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + } + }, + "vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "requires": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + } + } + } + }, "hast-util-sanitize": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/hast-util-sanitize/-/hast-util-sanitize-5.0.1.tgz", @@ -103123,15 +103838,45 @@ "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", "dev": true - }, - "style-to-object": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.4.4.tgz", - "integrity": "sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg==", - "dev": true, + } + } + }, + "hast-util-to-parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz", + "integrity": "sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==", + "requires": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "dependencies": { + "@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", "requires": { - "inline-style-parser": "0.1.1" + "@types/unist": "*" } + }, + "comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==" + }, + "property-information": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", + "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==" + }, + "space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==" } } }, @@ -103285,6 +104030,11 @@ "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==" }, + "html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==" + }, "html-webpack-plugin": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.0.tgz", @@ -112589,6 +113339,141 @@ } } }, + "mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "requires": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "dependencies": { + "@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "requires": { + "@types/unist": "*" + } + }, + "@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "requires": { + "@types/unist": "*" + } + }, + "@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==" + }, + "micromark-util-character": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "requires": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "micromark-util-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.0.tgz", + "integrity": "sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==" + }, + "micromark-util-sanitize-uri": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.0.tgz", + "integrity": "sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==", + "requires": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "micromark-util-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==" + }, + "micromark-util-types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.0.tgz", + "integrity": "sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==" + }, + "unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "requires": { + "@types/unist": "^3.0.0" + } + }, + "unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "requires": { + "@types/unist": "^3.0.0" + } + }, + "unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "requires": { + "@types/unist": "^3.0.0" + } + }, + "unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "requires": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + } + }, + "unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "requires": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + } + }, + "vfile": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.1.tgz", + "integrity": "sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==", + "requires": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + } + }, + "vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "requires": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + } + } + } + }, "mdast-util-to-string": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-1.1.0.tgz", @@ -118194,9 +119079,9 @@ } }, "react-markdown": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-8.0.3.tgz", - "integrity": "sha512-We36SfqaKoVNpN1QqsZwWSv/OZt5J15LNgTLWynwAN5b265hrQrsjMtlRNwUvS+YyR3yDM8HpTNc4pK9H/Gc0A==", + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-8.0.7.tgz", + "integrity": "sha512-bvWbzG4MtOU62XqBx3Xx+zB2raaFFsq4mYiAzfjXJMEz2sixgeAfraA3tvzULF02ZdOMUOKTBFFaZJDDrq+BJQ==", "requires": { "@types/hast": "^2.0.0", "@types/prop-types": "^15.0.0", @@ -118209,7 +119094,7 @@ "remark-parse": "^10.0.0", "remark-rehype": "^10.0.0", "space-separated-tokens": "^2.0.0", - "style-to-object": "^0.3.0", + "style-to-object": "^0.4.0", "unified": "^10.0.0", "unist-util-visit": "^4.0.0", "vfile": "^5.0.0" @@ -119112,124 +119997,54 @@ } }, "rehype-raw": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-6.1.1.tgz", - "integrity": "sha512-d6AKtisSRtDRX4aSPsJGTfnzrX2ZkHQLE5kiUuGOeEoLpbEulFF4hj0mLPbsa+7vmguDKOVVEQdHKDSwoaIDsQ==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", + "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", "requires": { - "@types/hast": "^2.0.0", - "hast-util-raw": "^7.2.0", - "unified": "^10.0.0" + "@types/hast": "^3.0.0", + "hast-util-raw": "^9.0.0", + "vfile": "^6.0.0" }, "dependencies": { - "comma-separated-tokens": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.2.tgz", - "integrity": "sha512-G5yTt3KQN4Yn7Yk4ed73hlZ1evrFKXeUW3086p3PRFNp7m2vIjI6Pg+Kgb+oyzhd9F2qdcoj67+y3SdxL5XWsg==" - }, - "hast-to-hyperscript": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/hast-to-hyperscript/-/hast-to-hyperscript-10.0.1.tgz", - "integrity": "sha512-dhIVGoKCQVewFi+vz3Vt567E4ejMppS1haBRL6TEmeLeJVB1i/FJIIg/e6s1Bwn0g5qtYojHEKvyGA+OZuyifw==", - "requires": { - "@types/unist": "^2.0.0", - "comma-separated-tokens": "^2.0.0", - "property-information": "^6.0.0", - "space-separated-tokens": "^2.0.0", - "style-to-object": "^0.3.0", - "unist-util-is": "^5.0.0", - "web-namespaces": "^2.0.0" - } - }, - "hast-util-from-parse5": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-7.1.0.tgz", - "integrity": "sha512-m8yhANIAccpU4K6+121KpPP55sSl9/samzQSQGpb0mTExcNh2WlvjtMwSWFhg6uqD4Rr6Nfa8N6TMypQM51rzQ==", + "@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", "requires": { - "@types/hast": "^2.0.0", - "@types/parse5": "^6.0.0", - "@types/unist": "^2.0.0", - "hastscript": "^7.0.0", - "property-information": "^6.0.0", - "vfile": "^5.0.0", - "vfile-location": "^4.0.0", - "web-namespaces": "^2.0.0" + "@types/unist": "*" } }, - "hast-util-parse-selector": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.0.tgz", - "integrity": "sha512-AyjlI2pTAZEOeu7GeBPZhROx0RHBnydkQIXlhnFzDi0qfXTmGUWoCYZtomHbrdrheV4VFUlPcfJ6LMF5T6sQzg==", - "requires": { - "@types/hast": "^2.0.0" - } + "@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==" }, - "hast-util-raw": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-7.2.2.tgz", - "integrity": "sha512-0x3BhhdlBcqRIKyc095lBSDvmQNMY3Eulj2PLsT5XCyKYrxssI5yr3P4Kv/PBo1s/DMkZy2voGkMXECnFCZRLQ==", + "unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", "requires": { - "@types/hast": "^2.0.0", - "@types/parse5": "^6.0.0", - "hast-util-from-parse5": "^7.0.0", - "hast-util-to-parse5": "^7.0.0", - "html-void-elements": "^2.0.0", - "parse5": "^6.0.0", - "unist-util-position": "^4.0.0", - "unist-util-visit": "^4.0.0", - "vfile": "^5.0.0", - "web-namespaces": "^2.0.0", - "zwitch": "^2.0.0" + "@types/unist": "^3.0.0" } }, - "hast-util-to-parse5": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-7.0.0.tgz", - "integrity": "sha512-YHiS6aTaZ3N0Q3nxaY/Tj98D6kM8QX5Q8xqgg8G45zR7PvWnPGPP0vcKCgb/moIydEJ/QWczVrX0JODCVeoV7A==", + "vfile": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.1.tgz", + "integrity": "sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==", "requires": { - "@types/hast": "^2.0.0", - "@types/parse5": "^6.0.0", - "hast-to-hyperscript": "^10.0.0", - "property-information": "^6.0.0", - "web-namespaces": "^2.0.0", - "zwitch": "^2.0.0" + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" } }, - "hastscript": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-7.1.0.tgz", - "integrity": "sha512-uBjaTTLN0MkCZxY/R2fWUOcu7FRtUVzKRO5P/RAfgsu3yFiMB1JWCO4AjeVkgHxAira1f2UecHK5WfS9QurlWA==", + "vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", "requires": { - "@types/hast": "^2.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-parse-selector": "^3.0.0", - "property-information": "^6.0.0", - "space-separated-tokens": "^2.0.0" + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" } - }, - "html-void-elements": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz", - "integrity": "sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==" - }, - "parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" - }, - "property-information": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.1.1.tgz", - "integrity": "sha512-hrzC564QIl0r0vy4l6MvRLhafmUowhO/O3KgVSoXIbbA2Sz4j8HGpJc6T2cubRVwMwpdiG/vKGfhT4IixmKN9w==" - }, - "space-separated-tokens": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.1.tgz", - "integrity": "sha512-ekwEbFp5aqSPKaqeY1PGrlGQxPNaq+Cnx4+bE2D8sciBQrHpbwoBbawqTN2+6jPs9IdWxxiUcN0K2pkczD3zmw==" - }, - "web-namespaces": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", - "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==" } } }, @@ -121412,9 +122227,9 @@ "requires": {} }, "style-to-object": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.3.0.tgz", - "integrity": "sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA==", + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.4.4.tgz", + "integrity": "sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg==", "requires": { "inline-style-parser": "0.1.1" } @@ -123128,12 +123943,46 @@ } }, "vfile-location": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-4.0.1.tgz", - "integrity": "sha512-JDxPlTbZrZCQXogGheBHjbRWjESSPEak770XwWPfw5mTc1v1nWGLB/apzZxsx8a0SJVfF8HK8ql8RD308vXRUw==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.2.tgz", + "integrity": "sha512-NXPYyxyBSH7zB5U6+3uDdd6Nybz6o6/od9rk8bp9H8GR3L+cm/fC0uUTbqBmUTnMCUDslAGBOIKNfvvb+gGlDg==", "requires": { - "@types/unist": "^2.0.0", - "vfile": "^5.0.0" + "@types/unist": "^3.0.0", + "vfile": "^6.0.0" + }, + "dependencies": { + "@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==" + }, + "unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "requires": { + "@types/unist": "^3.0.0" + } + }, + "vfile": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.1.tgz", + "integrity": "sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==", + "requires": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + } + }, + "vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "requires": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + } + } } }, "vfile-message": { @@ -123290,6 +124139,11 @@ "defaults": "^1.0.3" } }, + "web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==" + }, "web-streams-polyfill": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.2.tgz", diff --git a/superset-frontend/package.json b/superset-frontend/package.json index 34c8ebdd2140..9bdce10c7343 100644 --- a/superset-frontend/package.json +++ b/superset-frontend/package.json @@ -294,7 +294,7 @@ "eslint": "^8.56.0", "eslint-config-airbnb": "^19.0.4", "eslint-config-prettier": "^7.2.0", - "eslint-import-resolver-typescript": "^2.5.0", + "eslint-import-resolver-typescript": "^3.6.1", "eslint-plugin-cypress": "^2.11.2", "eslint-plugin-file-progress": "^1.2.0", "eslint-plugin-import": "^2.24.2", diff --git a/superset-frontend/packages/superset-ui-core/package.json b/superset-frontend/packages/superset-ui-core/package.json index 08bbf7806fa8..7f9144cda064 100644 --- a/superset-frontend/packages/superset-ui-core/package.json +++ b/superset-frontend/packages/superset-ui-core/package.json @@ -53,8 +53,8 @@ "math-expression-evaluator": "^1.3.8", "pretty-ms": "^7.0.0", "react-error-boundary": "^1.2.5", - "react-markdown": "^8.0.3", - "rehype-raw": "^6.1.1", + "react-markdown": "^8.0.7", + "rehype-raw": "^7.0.0", "rehype-sanitize": "^6.0.0", "remark-gfm": "^3.0.1", "reselect": "^4.0.0", diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/package.json b/superset-frontend/plugins/legacy-preset-chart-deckgl/package.json index 73b8128f04e6..04c3ce2cfe95 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/package.json +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/package.json @@ -31,7 +31,7 @@ "d3-array": "^1.2.4", "d3-color": "^1.4.1", "d3-scale": "^3.0.0", - "deck.gl": "9.0.12", + "deck.gl": "9.0.20", "lodash": "^4.17.21", "moment": "^2.30.1", "mousetrap": "^1.6.5", diff --git a/superset-frontend/plugins/plugin-chart-handlebars/package.json b/superset-frontend/plugins/plugin-chart-handlebars/package.json index ed63f04ad9bd..9ce608cd1be2 100644 --- a/superset-frontend/plugins/plugin-chart-handlebars/package.json +++ b/superset-frontend/plugins/plugin-chart-handlebars/package.json @@ -42,7 +42,7 @@ }, "devDependencies": { "@types/jest": "^29.5.12", - "@types/lodash": "^4.17.4", + "@types/lodash": "^4.17.6", "jest": "^29.7.0" } } diff --git a/superset-websocket/package-lock.json b/superset-websocket/package-lock.json index d6c0e54292a1..a0653df947dc 100644 --- a/superset-websocket/package-lock.json +++ b/superset-websocket/package-lock.json @@ -35,7 +35,7 @@ "eslint-plugin-lodash": "^7.4.0", "jest": "^29.7.0", "prettier": "^3.2.5", - "ts-jest": "^29.1.2", + "ts-jest": "^29.1.5", "ts-node": "^10.9.2", "typescript": "^4.9.5" }, @@ -6317,9 +6317,9 @@ } }, "node_modules/ts-jest": { - "version": "29.1.2", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.2.tgz", - "integrity": "sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==", + "version": "29.1.5", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.5.tgz", + "integrity": "sha512-UuClSYxM7byvvYfyWdFI+/2UxMmwNyJb0NPkZPQE2hew3RurV7l7zURgOHAd/1I1ZdPpe3GUsXNXAcN8TFKSIg==", "dev": true, "dependencies": { "bs-logger": "0.x", @@ -6335,10 +6335,11 @@ "ts-jest": "cli.js" }, "engines": { - "node": "^16.10.0 || ^18.0.0 || >=20.0.0" + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" }, "peerDependencies": { "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0", "@jest/types": "^29.0.0", "babel-jest": "^29.0.0", "jest": "^29.0.0", @@ -6348,6 +6349,9 @@ "@babel/core": { "optional": true }, + "@jest/transform": { + "optional": true + }, "@jest/types": { "optional": true }, @@ -11545,9 +11549,9 @@ "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==" }, "ts-jest": { - "version": "29.1.2", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.2.tgz", - "integrity": "sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==", + "version": "29.1.5", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.5.tgz", + "integrity": "sha512-UuClSYxM7byvvYfyWdFI+/2UxMmwNyJb0NPkZPQE2hew3RurV7l7zURgOHAd/1I1ZdPpe3GUsXNXAcN8TFKSIg==", "dev": true, "requires": { "bs-logger": "0.x", diff --git a/superset-websocket/package.json b/superset-websocket/package.json index bf481e00eae0..b3fd45441f9e 100644 --- a/superset-websocket/package.json +++ b/superset-websocket/package.json @@ -43,7 +43,7 @@ "eslint-plugin-lodash": "^7.4.0", "jest": "^29.7.0", "prettier": "^3.2.5", - "ts-jest": "^29.1.2", + "ts-jest": "^29.1.5", "ts-node": "^10.9.2", "typescript": "^4.9.5" }, diff --git a/superset/commands/dashboard/permalink/create.py b/superset/commands/dashboard/permalink/create.py index 7d08f78e9a9b..20bc5118f576 100644 --- a/superset/commands/dashboard/permalink/create.py +++ b/superset/commands/dashboard/permalink/create.py @@ -19,9 +19,10 @@ from sqlalchemy.exc import SQLAlchemyError +from superset import db from superset.commands.dashboard.permalink.base import BaseDashboardPermalinkCommand -from superset.commands.key_value.upsert import UpsertKeyValueCommand from superset.daos.dashboard import DashboardDAO +from superset.daos.key_value import KeyValueDAO from superset.dashboards.permalink.exceptions import DashboardPermalinkCreateFailedError from superset.dashboards.permalink.types import DashboardPermalinkState from superset.key_value.exceptions import ( @@ -70,14 +71,15 @@ def run(self) -> str: "state": self.state, } user_id = get_user_id() - key = UpsertKeyValueCommand( + entry = KeyValueDAO.upsert_entry( resource=self.resource, key=get_deterministic_uuid(self.salt, (user_id, value)), value=value, codec=self.codec, - ).run() - assert key.id # for type checks - return encode_permalink_key(key=key.id, salt=self.salt) + ) + db.session.flush() + assert entry.id # for type checks + return encode_permalink_key(key=entry.id, salt=self.salt) def validate(self) -> None: pass diff --git a/superset/commands/dashboard/permalink/get.py b/superset/commands/dashboard/permalink/get.py index 32efa688813c..e87711a5bfeb 100644 --- a/superset/commands/dashboard/permalink/get.py +++ b/superset/commands/dashboard/permalink/get.py @@ -21,8 +21,8 @@ from superset.commands.dashboard.exceptions import DashboardNotFoundError from superset.commands.dashboard.permalink.base import BaseDashboardPermalinkCommand -from superset.commands.key_value.get import GetKeyValueCommand from superset.daos.dashboard import DashboardDAO +from superset.daos.key_value import KeyValueDAO from superset.dashboards.permalink.exceptions import DashboardPermalinkGetFailedError from superset.dashboards.permalink.types import DashboardPermalinkValue from superset.key_value.exceptions import ( @@ -43,12 +43,7 @@ def run(self) -> Optional[DashboardPermalinkValue]: self.validate() try: key = decode_permalink_id(self.key, salt=self.salt) - command = GetKeyValueCommand( - resource=self.resource, - key=key, - codec=self.codec, - ) - value: Optional[DashboardPermalinkValue] = command.run() + value = KeyValueDAO.get_value(self.resource, key, self.codec) if value: DashboardDAO.get_by_id_or_slug(value["dashboardId"]) return value diff --git a/superset/commands/distributed_lock/__init__.py b/superset/commands/distributed_lock/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/integration_tests/key_value/__init__.py b/superset/commands/distributed_lock/base.py similarity index 54% rename from tests/integration_tests/key_value/__init__.py rename to superset/commands/distributed_lock/base.py index 13a83393a912..322063f54e89 100644 --- a/tests/integration_tests/key_value/__init__.py +++ b/superset/commands/distributed_lock/base.py @@ -14,3 +14,28 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. + +import logging +import uuid +from typing import Any + +from flask import current_app + +from superset.commands.base import BaseCommand +from superset.distributed_lock.utils import get_key +from superset.key_value.types import JsonKeyValueCodec, KeyValueResource + +logger = logging.getLogger(__name__) +stats_logger = current_app.config["STATS_LOGGER"] + + +class BaseDistributedLockCommand(BaseCommand): + key: uuid.UUID + codec = JsonKeyValueCodec() + resource = KeyValueResource.LOCK + + def __init__(self, namespace: str, params: dict[str, Any] | None = None): + self.key = get_key(namespace, **(params or {})) + + def validate(self) -> None: + pass diff --git a/superset/commands/distributed_lock/create.py b/superset/commands/distributed_lock/create.py new file mode 100644 index 000000000000..c654089336af --- /dev/null +++ b/superset/commands/distributed_lock/create.py @@ -0,0 +1,64 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +import logging +from datetime import datetime, timedelta +from functools import partial + +from flask import current_app +from sqlalchemy.exc import SQLAlchemyError + +from superset.commands.distributed_lock.base import BaseDistributedLockCommand +from superset.daos.key_value import KeyValueDAO +from superset.exceptions import CreateKeyValueDistributedLockFailedException +from superset.key_value.exceptions import ( + KeyValueCodecEncodeException, + KeyValueUpsertFailedError, +) +from superset.key_value.types import KeyValueResource +from superset.utils.decorators import on_error, transaction + +logger = logging.getLogger(__name__) +stats_logger = current_app.config["STATS_LOGGER"] + + +class CreateDistributedLock(BaseDistributedLockCommand): + lock_expiration = timedelta(seconds=30) + + def validate(self) -> None: + pass + + @transaction( + on_error=partial( + on_error, + catches=( + KeyValueCodecEncodeException, + KeyValueUpsertFailedError, + SQLAlchemyError, + ), + reraise=CreateKeyValueDistributedLockFailedException, + ), + ) + def run(self) -> None: + KeyValueDAO.delete_expired_entries(self.resource) + KeyValueDAO.create_entry( + resource=KeyValueResource.LOCK, + value={"value": True}, + codec=self.codec, + key=self.key, + expires_on=datetime.now() + self.lock_expiration, + ) diff --git a/superset/commands/key_value/delete_expired.py b/superset/commands/distributed_lock/delete.py similarity index 51% rename from superset/commands/key_value/delete_expired.py rename to superset/commands/distributed_lock/delete.py index 54991c7531d2..cd279dbe2409 100644 --- a/superset/commands/key_value/delete_expired.py +++ b/superset/commands/distributed_lock/delete.py @@ -14,49 +14,36 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. + import logging -from datetime import datetime from functools import partial -from sqlalchemy import and_ +from flask import current_app +from sqlalchemy.exc import SQLAlchemyError -from superset import db -from superset.commands.base import BaseCommand +from superset.commands.distributed_lock.base import BaseDistributedLockCommand +from superset.daos.key_value import KeyValueDAO +from superset.exceptions import DeleteKeyValueDistributedLockFailedException from superset.key_value.exceptions import KeyValueDeleteFailedError -from superset.key_value.models import KeyValueEntry -from superset.key_value.types import KeyValueResource from superset.utils.decorators import on_error, transaction logger = logging.getLogger(__name__) +stats_logger = current_app.config["STATS_LOGGER"] -class DeleteExpiredKeyValueCommand(BaseCommand): - resource: KeyValueResource - - def __init__(self, resource: KeyValueResource): - """ - Delete all expired key-value pairs - - :param resource: the resource (dashboard, chart etc) - :return: was the entry deleted or not - """ - self.resource = resource - - @transaction(on_error=partial(on_error, reraise=KeyValueDeleteFailedError)) - def run(self) -> None: - self.delete_expired() - +class DeleteDistributedLock(BaseDistributedLockCommand): def validate(self) -> None: pass - def delete_expired(self) -> None: - ( - db.session.query(KeyValueEntry) - .filter( - and_( - KeyValueEntry.resource == self.resource.value, - KeyValueEntry.expires_on <= datetime.now(), - ) - ) - .delete() - ) + @transaction( + on_error=partial( + on_error, + catches=( + KeyValueDeleteFailedError, + SQLAlchemyError, + ), + reraise=DeleteKeyValueDistributedLockFailedException, + ), + ) + def run(self) -> None: + KeyValueDAO.delete_entry(self.resource, self.key) diff --git a/superset/commands/distributed_lock/get.py b/superset/commands/distributed_lock/get.py new file mode 100644 index 000000000000..562456410935 --- /dev/null +++ b/superset/commands/distributed_lock/get.py @@ -0,0 +1,45 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +from __future__ import annotations + +import logging +from typing import cast + +from flask import current_app + +from superset.commands.distributed_lock.base import BaseDistributedLockCommand +from superset.daos.key_value import KeyValueDAO +from superset.distributed_lock.types import LockValue + +logger = logging.getLogger(__name__) +stats_logger = current_app.config["STATS_LOGGER"] + + +class GetDistributedLock(BaseDistributedLockCommand): + def validate(self) -> None: + pass + + def run(self) -> LockValue | None: + entry = KeyValueDAO.get_entry( + resource=self.resource, + key=self.key, + ) + if not entry or entry.is_expired(): + return None + + return cast(LockValue, self.codec.decode(entry.value)) diff --git a/superset/commands/explore/permalink/create.py b/superset/commands/explore/permalink/create.py index 2128fa4b8c40..926b9ba919f4 100644 --- a/superset/commands/explore/permalink/create.py +++ b/superset/commands/explore/permalink/create.py @@ -20,8 +20,9 @@ from sqlalchemy.exc import SQLAlchemyError +from superset import db from superset.commands.explore.permalink.base import BaseExplorePermalinkCommand -from superset.commands.key_value.create import CreateKeyValueCommand +from superset.daos.key_value import KeyValueDAO from superset.explore.permalink.exceptions import ExplorePermalinkCreateFailedError from superset.explore.utils import check_access as check_chart_access from superset.key_value.exceptions import ( @@ -65,15 +66,12 @@ def run(self) -> str: "datasource": self.datasource, "state": self.state, } - command = CreateKeyValueCommand( - resource=self.resource, - value=value, - codec=self.codec, - ) - key = command.run() - if key.id is None: + entry = KeyValueDAO.create_entry(self.resource, value, self.codec) + db.session.flush() + key = entry.id + if key is None: raise ExplorePermalinkCreateFailedError("Unexpected missing key id") - return encode_permalink_key(key=key.id, salt=self.salt) + return encode_permalink_key(key=key, salt=self.salt) def validate(self) -> None: pass diff --git a/superset/commands/explore/permalink/get.py b/superset/commands/explore/permalink/get.py index 4c01db1ccab4..7dc1db40df24 100644 --- a/superset/commands/explore/permalink/get.py +++ b/superset/commands/explore/permalink/get.py @@ -21,7 +21,7 @@ from superset.commands.dataset.exceptions import DatasetNotFoundError from superset.commands.explore.permalink.base import BaseExplorePermalinkCommand -from superset.commands.key_value.get import GetKeyValueCommand +from superset.daos.key_value import KeyValueDAO from superset.explore.permalink.exceptions import ExplorePermalinkGetFailedError from superset.explore.permalink.types import ExplorePermalinkValue from superset.explore.utils import check_access as check_chart_access @@ -44,11 +44,7 @@ def run(self) -> Optional[ExplorePermalinkValue]: self.validate() try: key = decode_permalink_id(self.key, salt=self.salt) - value: Optional[ExplorePermalinkValue] = GetKeyValueCommand( - resource=self.resource, - key=key, - codec=self.codec, - ).run() + value = KeyValueDAO.get_value(self.resource, key, self.codec) if value: chart_id: Optional[int] = value.get("chartId") # keep this backward compatible for old permalinks diff --git a/superset/commands/key_value/create.py b/superset/commands/key_value/create.py deleted file mode 100644 index 81b7c4c3d4a9..000000000000 --- a/superset/commands/key_value/create.py +++ /dev/null @@ -1,102 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -import logging -from datetime import datetime -from functools import partial -from typing import Any, Optional, Union -from uuid import UUID - -from superset import db -from superset.commands.base import BaseCommand -from superset.key_value.exceptions import KeyValueCreateFailedError -from superset.key_value.models import KeyValueEntry -from superset.key_value.types import Key, KeyValueCodec, KeyValueResource -from superset.utils.core import get_user_id -from superset.utils.decorators import on_error, transaction - -logger = logging.getLogger(__name__) - - -class CreateKeyValueCommand(BaseCommand): - resource: KeyValueResource - value: Any - codec: KeyValueCodec - key: Optional[Union[int, UUID]] - expires_on: Optional[datetime] - - def __init__( # pylint: disable=too-many-arguments - self, - resource: KeyValueResource, - value: Any, - codec: KeyValueCodec, - key: Optional[Union[int, UUID]] = None, - expires_on: Optional[datetime] = None, - ): - """ - Create a new key-value pair - - :param resource: the resource (dashboard, chart etc) - :param value: the value to persist in the key-value store - :param codec: codec used to encode the value - :param key: id of entry (autogenerated if undefined) - :param expires_on: entry expiration time - : - """ - self.resource = resource - self.value = value - self.codec = codec - self.key = key - self.expires_on = expires_on - - @transaction(on_error=partial(on_error, reraise=KeyValueCreateFailedError)) - def run(self) -> Key: - """ - Persist the value - - :return: the key associated with the persisted value - - """ - - return self.create() - - def validate(self) -> None: - pass - - def create(self) -> Key: - try: - value = self.codec.encode(self.value) - except Exception as ex: - raise KeyValueCreateFailedError("Unable to encode value") from ex - entry = KeyValueEntry( - resource=self.resource.value, - value=value, - created_on=datetime.now(), - created_by_fk=get_user_id(), - expires_on=self.expires_on, - ) - if self.key is not None: - try: - if isinstance(self.key, UUID): - entry.uuid = self.key - else: - entry.id = self.key - except ValueError as ex: - raise KeyValueCreateFailedError() from ex - - db.session.add(entry) - db.session.flush() - return Key(id=entry.id, uuid=entry.uuid) diff --git a/superset/commands/key_value/delete.py b/superset/commands/key_value/delete.py deleted file mode 100644 index a3fdf079c73c..000000000000 --- a/superset/commands/key_value/delete.py +++ /dev/null @@ -1,63 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -import logging -from functools import partial -from typing import Union -from uuid import UUID - -from superset import db -from superset.commands.base import BaseCommand -from superset.key_value.exceptions import KeyValueDeleteFailedError -from superset.key_value.models import KeyValueEntry -from superset.key_value.types import KeyValueResource -from superset.key_value.utils import get_filter -from superset.utils.decorators import on_error, transaction - -logger = logging.getLogger(__name__) - - -class DeleteKeyValueCommand(BaseCommand): - key: Union[int, UUID] - resource: KeyValueResource - - def __init__(self, resource: KeyValueResource, key: Union[int, UUID]): - """ - Delete a key-value pair - - :param resource: the resource (dashboard, chart etc) - :param key: the key to delete - :return: was the entry deleted or not - """ - self.resource = resource - self.key = key - - @transaction(on_error=partial(on_error, reraise=KeyValueDeleteFailedError)) - def run(self) -> bool: - return self.delete() - - def validate(self) -> None: - pass - - def delete(self) -> bool: - if ( - entry := db.session.query(KeyValueEntry) - .filter_by(**get_filter(self.resource, self.key)) - .first() - ): - db.session.delete(entry) - return True - return False diff --git a/superset/commands/key_value/get.py b/superset/commands/key_value/get.py deleted file mode 100644 index 93550ee840c3..000000000000 --- a/superset/commands/key_value/get.py +++ /dev/null @@ -1,71 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - -import logging -from typing import Any, Optional, Union -from uuid import UUID - -from sqlalchemy.exc import SQLAlchemyError - -from superset import db -from superset.commands.base import BaseCommand -from superset.key_value.exceptions import KeyValueGetFailedError -from superset.key_value.models import KeyValueEntry -from superset.key_value.types import KeyValueCodec, KeyValueResource -from superset.key_value.utils import get_filter - -logger = logging.getLogger(__name__) - - -class GetKeyValueCommand(BaseCommand): - resource: KeyValueResource - key: Union[int, UUID] - codec: KeyValueCodec - - def __init__( - self, - resource: KeyValueResource, - key: Union[int, UUID], - codec: KeyValueCodec, - ): - """ - Retrieve a key value entry - - :param resource: the resource (dashboard, chart etc) - :param key: the key to retrieve - :param codec: codec used to decode the value - :return: the value associated with the key if present - """ - self.resource = resource - self.key = key - self.codec = codec - - def run(self) -> Any: - try: - return self.get() - except SQLAlchemyError as ex: - raise KeyValueGetFailedError() from ex - - def validate(self) -> None: - pass - - def get(self) -> Optional[Any]: - filter_ = get_filter(self.resource, self.key) - entry = db.session.query(KeyValueEntry).filter_by(**filter_).first() - if entry and not entry.is_expired(): - return self.codec.decode(entry.value) - return None diff --git a/superset/commands/key_value/update.py b/superset/commands/key_value/update.py deleted file mode 100644 index b6ffc22174f6..000000000000 --- a/superset/commands/key_value/update.py +++ /dev/null @@ -1,87 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - -import logging -from datetime import datetime -from functools import partial -from typing import Any, Optional, Union -from uuid import UUID - -from superset import db -from superset.commands.base import BaseCommand -from superset.key_value.exceptions import KeyValueUpdateFailedError -from superset.key_value.models import KeyValueEntry -from superset.key_value.types import Key, KeyValueCodec, KeyValueResource -from superset.key_value.utils import get_filter -from superset.utils.core import get_user_id -from superset.utils.decorators import on_error, transaction - -logger = logging.getLogger(__name__) - - -class UpdateKeyValueCommand(BaseCommand): - resource: KeyValueResource - value: Any - codec: KeyValueCodec - key: Union[int, UUID] - expires_on: Optional[datetime] - - def __init__( # pylint: disable=too-many-arguments - self, - resource: KeyValueResource, - key: Union[int, UUID], - value: Any, - codec: KeyValueCodec, - expires_on: Optional[datetime] = None, - ): - """ - Update a key value entry - - :param resource: the resource (dashboard, chart etc) - :param key: the key to update - :param value: the value to persist in the key-value store - :param codec: codec used to encode the value - :param expires_on: entry expiration time - :return: the key associated with the updated value - """ - self.resource = resource - self.key = key - self.value = value - self.codec = codec - self.expires_on = expires_on - - @transaction(on_error=partial(on_error, reraise=KeyValueUpdateFailedError)) - def run(self) -> Optional[Key]: - return self.update() - - def validate(self) -> None: - pass - - def update(self) -> Optional[Key]: - filter_ = get_filter(self.resource, self.key) - entry: KeyValueEntry = ( - db.session.query(KeyValueEntry).filter_by(**filter_).first() - ) - if entry: - entry.value = self.codec.encode(self.value) - entry.expires_on = self.expires_on - entry.changed_on = datetime.now() - entry.changed_by_fk = get_user_id() - db.session.flush() - return Key(id=entry.id, uuid=entry.uuid) - - return None diff --git a/superset/commands/key_value/upsert.py b/superset/commands/key_value/upsert.py deleted file mode 100644 index 32918d9b1439..000000000000 --- a/superset/commands/key_value/upsert.py +++ /dev/null @@ -1,104 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - -import logging -from datetime import datetime -from functools import partial -from typing import Any, Optional, Union -from uuid import UUID - -from sqlalchemy.exc import SQLAlchemyError - -from superset import db -from superset.commands.base import BaseCommand -from superset.commands.key_value.create import CreateKeyValueCommand -from superset.key_value.exceptions import ( - KeyValueCreateFailedError, - KeyValueUpsertFailedError, -) -from superset.key_value.models import KeyValueEntry -from superset.key_value.types import Key, KeyValueCodec, KeyValueResource -from superset.key_value.utils import get_filter -from superset.utils.core import get_user_id -from superset.utils.decorators import on_error, transaction - -logger = logging.getLogger(__name__) - - -class UpsertKeyValueCommand(BaseCommand): - resource: KeyValueResource - value: Any - key: Union[int, UUID] - codec: KeyValueCodec - expires_on: Optional[datetime] - - def __init__( # pylint: disable=too-many-arguments - self, - resource: KeyValueResource, - key: Union[int, UUID], - value: Any, - codec: KeyValueCodec, - expires_on: Optional[datetime] = None, - ): - """ - Upsert a key value entry - - :param resource: the resource (dashboard, chart etc) - :param key: the key to update - :param value: the value to persist in the key-value store - :param codec: codec used to encode the value - :param expires_on: entry expiration time - :return: the key associated with the updated value - """ - self.resource = resource - self.key = key - self.value = value - self.codec = codec - self.expires_on = expires_on - - @transaction( - on_error=partial( - on_error, - catches=(KeyValueCreateFailedError, SQLAlchemyError), - reraise=KeyValueUpsertFailedError, - ), - ) - def run(self) -> Key: - return self.upsert() - - def validate(self) -> None: - pass - - def upsert(self) -> Key: - if ( - entry := db.session.query(KeyValueEntry) - .filter_by(**get_filter(self.resource, self.key)) - .first() - ): - entry.value = self.codec.encode(self.value) - entry.expires_on = self.expires_on - entry.changed_on = datetime.now() - entry.changed_by_fk = get_user_id() - return Key(entry.id, entry.uuid) - - return CreateKeyValueCommand( - resource=self.resource, - value=self.value, - codec=self.codec, - key=self.key, - expires_on=self.expires_on, - ).run() diff --git a/superset/daos/key_value.py b/superset/daos/key_value.py new file mode 100644 index 000000000000..f15293abcab8 --- /dev/null +++ b/superset/daos/key_value.py @@ -0,0 +1,145 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +from __future__ import annotations + +import logging +from datetime import datetime +from typing import Any +from uuid import UUID + +from sqlalchemy import and_ + +from superset import db +from superset.daos.base import BaseDAO +from superset.key_value.exceptions import ( + KeyValueCreateFailedError, + KeyValueUpdateFailedError, +) +from superset.key_value.models import KeyValueEntry +from superset.key_value.types import Key, KeyValueCodec, KeyValueResource +from superset.key_value.utils import get_filter +from superset.utils.core import get_user_id + +logger = logging.getLogger(__name__) + + +class KeyValueDAO(BaseDAO[KeyValueEntry]): + @staticmethod + def get_entry( + resource: KeyValueResource, + key: Key, + ) -> KeyValueEntry | None: + filter_ = get_filter(resource, key) + return db.session.query(KeyValueEntry).filter_by(**filter_).first() + + @classmethod + def get_value( + cls, + resource: KeyValueResource, + key: Key, + codec: KeyValueCodec, + ) -> Any: + entry = cls.get_entry(resource, key) + if not entry or entry.is_expired(): + return None + + return codec.decode(entry.value) + + @staticmethod + def delete_entry(resource: KeyValueResource, key: Key) -> bool: + if entry := KeyValueDAO.get_entry(resource, key): + db.session.delete(entry) + return True + + return False + + @staticmethod + def delete_expired_entries(resource: KeyValueResource) -> None: + ( + db.session.query(KeyValueEntry) + .filter( + and_( + KeyValueEntry.resource == resource.value, + KeyValueEntry.expires_on <= datetime.now(), + ) + ) + .delete() + ) + + @staticmethod + def create_entry( + resource: KeyValueResource, + value: Any, + codec: KeyValueCodec, + key: Key | None = None, + expires_on: datetime | None = None, + ) -> KeyValueEntry: + try: + encoded_value = codec.encode(value) + except Exception as ex: + raise KeyValueCreateFailedError("Unable to encode value") from ex + entry = KeyValueEntry( + resource=resource.value, + value=encoded_value, + created_on=datetime.now(), + created_by_fk=get_user_id(), + expires_on=expires_on, + ) + if key is not None: + try: + if isinstance(key, UUID): + entry.uuid = key + else: + entry.id = key + except ValueError as ex: + raise KeyValueCreateFailedError() from ex + db.session.add(entry) + return entry + + @staticmethod + def upsert_entry( + resource: KeyValueResource, + value: Any, + codec: KeyValueCodec, + key: Key, + expires_on: datetime | None = None, + ) -> KeyValueEntry: + if entry := KeyValueDAO.get_entry(resource, key): + entry.value = codec.encode(value) + entry.expires_on = expires_on + entry.changed_on = datetime.now() + entry.changed_by_fk = get_user_id() + return entry + + return KeyValueDAO.create_entry(resource, value, codec, key, expires_on) + + @staticmethod + def update_entry( + resource: KeyValueResource, + value: Any, + codec: KeyValueCodec, + key: Key, + expires_on: datetime | None = None, + ) -> KeyValueEntry: + if entry := KeyValueDAO.get_entry(resource, key): + entry.value = codec.encode(value) + entry.expires_on = expires_on + entry.changed_on = datetime.now() + entry.changed_by_fk = get_user_id() + return entry + + raise KeyValueUpdateFailedError() diff --git a/superset/utils/lock.py b/superset/distributed_lock/__init__.py similarity index 55% rename from superset/utils/lock.py rename to superset/distributed_lock/__init__.py index 4723b57fa1b0..c4af73ac0f09 100644 --- a/superset/utils/lock.py +++ b/superset/distributed_lock/__init__.py @@ -21,40 +21,18 @@ import uuid from collections.abc import Iterator from contextlib import contextmanager -from datetime import datetime, timedelta -from typing import Any, cast, TypeVar, Union +from datetime import timedelta +from typing import Any +from superset.distributed_lock.utils import get_key from superset.exceptions import CreateKeyValueDistributedLockFailedException -from superset.key_value.exceptions import KeyValueCreateFailedError from superset.key_value.types import JsonKeyValueCodec, KeyValueResource -from superset.utils import json -LOCK_EXPIRATION = timedelta(seconds=30) logger = logging.getLogger(__name__) - -def serialize(params: dict[str, Any]) -> str: - """ - Serialize parameters into a string. - """ - - T = TypeVar( - "T", - bound=Union[dict[str, Any], list[Any], int, float, str, bool, None], - ) - - def sort(obj: T) -> T: - if isinstance(obj, dict): - return cast(T, {k: sort(v) for k, v in sorted(obj.items())}) - if isinstance(obj, list): - return cast(T, [sort(x) for x in obj]) - return obj - - return json.dumps(params) - - -def get_key(namespace: str, **kwargs: Any) -> uuid.UUID: - return uuid.uuid5(uuid.uuid5(uuid.NAMESPACE_DNS, namespace), serialize(kwargs)) +CODEC = JsonKeyValueCodec() +LOCK_EXPIRATION = timedelta(seconds=30) +RESOURCE = KeyValueResource.LOCK @contextmanager @@ -75,28 +53,25 @@ def KeyValueDistributedLock( # pylint: disable=invalid-name :yields: A unique identifier (UUID) for the acquired lock (the KV key). :raises CreateKeyValueDistributedLockFailedException: If the lock is taken. """ + # pylint: disable=import-outside-toplevel - from superset.commands.key_value.create import CreateKeyValueCommand - from superset.commands.key_value.delete import DeleteKeyValueCommand - from superset.commands.key_value.delete_expired import DeleteExpiredKeyValueCommand + from superset.commands.distributed_lock.create import CreateDistributedLock + from superset.commands.distributed_lock.delete import DeleteDistributedLock + from superset.commands.distributed_lock.get import GetDistributedLock key = get_key(namespace, **kwargs) + value = GetDistributedLock(namespace=namespace, params=kwargs).run() + if value: + logger.debug("Lock on namespace %s for key %s already taken", namespace, key) + raise CreateKeyValueDistributedLockFailedException("Lock already taken") + logger.debug("Acquiring lock on namespace %s for key %s", namespace, key) try: - DeleteExpiredKeyValueCommand(resource=KeyValueResource.LOCK).run() - CreateKeyValueCommand( - resource=KeyValueResource.LOCK, - codec=JsonKeyValueCodec(), - key=key, - value=True, - expires_on=datetime.now() + LOCK_EXPIRATION, - ).run() - - yield key - - DeleteKeyValueCommand(resource=KeyValueResource.LOCK, key=key).run() - logger.debug("Removed lock on namespace %s for key %s", namespace, key) - except KeyValueCreateFailedError as ex: - raise CreateKeyValueDistributedLockFailedException( - "Error acquiring lock" - ) from ex + CreateDistributedLock(namespace=namespace, params=kwargs).run() + except CreateKeyValueDistributedLockFailedException as ex: + logger.debug("Lock on namespace %s for key %s already taken", namespace, key) + raise CreateKeyValueDistributedLockFailedException("Lock already taken") from ex + + yield key + DeleteDistributedLock(namespace=namespace, params=kwargs).run() + logger.debug("Removed lock on namespace %s for key %s", namespace, key) diff --git a/superset/commands/key_value/__init__.py b/superset/distributed_lock/types.py similarity index 91% rename from superset/commands/key_value/__init__.py rename to superset/distributed_lock/types.py index 13a83393a912..b714913e8e8b 100644 --- a/superset/commands/key_value/__init__.py +++ b/superset/distributed_lock/types.py @@ -14,3 +14,8 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. +from typing import TypedDict + + +class LockValue(TypedDict): + value: bool diff --git a/tests/integration_tests/key_value/commands/__init__.py b/superset/distributed_lock/utils.py similarity index 52% rename from tests/integration_tests/key_value/commands/__init__.py rename to superset/distributed_lock/utils.py index 13a83393a912..09ed12d704d9 100644 --- a/tests/integration_tests/key_value/commands/__init__.py +++ b/superset/distributed_lock/utils.py @@ -14,3 +14,32 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. + +import uuid +from typing import Any, cast, TypeVar, Union + +from superset.utils import json + + +def serialize(params: dict[str, Any]) -> str: + """ + Serialize parameters into a string. + """ + + T = TypeVar( + "T", + bound=Union[dict[str, Any], list[Any], int, float, str, bool, None], + ) + + def sort(obj: T) -> T: + if isinstance(obj, dict): + return cast(T, {k: sort(v) for k, v in sorted(obj.items())}) + if isinstance(obj, list): + return cast(T, [sort(x) for x in obj]) + return obj + + return json.dumps(params) + + +def get_key(namespace: str, **kwargs: Any) -> uuid.UUID: + return uuid.uuid5(uuid.uuid5(uuid.NAMESPACE_DNS, namespace), serialize(kwargs)) diff --git a/superset/exceptions.py b/superset/exceptions.py index 47cd511f8f20..dd669f5b72ae 100644 --- a/superset/exceptions.py +++ b/superset/exceptions.py @@ -379,6 +379,12 @@ class CreateKeyValueDistributedLockFailedException(Exception): """ +class DeleteKeyValueDistributedLockFailedException(Exception): + """ + Exception to signalize failure to delete lock. + """ + + class DatabaseNotFoundException(SupersetErrorException): status = 404 diff --git a/superset/extensions/metastore_cache.py b/superset/extensions/metastore_cache.py index 1c89e8459774..197dac9386d3 100644 --- a/superset/extensions/metastore_cache.py +++ b/superset/extensions/metastore_cache.py @@ -21,7 +21,9 @@ from flask import current_app, Flask, has_app_context from flask_caching import BaseCache +from sqlalchemy.exc import SQLAlchemyError +from superset import db from superset.key_value.exceptions import KeyValueCreateFailedError from superset.key_value.types import ( KeyValueCodec, @@ -29,6 +31,7 @@ PickleKeyValueCodec, ) from superset.key_value.utils import get_uuid_namespace +from superset.utils.decorators import transaction RESOURCE = KeyValueResource.METASTORE_CACHE @@ -68,15 +71,6 @@ def factory( def get_key(self, key: str) -> UUID: return uuid3(self.namespace, key) - @staticmethod - def _prune() -> None: - # pylint: disable=import-outside-toplevel - from superset.commands.key_value.delete_expired import ( - DeleteExpiredKeyValueCommand, - ) - - DeleteExpiredKeyValueCommand(resource=RESOURCE).run() - def _get_expiry(self, timeout: Optional[int]) -> Optional[datetime]: timeout = self._normalize_timeout(timeout) if timeout is not None and timeout > 0: @@ -85,43 +79,42 @@ def _get_expiry(self, timeout: Optional[int]) -> Optional[datetime]: def set(self, key: str, value: Any, timeout: Optional[int] = None) -> bool: # pylint: disable=import-outside-toplevel - from superset.commands.key_value.upsert import UpsertKeyValueCommand + from superset.daos.key_value import KeyValueDAO - UpsertKeyValueCommand( + KeyValueDAO.upsert_entry( resource=RESOURCE, key=self.get_key(key), value=value, codec=self.codec, expires_on=self._get_expiry(timeout), - ).run() + ) + db.session.commit() # pylint: disable=consider-using-transaction return True def add(self, key: str, value: Any, timeout: Optional[int] = None) -> bool: # pylint: disable=import-outside-toplevel - from superset.commands.key_value.create import CreateKeyValueCommand + from superset.daos.key_value import KeyValueDAO try: - self._prune() - CreateKeyValueCommand( + KeyValueDAO.delete_expired_entries(RESOURCE) + KeyValueDAO.create_entry( resource=RESOURCE, value=value, codec=self.codec, key=self.get_key(key), expires_on=self._get_expiry(timeout), - ).run() + ) + db.session.commit() # pylint: disable=consider-using-transaction return True - except KeyValueCreateFailedError: + except (SQLAlchemyError, KeyValueCreateFailedError): + db.session.rollback() # pylint: disable=consider-using-transaction return False def get(self, key: str) -> Any: # pylint: disable=import-outside-toplevel - from superset.commands.key_value.get import GetKeyValueCommand + from superset.daos.key_value import KeyValueDAO - return GetKeyValueCommand( - resource=RESOURCE, - key=self.get_key(key), - codec=self.codec, - ).run() + return KeyValueDAO.get_value(RESOURCE, self.get_key(key), self.codec) def has(self, key: str) -> bool: entry = self.get(key) @@ -129,8 +122,9 @@ def has(self, key: str) -> bool: return True return False + @transaction() def delete(self, key: str) -> Any: # pylint: disable=import-outside-toplevel - from superset.commands.key_value.delete import DeleteKeyValueCommand + from superset.daos.key_value import KeyValueDAO - return DeleteKeyValueCommand(resource=RESOURCE, key=self.get_key(key)).run() + return KeyValueDAO.delete_entry(RESOURCE, self.get_key(key)) diff --git a/superset/key_value/shared_entries.py b/superset/key_value/shared_entries.py index 130313157a53..c2acafa80752 100644 --- a/superset/key_value/shared_entries.py +++ b/superset/key_value/shared_entries.py @@ -18,8 +18,10 @@ from typing import Any, Optional from uuid import uuid3 +from superset.daos.key_value import KeyValueDAO from superset.key_value.types import JsonKeyValueCodec, KeyValueResource, SharedKey from superset.key_value.utils import get_uuid_namespace, random_key +from superset.utils.decorators import transaction RESOURCE = KeyValueResource.APP NAMESPACE = get_uuid_namespace("") @@ -27,24 +29,14 @@ def get_shared_value(key: SharedKey) -> Optional[Any]: - # pylint: disable=import-outside-toplevel - from superset.commands.key_value.get import GetKeyValueCommand - uuid_key = uuid3(NAMESPACE, key) - return GetKeyValueCommand(RESOURCE, key=uuid_key, codec=CODEC).run() + return KeyValueDAO.get_value(RESOURCE, uuid_key, CODEC) +@transaction() def set_shared_value(key: SharedKey, value: Any) -> None: - # pylint: disable=import-outside-toplevel - from superset.commands.key_value.create import CreateKeyValueCommand - uuid_key = uuid3(NAMESPACE, key) - CreateKeyValueCommand( - resource=RESOURCE, - value=value, - key=uuid_key, - codec=CODEC, - ).run() + KeyValueDAO.create_entry(RESOURCE, value, CODEC, uuid_key) def get_permalink_salt(key: SharedKey) -> str: diff --git a/superset/key_value/types.py b/superset/key_value/types.py index 7b0130c0e6ce..f6459c330283 100644 --- a/superset/key_value/types.py +++ b/superset/key_value/types.py @@ -19,8 +19,7 @@ import json import pickle from abc import ABC, abstractmethod -from dataclasses import dataclass -from typing import Any, TypedDict +from typing import Any, TypedDict, Union from uuid import UUID from marshmallow import Schema, ValidationError @@ -31,11 +30,7 @@ ) from superset.utils.backports import StrEnum - -@dataclass -class Key: - id: int | None - uuid: UUID | None +Key = Union[int, UUID] class KeyValueFilter(TypedDict, total=False): diff --git a/superset/key_value/utils.py b/superset/key_value/utils.py index 1a22cfaa747b..0a4e63778aa6 100644 --- a/superset/key_value/utils.py +++ b/superset/key_value/utils.py @@ -25,7 +25,7 @@ from flask_babel import gettext as _ from superset.key_value.exceptions import KeyValueParseKeyError -from superset.key_value.types import KeyValueFilter, KeyValueResource +from superset.key_value.types import Key, KeyValueFilter, KeyValueResource from superset.utils.json import json_dumps_w_dates HASHIDS_MIN_LENGTH = 11 @@ -35,7 +35,7 @@ def random_key() -> str: return token_urlsafe(48) -def get_filter(resource: KeyValueResource, key: int | UUID) -> KeyValueFilter: +def get_filter(resource: KeyValueResource, key: Key) -> KeyValueFilter: try: filter_: KeyValueFilter = {"resource": resource.value} if isinstance(key, UUID): diff --git a/superset/models/helpers.py b/superset/models/helpers.py index ecf73ee42bad..5354cb514dd4 100644 --- a/superset/models/helpers.py +++ b/superset/models/helpers.py @@ -1712,7 +1712,9 @@ def get_sqla_query( # pylint: disable=too-many-arguments,too-many-locals,too-ma select_exprs.append( self.convert_tbl_column_to_sqla_col( - columns_by_name[selected], template_processor=template_processor + columns_by_name[selected], + template_processor=template_processor, + label=_column_label, ) if isinstance(selected, str) and selected in columns_by_name else self.make_sqla_column_compatible( diff --git a/superset/utils/oauth2.py b/superset/utils/oauth2.py index bc4805fd8192..b889ef83c5e7 100644 --- a/superset/utils/oauth2.py +++ b/superset/utils/oauth2.py @@ -26,9 +26,9 @@ from marshmallow import EXCLUDE, fields, post_load, Schema from superset import db +from superset.distributed_lock import KeyValueDistributedLock from superset.exceptions import CreateKeyValueDistributedLockFailedException from superset.superset_typing import OAuth2ClientConfig, OAuth2State -from superset.utils.lock import KeyValueDistributedLock if TYPE_CHECKING: from superset.db_engine_specs.base import BaseEngineSpec diff --git a/tests/integration_tests/explore/permalink/commands_tests.py b/tests/integration_tests/explore/permalink/commands_tests.py index 4993e33f1895..c17d8bafdb00 100644 --- a/tests/integration_tests/explore/permalink/commands_tests.py +++ b/tests/integration_tests/explore/permalink/commands_tests.py @@ -133,11 +133,11 @@ def test_get_permalink_command(self, mock_g): assert cache_data.get("datasource") == datasource @patch("superset.security.manager.g") - @patch("superset.commands.key_value.get.GetKeyValueCommand.run") + @patch("superset.daos.key_value.KeyValueDAO.get_value") @patch("superset.commands.explore.permalink.get.decode_permalink_id") @pytest.mark.usefixtures("create_dataset", "create_slice") def test_get_permalink_command_with_old_dataset_key( - self, decode_id_mock, get_kv_command_mock, mock_g + self, decode_id_mock, kv_get_value_mock, mock_g ): mock_g.user = security_manager.find_user("admin") app.config["EXPLORE_FORM_DATA_CACHE_CONFIG"] = { @@ -149,13 +149,14 @@ def test_get_permalink_command_with_old_dataset_key( ) slice = db.session.query(Slice).filter_by(slice_name="slice_name").first() - datasource_string = f"{dataset.id}__{DatasourceType.TABLE}" + datasource_string = f"{dataset.id}__{DatasourceType.TABLE.value}" decode_id_mock.return_value = "123456" - get_kv_command_mock.return_value = { + kv_get_value_mock.return_value = { "chartId": slice.id, "datasetId": dataset.id, "datasource": datasource_string, + "datasourceType": DatasourceType.TABLE.value, "state": { "formData": {"datasource": datasource_string, "slice_id": slice.id} }, diff --git a/tests/integration_tests/extensions/metastore_cache_test.py b/tests/integration_tests/extensions/metastore_cache_test.py index c69340a7a2a3..401c2f988bed 100644 --- a/tests/integration_tests/extensions/metastore_cache_test.py +++ b/tests/integration_tests/extensions/metastore_cache_test.py @@ -18,13 +18,14 @@ from contextlib import nullcontext from datetime import datetime, timedelta -from typing import Any, TYPE_CHECKING +from typing import Any from uuid import UUID import pytest from flask.ctx import AppContext from freezegun import freeze_time +from superset.extensions.metastore_cache import SupersetMetastoreCache from superset.key_value.exceptions import KeyValueCodecEncodeException from superset.key_value.types import ( JsonKeyValueCodec, @@ -32,9 +33,6 @@ PickleKeyValueCodec, ) -if TYPE_CHECKING: - from superset.extensions.metastore_cache import SupersetMetastoreCache - NAMESPACE = UUID("ee173d1b-ccf3-40aa-941c-985c15224496") FIRST_KEY = "foo" @@ -47,8 +45,6 @@ @pytest.fixture def cache() -> SupersetMetastoreCache: - from superset.extensions.metastore_cache import SupersetMetastoreCache - return SupersetMetastoreCache( namespace=NAMESPACE, default_timeout=600, @@ -60,6 +56,7 @@ def test_caching_flow(app_context: AppContext, cache: SupersetMetastoreCache) -> assert cache.has(FIRST_KEY) is False assert cache.add(FIRST_KEY, FIRST_KEY_INITIAL_VALUE) is True assert cache.has(FIRST_KEY) is True + assert cache.get(FIRST_KEY) == FIRST_KEY_INITIAL_VALUE cache.set(SECOND_KEY, SECOND_VALUE) assert cache.get(FIRST_KEY) == FIRST_KEY_INITIAL_VALUE assert cache.get(SECOND_KEY) == SECOND_VALUE diff --git a/tests/integration_tests/key_value/commands/create_test.py b/tests/integration_tests/key_value/commands/create_test.py deleted file mode 100644 index b18b9886d6ff..000000000000 --- a/tests/integration_tests/key_value/commands/create_test.py +++ /dev/null @@ -1,96 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -from __future__ import annotations - -import pickle - -import pytest -from flask.ctx import AppContext -from flask_appbuilder.security.sqla.models import User - -from superset.extensions import db -from superset.key_value.exceptions import KeyValueCreateFailedError -from superset.utils import json -from superset.utils.core import override_user -from tests.integration_tests.key_value.commands.fixtures import ( - admin, # noqa: F401 - JSON_CODEC, - JSON_VALUE, - PICKLE_CODEC, - PICKLE_VALUE, - RESOURCE, -) - - -def test_create_id_entry(app_context: AppContext, admin: User) -> None: # noqa: F811 - from superset.commands.key_value.create import CreateKeyValueCommand - from superset.key_value.models import KeyValueEntry - - with override_user(admin): - key = CreateKeyValueCommand( - resource=RESOURCE, - value=JSON_VALUE, - codec=JSON_CODEC, - ).run() - entry = db.session.query(KeyValueEntry).filter_by(id=key.id).one() - assert json.loads(entry.value) == JSON_VALUE - assert entry.created_by_fk == admin.id - db.session.delete(entry) - db.session.commit() - - -def test_create_uuid_entry(app_context: AppContext, admin: User) -> None: # noqa: F811 - from superset.commands.key_value.create import CreateKeyValueCommand - from superset.key_value.models import KeyValueEntry - - with override_user(admin): - key = CreateKeyValueCommand( - resource=RESOURCE, value=JSON_VALUE, codec=JSON_CODEC - ).run() - entry = db.session.query(KeyValueEntry).filter_by(uuid=key.uuid).one() - assert json.loads(entry.value) == JSON_VALUE - assert entry.created_by_fk == admin.id - db.session.delete(entry) - db.session.commit() - - -def test_create_fail_json_entry(app_context: AppContext, admin: User) -> None: # noqa: F811 - from superset.commands.key_value.create import CreateKeyValueCommand - - with pytest.raises(KeyValueCreateFailedError): - CreateKeyValueCommand( - resource=RESOURCE, - value=PICKLE_VALUE, - codec=JSON_CODEC, - ).run() - - -def test_create_pickle_entry(app_context: AppContext, admin: User) -> None: # noqa: F811 - from superset.commands.key_value.create import CreateKeyValueCommand - from superset.key_value.models import KeyValueEntry - - with override_user(admin): - key = CreateKeyValueCommand( - resource=RESOURCE, - value=PICKLE_VALUE, - codec=PICKLE_CODEC, - ).run() - entry = db.session.query(KeyValueEntry).filter_by(id=key.id).one() - assert type(pickle.loads(entry.value)) == type(PICKLE_VALUE) - assert entry.created_by_fk == admin.id - db.session.delete(entry) - db.session.commit() diff --git a/tests/integration_tests/key_value/commands/delete_test.py b/tests/integration_tests/key_value/commands/delete_test.py deleted file mode 100644 index b45a5d075d21..000000000000 --- a/tests/integration_tests/key_value/commands/delete_test.py +++ /dev/null @@ -1,84 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -from __future__ import annotations - -from typing import TYPE_CHECKING -from uuid import UUID - -import pytest -from flask.ctx import AppContext -from flask_appbuilder.security.sqla.models import User - -from superset.extensions import db -from superset.utils import json -from tests.integration_tests.key_value.commands.fixtures import ( - admin, # noqa: F401 - JSON_VALUE, - RESOURCE, -) - -if TYPE_CHECKING: - from superset.key_value.models import KeyValueEntry - -ID_KEY = 234 -UUID_KEY = UUID("5aae143c-44f1-478e-9153-ae6154df333a") - - -@pytest.fixture -def key_value_entry() -> KeyValueEntry: - from superset.key_value.models import KeyValueEntry - - entry = KeyValueEntry( - id=ID_KEY, - uuid=UUID_KEY, - resource=RESOURCE, - value=bytes(json.dumps(JSON_VALUE), encoding="utf-8"), - ) - db.session.add(entry) - db.session.flush() - return entry - - -def test_delete_id_entry( - app_context: AppContext, - admin: User, # noqa: F811 - key_value_entry: KeyValueEntry, -) -> None: - from superset.commands.key_value.delete import DeleteKeyValueCommand - - assert DeleteKeyValueCommand(resource=RESOURCE, key=ID_KEY).run() is True - db.session.commit() - - -def test_delete_uuid_entry( - app_context: AppContext, - admin: User, # noqa: F811 - key_value_entry: KeyValueEntry, -) -> None: - from superset.commands.key_value.delete import DeleteKeyValueCommand - - assert DeleteKeyValueCommand(resource=RESOURCE, key=UUID_KEY).run() is True - db.session.commit() - - -def test_delete_entry_missing( - app_context: AppContext, - admin: User, # noqa: F811 -) -> None: - from superset.commands.key_value.delete import DeleteKeyValueCommand - - assert DeleteKeyValueCommand(resource=RESOURCE, key=456).run() is False diff --git a/tests/integration_tests/key_value/commands/fixtures.py b/tests/integration_tests/key_value/commands/fixtures.py deleted file mode 100644 index 74bf809301c1..000000000000 --- a/tests/integration_tests/key_value/commands/fixtures.py +++ /dev/null @@ -1,69 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - -from __future__ import annotations - -from collections.abc import Generator -from typing import TYPE_CHECKING -from uuid import UUID - -import pytest -from flask_appbuilder.security.sqla.models import User - -from superset.extensions import db -from superset.key_value.types import ( - JsonKeyValueCodec, - KeyValueResource, - PickleKeyValueCodec, -) -from superset.utils import json -from tests.integration_tests.test_app import app - -if TYPE_CHECKING: - from superset.key_value.models import KeyValueEntry - -ID_KEY = 123 -UUID_KEY = UUID("3e7a2ab8-bcaf-49b0-a5df-dfb432f291cc") -RESOURCE = KeyValueResource.APP -JSON_VALUE = {"foo": "bar"} -PICKLE_VALUE = object() -JSON_CODEC = JsonKeyValueCodec() -PICKLE_CODEC = PickleKeyValueCodec() - - -@pytest.fixture -def key_value_entry() -> Generator[KeyValueEntry, None, None]: - from superset.key_value.models import KeyValueEntry - - entry = KeyValueEntry( - id=ID_KEY, - uuid=UUID_KEY, - resource=RESOURCE, - value=bytes(json.dumps(JSON_VALUE), encoding="utf-8"), - ) - db.session.add(entry) - db.session.flush() - yield entry - db.session.delete(entry) - db.session.commit() - - -@pytest.fixture -def admin() -> User: - with app.app_context(): # noqa: F841 - admin = db.session.query(User).filter_by(username="admin").one() - return admin diff --git a/tests/integration_tests/key_value/commands/get_test.py b/tests/integration_tests/key_value/commands/get_test.py deleted file mode 100644 index 131b615b7c2e..000000000000 --- a/tests/integration_tests/key_value/commands/get_test.py +++ /dev/null @@ -1,103 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -from __future__ import annotations - -import uuid -from datetime import datetime, timedelta -from typing import TYPE_CHECKING - -from flask.ctx import AppContext - -from superset.extensions import db -from superset.utils import json -from tests.integration_tests.key_value.commands.fixtures import ( - ID_KEY, - JSON_CODEC, - JSON_VALUE, - key_value_entry, # noqa: F401 - RESOURCE, - UUID_KEY, -) - -if TYPE_CHECKING: - from superset.key_value.models import KeyValueEntry - - -def test_get_id_entry(app_context: AppContext, key_value_entry: KeyValueEntry) -> None: # noqa: F811 - from superset.commands.key_value.get import GetKeyValueCommand - - value = GetKeyValueCommand(resource=RESOURCE, key=ID_KEY, codec=JSON_CODEC).run() - assert value == JSON_VALUE - - -def test_get_uuid_entry( - app_context: AppContext, - key_value_entry: KeyValueEntry, # noqa: F811 -) -> None: - from superset.commands.key_value.get import GetKeyValueCommand - - value = GetKeyValueCommand(resource=RESOURCE, key=UUID_KEY, codec=JSON_CODEC).run() - assert value == JSON_VALUE - - -def test_get_id_entry_missing( - app_context: AppContext, - key_value_entry: KeyValueEntry, # noqa: F811 -) -> None: - from superset.commands.key_value.get import GetKeyValueCommand - - value = GetKeyValueCommand(resource=RESOURCE, key=456, codec=JSON_CODEC).run() - assert value is None - - -def test_get_expired_entry(app_context: AppContext) -> None: - from superset.commands.key_value.get import GetKeyValueCommand - from superset.key_value.models import KeyValueEntry - - entry = KeyValueEntry( - id=678, - uuid=uuid.uuid4(), - resource=RESOURCE, - value=bytes(json.dumps(JSON_VALUE), encoding="utf-8"), - expires_on=datetime.now() - timedelta(days=1), - ) - db.session.add(entry) - db.session.flush() - value = GetKeyValueCommand(resource=RESOURCE, key=ID_KEY, codec=JSON_CODEC).run() - assert value is None - db.session.delete(entry) - db.session.commit() - - -def test_get_future_expiring_entry(app_context: AppContext) -> None: - from superset.commands.key_value.get import GetKeyValueCommand - from superset.key_value.models import KeyValueEntry - - id_ = 789 - entry = KeyValueEntry( - id=id_, - uuid=uuid.uuid4(), - resource=RESOURCE, - value=bytes(json.dumps(JSON_VALUE), encoding="utf-8"), - expires_on=datetime.now() + timedelta(days=1), - ) - db.session.add(entry) - db.session.flush() - value = GetKeyValueCommand(resource=RESOURCE, key=id_, codec=JSON_CODEC).run() - assert value == JSON_VALUE - db.session.delete(entry) - db.session.commit() diff --git a/tests/integration_tests/key_value/commands/update_test.py b/tests/integration_tests/key_value/commands/update_test.py deleted file mode 100644 index bb434ec3b98b..000000000000 --- a/tests/integration_tests/key_value/commands/update_test.py +++ /dev/null @@ -1,97 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -from __future__ import annotations - -from typing import TYPE_CHECKING - -from flask.ctx import AppContext -from flask_appbuilder.security.sqla.models import User - -from superset.extensions import db -from superset.utils import json -from superset.utils.core import override_user -from tests.integration_tests.key_value.commands.fixtures import ( - admin, # noqa: F401 - ID_KEY, - JSON_CODEC, - key_value_entry, # noqa: F401 - RESOURCE, - UUID_KEY, -) - -if TYPE_CHECKING: - from superset.key_value.models import KeyValueEntry - - -NEW_VALUE = "new value" - - -def test_update_id_entry( - app_context: AppContext, - admin: User, # noqa: F811 - key_value_entry: KeyValueEntry, # noqa: F811 -) -> None: - from superset.commands.key_value.update import UpdateKeyValueCommand - from superset.key_value.models import KeyValueEntry - - with override_user(admin): - key = UpdateKeyValueCommand( - resource=RESOURCE, - key=ID_KEY, - value=NEW_VALUE, - codec=JSON_CODEC, - ).run() - assert key is not None - assert key.id == ID_KEY - entry = db.session.query(KeyValueEntry).filter_by(id=ID_KEY).one() - assert json.loads(entry.value) == NEW_VALUE - assert entry.changed_by_fk == admin.id - - -def test_update_uuid_entry( - app_context: AppContext, - admin: User, # noqa: F811 - key_value_entry: KeyValueEntry, # noqa: F811 -) -> None: - from superset.commands.key_value.update import UpdateKeyValueCommand - from superset.key_value.models import KeyValueEntry - - with override_user(admin): - key = UpdateKeyValueCommand( - resource=RESOURCE, - key=UUID_KEY, - value=NEW_VALUE, - codec=JSON_CODEC, - ).run() - assert key is not None - assert key.uuid == UUID_KEY - entry = db.session.query(KeyValueEntry).filter_by(uuid=UUID_KEY).one() - assert json.loads(entry.value) == NEW_VALUE - assert entry.changed_by_fk == admin.id - - -def test_update_missing_entry(app_context: AppContext, admin: User) -> None: # noqa: F811 - from superset.commands.key_value.update import UpdateKeyValueCommand - - with override_user(admin): - key = UpdateKeyValueCommand( - resource=RESOURCE, - key=456, - value=NEW_VALUE, - codec=JSON_CODEC, - ).run() - assert key is None diff --git a/tests/integration_tests/key_value/commands/upsert_test.py b/tests/integration_tests/key_value/commands/upsert_test.py deleted file mode 100644 index 6ff61423f1a7..000000000000 --- a/tests/integration_tests/key_value/commands/upsert_test.py +++ /dev/null @@ -1,101 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -from __future__ import annotations - -from typing import TYPE_CHECKING - -from flask.ctx import AppContext -from flask_appbuilder.security.sqla.models import User - -from superset.extensions import db -from superset.utils import json -from superset.utils.core import override_user -from tests.integration_tests.key_value.commands.fixtures import ( - admin, # noqa: F401 - ID_KEY, - JSON_CODEC, - key_value_entry, # noqa: F401 - RESOURCE, - UUID_KEY, -) - -if TYPE_CHECKING: - from superset.key_value.models import KeyValueEntry - - -NEW_VALUE = "new value" - - -def test_upsert_id_entry( - app_context: AppContext, - admin: User, # noqa: F811 - key_value_entry: KeyValueEntry, # noqa: F811 -) -> None: - from superset.commands.key_value.upsert import UpsertKeyValueCommand - from superset.key_value.models import KeyValueEntry - - with override_user(admin): - key = UpsertKeyValueCommand( - resource=RESOURCE, - key=ID_KEY, - value=NEW_VALUE, - codec=JSON_CODEC, - ).run() - assert key is not None - assert key.id == ID_KEY - entry = db.session.query(KeyValueEntry).filter_by(id=int(ID_KEY)).one() - assert json.loads(entry.value) == NEW_VALUE - assert entry.changed_by_fk == admin.id - - -def test_upsert_uuid_entry( - app_context: AppContext, - admin: User, # noqa: F811 - key_value_entry: KeyValueEntry, # noqa: F811 -) -> None: - from superset.commands.key_value.upsert import UpsertKeyValueCommand - from superset.key_value.models import KeyValueEntry - - with override_user(admin): - key = UpsertKeyValueCommand( - resource=RESOURCE, - key=UUID_KEY, - value=NEW_VALUE, - codec=JSON_CODEC, - ).run() - assert key is not None - assert key.uuid == UUID_KEY - entry = db.session.query(KeyValueEntry).filter_by(uuid=UUID_KEY).one() - assert json.loads(entry.value) == NEW_VALUE - assert entry.changed_by_fk == admin.id - - -def test_upsert_missing_entry(app_context: AppContext, admin: User) -> None: # noqa: F811 - from superset.commands.key_value.upsert import UpsertKeyValueCommand - from superset.key_value.models import KeyValueEntry - - with override_user(admin): - key = UpsertKeyValueCommand( - resource=RESOURCE, - key=456, - value=NEW_VALUE, - codec=JSON_CODEC, - ).run() - assert key is not None - assert key.id == 456 - db.session.query(KeyValueEntry).filter_by(id=456).delete() - db.session.commit() diff --git a/tests/integration_tests/model_tests.py b/tests/integration_tests/model_tests.py index 458168009be1..fb22f40fb221 100644 --- a/tests/integration_tests/model_tests.py +++ b/tests/integration_tests/model_tests.py @@ -556,6 +556,29 @@ def test_query_with_non_existent_metrics(self): self.assertTrue("Metric 'invalid' does not exist", context.exception) + def test_query_label_without_group_by(self): + tbl = self.get_table(name="birth_names") + query_obj = dict( + groupby=[], + columns=[ + "gender", + { + "label": "Given Name", + "sqlExpression": "name", + "expressionType": "SQL", + }, + ], + filter=[], + is_timeseries=False, + granularity=None, + from_dttm=None, + to_dttm=None, + extras={}, + ) + + sql = tbl.get_query_str(query_obj) + self.assertRegex(sql, r'name AS ["`]?Given Name["`]?') + @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices") def test_data_for_slices_with_no_query_context(self): tbl = self.get_table(name="birth_names") diff --git a/tests/unit_tests/dao/key_value_test.py b/tests/unit_tests/dao/key_value_test.py new file mode 100644 index 000000000000..18c0dfb25f94 --- /dev/null +++ b/tests/unit_tests/dao/key_value_test.py @@ -0,0 +1,395 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# pylint: disable=unused-argument, import-outside-toplevel, unused-import +from __future__ import annotations + +import pickle +from datetime import datetime, timedelta +from typing import Generator, TYPE_CHECKING +from uuid import UUID + +import pytest +from flask.ctx import AppContext +from flask_appbuilder.security.sqla.models import User + +from superset.extensions import db +from superset.key_value.exceptions import ( + KeyValueCreateFailedError, + KeyValueUpdateFailedError, +) +from superset.key_value.types import ( + JsonKeyValueCodec, + KeyValueResource, + PickleKeyValueCodec, +) +from superset.utils import json +from superset.utils.core import override_user +from tests.unit_tests.fixtures.common import admin_user, after_each # noqa: F401 + +if TYPE_CHECKING: + from superset.key_value.models import KeyValueEntry + +ID_KEY = 123 +UUID_KEY = UUID("3e7a2ab8-bcaf-49b0-a5df-dfb432f291cc") +RESOURCE = KeyValueResource.APP +JSON_VALUE = {"foo": "bar"} +PICKLE_VALUE = object() +JSON_CODEC = JsonKeyValueCodec() +PICKLE_CODEC = PickleKeyValueCodec() +NEW_VALUE = {"foo": "baz"} + + +@pytest.fixture +def key_value_entry() -> Generator[KeyValueEntry, None, None]: + from superset.key_value.models import KeyValueEntry + + entry = KeyValueEntry( + id=ID_KEY, + uuid=UUID_KEY, + resource=RESOURCE, + value=JSON_CODEC.encode(JSON_VALUE), + ) + db.session.add(entry) + db.session.flush() + yield entry + + +def test_create_id_entry( + app_context: AppContext, + admin_user: User, # noqa: F811 + after_each: None, # noqa: F811 +) -> None: + from superset.daos.key_value import KeyValueDAO + from superset.key_value.models import KeyValueEntry + + with override_user(admin_user): + created_entry = KeyValueDAO.create_entry( + resource=RESOURCE, + value=JSON_VALUE, + codec=JSON_CODEC, + ) + db.session.flush() + found_entry = ( + db.session.query(KeyValueEntry).filter_by(id=created_entry.id).one() + ) + assert json.loads(found_entry.value) == JSON_VALUE + assert found_entry.created_by_fk == admin_user.id + + +def test_create_uuid_entry( + app_context: AppContext, + admin_user: User, # noqa: F811 + after_each: None, # noqa: F811 +) -> None: + from superset.daos.key_value import KeyValueDAO + from superset.key_value.models import KeyValueEntry + + with override_user(admin_user): + created_entry = KeyValueDAO.create_entry( + resource=RESOURCE, value=JSON_VALUE, codec=JSON_CODEC + ) + db.session.flush() + + found_entry = ( + db.session.query(KeyValueEntry).filter_by(uuid=created_entry.uuid).one() + ) + assert json.loads(found_entry.value) == JSON_VALUE + assert found_entry.created_by_fk == admin_user.id + + +def test_create_fail_json_entry( + app_context: AppContext, + after_each: None, # noqa: F811 +) -> None: + from superset.daos.key_value import KeyValueDAO + + with pytest.raises(KeyValueCreateFailedError): + KeyValueDAO.create_entry( + resource=RESOURCE, + value=PICKLE_VALUE, + codec=JSON_CODEC, + ) + + +def test_create_pickle_entry( + app_context: AppContext, + admin_user: User, # noqa: F811 + after_each: None, # noqa: F811 +) -> None: + from superset.daos.key_value import KeyValueDAO + from superset.key_value.models import KeyValueEntry + + with override_user(admin_user): + created_entry = KeyValueDAO.create_entry( + resource=RESOURCE, + value=PICKLE_VALUE, + codec=PICKLE_CODEC, + ) + db.session.flush() + found_entry = ( + db.session.query(KeyValueEntry).filter_by(id=created_entry.id).one() + ) + assert type(pickle.loads(found_entry.value)) == type(PICKLE_VALUE) + assert found_entry.created_by_fk == admin_user.id + + +def test_get_value( + app_context: AppContext, + key_value_entry: KeyValueEntry, + after_each: None, # noqa: F811 +) -> None: + from superset.daos.key_value import KeyValueDAO + + value = KeyValueDAO.get_value( + resource=RESOURCE, + key=key_value_entry.id, + codec=JSON_CODEC, + ) + assert value == JSON_VALUE + + +def test_get_id_entry( + app_context: AppContext, + key_value_entry: KeyValueEntry, + after_each: None, # noqa: F811 +) -> None: + from superset.daos.key_value import KeyValueDAO + + found_entry = KeyValueDAO.get_entry(resource=RESOURCE, key=key_value_entry.id) + assert found_entry is not None + assert found_entry.id == key_value_entry.id + + +def test_get_uuid_entry( + app_context: AppContext, + key_value_entry: KeyValueEntry, # noqa: F811 + after_each: None, # noqa: F811 +) -> None: + from superset.daos.key_value import KeyValueDAO + + found_entry = KeyValueDAO.get_entry(resource=RESOURCE, key=key_value_entry.uuid) + assert found_entry is not None + assert JSON_CODEC.decode(found_entry.value) == JSON_VALUE + + +def test_get_id_entry_missing( + app_context: AppContext, + after_each: None, # noqa: F811 +) -> None: + from superset.daos.key_value import KeyValueDAO + + entry = KeyValueDAO.get_entry(resource=RESOURCE, key=456) + assert entry is None + + +def test_get_expired_entry( + app_context: AppContext, + after_each: None, # noqa: F811 +) -> None: + from superset.daos.key_value import KeyValueDAO + + created_entry = KeyValueDAO.create_entry( + resource=RESOURCE, + value=JSON_VALUE, + codec=JSON_CODEC, + key=ID_KEY, + expires_on=datetime.now() - timedelta(days=1), + ) + found_entry = KeyValueDAO.get_entry(resource=RESOURCE, key=created_entry.id) + assert found_entry is not None + assert found_entry.is_expired() is True + + +def test_get_future_expiring_entry( + app_context: AppContext, + after_each: None, # noqa: F811 +) -> None: + from superset.daos.key_value import KeyValueDAO + + created_entry = KeyValueDAO.create_entry( + resource=RESOURCE, + value=JSON_VALUE, + codec=JSON_CODEC, + key=ID_KEY, + expires_on=datetime.now() + timedelta(days=1), + ) + found_entry = KeyValueDAO.get_entry(resource=RESOURCE, key=created_entry.id) + assert found_entry is not None + assert found_entry.is_expired() is False + + +def test_update_id_entry( + app_context: AppContext, + key_value_entry: KeyValueEntry, # noqa: F811 + admin_user: User, # noqa: F811 + after_each: None, # noqa: F811 +) -> None: + from superset.daos.key_value import KeyValueDAO + + with override_user(admin_user): + updated_entry = KeyValueDAO.update_entry( + resource=RESOURCE, + key=ID_KEY, + value=NEW_VALUE, + codec=JSON_CODEC, + ) + db.session.flush() + assert updated_entry is not None + assert JSON_CODEC.decode(updated_entry.value) == NEW_VALUE + assert updated_entry.id == ID_KEY + assert updated_entry.uuid == UUID_KEY + found_entry = KeyValueDAO.get_entry(resource=RESOURCE, key=ID_KEY) + assert found_entry is not None + assert JSON_CODEC.decode(found_entry.value) == NEW_VALUE + assert found_entry.changed_by_fk == admin_user.id + + +def test_update_uuid_entry( + app_context: AppContext, + key_value_entry: KeyValueEntry, # noqa: F811 + admin_user: User, # noqa: F811 + after_each: None, # noqa: F811 +) -> None: + from superset.daos.key_value import KeyValueDAO + + with override_user(admin_user): + updated_entry = KeyValueDAO.update_entry( + resource=RESOURCE, + key=UUID_KEY, + value=NEW_VALUE, + codec=JSON_CODEC, + ) + db.session.flush() + assert updated_entry is not None + assert JSON_CODEC.decode(updated_entry.value) == NEW_VALUE + assert updated_entry.id == ID_KEY + assert updated_entry.uuid == UUID_KEY + found_entry = KeyValueDAO.get_entry(resource=RESOURCE, key=UUID_KEY) + assert found_entry is not None + assert JSON_CODEC.decode(found_entry.value) == NEW_VALUE + assert found_entry.changed_by_fk == admin_user.id + + +def test_update_missing_entry( + app_context: AppContext, + admin_user: User, # noqa: F811 + after_each: None, # noqa: F811 +) -> None: + from superset.daos.key_value import KeyValueDAO + + with override_user(admin_user): + with pytest.raises(KeyValueUpdateFailedError): + KeyValueDAO.update_entry( + resource=RESOURCE, + key=456, + value=NEW_VALUE, + codec=JSON_CODEC, + ) + + +def test_upsert_id_entry( + app_context: AppContext, + key_value_entry: KeyValueEntry, # noqa: F811 + admin_user: User, # noqa: F811 + after_each: None, # noqa: F811 +) -> None: + from superset.daos.key_value import KeyValueDAO + + with override_user(admin_user): + entry = KeyValueDAO.upsert_entry( + resource=RESOURCE, + key=ID_KEY, + value=NEW_VALUE, + codec=JSON_CODEC, + ) + found_entry = KeyValueDAO.get_entry(resource=RESOURCE, key=ID_KEY) + assert found_entry is not None + assert JSON_CODEC.decode(found_entry.value) == NEW_VALUE + assert entry.changed_by_fk == admin_user.id + + +def test_upsert_uuid_entry( + app_context: AppContext, + key_value_entry: KeyValueEntry, # noqa: F811 + admin_user: User, # noqa: F811 + after_each: None, # noqa: F811 +) -> None: + from superset.daos.key_value import KeyValueDAO + + with override_user(admin_user): + entry = KeyValueDAO.upsert_entry( + resource=RESOURCE, + key=UUID_KEY, + value=NEW_VALUE, + codec=JSON_CODEC, + ) + db.session.flush() + assert entry is not None + assert entry.id == ID_KEY + assert entry.uuid == UUID_KEY + found_entry = KeyValueDAO.get_entry(resource=RESOURCE, key=UUID_KEY) + assert found_entry is not None + assert JSON_CODEC.decode(found_entry.value) == NEW_VALUE + assert entry.changed_by_fk == admin_user.id + + +def test_upsert_missing_entry( + app_context: AppContext, + after_each: None, # noqa: F811 +) -> None: + from superset.daos.key_value import KeyValueDAO + + entry = KeyValueDAO.get_entry(resource=RESOURCE, key=ID_KEY) + assert entry is None + KeyValueDAO.upsert_entry( + resource=RESOURCE, + key=ID_KEY, + value=NEW_VALUE, + codec=JSON_CODEC, + ) + entry = KeyValueDAO.get_entry(resource=RESOURCE, key=ID_KEY) + assert entry is not None + assert JSON_CODEC.decode(entry.value) == NEW_VALUE + + +def test_delete_id_entry( + app_context: AppContext, + key_value_entry: KeyValueEntry, + after_each: None, # noqa: F811 +) -> None: + from superset.daos.key_value import KeyValueDAO + + assert KeyValueDAO.delete_entry(resource=RESOURCE, key=ID_KEY) is True + + +def test_delete_uuid_entry( + app_context: AppContext, + key_value_entry: KeyValueEntry, + after_each: None, # noqa: F811 +) -> None: + from superset.daos.key_value import KeyValueDAO + + assert KeyValueDAO.delete_entry(resource=RESOURCE, key=UUID_KEY) is True + + +def test_delete_entry_missing( + app_context: AppContext, + after_each: None, # noqa: F811 +) -> None: + from superset.daos.key_value import KeyValueDAO + + assert KeyValueDAO.delete_entry(resource=RESOURCE, key=12345678) is False diff --git a/tests/unit_tests/distributed_lock/__init__.py b/tests/unit_tests/distributed_lock/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/unit_tests/utils/lock_tests.py b/tests/unit_tests/distributed_lock/distributed_lock_tests.py similarity index 51% rename from tests/unit_tests/utils/lock_tests.py rename to tests/unit_tests/distributed_lock/distributed_lock_tests.py index 4c9121fe3874..6fe363f0978d 100644 --- a/tests/unit_tests/utils/lock_tests.py +++ b/tests/unit_tests/distributed_lock/distributed_lock_tests.py @@ -22,17 +22,21 @@ import pytest from freezegun import freeze_time +from sqlalchemy.orm import Session, sessionmaker from superset import db +from superset.distributed_lock import KeyValueDistributedLock +from superset.distributed_lock.types import LockValue +from superset.distributed_lock.utils import get_key from superset.exceptions import CreateKeyValueDistributedLockFailedException from superset.key_value.types import JsonKeyValueCodec -from superset.utils.lock import get_key, KeyValueDistributedLock +LOCK_VALUE: LockValue = {"value": True} MAIN_KEY = get_key("ns", a=1, b=2) OTHER_KEY = get_key("ns2", a=1, b=2) -def _get_lock(key: UUID) -> Any: +def _get_lock(key: UUID, session: Session) -> Any: from superset.key_value.models import KeyValueEntry entry = db.session.query(KeyValueEntry).filter_by(uuid=key).first() @@ -42,41 +46,56 @@ def _get_lock(key: UUID) -> Any: return JsonKeyValueCodec().decode(entry.value) +def _get_other_session() -> Session: + # This session is used to simulate what another worker will find in the metastore + # during the locking process. + from superset import db + + bind = db.session.get_bind() + SessionMaker = sessionmaker(bind=bind) + return SessionMaker() + + def test_key_value_distributed_lock_happy_path() -> None: """ Test successfully acquiring and returning the distributed lock. - Note we use a nested transaction to ensure that the cleanup from the outer context - manager is correctly invoked, otherwise a partial rollback would occur leaving the - database in a fractured state. + Note, we're using another session for asserting the lock state in the Metastore + to simulate what another worker will observe. Otherwise, there's the risk that + the assertions would only be using the non-committed state from the main session. """ + session = _get_other_session() with freeze_time("2021-01-01"): - assert _get_lock(MAIN_KEY) is None + assert _get_lock(MAIN_KEY, session) is None with KeyValueDistributedLock("ns", a=1, b=2) as key: assert key == MAIN_KEY - assert _get_lock(key) is True - assert _get_lock(OTHER_KEY) is None + assert _get_lock(key, session) == LOCK_VALUE + assert _get_lock(OTHER_KEY, session) is None - with db.session.begin_nested(): - with pytest.raises(CreateKeyValueDistributedLockFailedException): - with KeyValueDistributedLock("ns", a=1, b=2): - pass + with pytest.raises(CreateKeyValueDistributedLockFailedException): + with KeyValueDistributedLock("ns", a=1, b=2): + pass - assert _get_lock(MAIN_KEY) is None + assert _get_lock(MAIN_KEY, session) is None def test_key_value_distributed_lock_expired() -> None: """ Test expiration of the distributed lock + + Note, we're using another session for asserting the lock state in the Metastore + to simulate what another worker will observe. Otherwise, there's the risk that + the assertions would only be using the non-committed state from the main session. """ + session = _get_other_session() with freeze_time("2021-01-01"): - assert _get_lock(MAIN_KEY) is None + assert _get_lock(MAIN_KEY, session) is None with KeyValueDistributedLock("ns", a=1, b=2): - assert _get_lock(MAIN_KEY) is True + assert _get_lock(MAIN_KEY, session) == LOCK_VALUE with freeze_time("2022-01-01"): - assert _get_lock(MAIN_KEY) is None + assert _get_lock(MAIN_KEY, session) is None - assert _get_lock(MAIN_KEY) is None + assert _get_lock(MAIN_KEY, session) is None diff --git a/tests/unit_tests/fixtures/common.py b/tests/unit_tests/fixtures/common.py index 5aea8472c04a..4ee1d9d0ee34 100644 --- a/tests/unit_tests/fixtures/common.py +++ b/tests/unit_tests/fixtures/common.py @@ -20,12 +20,15 @@ import csv from datetime import datetime from io import BytesIO, StringIO -from typing import Any +from typing import Any, Generator import pandas as pd import pytest +from flask_appbuilder.security.sqla.models import Role, User from werkzeug.datastructures import FileStorage +from superset import db + @pytest.fixture def dttm() -> datetime: @@ -73,3 +76,24 @@ def create_columnar_file( df.to_parquet(buffer, index=False) buffer.seek(0) return FileStorage(stream=buffer, filename=filename) + + +@pytest.fixture +def admin_user() -> Generator[User, None, None]: + role = db.session.query(Role).filter_by(name="Admin").one() + user = User( + first_name="Alice", + last_name="Admin", + email="alice_admin@example.org", + username="alice_admin", + roles=[role], + ) + db.session.add(user) + db.session.flush() + yield user + + +@pytest.fixture +def after_each() -> Generator[None, None, None]: + yield + db.session.rollback()