mirror of
https://github.com/apache/superset.git
synced 2026-07-29 10:02:32 +00:00
Compare commits
70 Commits
fix-mysql-
...
fix-oom-ts
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cba539b91d | ||
|
|
96170e43c0 | ||
|
|
8fde970b6b | ||
|
|
38cb577fc6 | ||
|
|
8ebe045846 | ||
|
|
62dc5c0306 | ||
|
|
6ad8d29fcd | ||
|
|
d83a88d5d9 | ||
|
|
a7cc48dcda | ||
|
|
e2a7dc9256 | ||
|
|
0364933e8b | ||
|
|
3202ff4b3f | ||
|
|
553204e613 | ||
|
|
fe8348c03a | ||
|
|
30021f8ede | ||
|
|
f3349388d0 | ||
|
|
449a89c214 | ||
|
|
5428376662 | ||
|
|
5493e2c96d | ||
|
|
2f8657f122 | ||
|
|
aa97d2fe03 | ||
|
|
28389de93e | ||
|
|
7c415c7cd9 | ||
|
|
f6b2abee81 | ||
|
|
2ef7232959 | ||
|
|
2991f60368 | ||
|
|
85ec3b1901 | ||
|
|
0c639abe17 | ||
|
|
50ed9b9e0e | ||
|
|
13a164dd63 | ||
|
|
6b0b13e4e7 | ||
|
|
c188952de2 | ||
|
|
a3b91f924d | ||
|
|
d8688cf8b1 | ||
|
|
40378afbf8 | ||
|
|
84e21434b3 | ||
|
|
d27210794c | ||
|
|
5a90a84685 | ||
|
|
6fbbc85df7 | ||
|
|
a66c230058 | ||
|
|
19db0353a9 | ||
|
|
88e5581d04 | ||
|
|
bf88d9bb1c | ||
|
|
d51b35f61b | ||
|
|
220480b627 | ||
|
|
ef78d2af06 | ||
|
|
7deed00def | ||
|
|
4e093a8e2a | ||
|
|
0b73b4842f | ||
|
|
90f281f585 | ||
|
|
d62249d13f | ||
|
|
ff102aadb3 | ||
|
|
82e2bc6181 | ||
|
|
784ff82847 | ||
|
|
027b25e6b8 | ||
|
|
b652fab042 | ||
|
|
77a5969dc1 | ||
|
|
fb9032c05c | ||
|
|
7a9dbfe879 | ||
|
|
0de78d8203 | ||
|
|
abc2d46fed | ||
|
|
927cc1cda1 | ||
|
|
7f3840557a | ||
|
|
0defcb604b | ||
|
|
94686ddfbe | ||
|
|
ec322dfd8d | ||
|
|
cb88d886c7 | ||
|
|
608e3baf43 | ||
|
|
b6f6b75348 | ||
|
|
a5ad1d186c |
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@@ -20,7 +20,7 @@
|
||||
|
||||
# Notify PMC members of changes to GitHub Actions
|
||||
|
||||
/.github/ @villebro @geido @eschutho @rusackas @betodealmeida @nytai @mistercrunch @craig-rueda @kgabryje @dpgaspar
|
||||
/.github/ @villebro @geido @eschutho @rusackas @betodealmeida @nytai @mistercrunch @craig-rueda @kgabryje @dpgaspar @sadpandajoe
|
||||
|
||||
# Notify PMC members of changes to required GitHub Actions
|
||||
|
||||
|
||||
2
.github/copilot-instructions.md
vendored
2
.github/copilot-instructions.md
vendored
@@ -1 +1 @@
|
||||
../LLMS.md
|
||||
../AGENTS.md
|
||||
70
.github/dependabot.yml
vendored
70
.github/dependabot.yml
vendored
@@ -5,7 +5,7 @@ updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
ignore:
|
||||
@@ -18,7 +18,7 @@ updates:
|
||||
- dependency-name: "jest-environment-jsdom"
|
||||
directory: "/superset-frontend/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -40,21 +40,21 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: ".github/actions"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
open-pull-requests-limit: 10
|
||||
versioning-strategy: increase
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/docs/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
open-pull-requests-limit: 10
|
||||
versioning-strategy: increase
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-websocket/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -63,7 +63,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-websocket/utils/client-ws-app/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -75,7 +75,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/legacy-plugin-chart-calendar/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -85,7 +85,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/legacy-plugin-chart-histogram/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -95,7 +95,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/legacy-plugin-chart-partition/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -105,7 +105,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/legacy-plugin-chart-world-map/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -115,7 +115,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/plugin-chart-pivot-table/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -125,7 +125,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/legacy-plugin-chart-chord/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -135,7 +135,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/legacy-plugin-chart-horizon/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -145,7 +145,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/legacy-plugin-chart-rose/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -155,7 +155,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/legacy-preset-chart-deckgl/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -165,7 +165,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/plugin-chart-table/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -175,7 +175,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/legacy-plugin-chart-country-map/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -185,7 +185,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/legacy-plugin-chart-map-box/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -195,7 +195,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/legacy-plugin-chart-sankey/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -205,7 +205,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/legacy-preset-chart-nvd3/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -215,7 +215,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/plugin-chart-word-cloud/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -225,7 +225,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/legacy-plugin-chart-event-flow/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -235,7 +235,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/legacy-plugin-chart-paired-t-test/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -245,7 +245,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/legacy-plugin-chart-sankey-loop/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -255,7 +255,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/plugin-chart-echarts/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -265,7 +265,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/preset-chart-xy/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -275,7 +275,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/legacy-plugin-chart-heatmap/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -285,7 +285,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/legacy-plugin-chart-parallel-coordinates/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -295,7 +295,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/legacy-plugin-chart-sunburst/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -305,7 +305,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/plugins/plugin-chart-handlebars/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -315,7 +315,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/packages/generator-superset/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -325,7 +325,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/packages/superset-ui-chart-controls/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -339,7 +339,7 @@ updates:
|
||||
- dependency-name: "react-markdown"
|
||||
- dependency-name: "remark-gfm"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -349,7 +349,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/packages/superset-ui-demo/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
@@ -359,7 +359,7 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/superset-frontend/packages/superset-ui-switchboard/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "daily"
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
|
||||
2
.github/workflows/bump-python-package.yml
vendored
2
.github/workflows/bump-python-package.yml
vendored
@@ -41,7 +41,7 @@ jobs:
|
||||
uses: ./.github/actions/setup-supersetbot/
|
||||
|
||||
- name: Set up Python ${{ inputs.python-version }}
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.10"
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@v5
|
||||
- name: Check and notify
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
script: |
|
||||
|
||||
2
.github/workflows/claude.yml
vendored
2
.github/workflows/claude.yml
vendored
@@ -44,7 +44,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Comment access denied
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const message = `👋 Hi @${{ github.event.comment.user.login || github.event.review.user.login || github.event.issue.user.login }}!
|
||||
|
||||
2
.github/workflows/embedded-sdk-release.yml
vendored
2
.github/workflows/embedded-sdk-release.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
working-directory: superset-embedded-sdk
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version-file: './superset-embedded-sdk/.nvmrc'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
2
.github/workflows/embedded-sdk-test.yml
vendored
2
.github/workflows/embedded-sdk-test.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
working-directory: superset-embedded-sdk
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version-file: './superset-embedded-sdk/.nvmrc'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
4
.github/workflows/ephemeral-env-pr-close.yml
vendored
4
.github/workflows/ephemeral-env-pr-close.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
uses: aws-actions/configure-aws-credentials@v5
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
|
||||
- name: Comment (success)
|
||||
if: steps.describe-services.outputs.active == 'true'
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
github-token: ${{github.token}}
|
||||
script: |
|
||||
|
||||
16
.github/workflows/ephemeral-env.yml
vendored
16
.github/workflows/ephemeral-env.yml
vendored
@@ -63,7 +63,7 @@ jobs:
|
||||
- name: Get event SHA
|
||||
id: get-sha
|
||||
if: steps.eval-label.outputs.result == 'up'
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
@@ -94,7 +94,7 @@ jobs:
|
||||
core.setOutput("sha", prSha);
|
||||
|
||||
- name: Looking for feature flags in PR description
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
id: eval-feature-flags
|
||||
if: steps.eval-label.outputs.result == 'up'
|
||||
with:
|
||||
@@ -116,7 +116,7 @@ jobs:
|
||||
return results;
|
||||
|
||||
- name: Reply with confirmation comment
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
if: steps.eval-label.outputs.result == 'up'
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -189,7 +189,7 @@ jobs:
|
||||
--extra-flags "--build-arg INCLUDE_CHROMIUM=false"
|
||||
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
uses: aws-actions/configure-aws-credentials@v5
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
@@ -225,7 +225,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
uses: aws-actions/configure-aws-credentials@v5
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
@@ -248,7 +248,7 @@ jobs:
|
||||
|
||||
- name: Fail on missing container image
|
||||
if: steps.check-image.outcome == 'failure'
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
script: |
|
||||
@@ -318,7 +318,7 @@ jobs:
|
||||
echo "ip=$(aws ec2 describe-network-interfaces --network-interface-ids ${{ steps.get-eni.outputs.eni }} | jq -r '.NetworkInterfaces | first | .Association.PublicIp')" >> $GITHUB_OUTPUT
|
||||
- name: Comment (success)
|
||||
if: ${{ success() }}
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
github-token: ${{github.token}}
|
||||
script: |
|
||||
@@ -331,7 +331,7 @@ jobs:
|
||||
});
|
||||
- name: Comment (failure)
|
||||
if: ${{ failure() }}
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
github-token: ${{github.token}}
|
||||
script: |
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
|
||||
2
.github/workflows/labeler.yml
vendored
2
.github/workflows/labeler.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/labeler@v5
|
||||
- uses: actions/labeler@v6
|
||||
with:
|
||||
sync-labels: true
|
||||
|
||||
|
||||
2
.github/workflows/no-hold-label.yml
vendored
2
.github/workflows/no-hold-label.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Check for 'hold' label
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
|
||||
2
.github/workflows/pre-commit.yml
vendored
2
.github/workflows/pre-commit.yml
vendored
@@ -39,7 +39,7 @@ jobs:
|
||||
echo "HOMEBREW_REPOSITORY=$HOMEBREW_REPOSITORY" >>"${GITHUB_ENV}"
|
||||
brew install norwoodj/tap/helm-docs
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
|
||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -42,7 +42,7 @@ jobs:
|
||||
|
||||
- name: Install Node.js
|
||||
if: env.HAS_TAGS
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version-file: './superset-frontend/.nvmrc'
|
||||
|
||||
|
||||
2
.github/workflows/showtime-trigger.yml
vendored
2
.github/workflows/showtime-trigger.yml
vendored
@@ -37,7 +37,7 @@ jobs:
|
||||
steps:
|
||||
- name: Security Check - Authorize Maintainers Only
|
||||
id: auth
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
|
||||
@@ -63,7 +63,7 @@ jobs:
|
||||
with:
|
||||
run: testdata
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version-file: './superset-frontend/.nvmrc'
|
||||
- name: Install npm dependencies
|
||||
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
submodules: recursive
|
||||
ref: master
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version-file: './superset-frontend/.nvmrc'
|
||||
- name: Install eyes-storybook dependencies
|
||||
|
||||
2
.github/workflows/superset-docs-deploy.yml
vendored
2
.github/workflows/superset-docs-deploy.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version-file: './docs/.nvmrc'
|
||||
- name: Setup Python
|
||||
|
||||
22
.github/workflows/superset-docs-verify.yml
vendored
22
.github/workflows/superset-docs-verify.yml
vendored
@@ -21,12 +21,14 @@ jobs:
|
||||
- uses: actions/checkout@v5
|
||||
# Do not bump this linkinator-action version without opening
|
||||
# an ASF Infra ticket to allow the new version first!
|
||||
- uses: JustinBeckwith/linkinator-action@v1.11.0
|
||||
- uses: JustinBeckwith/linkinator-action@3d5ba091319fa7b0ac14703761eebb7d100e6f6d # v1.11.0
|
||||
continue-on-error: true # This will make the job advisory (non-blocking, no red X)
|
||||
with:
|
||||
paths: "**/*.md, **/*.mdx, !superset-frontend/CHANGELOG.md"
|
||||
paths: "**/*.md, **/*.mdx"
|
||||
linksToSkip: >-
|
||||
^https://github.com/apache/(superset|incubator-superset)/(pull|issue)/\d+,
|
||||
^https://github.com/apache/(superset|incubator-superset)/(pull|issues)/\d+,
|
||||
^https://github.com/apache/(superset|incubator-superset)/commit/[a-f0-9]+,
|
||||
superset-frontend/.*CHANGELOG\.md,
|
||||
http://localhost:8088/,
|
||||
http://127.0.0.1:3000/,
|
||||
http://localhost:9001/,
|
||||
@@ -41,12 +43,12 @@ jobs:
|
||||
http://theiconic.com.au/,
|
||||
https://dev.mysql.com/doc/refman/5.7/en/innodb-limits.html,
|
||||
^https://img\.shields\.io/.*,
|
||||
https://vkusvill.ru/
|
||||
https://www.linkedin.com/in/mark-thomas-b16751158/
|
||||
https://theiconic.com.au/
|
||||
https://wattbewerb.de/
|
||||
https://timbr.ai/
|
||||
https://opensource.org/license/apache-2-0
|
||||
https://vkusvill.ru/,
|
||||
https://www.linkedin.com/in/mark-thomas-b16751158/,
|
||||
https://theiconic.com.au/,
|
||||
https://wattbewerb.de/,
|
||||
https://timbr.ai/,
|
||||
https://opensource.org/license/apache-2-0,
|
||||
https://www.plaidcloud.com/
|
||||
build-deploy:
|
||||
name: Build & Deploy
|
||||
@@ -61,7 +63,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version-file: './docs/.nvmrc'
|
||||
- name: yarn install
|
||||
|
||||
2
.github/workflows/superset-e2e.yml
vendored
2
.github/workflows/superset-e2e.yml
vendored
@@ -109,7 +109,7 @@ jobs:
|
||||
run: testdata
|
||||
- name: Setup Node.js
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version-file: './superset-frontend/.nvmrc'
|
||||
- name: Install npm dependencies
|
||||
|
||||
2
.github/workflows/superset-helm-release.yml
vendored
2
.github/workflows/superset-helm-release.yml
vendored
@@ -101,7 +101,7 @@ jobs:
|
||||
CR_RELEASE_NAME_TEMPLATE: "superset-helm-chart-{{ .Version }}"
|
||||
|
||||
- name: Open Pull Request
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const branchName = '${{ env.branch_name }}';
|
||||
|
||||
2
.github/workflows/superset-playwright.yml
vendored
2
.github/workflows/superset-playwright.yml
vendored
@@ -99,7 +99,7 @@ jobs:
|
||||
run: testdata
|
||||
- name: Setup Node.js
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version-file: './superset-frontend/.nvmrc'
|
||||
- name: Install npm dependencies
|
||||
|
||||
2
.github/workflows/superset-translations.yml
vendored
2
.github/workflows/superset-translations.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
||||
|
||||
- name: Setup Node.js
|
||||
if: steps.check.outputs.frontend
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version-file: './superset-frontend/.nvmrc'
|
||||
- name: Install dependencies
|
||||
|
||||
2
.github/workflows/supersetbot.yml
vendored
2
.github/workflows/supersetbot.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
steps:
|
||||
- name: Quickly add thumbs up!
|
||||
if: github.event_name == 'issue_comment' && contains(github.event.comment.body, '@supersetbot')
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/')
|
||||
|
||||
4
.github/workflows/tag-release.yml
vendored
4
.github/workflows/tag-release.yml
vendored
@@ -60,7 +60,7 @@ jobs:
|
||||
build: "true"
|
||||
|
||||
- name: Use Node.js 20
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
@@ -112,7 +112,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Use Node.js 20
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
|
||||
2
.github/workflows/tech-debt.yml
vendored
2
.github/workflows/tech-debt.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version-file: './superset-frontend/.nvmrc'
|
||||
|
||||
|
||||
@@ -82,6 +82,7 @@ intro_header.txt
|
||||
|
||||
# for LLMs
|
||||
llm-context.md
|
||||
AGENTS.md
|
||||
LLMS.md
|
||||
CLAUDE.md
|
||||
CURSOR.md
|
||||
|
||||
@@ -68,7 +68,11 @@ superset/
|
||||
|
||||
### Apache License Headers
|
||||
- **New files require ASF license headers** - When creating new code files, include the standard Apache Software Foundation license header
|
||||
- **LLM instruction files are excluded** - Files like LLMS.md, CLAUDE.md, etc. are in `.rat-excludes` to avoid header token overhead
|
||||
- **LLM instruction files are excluded** - Files like AGENTS.md, CLAUDE.md, etc. are in `.rat-excludes` to avoid header token overhead
|
||||
|
||||
### Code Comments
|
||||
- **Avoid time-specific language** - Don't use words like "now", "currently", "today" in code comments as they become outdated
|
||||
- **Write timeless comments** - Comments should remain accurate regardless of when they're read
|
||||
|
||||
## Documentation Requirements
|
||||
|
||||
@@ -26,6 +26,9 @@ ARG BUILDPLATFORM=${BUILDPLATFORM:-amd64}
|
||||
# Include translations in the final build
|
||||
ARG BUILD_TRANSLATIONS="false"
|
||||
|
||||
# Build arg to pre-populate examples DuckDB file
|
||||
ARG LOAD_EXAMPLES_DUCKDB="false"
|
||||
|
||||
######################################################################
|
||||
# superset-node-ci used as a base for building frontend assets and CI
|
||||
######################################################################
|
||||
@@ -143,8 +146,8 @@ RUN if [ "${BUILD_TRANSLATIONS}" = "true" ]; then \
|
||||
######################################################################
|
||||
FROM python-base AS python-common
|
||||
|
||||
# Build arg to pre-populate examples DuckDB file
|
||||
ARG LOAD_EXAMPLES_DUCKDB="false"
|
||||
# Re-declare build arg to receive it in this stage
|
||||
ARG LOAD_EXAMPLES_DUCKDB
|
||||
|
||||
ENV SUPERSET_HOME="/app/superset_home" \
|
||||
HOME="/app/superset_home" \
|
||||
|
||||
@@ -1769,6 +1769,48 @@ You can use the `Extra` field in the **Edit Databases** form to configure SSL:
|
||||
}
|
||||
}
|
||||
```
|
||||
##### Custom Error Messages
|
||||
You can use the `CUSTOM_DATABASE_ERRORS` in the `superset/custom_database_errors.py` file or overwrite it in your config file to configure custom error messages for database exceptions.
|
||||
|
||||
This feature lets you transform raw database errors into user-friendly messages, optionally including documentation links and hiding default error codes.
|
||||
|
||||
Provide an empty string as the first value to keep the original error message. This way, you can add just a link to the documentation
|
||||
**Example usage:**
|
||||
```Python
|
||||
CUSTOM_DATABASE_ERRORS = {
|
||||
"database_name": {
|
||||
re.compile('permission denied for view'): (
|
||||
__(
|
||||
'Permission denied'
|
||||
),
|
||||
SupersetErrorType.GENERIC_DB_ENGINE_ERROR,
|
||||
{
|
||||
"custom_doc_links": [
|
||||
{
|
||||
"url": "https://example.com/docs/1",
|
||||
"label": "Check documentation"
|
||||
},
|
||||
],
|
||||
"show_issue_info": False,
|
||||
}
|
||||
)
|
||||
},
|
||||
"examples": {
|
||||
re.compile(r'message="(?P<message>[^"]*)"'): (
|
||||
__(
|
||||
'Unexpected error: "%(message)s"'
|
||||
),
|
||||
SupersetErrorType.GENERIC_DB_ENGINE_ERROR,
|
||||
{}
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Options:**
|
||||
|
||||
- ``custom_doc_links``: List of documentation links to display with the error.
|
||||
- ``show_issue_info``: Set to ``False`` to hide default error codes.
|
||||
|
||||
## Misc
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
"version:remove:components": "node scripts/manage-versions.mjs remove components"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^6.0.0",
|
||||
"@ant-design/icons": "^6.1.0",
|
||||
"@docusaurus/core": "3.8.1",
|
||||
"@docusaurus/plugin-client-redirects": "3.8.1",
|
||||
"@docusaurus/preset-classic": "3.8.1",
|
||||
@@ -49,7 +49,7 @@
|
||||
"@storybook/preview-api": "^8.6.11",
|
||||
"@storybook/theming": "^8.6.11",
|
||||
"@superset-ui/core": "^0.20.4",
|
||||
"antd": "^5.26.7",
|
||||
"antd": "^5.27.4",
|
||||
"caniuse-lite": "^1.0.30001739",
|
||||
"docusaurus-plugin-less": "^2.0.2",
|
||||
"json-bigint": "^1.0.0",
|
||||
@@ -63,7 +63,7 @@
|
||||
"remark-import-partial": "^0.0.2",
|
||||
"reselect": "^5.1.1",
|
||||
"storybook": "^8.6.11",
|
||||
"swagger-ui-react": "^5.27.1",
|
||||
"swagger-ui-react": "^5.29.1",
|
||||
"tinycolor2": "^1.4.2",
|
||||
"ts-loader": "^9.5.4"
|
||||
},
|
||||
@@ -74,15 +74,15 @@
|
||||
"@types/react": "^19.1.8",
|
||||
"@typescript-eslint/eslint-plugin": "^8.37.0",
|
||||
"@typescript-eslint/parser": "^8.42.0",
|
||||
"eslint": "^9.34.0",
|
||||
"eslint": "^9.36.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-prettier": "^5.5.3",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"globals": "^16.3.0",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "~5.9.2",
|
||||
"typescript-eslint": "^8.39.0",
|
||||
"webpack": "^5.101.0"
|
||||
"typescript-eslint": "^8.45.0",
|
||||
"webpack": "^5.102.0"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
|
||||
@@ -16,10 +16,17 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
{
|
||||
"settings": {
|
||||
"import/resolver": {
|
||||
"typescript": {}
|
||||
}
|
||||
|
||||
declare module '@theme/Layout' {
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export interface Props {
|
||||
readonly children?: ReactNode;
|
||||
readonly noFooter?: boolean;
|
||||
readonly wrapperClassName?: string;
|
||||
readonly title?: string;
|
||||
readonly description?: string;
|
||||
}
|
||||
|
||||
export default function Layout(props: Props): ReactNode;
|
||||
}
|
||||
@@ -23,8 +23,8 @@ import type { Plugin } from '@docusaurus/types';
|
||||
export default function webpackExtendPlugin(): Plugin<void> {
|
||||
return {
|
||||
name: 'custom-webpack-plugin',
|
||||
configureWebpack(config, isServer, utils) {
|
||||
const { isDev } = utils;
|
||||
configureWebpack(config) {
|
||||
const isDev = process.env.NODE_ENV === 'development';
|
||||
return {
|
||||
devtool: isDev ? 'eval-source-map' : config.devtool,
|
||||
...(isDev && {
|
||||
|
||||
@@ -2,12 +2,27 @@
|
||||
// This file is not used in compilation. It is here just for a nice editor experience.
|
||||
"extends": "@docusaurus/tsconfig",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "."
|
||||
"baseUrl": ".",
|
||||
"skipLibCheck": true,
|
||||
"noImplicitAny": false,
|
||||
"strict": false,
|
||||
"types": ["@docusaurus/module-type-aliases"]
|
||||
},
|
||||
"jsx": "react-jsx",
|
||||
"moduleResolution": "node",
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@superset-ui/core": ["../superset-frontend/packages/superset-ui-core/src"],
|
||||
"@superset-ui/core/*": ["../superset-frontend/packages/superset-ui-core/src/*"],
|
||||
"*": ["src/*", "node_modules/*"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"../superset-frontend/**/*",
|
||||
"src/webpack.extend.ts"
|
||||
]
|
||||
}
|
||||
|
||||
578
docs/yarn.lock
578
docs/yarn.lock
@@ -232,15 +232,15 @@
|
||||
classnames "^2.2.6"
|
||||
rc-util "^5.31.1"
|
||||
|
||||
"@ant-design/icons@^6.0.0":
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@ant-design/icons/-/icons-6.0.0.tgz#302c935b8b0b429e4444cbc45809247276186d94"
|
||||
integrity sha512-o0aCCAlHc1o4CQcapAwWzHeaW2x9F49g7P3IDtvtNXgHowtRWYb7kiubt8sQPFvfVIVU/jLw2hzeSlNt0FU+Uw==
|
||||
"@ant-design/icons@^6.1.0":
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@ant-design/icons/-/icons-6.1.0.tgz#97cc14a3c0528b8e2b37f41f232b019f2ca38c2c"
|
||||
integrity sha512-KrWMu1fIg3w/1F2zfn+JlfNDU8dDqILfA5Tg85iqs1lf8ooyGlbkA+TkwfOKKgqpUmAiRY1PTFpuOU2DAIgSUg==
|
||||
dependencies:
|
||||
"@ant-design/colors" "^8.0.0"
|
||||
"@ant-design/icons-svg" "^4.4.0"
|
||||
"@rc-component/util" "^1.2.1"
|
||||
classnames "^2.2.6"
|
||||
"@rc-component/util" "^1.3.0"
|
||||
clsx "^2.1.1"
|
||||
|
||||
"@ant-design/react-slick@~1.1.2":
|
||||
version "1.1.2"
|
||||
@@ -2385,10 +2385,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz#585624dc829cfb6e7c0aa6c3ca7d7e6daa87e34f"
|
||||
integrity sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==
|
||||
|
||||
"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.7.0":
|
||||
version "4.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz#607084630c6c033992a082de6e6fbc1a8b52175a"
|
||||
integrity sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==
|
||||
"@eslint-community/eslint-utils@^4.7.0", "@eslint-community/eslint-utils@^4.8.0":
|
||||
version "4.9.0"
|
||||
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz#7308df158e064f0dd8b8fdb58aa14fa2a7f913b3"
|
||||
integrity sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==
|
||||
dependencies:
|
||||
eslint-visitor-keys "^3.4.3"
|
||||
|
||||
@@ -2433,10 +2433,10 @@
|
||||
minimatch "^3.1.2"
|
||||
strip-json-comments "^3.1.1"
|
||||
|
||||
"@eslint/js@9.34.0", "@eslint/js@^9.32.0":
|
||||
version "9.34.0"
|
||||
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.34.0.tgz#fc423168b9d10e08dea9088d083788ec6442996b"
|
||||
integrity sha512-EoyvqQnBNsV1CWaEJ559rxXL4c8V92gxirbawSmVUOWXlsRxxQXl6LmCpdUblgxgSkDIqKnhzba2SjRTI/A5Rw==
|
||||
"@eslint/js@9.36.0", "@eslint/js@^9.32.0":
|
||||
version "9.36.0"
|
||||
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.36.0.tgz#b1a3893dd6ce2defed5fd49de805ba40368e8fef"
|
||||
integrity sha512-uhCbYtYynH30iZErszX78U+nR3pJU3RHGQ57NXy5QupD4SBVwDeU8TNBy+MjMngc1UyIW9noKqsRqfjQTBU2dw==
|
||||
|
||||
"@eslint/object-schema@^2.1.6":
|
||||
version "2.1.6"
|
||||
@@ -2746,10 +2746,10 @@
|
||||
rc-resize-observer "^1.3.1"
|
||||
rc-util "^5.44.0"
|
||||
|
||||
"@rc-component/util@^1.2.1":
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@rc-component/util/-/util-1.2.2.tgz#f8363b0e1cc78af3ec56e2235cc3438eb8832040"
|
||||
integrity sha512-p3zQr9Wu8BKncqmuW23olzBoAFsN8PYMS9FaI4JwJLwknH7DvfHAr1fwbfl9aAWw4Jva64ucpenbgG4fznLUSw==
|
||||
"@rc-component/util@^1.3.0":
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@rc-component/util/-/util-1.3.0.tgz#fc8e1ce1e5292592ef7f45839a3c07366288275c"
|
||||
integrity sha512-hfXE04CVsxI/slmWKeSh6du7sSKpbvVdVEZCa8A+2QWDlL97EsCYme2c3ZWLn1uC9FR21JoewlrhUPWO4QgO8w==
|
||||
dependencies:
|
||||
is-mobile "^5.0.0"
|
||||
react-is "^18.2.0"
|
||||
@@ -3050,7 +3050,34 @@
|
||||
ramda-adjunct "^5.0.0"
|
||||
unraw "^3.0.0"
|
||||
|
||||
"@swagger-api/apidom-core@>=1.0.0-beta.41 <1.0.0-rc.0", "@swagger-api/apidom-core@^1.0.0-beta.46":
|
||||
"@swagger-api/apidom-ast@^1.0.0-beta.50":
|
||||
version "1.0.0-beta.50"
|
||||
resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ast/-/apidom-ast-1.0.0-beta.50.tgz#9e0f4132ea54dd4fca0f31f919a0c5aad12d93c1"
|
||||
integrity sha512-uUBUm6J6KlyKppyfS7DIW37De6oyMVIpHYmaNV3YAaDMuRMov5KHHWXKbqWlI+l493OljOcXEqDIPeLzm6B5PQ==
|
||||
dependencies:
|
||||
"@babel/runtime-corejs3" "^7.26.10"
|
||||
"@swagger-api/apidom-error" "^1.0.0-beta.50"
|
||||
"@types/ramda" "~0.30.0"
|
||||
ramda "~0.30.0"
|
||||
ramda-adjunct "^5.0.0"
|
||||
unraw "^3.0.0"
|
||||
|
||||
"@swagger-api/apidom-core@>=1.0.0-beta.50 <1.0.0-rc.0", "@swagger-api/apidom-core@^1.0.0-beta.50":
|
||||
version "1.0.0-beta.50"
|
||||
resolved "https://registry.yarnpkg.com/@swagger-api/apidom-core/-/apidom-core-1.0.0-beta.50.tgz#d9ed6821d1b38b66ed8cd0b8d8bf5de997354178"
|
||||
integrity sha512-9N7ySdyzx/3kUnprAi63GQNt+Kq8VUvErwDgPcMRAsZX8jUhk9KLJ9N0fup4mWm6+xGs0JH35wxBxnanS6aiqw==
|
||||
dependencies:
|
||||
"@babel/runtime-corejs3" "^7.26.10"
|
||||
"@swagger-api/apidom-ast" "^1.0.0-beta.50"
|
||||
"@swagger-api/apidom-error" "^1.0.0-beta.50"
|
||||
"@types/ramda" "~0.30.0"
|
||||
minim "~0.23.8"
|
||||
ramda "~0.30.0"
|
||||
ramda-adjunct "^5.0.0"
|
||||
short-unique-id "^5.3.2"
|
||||
ts-mixer "^6.0.3"
|
||||
|
||||
"@swagger-api/apidom-core@^1.0.0-beta.46":
|
||||
version "1.0.0-beta.46"
|
||||
resolved "https://registry.yarnpkg.com/@swagger-api/apidom-core/-/apidom-core-1.0.0-beta.46.tgz#6c879f6cf9e67fa0cc36955d3a7f0dd185e36b4f"
|
||||
integrity sha512-CC8SdAkjSA/l31xfg5l4860cixbYABIXamJeNcXJr+O1z9YNKHIIENeI0zIwORk/TYUaSOg1KUjDnV2Pdctufg==
|
||||
@@ -3065,14 +3092,31 @@
|
||||
short-unique-id "^5.3.2"
|
||||
ts-mixer "^6.0.3"
|
||||
|
||||
"@swagger-api/apidom-error@>=1.0.0-beta.41 <1.0.0-rc.0", "@swagger-api/apidom-error@^1.0.0-beta.46":
|
||||
"@swagger-api/apidom-error@>=1.0.0-beta.50 <1.0.0-rc.0", "@swagger-api/apidom-error@^1.0.0-beta.50":
|
||||
version "1.0.0-beta.50"
|
||||
resolved "https://registry.yarnpkg.com/@swagger-api/apidom-error/-/apidom-error-1.0.0-beta.50.tgz#4354c4ade0482824ec7b17667390057f87838ddd"
|
||||
integrity sha512-vdpi2nRVcxXLGc68JPNwTcKrCKl8PnOEPuykZSxeNbDKnZY80APbsoLDX+1gdRgafK/7k5XdsBkpDQscsTkDng==
|
||||
dependencies:
|
||||
"@babel/runtime-corejs3" "^7.20.7"
|
||||
|
||||
"@swagger-api/apidom-error@^1.0.0-beta.46":
|
||||
version "1.0.0-beta.46"
|
||||
resolved "https://registry.yarnpkg.com/@swagger-api/apidom-error/-/apidom-error-1.0.0-beta.46.tgz#fe9039c23ac655930e1f06d99b8ea28bc40d905d"
|
||||
integrity sha512-AN2ZOHp7gtVsCa6hWxH3nk/1PS7bRtqDVCih3C5qA8k5JfHjP2wfks7b14Ns971SUvDD/SubaCmXmu0CRBHLag==
|
||||
dependencies:
|
||||
"@babel/runtime-corejs3" "^7.20.7"
|
||||
|
||||
"@swagger-api/apidom-json-pointer@>=1.0.0-beta.41 <1.0.0-rc.0", "@swagger-api/apidom-json-pointer@^1.0.0-beta.40 <1.0.0-rc.0", "@swagger-api/apidom-json-pointer@^1.0.0-beta.46":
|
||||
"@swagger-api/apidom-json-pointer@>=1.0.0-beta.50 <1.0.0-rc.0", "@swagger-api/apidom-json-pointer@^1.0.0-beta.50":
|
||||
version "1.0.0-beta.50"
|
||||
resolved "https://registry.yarnpkg.com/@swagger-api/apidom-json-pointer/-/apidom-json-pointer-1.0.0-beta.50.tgz#8d2c4129c0d59cf059df3f0e40703114a5c3b49a"
|
||||
integrity sha512-2TgFKHlZ/SlnTZzY7EwE8xx5Pr2BYePX52xZJFqWnueSAIcCcsrqZeazWIAaDe/gXd47CDqU95nDChMECERspA==
|
||||
dependencies:
|
||||
"@babel/runtime-corejs3" "^7.26.10"
|
||||
"@swagger-api/apidom-core" "^1.0.0-beta.50"
|
||||
"@swagger-api/apidom-error" "^1.0.0-beta.50"
|
||||
"@swaggerexpert/json-pointer" "^2.10.1"
|
||||
|
||||
"@swagger-api/apidom-json-pointer@^1.0.0-beta.40 <1.0.0-rc.0", "@swagger-api/apidom-json-pointer@^1.0.0-beta.46":
|
||||
version "1.0.0-beta.46"
|
||||
resolved "https://registry.yarnpkg.com/@swagger-api/apidom-json-pointer/-/apidom-json-pointer-1.0.0-beta.46.tgz#40edcaf5fddc5c748ed4084c9042a2669cbf507c"
|
||||
integrity sha512-JsbCkYDG7rbf1QmEhtXgbuCGJ4oW+uWDOEnW+Gar49sjk/OKX/2GSGUpywRstnZcopjNarLcEx/H1mHRLoL45A==
|
||||
@@ -3136,6 +3180,20 @@
|
||||
ramda-adjunct "^5.0.0"
|
||||
ts-mixer "^6.0.4"
|
||||
|
||||
"@swagger-api/apidom-ns-json-schema-2019-09@^1.0.0-beta.50":
|
||||
version "1.0.0-beta.50"
|
||||
resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ns-json-schema-2019-09/-/apidom-ns-json-schema-2019-09-1.0.0-beta.50.tgz#83c0eae65b2c4563c5233acdfbccb66c07065e93"
|
||||
integrity sha512-QP6DuthV8ZWQnthYbPEVikK5rTN4T5lhnAnmO1v6zOCS9B1heKCFcIYgBhcqCnuZ0Tt8kGOfLyqGMb57lPkCdw==
|
||||
dependencies:
|
||||
"@babel/runtime-corejs3" "^7.26.10"
|
||||
"@swagger-api/apidom-core" "^1.0.0-beta.50"
|
||||
"@swagger-api/apidom-error" "^1.0.0-beta.50"
|
||||
"@swagger-api/apidom-ns-json-schema-draft-7" "^1.0.0-beta.50"
|
||||
"@types/ramda" "~0.30.0"
|
||||
ramda "~0.30.0"
|
||||
ramda-adjunct "^5.0.0"
|
||||
ts-mixer "^6.0.4"
|
||||
|
||||
"@swagger-api/apidom-ns-json-schema-2020-12@^1.0.0-beta.46":
|
||||
version "1.0.0-beta.46"
|
||||
resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ns-json-schema-2020-12/-/apidom-ns-json-schema-2020-12-1.0.0-beta.46.tgz#779dbfb80ad10c3d6b2fd182a35231803e9ee387"
|
||||
@@ -3150,6 +3208,20 @@
|
||||
ramda-adjunct "^5.0.0"
|
||||
ts-mixer "^6.0.4"
|
||||
|
||||
"@swagger-api/apidom-ns-json-schema-2020-12@^1.0.0-beta.50":
|
||||
version "1.0.0-beta.50"
|
||||
resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ns-json-schema-2020-12/-/apidom-ns-json-schema-2020-12-1.0.0-beta.50.tgz#0310fc351efd42695227c012b1407728e1400e92"
|
||||
integrity sha512-ZaqrtZEXUx35x66ND8sc5vf1sIuWPERA15EdRHeca56E09RnjZMUHkiDvdx78165h31QmM67YLi04zEBYhQS0g==
|
||||
dependencies:
|
||||
"@babel/runtime-corejs3" "^7.26.10"
|
||||
"@swagger-api/apidom-core" "^1.0.0-beta.50"
|
||||
"@swagger-api/apidom-error" "^1.0.0-beta.50"
|
||||
"@swagger-api/apidom-ns-json-schema-2019-09" "^1.0.0-beta.50"
|
||||
"@types/ramda" "~0.30.0"
|
||||
ramda "~0.30.0"
|
||||
ramda-adjunct "^5.0.0"
|
||||
ts-mixer "^6.0.4"
|
||||
|
||||
"@swagger-api/apidom-ns-json-schema-draft-4@^1.0.0-beta.46":
|
||||
version "1.0.0-beta.46"
|
||||
resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ns-json-schema-draft-4/-/apidom-ns-json-schema-draft-4-1.0.0-beta.46.tgz#f17bb2010dd933fb6875c82d83826eda62f87bb6"
|
||||
@@ -3163,6 +3235,19 @@
|
||||
ramda-adjunct "^5.0.0"
|
||||
ts-mixer "^6.0.4"
|
||||
|
||||
"@swagger-api/apidom-ns-json-schema-draft-4@^1.0.0-beta.50":
|
||||
version "1.0.0-beta.50"
|
||||
resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ns-json-schema-draft-4/-/apidom-ns-json-schema-draft-4-1.0.0-beta.50.tgz#43b0e37d135b805ab3ea7efce0c5fc5678e95abc"
|
||||
integrity sha512-aqCwW+iuN7RokH10vDp/eEwlrT4LAlHGy1pLzAS9aFVJyUutfm0I4fxLfddOKD2yd04z858zhLwOVSo4BjrLHg==
|
||||
dependencies:
|
||||
"@babel/runtime-corejs3" "^7.26.10"
|
||||
"@swagger-api/apidom-ast" "^1.0.0-beta.50"
|
||||
"@swagger-api/apidom-core" "^1.0.0-beta.50"
|
||||
"@types/ramda" "~0.30.0"
|
||||
ramda "~0.30.0"
|
||||
ramda-adjunct "^5.0.0"
|
||||
ts-mixer "^6.0.4"
|
||||
|
||||
"@swagger-api/apidom-ns-json-schema-draft-6@^1.0.0-beta.46":
|
||||
version "1.0.0-beta.46"
|
||||
resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ns-json-schema-draft-6/-/apidom-ns-json-schema-draft-6-1.0.0-beta.46.tgz#6629cb35e9e10e670e34a4fd50b6d159f5666766"
|
||||
@@ -3177,6 +3262,20 @@
|
||||
ramda-adjunct "^5.0.0"
|
||||
ts-mixer "^6.0.4"
|
||||
|
||||
"@swagger-api/apidom-ns-json-schema-draft-6@^1.0.0-beta.50":
|
||||
version "1.0.0-beta.50"
|
||||
resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ns-json-schema-draft-6/-/apidom-ns-json-schema-draft-6-1.0.0-beta.50.tgz#a311488627a12953a4a331d3b4231c20311aed9c"
|
||||
integrity sha512-trF1TZZ79WJOjQw3C1Y7wcqNMxxgHMZtJW2/tP5MwII1hqsExGzmGyUuNlVuSC9k9v/9sCj85hQlJ4TW6HFciQ==
|
||||
dependencies:
|
||||
"@babel/runtime-corejs3" "^7.26.10"
|
||||
"@swagger-api/apidom-core" "^1.0.0-beta.50"
|
||||
"@swagger-api/apidom-error" "^1.0.0-beta.50"
|
||||
"@swagger-api/apidom-ns-json-schema-draft-4" "^1.0.0-beta.50"
|
||||
"@types/ramda" "~0.30.0"
|
||||
ramda "~0.30.0"
|
||||
ramda-adjunct "^5.0.0"
|
||||
ts-mixer "^6.0.4"
|
||||
|
||||
"@swagger-api/apidom-ns-json-schema-draft-7@^1.0.0-beta.46":
|
||||
version "1.0.0-beta.46"
|
||||
resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ns-json-schema-draft-7/-/apidom-ns-json-schema-draft-7-1.0.0-beta.46.tgz#04fa39cfe0f869e0c7be0f70ceb34ffb6cc830ad"
|
||||
@@ -3191,6 +3290,20 @@
|
||||
ramda-adjunct "^5.0.0"
|
||||
ts-mixer "^6.0.4"
|
||||
|
||||
"@swagger-api/apidom-ns-json-schema-draft-7@^1.0.0-beta.50":
|
||||
version "1.0.0-beta.50"
|
||||
resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ns-json-schema-draft-7/-/apidom-ns-json-schema-draft-7-1.0.0-beta.50.tgz#eb99fdecf9746905331f281578e924082659aef0"
|
||||
integrity sha512-g9VscnMwjPUYCfR6UxUwsLiIKnyXy2W28J+zN0rbijoSEtUdakcrxwdPhqwgJZHPci8NHNE8574zaocqKBiqSg==
|
||||
dependencies:
|
||||
"@babel/runtime-corejs3" "^7.26.10"
|
||||
"@swagger-api/apidom-core" "^1.0.0-beta.50"
|
||||
"@swagger-api/apidom-error" "^1.0.0-beta.50"
|
||||
"@swagger-api/apidom-ns-json-schema-draft-6" "^1.0.0-beta.50"
|
||||
"@types/ramda" "~0.30.0"
|
||||
ramda "~0.30.0"
|
||||
ramda-adjunct "^5.0.0"
|
||||
ts-mixer "^6.0.4"
|
||||
|
||||
"@swagger-api/apidom-ns-openapi-2@^1.0.0-beta.40 <1.0.0-rc.0", "@swagger-api/apidom-ns-openapi-2@^1.0.0-beta.46":
|
||||
version "1.0.0-beta.46"
|
||||
resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ns-openapi-2/-/apidom-ns-openapi-2-1.0.0-beta.46.tgz#448f5b7bacd29a3dc37124a7a62f154c94cb818d"
|
||||
@@ -3219,7 +3332,37 @@
|
||||
ramda-adjunct "^5.0.0"
|
||||
ts-mixer "^6.0.3"
|
||||
|
||||
"@swagger-api/apidom-ns-openapi-3-1@>=1.0.0-beta.41 <1.0.0-rc.0", "@swagger-api/apidom-ns-openapi-3-1@^1.0.0-beta.40 <1.0.0-rc.0", "@swagger-api/apidom-ns-openapi-3-1@^1.0.0-beta.46":
|
||||
"@swagger-api/apidom-ns-openapi-3-0@^1.0.0-beta.50":
|
||||
version "1.0.0-beta.50"
|
||||
resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ns-openapi-3-0/-/apidom-ns-openapi-3-0-1.0.0-beta.50.tgz#1d9631c0c7504a8985671f2f8e5ca435235b1a54"
|
||||
integrity sha512-I4GHyNILNxDsYKYeG1+ZA3rnfU1RAYtNp3dA+G8LCX5AB/2N7dT2VPK8HS4cj9m3ZVz7dl1o+X6tpaJIN5kDsA==
|
||||
dependencies:
|
||||
"@babel/runtime-corejs3" "^7.26.10"
|
||||
"@swagger-api/apidom-core" "^1.0.0-beta.50"
|
||||
"@swagger-api/apidom-error" "^1.0.0-beta.50"
|
||||
"@swagger-api/apidom-ns-json-schema-draft-4" "^1.0.0-beta.50"
|
||||
"@types/ramda" "~0.30.0"
|
||||
ramda "~0.30.0"
|
||||
ramda-adjunct "^5.0.0"
|
||||
ts-mixer "^6.0.3"
|
||||
|
||||
"@swagger-api/apidom-ns-openapi-3-1@>=1.0.0-beta.50 <1.0.0-rc.0":
|
||||
version "1.0.0-beta.50"
|
||||
resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ns-openapi-3-1/-/apidom-ns-openapi-3-1-1.0.0-beta.50.tgz#8b9a0c97849cc107228e66dd746bed858b78c50d"
|
||||
integrity sha512-kxwuaFl1kQddk/RBS5Mz3rE/6v5mXggqhzVwDBObGjgkRmDRVF5nUalziBRNg6A3NcpYbsjNMU/OCA1JihFkrg==
|
||||
dependencies:
|
||||
"@babel/runtime-corejs3" "^7.26.10"
|
||||
"@swagger-api/apidom-ast" "^1.0.0-beta.50"
|
||||
"@swagger-api/apidom-core" "^1.0.0-beta.50"
|
||||
"@swagger-api/apidom-json-pointer" "^1.0.0-beta.50"
|
||||
"@swagger-api/apidom-ns-json-schema-2020-12" "^1.0.0-beta.50"
|
||||
"@swagger-api/apidom-ns-openapi-3-0" "^1.0.0-beta.50"
|
||||
"@types/ramda" "~0.30.0"
|
||||
ramda "~0.30.0"
|
||||
ramda-adjunct "^5.0.0"
|
||||
ts-mixer "^6.0.3"
|
||||
|
||||
"@swagger-api/apidom-ns-openapi-3-1@^1.0.0-beta.40 <1.0.0-rc.0", "@swagger-api/apidom-ns-openapi-3-1@^1.0.0-beta.46":
|
||||
version "1.0.0-beta.46"
|
||||
resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ns-openapi-3-1/-/apidom-ns-openapi-3-1-1.0.0-beta.46.tgz#87056343a5e2ff48992a27af99611fcbdfbc0f22"
|
||||
integrity sha512-dSs9h4YEty7h8HJDtWSeC0cg0O2XUTwTVbnOylHX4Z7tXYub8TJ2LEbngRic/R4T58xt76Ro5rZqGUcrt5qXDQ==
|
||||
@@ -3423,16 +3566,16 @@
|
||||
tree-sitter "=0.22.4"
|
||||
web-tree-sitter "=0.24.5"
|
||||
|
||||
"@swagger-api/apidom-reference@>=1.0.0-beta.41 <1.0.0-rc.0":
|
||||
version "1.0.0-beta.46"
|
||||
resolved "https://registry.yarnpkg.com/@swagger-api/apidom-reference/-/apidom-reference-1.0.0-beta.46.tgz#c9cb8edfc1099f05e19075c880c4e7c4a87d5015"
|
||||
integrity sha512-OpJ72k17hyrUJYvj5HgNg93uUsk/28cBWkDAna/cZtCiTdz9zji31JiDUr5GoUd81rpVGuxo4AuUvxXVsH35tA==
|
||||
"@swagger-api/apidom-reference@>=1.0.0-beta.50 <1.0.0-rc.0":
|
||||
version "1.0.0-beta.50"
|
||||
resolved "https://registry.yarnpkg.com/@swagger-api/apidom-reference/-/apidom-reference-1.0.0-beta.50.tgz#d03f5506327e92dff81a4fac6460edcd2bf3989e"
|
||||
integrity sha512-aD7gTWPgkJb9oYaC4jZPvxb7YbQKG9pWDYZigAkVGqOAbeYxUXeI00XyCLj/cH8l7KwyhTZNX70F7VnfxOkq7w==
|
||||
dependencies:
|
||||
"@babel/runtime-corejs3" "^7.26.10"
|
||||
"@swagger-api/apidom-core" "^1.0.0-beta.46"
|
||||
"@swagger-api/apidom-error" "^1.0.0-beta.46"
|
||||
"@swagger-api/apidom-core" "^1.0.0-beta.50"
|
||||
"@swagger-api/apidom-error" "^1.0.0-beta.50"
|
||||
"@types/ramda" "~0.30.0"
|
||||
axios "^1.9.0"
|
||||
axios "^1.12.2"
|
||||
minimatch "^7.4.3"
|
||||
process "^0.11.10"
|
||||
ramda "~0.30.0"
|
||||
@@ -4087,117 +4230,79 @@
|
||||
dependencies:
|
||||
"@types/yargs-parser" "*"
|
||||
|
||||
"@typescript-eslint/eslint-plugin@8.40.0", "@typescript-eslint/eslint-plugin@^8.37.0":
|
||||
version "8.40.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.40.0.tgz#19f959f273b32f5082c891903645e6a85328db4e"
|
||||
integrity sha512-w/EboPlBwnmOBtRbiOvzjD+wdiZdgFeo17lkltrtn7X37vagKKWJABvyfsJXTlHe6XBzugmYgd4A4nW+k8Mixw==
|
||||
"@typescript-eslint/eslint-plugin@8.45.0", "@typescript-eslint/eslint-plugin@^8.37.0":
|
||||
version "8.45.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.45.0.tgz#9f251d4e85ec5089e7cccb09257ce93dbf0d7744"
|
||||
integrity sha512-HC3y9CVuevvWCl/oyZuI47dOeDF9ztdMEfMH8/DW/Mhwa9cCLnK1oD7JoTVGW/u7kFzNZUKUoyJEqkaJh5y3Wg==
|
||||
dependencies:
|
||||
"@eslint-community/regexpp" "^4.10.0"
|
||||
"@typescript-eslint/scope-manager" "8.40.0"
|
||||
"@typescript-eslint/type-utils" "8.40.0"
|
||||
"@typescript-eslint/utils" "8.40.0"
|
||||
"@typescript-eslint/visitor-keys" "8.40.0"
|
||||
"@typescript-eslint/scope-manager" "8.45.0"
|
||||
"@typescript-eslint/type-utils" "8.45.0"
|
||||
"@typescript-eslint/utils" "8.45.0"
|
||||
"@typescript-eslint/visitor-keys" "8.45.0"
|
||||
graphemer "^1.4.0"
|
||||
ignore "^7.0.0"
|
||||
natural-compare "^1.4.0"
|
||||
ts-api-utils "^2.1.0"
|
||||
|
||||
"@typescript-eslint/parser@8.40.0":
|
||||
version "8.40.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.40.0.tgz#1bc9f3701ced29540eb76ff2d95ce0d52ddc7e69"
|
||||
integrity sha512-jCNyAuXx8dr5KJMkecGmZ8KI61KBUhkCob+SD+C+I5+Y1FWI2Y3QmY4/cxMCC5WAsZqoEtEETVhUiUMIGCf6Bw==
|
||||
"@typescript-eslint/parser@8.45.0", "@typescript-eslint/parser@^8.42.0":
|
||||
version "8.45.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.45.0.tgz#571660c98824aefb4a6ec3b3766655d1348520a4"
|
||||
integrity sha512-TGf22kon8KW+DeKaUmOibKWktRY8b2NSAZNdtWh798COm1NWx8+xJ6iFBtk3IvLdv6+LGLJLRlyhrhEDZWargQ==
|
||||
dependencies:
|
||||
"@typescript-eslint/scope-manager" "8.40.0"
|
||||
"@typescript-eslint/types" "8.40.0"
|
||||
"@typescript-eslint/typescript-estree" "8.40.0"
|
||||
"@typescript-eslint/visitor-keys" "8.40.0"
|
||||
"@typescript-eslint/scope-manager" "8.45.0"
|
||||
"@typescript-eslint/types" "8.45.0"
|
||||
"@typescript-eslint/typescript-estree" "8.45.0"
|
||||
"@typescript-eslint/visitor-keys" "8.45.0"
|
||||
debug "^4.3.4"
|
||||
|
||||
"@typescript-eslint/parser@^8.42.0":
|
||||
version "8.42.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.42.0.tgz#20ea66f4867981fb5bb62cbe1454250fc4a440ab"
|
||||
integrity sha512-r1XG74QgShUgXph1BYseJ+KZd17bKQib/yF3SR+demvytiRXrwd12Blnz5eYGm8tXaeRdd4x88MlfwldHoudGg==
|
||||
"@typescript-eslint/project-service@8.45.0":
|
||||
version "8.45.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.45.0.tgz#f83dda1bca31dae2fd6821f9131daf1edebfd46c"
|
||||
integrity sha512-3pcVHwMG/iA8afdGLMuTibGR7pDsn9RjDev6CCB+naRsSYs2pns5QbinF4Xqw6YC/Sj3lMrm/Im0eMfaa61WUg==
|
||||
dependencies:
|
||||
"@typescript-eslint/scope-manager" "8.42.0"
|
||||
"@typescript-eslint/types" "8.42.0"
|
||||
"@typescript-eslint/typescript-estree" "8.42.0"
|
||||
"@typescript-eslint/visitor-keys" "8.42.0"
|
||||
"@typescript-eslint/tsconfig-utils" "^8.45.0"
|
||||
"@typescript-eslint/types" "^8.45.0"
|
||||
debug "^4.3.4"
|
||||
|
||||
"@typescript-eslint/project-service@8.40.0":
|
||||
version "8.40.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.40.0.tgz#1b7ba6079ff580c3215882fe75a43e5d3ed166b9"
|
||||
integrity sha512-/A89vz7Wf5DEXsGVvcGdYKbVM9F7DyFXj52lNYUDS1L9yJfqjW/fIp5PgMuEJL/KeqVTe2QSbXAGUZljDUpArw==
|
||||
"@typescript-eslint/scope-manager@8.45.0":
|
||||
version "8.45.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.45.0.tgz#59615ba506a9e3479d1efb0d09d6ab52f2a19142"
|
||||
integrity sha512-clmm8XSNj/1dGvJeO6VGH7EUSeA0FMs+5au/u3lrA3KfG8iJ4u8ym9/j2tTEoacAffdW1TVUzXO30W1JTJS7dA==
|
||||
dependencies:
|
||||
"@typescript-eslint/tsconfig-utils" "^8.40.0"
|
||||
"@typescript-eslint/types" "^8.40.0"
|
||||
debug "^4.3.4"
|
||||
"@typescript-eslint/types" "8.45.0"
|
||||
"@typescript-eslint/visitor-keys" "8.45.0"
|
||||
|
||||
"@typescript-eslint/project-service@8.42.0":
|
||||
version "8.42.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.42.0.tgz#636eb3418b6c42c98554dce884943708bf41a583"
|
||||
integrity sha512-vfVpLHAhbPjilrabtOSNcUDmBboQNrJUiNAGoImkZKnMjs2TIcWG33s4Ds0wY3/50aZmTMqJa6PiwkwezaAklg==
|
||||
"@typescript-eslint/tsconfig-utils@8.45.0", "@typescript-eslint/tsconfig-utils@^8.45.0":
|
||||
version "8.45.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.45.0.tgz#63d38282790a2566c571bad423e7c1cad1f3d64c"
|
||||
integrity sha512-aFdr+c37sc+jqNMGhH+ajxPXwjv9UtFZk79k8pLoJ6p4y0snmYpPA52GuWHgt2ZF4gRRW6odsEj41uZLojDt5w==
|
||||
|
||||
"@typescript-eslint/type-utils@8.45.0":
|
||||
version "8.45.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.45.0.tgz#04004bdf2598844faa29fb936fb6b0ee10d6d3f3"
|
||||
integrity sha512-bpjepLlHceKgyMEPglAeULX1vixJDgaKocp0RVJ5u4wLJIMNuKtUXIczpJCPcn2waII0yuvks/5m5/h3ZQKs0A==
|
||||
dependencies:
|
||||
"@typescript-eslint/tsconfig-utils" "^8.42.0"
|
||||
"@typescript-eslint/types" "^8.42.0"
|
||||
debug "^4.3.4"
|
||||
|
||||
"@typescript-eslint/scope-manager@8.40.0":
|
||||
version "8.40.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.40.0.tgz#2fbfcc8643340d8cd692267e61548b946190be8a"
|
||||
integrity sha512-y9ObStCcdCiZKzwqsE8CcpyuVMwRouJbbSrNuThDpv16dFAj429IkM6LNb1dZ2m7hK5fHyzNcErZf7CEeKXR4w==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "8.40.0"
|
||||
"@typescript-eslint/visitor-keys" "8.40.0"
|
||||
|
||||
"@typescript-eslint/scope-manager@8.42.0":
|
||||
version "8.42.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.42.0.tgz#36016757bc85b46ea42bae47b61f9421eddedde3"
|
||||
integrity sha512-51+x9o78NBAVgQzOPd17DkNTnIzJ8T/O2dmMBLoK9qbY0Gm52XJcdJcCl18ExBMiHo6jPMErUQWUv5RLE51zJw==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "8.42.0"
|
||||
"@typescript-eslint/visitor-keys" "8.42.0"
|
||||
|
||||
"@typescript-eslint/tsconfig-utils@8.40.0", "@typescript-eslint/tsconfig-utils@^8.40.0":
|
||||
version "8.40.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.40.0.tgz#8e8fdb9b988854aedd04abdde3239c4bdd2d26e4"
|
||||
integrity sha512-jtMytmUaG9d/9kqSl/W3E3xaWESo4hFDxAIHGVW/WKKtQhesnRIJSAJO6XckluuJ6KDB5woD1EiqknriCtAmcw==
|
||||
|
||||
"@typescript-eslint/tsconfig-utils@8.42.0", "@typescript-eslint/tsconfig-utils@^8.42.0":
|
||||
version "8.42.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.42.0.tgz#21a3e74396fd7443ff930bc41b27789ba7e9236e"
|
||||
integrity sha512-kHeFUOdwAJfUmYKjR3CLgZSglGHjbNTi1H8sTYRYV2xX6eNz4RyJ2LIgsDLKf8Yi0/GL1WZAC/DgZBeBft8QAQ==
|
||||
|
||||
"@typescript-eslint/type-utils@8.40.0":
|
||||
version "8.40.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.40.0.tgz#a7e4a1f0815dd0ba3e4eef945cc87193ca32c422"
|
||||
integrity sha512-eE60cK4KzAc6ZrzlJnflXdrMqOBaugeukWICO2rB0KNvwdIMaEaYiywwHMzA1qFpTxrLhN9Lp4E/00EgWcD3Ow==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "8.40.0"
|
||||
"@typescript-eslint/typescript-estree" "8.40.0"
|
||||
"@typescript-eslint/utils" "8.40.0"
|
||||
"@typescript-eslint/types" "8.45.0"
|
||||
"@typescript-eslint/typescript-estree" "8.45.0"
|
||||
"@typescript-eslint/utils" "8.45.0"
|
||||
debug "^4.3.4"
|
||||
ts-api-utils "^2.1.0"
|
||||
|
||||
"@typescript-eslint/types@8.40.0":
|
||||
version "8.40.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.40.0.tgz#0b580fdf643737aa5c01285314b5c6e9543846a9"
|
||||
integrity sha512-ETdbFlgbAmXHyFPwqUIYrfc12ArvpBhEVgGAxVYSwli26dn8Ko+lIo4Su9vI9ykTZdJn+vJprs/0eZU0YMAEQg==
|
||||
"@typescript-eslint/types@8.45.0", "@typescript-eslint/types@^8.45.0":
|
||||
version "8.45.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.45.0.tgz#fc01cd2a4690b9713b02f895e82fb43f7d960684"
|
||||
integrity sha512-WugXLuOIq67BMgQInIxxnsSyRLFxdkJEJu8r4ngLR56q/4Q5LrbfkFRH27vMTjxEK8Pyz7QfzuZe/G15qQnVRA==
|
||||
|
||||
"@typescript-eslint/types@8.42.0", "@typescript-eslint/types@^8.40.0", "@typescript-eslint/types@^8.42.0":
|
||||
version "8.42.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.42.0.tgz#ae15c09cebda20473772902033328e87372db008"
|
||||
integrity sha512-LdtAWMiFmbRLNP7JNeY0SqEtJvGMYSzfiWBSmx+VSZ1CH+1zyl8Mmw1TT39OrtsRvIYShjJWzTDMPWZJCpwBlw==
|
||||
|
||||
"@typescript-eslint/typescript-estree@8.40.0":
|
||||
version "8.40.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.40.0.tgz#295149440ce7da81c790a4e14e327599a3a1e5c9"
|
||||
integrity sha512-k1z9+GJReVVOkc1WfVKs1vBrR5MIKKbdAjDTPvIK3L8De6KbFfPFt6BKpdkdk7rZS2GtC/m6yI5MYX+UsuvVYQ==
|
||||
"@typescript-eslint/typescript-estree@8.45.0":
|
||||
version "8.45.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.45.0.tgz#3498500f109a89b104d2770497c707e56dfe062d"
|
||||
integrity sha512-GfE1NfVbLam6XQ0LcERKwdTTPlLvHvXXhOeUGC1OXi4eQBoyy1iVsW+uzJ/J9jtCz6/7GCQ9MtrQ0fml/jWCnA==
|
||||
dependencies:
|
||||
"@typescript-eslint/project-service" "8.40.0"
|
||||
"@typescript-eslint/tsconfig-utils" "8.40.0"
|
||||
"@typescript-eslint/types" "8.40.0"
|
||||
"@typescript-eslint/visitor-keys" "8.40.0"
|
||||
"@typescript-eslint/project-service" "8.45.0"
|
||||
"@typescript-eslint/tsconfig-utils" "8.45.0"
|
||||
"@typescript-eslint/types" "8.45.0"
|
||||
"@typescript-eslint/visitor-keys" "8.45.0"
|
||||
debug "^4.3.4"
|
||||
fast-glob "^3.3.2"
|
||||
is-glob "^4.0.3"
|
||||
@@ -4205,46 +4310,22 @@
|
||||
semver "^7.6.0"
|
||||
ts-api-utils "^2.1.0"
|
||||
|
||||
"@typescript-eslint/typescript-estree@8.42.0":
|
||||
version "8.42.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.42.0.tgz#593c3af87d4462252c0d7239d1720b84a1b56864"
|
||||
integrity sha512-ku/uYtT4QXY8sl9EDJETD27o3Ewdi72hcXg1ah/kkUgBvAYHLwj2ofswFFNXS+FL5G+AGkxBtvGt8pFBHKlHsQ==
|
||||
dependencies:
|
||||
"@typescript-eslint/project-service" "8.42.0"
|
||||
"@typescript-eslint/tsconfig-utils" "8.42.0"
|
||||
"@typescript-eslint/types" "8.42.0"
|
||||
"@typescript-eslint/visitor-keys" "8.42.0"
|
||||
debug "^4.3.4"
|
||||
fast-glob "^3.3.2"
|
||||
is-glob "^4.0.3"
|
||||
minimatch "^9.0.4"
|
||||
semver "^7.6.0"
|
||||
ts-api-utils "^2.1.0"
|
||||
|
||||
"@typescript-eslint/utils@8.40.0":
|
||||
version "8.40.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.40.0.tgz#8d0c6430ed2f5dc350784bb0d8be514da1e54054"
|
||||
integrity sha512-Cgzi2MXSZyAUOY+BFwGs17s7ad/7L+gKt6Y8rAVVWS+7o6wrjeFN4nVfTpbE25MNcxyJ+iYUXflbs2xR9h4UBg==
|
||||
"@typescript-eslint/utils@8.45.0":
|
||||
version "8.45.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.45.0.tgz#6e68e92d99019fdf56018d0e6664c76a70470c95"
|
||||
integrity sha512-bxi1ht+tLYg4+XV2knz/F7RVhU0k6VrSMc9sb8DQ6fyCTrGQLHfo7lDtN0QJjZjKkLA2ThrKuCdHEvLReqtIGg==
|
||||
dependencies:
|
||||
"@eslint-community/eslint-utils" "^4.7.0"
|
||||
"@typescript-eslint/scope-manager" "8.40.0"
|
||||
"@typescript-eslint/types" "8.40.0"
|
||||
"@typescript-eslint/typescript-estree" "8.40.0"
|
||||
"@typescript-eslint/scope-manager" "8.45.0"
|
||||
"@typescript-eslint/types" "8.45.0"
|
||||
"@typescript-eslint/typescript-estree" "8.45.0"
|
||||
|
||||
"@typescript-eslint/visitor-keys@8.40.0":
|
||||
version "8.40.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.40.0.tgz#c1b45196981311fed7256863be4bfb2d3eda332a"
|
||||
integrity sha512-8CZ47QwalyRjsypfwnbI3hKy5gJDPmrkLjkgMxhi0+DZZ2QNx2naS6/hWoVYUHU7LU2zleF68V9miaVZvhFfTA==
|
||||
"@typescript-eslint/visitor-keys@8.45.0":
|
||||
version "8.45.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.45.0.tgz#4e3bcc55da64ac61069ebfe62ca240567ac7d784"
|
||||
integrity sha512-qsaFBA3e09MIDAGFUrTk+dzqtfv1XPVz8t8d1f0ybTzrCY7BKiMC5cjrl1O/P7UmHsNyW90EYSkU/ZWpmXelag==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "8.40.0"
|
||||
eslint-visitor-keys "^4.2.1"
|
||||
|
||||
"@typescript-eslint/visitor-keys@8.42.0":
|
||||
version "8.42.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.42.0.tgz#87c6caaa1ac307bc73a87c1fc469f88f0162f27e"
|
||||
integrity sha512-3WbiuzoEowaEn8RSnhJBrxSwX8ULYE9CXaPepS2C2W3NSA5NNIvBaslpBSBElPq0UGr0xVJlXFWOAKIkyylydQ==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "8.42.0"
|
||||
"@typescript-eslint/types" "8.45.0"
|
||||
eslint-visitor-keys "^4.2.1"
|
||||
|
||||
"@ungap/structured-clone@^1.0.0":
|
||||
@@ -4544,10 +4625,10 @@ ansi-styles@^6.1.0:
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5"
|
||||
integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==
|
||||
|
||||
antd@^5.26.7:
|
||||
version "5.27.1"
|
||||
resolved "https://registry.yarnpkg.com/antd/-/antd-5.27.1.tgz#5378fc017cb4057ffefe2a670f20e54b924d897d"
|
||||
integrity sha512-jGMSdBN7hAMvPV27B4RhzZfL6n6yu8yDbo7oXrlJasaOqB7bSDPcjdEy1kXy3JPsny/Qazb1ykzRI4EfcByAPQ==
|
||||
antd@^5.27.4:
|
||||
version "5.27.4"
|
||||
resolved "https://registry.yarnpkg.com/antd/-/antd-5.27.4.tgz#13c97deb12e6aeb43adecd23f3dbe3139a62e579"
|
||||
integrity sha512-rhArohoAUCxhkPjGI/BXthOrrjaElL4Fb7d4vEHnIR3DpxFXfegd4rN21IgGdiF+Iz4EFuUZu8MdS8NuJHLSVQ==
|
||||
dependencies:
|
||||
"@ant-design/colors" "^7.2.1"
|
||||
"@ant-design/cssinjs" "^1.23.0"
|
||||
@@ -4585,10 +4666,10 @@ antd@^5.26.7:
|
||||
rc-resize-observer "^1.4.3"
|
||||
rc-segmented "~2.7.0"
|
||||
rc-select "~14.16.8"
|
||||
rc-slider "~11.1.8"
|
||||
rc-slider "~11.1.9"
|
||||
rc-steps "~6.0.1"
|
||||
rc-switch "~4.1.0"
|
||||
rc-table "~7.51.1"
|
||||
rc-table "~7.53.0"
|
||||
rc-tabs "~15.7.0"
|
||||
rc-textarea "~1.10.2"
|
||||
rc-tooltip "~6.4.0"
|
||||
@@ -4765,10 +4846,10 @@ available-typed-arrays@^1.0.7:
|
||||
dependencies:
|
||||
possible-typed-array-names "^1.0.0"
|
||||
|
||||
axios@^1.9.0:
|
||||
version "1.12.0"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-1.12.0.tgz#11248459be05a5ee493485628fa0e4323d0abfc3"
|
||||
integrity sha512-oXTDccv8PcfjZmPGlWsPSwtOJCZ/b6W5jAMCNcfwJbCzDckwG0jrYJFaWH1yvivfCXjVzV/SPDEhMB3Q+DSurg==
|
||||
axios@^1.12.2:
|
||||
version "1.12.2"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-1.12.2.tgz#6c307390136cf7a2278d09cec63b136dfc6e6da7"
|
||||
integrity sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==
|
||||
dependencies:
|
||||
follow-redirects "^1.15.6"
|
||||
form-data "^4.0.4"
|
||||
@@ -4862,11 +4943,16 @@ balanced-match@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
|
||||
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
|
||||
|
||||
base64-js@^1.5.1:
|
||||
base64-js@^1.3.1, base64-js@^1.5.1:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
|
||||
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
|
||||
|
||||
baseline-browser-mapping@^2.8.9:
|
||||
version "2.8.10"
|
||||
resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.8.10.tgz#32eb5e253d633fa3fa3ffb1685fabf41680d9e8a"
|
||||
integrity sha512-uLfgBi+7IBNay8ECBO2mVMGZAc1VgZWEChxm4lv+TobGdG82LnXMjuNGo/BSSZZL4UmkWhxEHP2f5ziLNwGWMA==
|
||||
|
||||
batch@0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16"
|
||||
@@ -4980,14 +5066,15 @@ browser-assert@^1.2.1:
|
||||
resolved "https://registry.yarnpkg.com/browser-assert/-/browser-assert-1.2.1.tgz#9aaa5a2a8c74685c2ae05bfe46efd606f068c200"
|
||||
integrity sha512-nfulgvOR6S4gt9UKCeGJOuSGBPGiFT6oQ/2UBnvTY/5aQ1PnksW72fhZkM30DzoRRv2WpwZf1vHHEr3mtuXIWQ==
|
||||
|
||||
browserslist@^4.0.0, browserslist@^4.23.0, browserslist@^4.24.0, browserslist@^4.24.4, browserslist@^4.25.0, browserslist@^4.25.3:
|
||||
version "4.25.3"
|
||||
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.25.3.tgz#9167c9cbb40473f15f75f85189290678b99b16c5"
|
||||
integrity sha512-cDGv1kkDI4/0e5yON9yM5G/0A5u8sf5TnmdX5C9qHzI9PPu++sQ9zjm1k9NiOrf3riY4OkK0zSGqfvJyJsgCBQ==
|
||||
browserslist@^4.0.0, browserslist@^4.23.0, browserslist@^4.24.0, browserslist@^4.24.4, browserslist@^4.24.5, browserslist@^4.25.0, browserslist@^4.25.3:
|
||||
version "4.26.3"
|
||||
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.26.3.tgz#40fbfe2d1cd420281ce5b1caa8840049c79afb56"
|
||||
integrity sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==
|
||||
dependencies:
|
||||
caniuse-lite "^1.0.30001735"
|
||||
electron-to-chromium "^1.5.204"
|
||||
node-releases "^2.0.19"
|
||||
baseline-browser-mapping "^2.8.9"
|
||||
caniuse-lite "^1.0.30001746"
|
||||
electron-to-chromium "^1.5.227"
|
||||
node-releases "^2.0.21"
|
||||
update-browserslist-db "^1.1.3"
|
||||
|
||||
buffer-from@^1.0.0:
|
||||
@@ -4995,6 +5082,14 @@ buffer-from@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
|
||||
integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
|
||||
|
||||
buffer@^6.0.3:
|
||||
version "6.0.3"
|
||||
resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6"
|
||||
integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==
|
||||
dependencies:
|
||||
base64-js "^1.3.1"
|
||||
ieee754 "^1.2.1"
|
||||
|
||||
bytes@3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
|
||||
@@ -5082,11 +5177,16 @@ caniuse-api@^3.0.0:
|
||||
lodash.memoize "^4.1.2"
|
||||
lodash.uniq "^4.5.0"
|
||||
|
||||
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001702, caniuse-lite@^1.0.30001735, caniuse-lite@^1.0.30001739:
|
||||
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001702, caniuse-lite@^1.0.30001739:
|
||||
version "1.0.30001739"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001739.tgz#b34ce2d56bfc22f4352b2af0144102d623a124f4"
|
||||
integrity sha512-y+j60d6ulelrNSwpPyrHdl+9mJnQzHBr08xm48Qno0nSk4h3Qojh+ziv2qE6rXf4k3tadF4o1J/1tAbVm1NtnA==
|
||||
|
||||
caniuse-lite@^1.0.30001746:
|
||||
version "1.0.30001746"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001746.tgz#199d20f04f5369825e00ff7067d45d5dfa03aee7"
|
||||
integrity sha512-eA7Ys/DGw+pnkWWSE/id29f2IcPHVoE8wxtvE5JdvD2V28VTDPy1yEeo11Guz0sJ4ZeGRcm3uaTcAqK1LXaphA==
|
||||
|
||||
ccount@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/ccount/-/ccount-2.0.1.tgz#17a3bf82302e0870d6da43a01311a8bc02a3ecf5"
|
||||
@@ -5254,7 +5354,7 @@ clone-deep@^4.0.1:
|
||||
kind-of "^6.0.2"
|
||||
shallow-clone "^3.0.0"
|
||||
|
||||
clsx@^2.0.0:
|
||||
clsx@^2.0.0, clsx@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999"
|
||||
integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==
|
||||
@@ -6340,14 +6440,7 @@ domhandler@^5.0.2, domhandler@^5.0.3:
|
||||
dependencies:
|
||||
domelementtype "^2.3.0"
|
||||
|
||||
dompurify@=3.2.4:
|
||||
version "3.2.4"
|
||||
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.2.4.tgz#af5a5a11407524431456cf18836c55d13441cd8e"
|
||||
integrity sha512-ysFSFEDVduQpyhzAob/kkuJjf5zWkZD8/A9ywSp1byueyuCfHamrCBa14/Oc2iiB0e51B+NpxSl5gmzn+Ms/mg==
|
||||
optionalDependencies:
|
||||
"@types/trusted-types" "^2.0.7"
|
||||
|
||||
dompurify@^3.2.5:
|
||||
dompurify@=3.2.6, dompurify@^3.2.5:
|
||||
version "3.2.6"
|
||||
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.2.6.tgz#ca040a6ad2b88e2a92dc45f38c79f84a714a1cad"
|
||||
integrity sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==
|
||||
@@ -6416,10 +6509,10 @@ ee-first@1.1.1:
|
||||
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
|
||||
integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
|
||||
|
||||
electron-to-chromium@^1.5.204:
|
||||
version "1.5.207"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.207.tgz#0fedde3eec615065ee95531c09a10578644c5552"
|
||||
integrity sha512-mryFrrL/GXDTmAtIVMVf+eIXM09BBPlO5IQ7lUyKmK8d+A4VpRGG+M3ofoVef6qyF8s60rJei8ymlJxjUA8Faw==
|
||||
electron-to-chromium@^1.5.227:
|
||||
version "1.5.228"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.228.tgz#38b849bc8714bd21fb64f5ad56bf8cfd8638e1e9"
|
||||
integrity sha512-nxkiyuqAn4MJ1QbobwqJILiDtu/jk14hEAWaMiJmNPh1Z+jqoFlBFZjdXwLWGeVSeu9hGLg6+2G9yJaW8rBIFA==
|
||||
|
||||
emoji-regex@^8.0.0:
|
||||
version "8.0.0"
|
||||
@@ -6775,18 +6868,18 @@ eslint-visitor-keys@^4.2.1:
|
||||
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz#4cfea60fe7dd0ad8e816e1ed026c1d5251b512c1"
|
||||
integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==
|
||||
|
||||
eslint@^9.34.0:
|
||||
version "9.34.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.34.0.tgz#0ea1f2c1b5d1671db8f01aa6b8ce722302016f7b"
|
||||
integrity sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg==
|
||||
eslint@^9.36.0:
|
||||
version "9.36.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.36.0.tgz#9cc5cbbfb9c01070425d9bfed81b4e79a1c09088"
|
||||
integrity sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==
|
||||
dependencies:
|
||||
"@eslint-community/eslint-utils" "^4.2.0"
|
||||
"@eslint-community/eslint-utils" "^4.8.0"
|
||||
"@eslint-community/regexpp" "^4.12.1"
|
||||
"@eslint/config-array" "^0.21.0"
|
||||
"@eslint/config-helpers" "^0.3.1"
|
||||
"@eslint/core" "^0.15.2"
|
||||
"@eslint/eslintrc" "^3.3.1"
|
||||
"@eslint/js" "9.34.0"
|
||||
"@eslint/js" "9.36.0"
|
||||
"@eslint/plugin-kit" "^0.3.5"
|
||||
"@humanfs/node" "^0.16.6"
|
||||
"@humanwhocodes/module-importer" "^1.0.1"
|
||||
@@ -9983,10 +10076,10 @@ node-gyp-build@^4.8.0, node-gyp-build@^4.8.2, node-gyp-build@^4.8.4:
|
||||
resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.8.4.tgz#8a70ee85464ae52327772a90d66c6077a900cfc8"
|
||||
integrity sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==
|
||||
|
||||
node-releases@^2.0.19:
|
||||
version "2.0.19"
|
||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314"
|
||||
integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==
|
||||
node-releases@^2.0.21:
|
||||
version "2.0.21"
|
||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.21.tgz#f59b018bc0048044be2d4c4c04e4c8b18160894c"
|
||||
integrity sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==
|
||||
|
||||
normalize-path@^3.0.0, normalize-path@~3.0.0:
|
||||
version "3.0.0"
|
||||
@@ -11455,10 +11548,10 @@ rc-select@~14.16.2, rc-select@~14.16.8:
|
||||
rc-util "^5.16.1"
|
||||
rc-virtual-list "^3.5.2"
|
||||
|
||||
rc-slider@~11.1.8:
|
||||
version "11.1.8"
|
||||
resolved "https://registry.yarnpkg.com/rc-slider/-/rc-slider-11.1.8.tgz#cf3b30dacac8f98d44f7685f733f6f7da146fc06"
|
||||
integrity sha512-2gg/72YFSpKP+Ja5AjC5DPL1YnV8DEITDQrcc1eASrUYjl0esptaBVJBh5nLTXCCp15eD8EuGjwezVGSHhs9tQ==
|
||||
rc-slider@~11.1.9:
|
||||
version "11.1.9"
|
||||
resolved "https://registry.yarnpkg.com/rc-slider/-/rc-slider-11.1.9.tgz#d872130fbf4ec51f28543d62e90451091d6f5208"
|
||||
integrity sha512-h8IknhzSh3FEM9u8ivkskh+Ef4Yo4JRIY2nj7MrH6GQmrwV6mcpJf5/4KgH5JaVI1H3E52yCdpOlVyGZIeph5A==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.10.1"
|
||||
classnames "^2.2.5"
|
||||
@@ -11482,10 +11575,10 @@ rc-switch@~4.1.0:
|
||||
classnames "^2.2.1"
|
||||
rc-util "^5.30.0"
|
||||
|
||||
rc-table@~7.51.1:
|
||||
version "7.51.1"
|
||||
resolved "https://registry.yarnpkg.com/rc-table/-/rc-table-7.51.1.tgz#cd69ae3262d3b61e4c93c979c12786906e944691"
|
||||
integrity sha512-5iq15mTHhvC42TlBLRCoCBLoCmGlbRZAlyF21FonFnS/DIC8DeRqnmdyVREwt2CFbPceM0zSNdEeVfiGaqYsKw==
|
||||
rc-table@~7.53.0:
|
||||
version "7.53.1"
|
||||
resolved "https://registry.yarnpkg.com/rc-table/-/rc-table-7.53.1.tgz#b891aa39e9d1d944711f018692d2c52013afc90f"
|
||||
integrity sha512-firAd7Z+liqIDS5TubJ1qqcoBd6YcANLKWQDZhFf3rfoOTt/UNPj4n3O+2vhl+z4QMqwPEUVAil661WHA8H8Aw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.10.1"
|
||||
"@rc-component/context" "^1.4.0"
|
||||
@@ -12515,7 +12608,7 @@ setprototypeof@1.2.0:
|
||||
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424"
|
||||
integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==
|
||||
|
||||
sha.js@^2.4.11:
|
||||
sha.js@^2.4.12:
|
||||
version "2.4.12"
|
||||
resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.12.tgz#eb8b568bf383dfd1867a32c3f2b74eb52bdbf23f"
|
||||
integrity sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==
|
||||
@@ -12990,18 +13083,18 @@ svgo@^3.0.2, svgo@^3.2.0:
|
||||
csso "^5.0.5"
|
||||
picocolors "^1.0.0"
|
||||
|
||||
swagger-client@^3.35.5:
|
||||
version "3.35.6"
|
||||
resolved "https://registry.yarnpkg.com/swagger-client/-/swagger-client-3.35.6.tgz#03892a1ec9995db44d7b49feb7aafafc06d9ed51"
|
||||
integrity sha512-OgwNneIdC45KXwOfwrlkwgWPeAKiV4K75mOnZioTddo1mpp9dTboCDVJas7185Ww1ziBwzShBqXpNGmyha9ZQg==
|
||||
swagger-client@^3.35.7:
|
||||
version "3.35.7"
|
||||
resolved "https://registry.yarnpkg.com/swagger-client/-/swagger-client-3.35.7.tgz#2f649c1875cb88a747254963ea85dad09605f238"
|
||||
integrity sha512-AAVk7lBFIw41wI0tsqyh/l4dwJ0/eslHL2Ex4hmsGtuKcD6/wXunetO8AsmE5MptK4YgRvpmUDvKnF1TaGzdiQ==
|
||||
dependencies:
|
||||
"@babel/runtime-corejs3" "^7.22.15"
|
||||
"@scarf/scarf" "=1.4.0"
|
||||
"@swagger-api/apidom-core" ">=1.0.0-beta.41 <1.0.0-rc.0"
|
||||
"@swagger-api/apidom-error" ">=1.0.0-beta.41 <1.0.0-rc.0"
|
||||
"@swagger-api/apidom-json-pointer" ">=1.0.0-beta.41 <1.0.0-rc.0"
|
||||
"@swagger-api/apidom-ns-openapi-3-1" ">=1.0.0-beta.41 <1.0.0-rc.0"
|
||||
"@swagger-api/apidom-reference" ">=1.0.0-beta.41 <1.0.0-rc.0"
|
||||
"@swagger-api/apidom-core" ">=1.0.0-beta.50 <1.0.0-rc.0"
|
||||
"@swagger-api/apidom-error" ">=1.0.0-beta.50 <1.0.0-rc.0"
|
||||
"@swagger-api/apidom-json-pointer" ">=1.0.0-beta.50 <1.0.0-rc.0"
|
||||
"@swagger-api/apidom-ns-openapi-3-1" ">=1.0.0-beta.50 <1.0.0-rc.0"
|
||||
"@swagger-api/apidom-reference" ">=1.0.0-beta.50 <1.0.0-rc.0"
|
||||
"@swaggerexpert/cookie" "^2.0.2"
|
||||
deepmerge "~4.3.0"
|
||||
fast-json-patch "^3.0.0-1"
|
||||
@@ -13014,18 +13107,19 @@ swagger-client@^3.35.5:
|
||||
ramda "^0.30.1"
|
||||
ramda-adjunct "^5.1.0"
|
||||
|
||||
swagger-ui-react@^5.27.1:
|
||||
version "5.27.1"
|
||||
resolved "https://registry.yarnpkg.com/swagger-ui-react/-/swagger-ui-react-5.27.1.tgz#315b59970c33933a5f62ca0f702789741dcedc7c"
|
||||
integrity sha512-wwDoavIeJI/Pwiavn32FMJ5dfptz0BAOKjSrj7EdU22QdP3gdk9+MZHdzzjxWURmVj0kc0XoQfsFgjln0toJaw==
|
||||
swagger-ui-react@^5.29.1:
|
||||
version "5.29.1"
|
||||
resolved "https://registry.yarnpkg.com/swagger-ui-react/-/swagger-ui-react-5.29.1.tgz#90875ef551f0fc271e369db60e4a8cd369ce70f9"
|
||||
integrity sha512-simkO3VoHrWIQWLH2vCpOJSPVbocV+RLGvHzzh+jhqs5heWsA1cnadx31BbKJVajfxIO4dC3lclxgMYbLUk3fQ==
|
||||
dependencies:
|
||||
"@babel/runtime-corejs3" "^7.27.1"
|
||||
"@scarf/scarf" "=1.4.0"
|
||||
base64-js "^1.5.1"
|
||||
buffer "^6.0.3"
|
||||
classnames "^2.5.1"
|
||||
css.escape "1.5.1"
|
||||
deep-extend "0.6.0"
|
||||
dompurify "=3.2.4"
|
||||
dompurify "=3.2.6"
|
||||
ieee754 "^1.2.1"
|
||||
immutable "^3.x.x"
|
||||
js-file-download "^0.4.12"
|
||||
@@ -13046,8 +13140,8 @@ swagger-ui-react@^5.27.1:
|
||||
remarkable "^2.0.1"
|
||||
reselect "^5.1.1"
|
||||
serialize-error "^8.1.0"
|
||||
sha.js "^2.4.11"
|
||||
swagger-client "^3.35.5"
|
||||
sha.js "^2.4.12"
|
||||
swagger-client "^3.35.7"
|
||||
url-parse "^1.5.10"
|
||||
xml "=1.0.1"
|
||||
xml-but-prettier "^1.0.1"
|
||||
@@ -13060,10 +13154,10 @@ synckit@^0.11.7:
|
||||
dependencies:
|
||||
"@pkgr/core" "^0.2.9"
|
||||
|
||||
tapable@^2.0.0, tapable@^2.1.1, tapable@^2.2.0, tapable@^2.2.1:
|
||||
version "2.2.2"
|
||||
resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.2.tgz#ab4984340d30cb9989a490032f086dbb8b56d872"
|
||||
integrity sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==
|
||||
tapable@^2.0.0, tapable@^2.2.0, tapable@^2.2.1, tapable@^2.2.3:
|
||||
version "2.2.3"
|
||||
resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.3.tgz#4b67b635b2d97578a06a2713d2f04800c237e99b"
|
||||
integrity sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==
|
||||
|
||||
terser-webpack-plugin@^5.3.11, terser-webpack-plugin@^5.3.9:
|
||||
version "5.3.14"
|
||||
@@ -13321,15 +13415,15 @@ types-ramda@^0.30.1:
|
||||
dependencies:
|
||||
ts-toolbelt "^9.6.0"
|
||||
|
||||
typescript-eslint@^8.39.0:
|
||||
version "8.40.0"
|
||||
resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.40.0.tgz#27541748f3ca889c9698327bdacf815f7dc61804"
|
||||
integrity sha512-Xvd2l+ZmFDPEt4oj1QEXzA4A2uUK6opvKu3eGN9aGjB8au02lIVcLyi375w94hHyejTOmzIU77L8ol2sRg9n7Q==
|
||||
typescript-eslint@^8.45.0:
|
||||
version "8.45.0"
|
||||
resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.45.0.tgz#98ab164234dc04c112747ec0a4ae29a94efe123b"
|
||||
integrity sha512-qzDmZw/Z5beNLUrXfd0HIW6MzIaAV5WNDxmMs9/3ojGOpYavofgNAAD/nC6tGV2PczIi0iw8vot2eAe/sBn7zg==
|
||||
dependencies:
|
||||
"@typescript-eslint/eslint-plugin" "8.40.0"
|
||||
"@typescript-eslint/parser" "8.40.0"
|
||||
"@typescript-eslint/typescript-estree" "8.40.0"
|
||||
"@typescript-eslint/utils" "8.40.0"
|
||||
"@typescript-eslint/eslint-plugin" "8.45.0"
|
||||
"@typescript-eslint/parser" "8.45.0"
|
||||
"@typescript-eslint/typescript-estree" "8.45.0"
|
||||
"@typescript-eslint/utils" "8.45.0"
|
||||
|
||||
typescript@~5.9.2:
|
||||
version "5.9.2"
|
||||
@@ -13745,7 +13839,7 @@ vscode-uri@~3.0.8:
|
||||
resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.0.8.tgz#1770938d3e72588659a172d0fd4642780083ff9f"
|
||||
integrity sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==
|
||||
|
||||
watchpack@^2.4.1:
|
||||
watchpack@^2.4.4:
|
||||
version "2.4.4"
|
||||
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.4.tgz#473bda72f0850453da6425081ea46fc0d7602947"
|
||||
integrity sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==
|
||||
@@ -13868,10 +13962,10 @@ webpack-virtual-modules@^0.6.2:
|
||||
resolved "https://registry.yarnpkg.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz#057faa9065c8acf48f24cb57ac0e77739ab9a7e8"
|
||||
integrity sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==
|
||||
|
||||
webpack@^5.101.0, webpack@^5.88.1, webpack@^5.95.0:
|
||||
version "5.101.3"
|
||||
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.101.3.tgz#3633b2375bb29ea4b06ffb1902734d977bc44346"
|
||||
integrity sha512-7b0dTKR3Ed//AD/6kkx/o7duS8H3f1a4w3BYpIriX4BzIhjkn4teo05cptsxvLesHFKK5KObnadmCHBwGc+51A==
|
||||
webpack@^5.102.0, webpack@^5.88.1, webpack@^5.95.0:
|
||||
version "5.102.0"
|
||||
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.102.0.tgz#7a2416e6da356c35f1fb35333d2f5cee0133e953"
|
||||
integrity sha512-hUtqAR3ZLVEYDEABdBioQCIqSoguHbFn1K7WlPPWSuXmx0031BD73PSE35jKyftdSh4YLDoQNgK4pqBt5Q82MA==
|
||||
dependencies:
|
||||
"@types/eslint-scope" "^3.7.7"
|
||||
"@types/estree" "^1.0.8"
|
||||
@@ -13881,7 +13975,7 @@ webpack@^5.101.0, webpack@^5.88.1, webpack@^5.95.0:
|
||||
"@webassemblyjs/wasm-parser" "^1.14.1"
|
||||
acorn "^8.15.0"
|
||||
acorn-import-phases "^1.0.3"
|
||||
browserslist "^4.24.0"
|
||||
browserslist "^4.24.5"
|
||||
chrome-trace-event "^1.0.2"
|
||||
enhanced-resolve "^5.17.3"
|
||||
es-module-lexer "^1.2.1"
|
||||
@@ -13894,9 +13988,9 @@ webpack@^5.101.0, webpack@^5.88.1, webpack@^5.95.0:
|
||||
mime-types "^2.1.27"
|
||||
neo-async "^2.6.2"
|
||||
schema-utils "^4.3.2"
|
||||
tapable "^2.1.1"
|
||||
tapable "^2.2.3"
|
||||
terser-webpack-plugin "^5.3.11"
|
||||
watchpack "^2.4.1"
|
||||
watchpack "^2.4.4"
|
||||
webpack-sources "^3.3.3"
|
||||
|
||||
webpackbar@^6.0.1:
|
||||
|
||||
@@ -133,7 +133,8 @@ denodo = ["denodo-sqlalchemy~=1.0.6"]
|
||||
dremio = ["sqlalchemy-dremio>=1.2.1, <4"]
|
||||
drill = ["sqlalchemy-drill>=1.1.4, <2"]
|
||||
druid = ["pydruid>=0.6.5,<0.7"]
|
||||
duckdb = ["duckdb-engine>=0.17.0"]
|
||||
# DuckDB 1.x has type system incompatibilities with duckdb-engine.
|
||||
duckdb = ["duckdb>=0.10.2,<0.11", "duckdb-engine>=0.17.0"]
|
||||
dynamodb = ["pydynamodb>=0.4.2"]
|
||||
solr = ["sqlalchemy-solr >= 0.2.0"]
|
||||
elasticsearch = ["elasticsearch-dbapi>=0.2.9, <0.3.0"]
|
||||
|
||||
@@ -181,8 +181,10 @@ dnspython==2.7.0
|
||||
# email-validator
|
||||
docker==7.0.0
|
||||
# via apache-superset
|
||||
duckdb==1.3.2
|
||||
# via duckdb-engine
|
||||
duckdb==0.10.3
|
||||
# via
|
||||
# apache-superset
|
||||
# duckdb-engine
|
||||
duckdb-engine==0.17.0
|
||||
# via apache-superset
|
||||
email-validator==2.2.0
|
||||
|
||||
@@ -83,7 +83,6 @@ module.exports = {
|
||||
'plugin:react-hooks/recommended',
|
||||
'plugin:react-prefer-function-component/recommended',
|
||||
'plugin:storybook/recommended',
|
||||
'plugin:react-you-might-not-need-an-effect/legacy-recommended',
|
||||
],
|
||||
parser: '@babel/eslint-parser',
|
||||
parserOptions: {
|
||||
@@ -273,6 +272,10 @@ module.exports = {
|
||||
{
|
||||
files: ['packages/**'],
|
||||
rules: {
|
||||
'import/no-extraneous-dependencies': [
|
||||
'error',
|
||||
{ devDependencies: true },
|
||||
],
|
||||
'no-restricted-imports': [
|
||||
'error',
|
||||
{
|
||||
@@ -324,7 +327,12 @@ module.exports = {
|
||||
'*.stories.tsx',
|
||||
'*.stories.jsx',
|
||||
'fixtures.*',
|
||||
'playwright/**/*',
|
||||
'**/test/**/*',
|
||||
'**/tests/**/*',
|
||||
'spec/**/*',
|
||||
'**/fixtures/**/*',
|
||||
'**/__mocks__/**/*',
|
||||
'**/spec/**/*',
|
||||
],
|
||||
excludedFiles: 'cypress-base/cypress/**/*',
|
||||
plugins: ['jest', 'jest-dom', 'no-only-tests', 'testing-library'],
|
||||
@@ -348,7 +356,9 @@ module.exports = {
|
||||
devDependencies: true,
|
||||
},
|
||||
],
|
||||
'jest/consistent-test-it': 'error',
|
||||
'no-only-tests/no-only-tests': 'error',
|
||||
'prefer-promise-reject-errors': 0,
|
||||
'max-classes-per-file': 0,
|
||||
// temporary rules to help with migration - please re-enable!
|
||||
'testing-library/await-async-queries': 0,
|
||||
@@ -387,6 +397,12 @@ module.exports = {
|
||||
'*.stories.tsx',
|
||||
'*.stories.jsx',
|
||||
'fixtures.*',
|
||||
'**/test/**/*',
|
||||
'**/tests/**/*',
|
||||
'spec/**/*',
|
||||
'**/fixtures/**/*',
|
||||
'**/__mocks__/**/*',
|
||||
'**/spec/**/*',
|
||||
'cypress-base/cypress/**/*',
|
||||
'Stories.tsx',
|
||||
'packages/superset-ui-core/src/theme/index.tsx',
|
||||
@@ -400,10 +416,27 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['playwright/**/*'],
|
||||
// Override specifically for packages stories and overview files
|
||||
// This must come LAST to override other rules
|
||||
files: [
|
||||
'packages/**/*.stories.*',
|
||||
'packages/**/*.overview.*',
|
||||
'packages/**/fixtures.*',
|
||||
],
|
||||
rules: {
|
||||
'import/no-unresolved': 0, // Playwright is not installed in main build
|
||||
'import/no-extraneous-dependencies': 0, // Playwright is not installed in main build
|
||||
'import/no-extraneous-dependencies': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
// Allow @playwright/test imports in Playwright test files
|
||||
files: ['playwright/**/*.ts', 'playwright/**/*.js'],
|
||||
rules: {
|
||||
'import/no-extraneous-dependencies': [
|
||||
'error',
|
||||
{
|
||||
devDependencies: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -412,7 +445,6 @@ module.exports = {
|
||||
'theme-colors/no-literal-colors': 'error',
|
||||
'icons/no-fa-icons-usage': 'error',
|
||||
'i18n-strings/no-template-vars': ['error', true],
|
||||
'i18n-strings/sentence-case-buttons': 'error',
|
||||
camelcase: [
|
||||
'error',
|
||||
{
|
||||
|
||||
1
superset-frontend/.gitignore
vendored
1
superset-frontend/.gitignore
vendored
@@ -3,3 +3,4 @@ cypress/screenshots
|
||||
cypress/videos
|
||||
src/temp
|
||||
.temp_cache/
|
||||
.tsbuildinfo
|
||||
|
||||
1322
superset-frontend/package-lock.json
generated
1322
superset-frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -129,7 +129,7 @@
|
||||
"@visx/xychart": "^3.5.1",
|
||||
"ag-grid-community": "34.2.0",
|
||||
"ag-grid-react": "34.2.0",
|
||||
"antd": "^5.24.6",
|
||||
"antd": "^5.24.9",
|
||||
"chrono-node": "^2.7.8",
|
||||
"classnames": "^2.2.5",
|
||||
"content-disposition": "^0.5.4",
|
||||
@@ -211,7 +211,7 @@
|
||||
"yargs": "^17.7.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@applitools/eyes-storybook": "^3.55.6",
|
||||
"@applitools/eyes-storybook": "^3.60.0",
|
||||
"@babel/cli": "^7.27.2",
|
||||
"@babel/compat-data": "^7.28.0",
|
||||
"@babel/core": "^7.28.3",
|
||||
@@ -225,7 +225,7 @@
|
||||
"@babel/preset-react": "^7.27.1",
|
||||
"@babel/preset-typescript": "^7.26.0",
|
||||
"@babel/register": "^7.23.7",
|
||||
"@babel/runtime": "^7.28.2",
|
||||
"@babel/runtime": "^7.28.4",
|
||||
"@babel/runtime-corejs3": "^7.28.2",
|
||||
"@babel/types": "^7.26.9",
|
||||
"@cypress/react": "^8.0.2",
|
||||
@@ -260,7 +260,7 @@
|
||||
"@types/node": "^22.12.0",
|
||||
"@types/react": "^17.0.83",
|
||||
"@types/react-dom": "^17.0.26",
|
||||
"@types/react-json-tree": "^0.6.11",
|
||||
"@types/react-json-tree": "^0.13.0",
|
||||
"@types/react-loadable": "^5.5.11",
|
||||
"@types/react-redux": "^7.1.10",
|
||||
"@types/react-resizable": "^3.0.8",
|
||||
@@ -318,7 +318,7 @@
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"jest-html-reporter": "^4.3.0",
|
||||
"jest-websocket-mock": "^2.5.0",
|
||||
"jsdom": "^26.0.0",
|
||||
"jsdom": "^27.0.0",
|
||||
"lerna": "^8.2.3",
|
||||
"mini-css-extract-plugin": "^2.9.0",
|
||||
"open-cli": "^8.0.0",
|
||||
@@ -341,7 +341,7 @@
|
||||
"tsx": "^4.20.3",
|
||||
"typescript": "5.4.5",
|
||||
"vm-browserify": "^1.1.2",
|
||||
"webpack": "^5.99.9",
|
||||
"webpack": "^5.102.0",
|
||||
"webpack-bundle-analyzer": "^4.10.1",
|
||||
"webpack-cli": "^6.0.1",
|
||||
"webpack-dev-server": "^5.2.2",
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
"typescript": "^5.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"antd": "^5.24.6",
|
||||
"antd": "^5.24.9",
|
||||
"react": "^17.0.2"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -40,7 +40,7 @@ describe('aggregationOperator', () => {
|
||||
granularity: 'month',
|
||||
};
|
||||
|
||||
test('should return undefined for LAST_VALUE aggregation', () => {
|
||||
it('should return undefined for LAST_VALUE aggregation', () => {
|
||||
const formDataWithLastValue = {
|
||||
...formData,
|
||||
aggregation: 'LAST_VALUE',
|
||||
@@ -51,7 +51,7 @@ describe('aggregationOperator', () => {
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
test('should return undefined when metrics is empty', () => {
|
||||
it('should return undefined when metrics is empty', () => {
|
||||
const queryObjectWithoutMetrics = {
|
||||
...queryObject,
|
||||
metrics: [],
|
||||
@@ -67,7 +67,7 @@ describe('aggregationOperator', () => {
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
test('should apply sum aggregation to all metrics', () => {
|
||||
it('should apply sum aggregation to all metrics', () => {
|
||||
const formDataWithSum = {
|
||||
...formData,
|
||||
aggregation: 'sum',
|
||||
@@ -91,7 +91,7 @@ describe('aggregationOperator', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('should apply mean aggregation to all metrics', () => {
|
||||
it('should apply mean aggregation to all metrics', () => {
|
||||
const formDataWithMean = {
|
||||
...formData,
|
||||
aggregation: 'mean',
|
||||
@@ -115,7 +115,7 @@ describe('aggregationOperator', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('should use default aggregation when not specified', () => {
|
||||
it('should use default aggregation when not specified', () => {
|
||||
expect(aggregationOperator(formData, queryObject)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,68 +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.
|
||||
*/
|
||||
{
|
||||
"plugins": ["jest", "jest-dom", "no-only-tests", "testing-library"],
|
||||
"env": {
|
||||
"jest/globals": true
|
||||
},
|
||||
"settings": {
|
||||
"jest": {
|
||||
"version": "detect"
|
||||
}
|
||||
},
|
||||
"extends": [
|
||||
"plugin:jest/recommended",
|
||||
"plugin:jest-dom/recommended",
|
||||
"plugin:testing-library/react"
|
||||
],
|
||||
"overrides": [
|
||||
{
|
||||
"files": [
|
||||
"**/*.stories.*",
|
||||
"**/*.overview.*",
|
||||
"**/fixtures.*"
|
||||
],
|
||||
"rules": {
|
||||
"import/no-extraneous-dependencies": "off"
|
||||
}
|
||||
}
|
||||
],
|
||||
"rules": {
|
||||
"import/no-extraneous-dependencies": ["error", { "devDependencies": true }],
|
||||
"jest/consistent-test-it": "error",
|
||||
"no-only-tests/no-only-tests": "error",
|
||||
"prefer-promise-reject-errors": 0,
|
||||
|
||||
"testing-library/no-node-access": "off",
|
||||
"testing-library/prefer-screen-queries": "off",
|
||||
"testing-library/no-container": "off",
|
||||
"testing-library/await-async-queries": "off",
|
||||
"testing-library/await-async-utils": "off",
|
||||
"testing-library/no-await-sync-events": "off",
|
||||
"testing-library/no-render-in-lifecycle": "off",
|
||||
"testing-library/no-unnecessary-act": "off",
|
||||
"testing-library/no-wait-for-multiple-assertions": "off",
|
||||
"testing-library/await-async-events": "off",
|
||||
"testing-library/no-wait-for-side-effects": "off",
|
||||
"testing-library/prefer-presence-queries": "off",
|
||||
"testing-library/render-result-naming-convention": "off",
|
||||
"testing-library/prefer-find-by": "off",
|
||||
"testing-library/no-manual-cleanup": "off"
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@
|
||||
"dependencies": {
|
||||
"@apache-superset/core": "*",
|
||||
"@ant-design/icons": "^5.2.6",
|
||||
"@babel/runtime": "^7.28.2",
|
||||
"@babel/runtime": "^7.28.4",
|
||||
"@fontsource/fira-code": "^5.2.6",
|
||||
"@fontsource/inter": "^5.2.6",
|
||||
"@types/json-bigint": "^1.0.4",
|
||||
@@ -78,7 +78,7 @@
|
||||
"@types/d3-time-format": "^4.0.3",
|
||||
"@types/react-table": "^7.7.20",
|
||||
"@types/react-syntax-highlighter": "^15.5.13",
|
||||
"@types/jquery": "^3.5.8",
|
||||
"@types/jquery": "^3.5.33",
|
||||
"@types/lodash": "^4.17.20",
|
||||
"@types/math-expression-evaluator": "^1.3.3",
|
||||
"@types/node": "^22.10.3",
|
||||
@@ -91,7 +91,7 @@
|
||||
"timezone-mock": "1.3.6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"antd": "^5.24.6",
|
||||
"antd": "^5.24.9",
|
||||
"@emotion/cache": "^11.4.0",
|
||||
"@emotion/react": "^11.4.1",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { render, screen } from '@superset-ui/core/spec';
|
||||
import { render, screen, fireEvent } from '@superset-ui/core/spec';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { TableInstance, useTable } from 'react-table';
|
||||
import TableCollection from '.';
|
||||
@@ -36,19 +36,28 @@ beforeEach(() => {
|
||||
accessor: 'col2',
|
||||
id: 'col2',
|
||||
},
|
||||
{
|
||||
Header: 'Nested Field',
|
||||
accessor: 'parent.child',
|
||||
id: 'parent.child',
|
||||
dataIndex: ['parent', 'child'],
|
||||
},
|
||||
];
|
||||
const data = [
|
||||
{
|
||||
col1: 'Line 01 - Col 01',
|
||||
col2: 'Line 01 - Col 02',
|
||||
parent: { child: 'Nested Value 1' },
|
||||
},
|
||||
{
|
||||
col1: 'Line 02 - Col 01',
|
||||
col2: 'Line 02 - Col 02',
|
||||
parent: { child: 'Nested Value 2' },
|
||||
},
|
||||
{
|
||||
col1: 'Line 03 - Col 01',
|
||||
col2: 'Line 03 - Col 02',
|
||||
parent: { child: 'Nested Value 3' },
|
||||
},
|
||||
];
|
||||
// @ts-ignore
|
||||
@@ -206,3 +215,28 @@ test('Bulk selection should work with pagination', () => {
|
||||
const checkboxes = screen.getAllByRole('checkbox');
|
||||
expect(checkboxes.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('should call setSortBy when clicking sortable column header', () => {
|
||||
const setSortBy = jest.fn();
|
||||
const sortingProps = {
|
||||
...defaultProps,
|
||||
setSortBy,
|
||||
};
|
||||
|
||||
render(<TableCollection {...sortingProps} />);
|
||||
|
||||
// Target the nested field column (the column that needs the array-to-dot conversion)
|
||||
const nestedFieldHeader = screen.getByText('Nested Field');
|
||||
expect(nestedFieldHeader).toBeInTheDocument();
|
||||
|
||||
// Click on the nested field column header to trigger sorting
|
||||
fireEvent.click(nestedFieldHeader);
|
||||
|
||||
// Verify setSortBy was called with the correct arguments and dot notation conversion
|
||||
expect(setSortBy).toHaveBeenCalledWith([
|
||||
{
|
||||
id: 'parent.child',
|
||||
desc: expect.any(Boolean),
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -215,9 +215,14 @@ function TableCollection<T extends object>({
|
||||
const handleTableChange = useCallback(
|
||||
(_pagination: any, _filters: any, sorter: SorterResult) => {
|
||||
if (sorter && sorter.field) {
|
||||
// Convert array field back to dot notation for nested fields
|
||||
const fieldId = Array.isArray(sorter.field)
|
||||
? sorter.field.join('.')
|
||||
: sorter.field;
|
||||
|
||||
setSortBy?.([
|
||||
{
|
||||
id: sorter.field,
|
||||
id: fieldId,
|
||||
desc: sorter.order === 'descend',
|
||||
},
|
||||
] as SortingRule<T>[]);
|
||||
|
||||
@@ -50,7 +50,7 @@ describe('Typography Component', () => {
|
||||
|
||||
it('renders strong text', () => {
|
||||
render(<Typography.Text strong>Strong Text</Typography.Text>);
|
||||
expect(screen.getByText('Strong Text')).toHaveStyle('font-weight: 500');
|
||||
expect(screen.getByText('Strong Text')).toHaveStyle('font-weight: 600');
|
||||
});
|
||||
|
||||
it('renders underlined text', () => {
|
||||
|
||||
@@ -16,6 +16,18 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
// @fontsource/* v5.1+ doesn't play nice with eslint-import plugin v2.31+
|
||||
/* eslint-disable import/extensions */
|
||||
import '@fontsource/inter/200.css';
|
||||
import '@fontsource/inter/400.css';
|
||||
import '@fontsource/inter/500.css';
|
||||
import '@fontsource/inter/600.css';
|
||||
import '@fontsource/fira-code/400.css';
|
||||
import '@fontsource/fira-code/500.css';
|
||||
import '@fontsource/fira-code/600.css';
|
||||
/* eslint-enable import/extensions */
|
||||
|
||||
import { css, useTheme, Global } from '@emotion/react';
|
||||
|
||||
export const GlobalStyles = () => {
|
||||
|
||||
@@ -49,14 +49,12 @@ describe('Theme', () => {
|
||||
});
|
||||
|
||||
describe('fromConfig', () => {
|
||||
it('creates a theme with default tokens when no config is provided', () => {
|
||||
it('creates a theme with Ant Design defaults when no config is provided', () => {
|
||||
const theme = Theme.fromConfig();
|
||||
|
||||
// Verify default primary color is set
|
||||
expect(theme.theme.colorPrimary).toBe('#2893b3');
|
||||
|
||||
// Verify default font family is set
|
||||
expect(theme.theme.fontFamily).toContain('Inter');
|
||||
// Verify Ant Design default tokens are set
|
||||
expect(theme.theme.colorPrimary).toBeDefined();
|
||||
expect(theme.theme.fontFamily).toBeDefined();
|
||||
|
||||
// Verify the theme is initialized with semantic color tokens
|
||||
expect(theme.theme.colorText).toBeDefined();
|
||||
@@ -79,8 +77,8 @@ describe('Theme', () => {
|
||||
// Verify custom font family is set
|
||||
expect(theme.theme.fontFamily).toBe('CustomFont, sans-serif');
|
||||
|
||||
// But default tokens should still be preserved for unspecified values
|
||||
expect(theme.theme.colorError).toBe('#e04355');
|
||||
// Unspecified values will use Ant Design defaults
|
||||
expect(theme.theme.colorError).toBeDefined();
|
||||
});
|
||||
|
||||
it('creates a theme with dark mode when dark algorithm is specified', () => {
|
||||
@@ -205,4 +203,586 @@ describe('Theme', () => {
|
||||
expect(serialized.algorithm).toBe(ThemeAlgorithm.DARK);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fromConfig with baseTheme', () => {
|
||||
it('applies base theme tokens under the main config', () => {
|
||||
const baseTheme: AnyThemeConfig = {
|
||||
token: {
|
||||
colorPrimary: '#ff0000',
|
||||
colorError: '#00ff00',
|
||||
fontFamily: 'BaseFont',
|
||||
},
|
||||
};
|
||||
|
||||
const userConfig: AnyThemeConfig = {
|
||||
token: {
|
||||
colorPrimary: '#0000ff',
|
||||
},
|
||||
};
|
||||
|
||||
const theme = Theme.fromConfig(userConfig, baseTheme);
|
||||
|
||||
// User config overrides base theme
|
||||
expect(theme.theme.colorPrimary).toBe('#0000ff');
|
||||
|
||||
// Base theme tokens are preserved when not overridden
|
||||
expect(theme.theme.colorError).toBe('#00ff00');
|
||||
expect(theme.theme.fontFamily).toBe('BaseFont');
|
||||
});
|
||||
|
||||
it('applies base theme when no user config is provided', () => {
|
||||
const baseTheme: AnyThemeConfig = {
|
||||
token: {
|
||||
colorPrimary: '#ff0000',
|
||||
fontFamily: 'TestFont',
|
||||
},
|
||||
algorithm: antdThemeImport.darkAlgorithm,
|
||||
};
|
||||
|
||||
const theme = Theme.fromConfig(undefined, baseTheme);
|
||||
|
||||
// Color may be transformed by dark algorithm, check fontFamily instead
|
||||
expect(theme.theme.fontFamily).toBe('TestFont');
|
||||
|
||||
const serialized = theme.toSerializedConfig();
|
||||
expect(serialized.algorithm).toBe(ThemeAlgorithm.DARK);
|
||||
});
|
||||
|
||||
it('handles empty config with base theme', () => {
|
||||
const baseTheme: AnyThemeConfig = {
|
||||
token: {
|
||||
colorPrimary: '#ff0000',
|
||||
},
|
||||
algorithm: antdThemeImport.defaultAlgorithm,
|
||||
};
|
||||
|
||||
const emptyConfig: AnyThemeConfig = {};
|
||||
|
||||
const theme = Theme.fromConfig(emptyConfig, baseTheme);
|
||||
|
||||
// Base theme tokens should be applied
|
||||
expect(theme.theme.colorPrimary).toBe('#ff0000');
|
||||
|
||||
const serialized = theme.toSerializedConfig();
|
||||
expect(serialized.algorithm).toBe(ThemeAlgorithm.DEFAULT);
|
||||
});
|
||||
|
||||
it('merges algorithms correctly with base theme', () => {
|
||||
const baseTheme: AnyThemeConfig = {
|
||||
algorithm: antdThemeImport.compactAlgorithm,
|
||||
};
|
||||
|
||||
const userConfig: AnyThemeConfig = {
|
||||
algorithm: antdThemeImport.darkAlgorithm,
|
||||
};
|
||||
|
||||
const theme = Theme.fromConfig(userConfig, baseTheme);
|
||||
|
||||
// User algorithm should override base algorithm
|
||||
const serialized = theme.toSerializedConfig();
|
||||
expect(serialized.algorithm).toBe(ThemeAlgorithm.DARK);
|
||||
});
|
||||
|
||||
it('merges component overrides with base theme', () => {
|
||||
const baseTheme: AnyThemeConfig = {
|
||||
components: {
|
||||
Button: {
|
||||
colorPrimary: '#basebutton',
|
||||
},
|
||||
Input: {
|
||||
colorBorder: '#baseinput',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const userConfig: AnyThemeConfig = {
|
||||
components: {
|
||||
Button: {
|
||||
colorPrimary: '#userbutton',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const theme = Theme.fromConfig(userConfig, baseTheme);
|
||||
const serialized = theme.toSerializedConfig();
|
||||
|
||||
// User component config overrides base
|
||||
expect(serialized.components?.Button?.colorPrimary).toBe('#userbutton');
|
||||
|
||||
// Base component config preserved when not overridden
|
||||
expect(serialized.components?.Input?.colorBorder).toBe('#baseinput');
|
||||
});
|
||||
|
||||
it('handles undefined config and undefined base theme', () => {
|
||||
const theme = Theme.fromConfig(undefined, undefined);
|
||||
|
||||
// Should get Ant Design defaults
|
||||
expect(theme.theme.colorPrimary).toBeDefined();
|
||||
expect(theme.theme.fontFamily).toBeDefined();
|
||||
});
|
||||
|
||||
it('preserves custom tokens in base theme', () => {
|
||||
const baseTheme: AnyThemeConfig = {
|
||||
token: {
|
||||
colorPrimary: '#ff0000',
|
||||
// Custom superset-specific tokens
|
||||
brandLogoAlt: 'CustomLogo',
|
||||
menuHoverBackgroundColor: '#00ff00',
|
||||
} as Record<string, any>,
|
||||
};
|
||||
|
||||
const theme = Theme.fromConfig({}, baseTheme);
|
||||
|
||||
// Standard token
|
||||
expect(theme.theme.colorPrimary).toBe('#ff0000');
|
||||
|
||||
// Custom tokens should be preserved
|
||||
expect((theme.theme as any).brandLogoAlt).toBe('CustomLogo');
|
||||
expect((theme.theme as any).menuHoverBackgroundColor).toBe('#00ff00');
|
||||
});
|
||||
});
|
||||
|
||||
describe('edge cases with base theme and dark mode', () => {
|
||||
it('correctly applies base theme tokens in dark mode', () => {
|
||||
const baseTheme: AnyThemeConfig = {
|
||||
token: {
|
||||
colorPrimary: '#1890ff',
|
||||
fontFamily: 'TestFont',
|
||||
},
|
||||
algorithm: antdThemeImport.defaultAlgorithm,
|
||||
};
|
||||
|
||||
const baseThemeDark: AnyThemeConfig = {
|
||||
...baseTheme,
|
||||
algorithm: antdThemeImport.darkAlgorithm,
|
||||
};
|
||||
|
||||
// Simulate light mode with base theme
|
||||
const lightTheme = Theme.fromConfig({}, baseTheme);
|
||||
expect(lightTheme.theme.colorPrimary).toBe('#1890ff');
|
||||
expect(lightTheme.theme.fontFamily).toBe('TestFont');
|
||||
|
||||
// Simulate dark mode with base theme dark
|
||||
const darkTheme = Theme.fromConfig({}, baseThemeDark);
|
||||
// Dark algorithm transforms colors, but fontFamily should be preserved
|
||||
expect(darkTheme.theme.fontFamily).toBe('TestFont');
|
||||
|
||||
// Verify the algorithm is different
|
||||
const lightSerialized = lightTheme.toSerializedConfig();
|
||||
const darkSerialized = darkTheme.toSerializedConfig();
|
||||
expect(lightSerialized.algorithm).toBe(ThemeAlgorithm.DEFAULT);
|
||||
expect(darkSerialized.algorithm).toBe(ThemeAlgorithm.DARK);
|
||||
});
|
||||
|
||||
it('handles switching from custom theme back to base theme', () => {
|
||||
const baseTheme: AnyThemeConfig = {
|
||||
token: {
|
||||
colorPrimary: '#1890ff',
|
||||
},
|
||||
algorithm: antdThemeImport.defaultAlgorithm,
|
||||
};
|
||||
|
||||
// First apply custom theme
|
||||
const customConfig: AnyThemeConfig = {
|
||||
token: {
|
||||
colorPrimary: '#52c41a',
|
||||
},
|
||||
};
|
||||
const themeWithCustom = Theme.fromConfig(customConfig, baseTheme);
|
||||
expect(themeWithCustom.theme.colorPrimary).toBe('#52c41a');
|
||||
|
||||
// Then switch back to empty config (simulating removal of custom theme)
|
||||
const themeWithEmpty = Theme.fromConfig({}, baseTheme);
|
||||
expect(themeWithEmpty.theme.colorPrimary).toBe('#1890ff');
|
||||
|
||||
// Verify they produce different outputs
|
||||
expect(themeWithCustom.theme.colorPrimary).not.toBe(
|
||||
themeWithEmpty.theme.colorPrimary,
|
||||
);
|
||||
});
|
||||
|
||||
it('handles algorithm-only config with base theme', () => {
|
||||
const baseTheme: AnyThemeConfig = {
|
||||
token: {
|
||||
fontFamily: 'TestFont',
|
||||
borderRadius: 8,
|
||||
},
|
||||
algorithm: antdThemeImport.defaultAlgorithm,
|
||||
};
|
||||
|
||||
// Config that only specifies algorithm (common for THEME_DARK)
|
||||
const algorithmOnlyConfig: AnyThemeConfig = {
|
||||
algorithm: antdThemeImport.darkAlgorithm,
|
||||
};
|
||||
|
||||
const theme = Theme.fromConfig(algorithmOnlyConfig, baseTheme);
|
||||
|
||||
// Should have base theme tokens
|
||||
expect(theme.theme.fontFamily).toBe('TestFont');
|
||||
expect(theme.theme.borderRadius).toBe(8);
|
||||
|
||||
// Should have user's algorithm
|
||||
const serialized = theme.toSerializedConfig();
|
||||
expect(serialized.algorithm).toBe(ThemeAlgorithm.DARK);
|
||||
});
|
||||
});
|
||||
|
||||
describe('base theme integration tests', () => {
|
||||
it('merges base theme tokens with empty user theme', () => {
|
||||
const baseTheme: AnyThemeConfig = {
|
||||
token: {
|
||||
colorPrimary: '#2893B3',
|
||||
colorError: '#e04355',
|
||||
fontFamily: 'Inter, Helvetica',
|
||||
},
|
||||
};
|
||||
|
||||
const userTheme: AnyThemeConfig = {
|
||||
algorithm: antdThemeImport.defaultAlgorithm,
|
||||
};
|
||||
|
||||
const theme = Theme.fromConfig(userTheme, baseTheme);
|
||||
|
||||
expect(theme.theme.colorPrimary).toBe('#2893B3');
|
||||
expect(theme.theme.colorError).toBe('#e04355');
|
||||
expect(theme.theme.fontFamily).toBe('Inter, Helvetica');
|
||||
|
||||
// Should have user's algorithm
|
||||
const serialized = theme.toSerializedConfig();
|
||||
expect(serialized.algorithm).toBe(ThemeAlgorithm.DEFAULT);
|
||||
});
|
||||
|
||||
it('allows user theme to override specific base theme tokens', () => {
|
||||
const baseTheme: AnyThemeConfig = {
|
||||
token: {
|
||||
colorPrimary: '#2893B3',
|
||||
colorError: '#e04355',
|
||||
fontFamily: 'Inter, Helvetica',
|
||||
borderRadius: 4,
|
||||
},
|
||||
};
|
||||
|
||||
const userTheme: AnyThemeConfig = {
|
||||
algorithm: antdThemeImport.defaultAlgorithm,
|
||||
token: {
|
||||
colorPrimary: '#123456', // Override primary color
|
||||
// Leave other tokens from base
|
||||
},
|
||||
};
|
||||
|
||||
const theme = Theme.fromConfig(userTheme, baseTheme);
|
||||
|
||||
// User override should win
|
||||
expect(theme.theme.colorPrimary).toBe('#123456');
|
||||
|
||||
// Base theme tokens should be preserved
|
||||
expect(theme.theme.colorError).toBe('#e04355');
|
||||
expect(theme.theme.fontFamily).toBe('Inter, Helvetica');
|
||||
expect(theme.theme.borderRadius).toBe(4);
|
||||
});
|
||||
|
||||
it('handles base theme with dark algorithm correctly', () => {
|
||||
const baseTheme: AnyThemeConfig = {
|
||||
token: {
|
||||
colorPrimary: '#2893B3',
|
||||
fontFamily: 'Inter, Helvetica',
|
||||
},
|
||||
};
|
||||
|
||||
const baseThemeDark: AnyThemeConfig = {
|
||||
...baseTheme,
|
||||
algorithm: antdThemeImport.darkAlgorithm,
|
||||
};
|
||||
|
||||
const userDarkTheme: AnyThemeConfig = {
|
||||
algorithm: antdThemeImport.darkAlgorithm,
|
||||
};
|
||||
|
||||
const theme = Theme.fromConfig(userDarkTheme, baseThemeDark);
|
||||
|
||||
// Should have base tokens
|
||||
expect(theme.theme.fontFamily).toBe('Inter, Helvetica');
|
||||
|
||||
// Should be in dark mode
|
||||
const serialized = theme.toSerializedConfig();
|
||||
expect(serialized.algorithm).toBe(ThemeAlgorithm.DARK);
|
||||
});
|
||||
|
||||
it('works with real-world Superset base theme configuration', () => {
|
||||
// Simulate actual Superset base theme (THEME_DEFAULT/THEME_DARK from config)
|
||||
const supersetBaseTheme: AnyThemeConfig = {
|
||||
token: {
|
||||
colorPrimary: '#2893B3',
|
||||
colorError: '#e04355',
|
||||
colorWarning: '#fcc700',
|
||||
colorSuccess: '#5ac189',
|
||||
colorInfo: '#66bcfe',
|
||||
fontFamily: "'Inter', Helvetica, Arial",
|
||||
fontFamilyCode: "'Fira Code', 'Courier New', monospace",
|
||||
},
|
||||
};
|
||||
|
||||
// Simulate THEME_DEFAULT from config
|
||||
const themeDefault: AnyThemeConfig = {
|
||||
algorithm: antdThemeImport.defaultAlgorithm,
|
||||
};
|
||||
|
||||
// Simulate THEME_DARK from config
|
||||
const themeDark: AnyThemeConfig = {
|
||||
algorithm: antdThemeImport.darkAlgorithm,
|
||||
};
|
||||
|
||||
// Test light mode
|
||||
const lightTheme = Theme.fromConfig(themeDefault, supersetBaseTheme);
|
||||
expect(lightTheme.theme.colorPrimary).toBe('#2893B3');
|
||||
expect(lightTheme.theme.fontFamily).toBe("'Inter', Helvetica, Arial");
|
||||
|
||||
// Test dark mode
|
||||
const darkTheme = Theme.fromConfig(themeDark, {
|
||||
...supersetBaseTheme,
|
||||
algorithm: antdThemeImport.darkAlgorithm,
|
||||
});
|
||||
expect(darkTheme.theme.fontFamily).toBe("'Inter', Helvetica, Arial");
|
||||
|
||||
const darkSerialized = darkTheme.toSerializedConfig();
|
||||
expect(darkSerialized.algorithm).toBe(ThemeAlgorithm.DARK);
|
||||
});
|
||||
|
||||
it('handles component overrides in base theme', () => {
|
||||
const baseTheme: AnyThemeConfig = {
|
||||
token: {
|
||||
colorPrimary: '#2893B3',
|
||||
},
|
||||
components: {
|
||||
Button: {
|
||||
primaryColor: '#custom-button',
|
||||
borderRadius: 8,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const userTheme: AnyThemeConfig = {
|
||||
algorithm: antdThemeImport.defaultAlgorithm,
|
||||
};
|
||||
|
||||
const theme = Theme.fromConfig(userTheme, baseTheme);
|
||||
|
||||
// Should preserve component overrides
|
||||
const serialized = theme.toSerializedConfig();
|
||||
expect(serialized.components?.Button?.primaryColor).toBe(
|
||||
'#custom-button',
|
||||
);
|
||||
expect(serialized.components?.Button?.borderRadius).toBe(8);
|
||||
});
|
||||
|
||||
it('properly handles algorithm property override', () => {
|
||||
const baseTheme: AnyThemeConfig = {
|
||||
token: {
|
||||
colorPrimary: '#2893B3',
|
||||
},
|
||||
algorithm: antdThemeImport.defaultAlgorithm,
|
||||
};
|
||||
|
||||
const userTheme: AnyThemeConfig = {
|
||||
algorithm: antdThemeImport.darkAlgorithm,
|
||||
token: {
|
||||
borderRadius: 8,
|
||||
},
|
||||
};
|
||||
|
||||
const theme = Theme.fromConfig(userTheme, baseTheme);
|
||||
const serialized = theme.toSerializedConfig();
|
||||
|
||||
// User algorithm should override base algorithm
|
||||
expect(serialized.algorithm).toBe(ThemeAlgorithm.DARK);
|
||||
|
||||
// Both base and user tokens should be merged
|
||||
expect(serialized.token?.colorPrimary).toBeTruthy();
|
||||
expect(serialized.token?.borderRadius).toBe(8);
|
||||
});
|
||||
|
||||
it('handles cssVar, hashed and inherit properties correctly', () => {
|
||||
const baseTheme: AnyThemeConfig = {
|
||||
token: {
|
||||
colorPrimary: '#2893B3',
|
||||
},
|
||||
cssVar: true,
|
||||
hashed: false,
|
||||
};
|
||||
|
||||
const userTheme: AnyThemeConfig = {
|
||||
token: {
|
||||
borderRadius: 8,
|
||||
},
|
||||
inherit: true,
|
||||
};
|
||||
|
||||
const theme = Theme.fromConfig(userTheme, baseTheme);
|
||||
const serialized = theme.toSerializedConfig();
|
||||
|
||||
// User properties override/add to base
|
||||
expect(serialized.inherit).toBe(true);
|
||||
expect(serialized.cssVar).toBe(true);
|
||||
expect(serialized.hashed).toBe(false);
|
||||
|
||||
// Tokens are still merged
|
||||
expect(serialized.token?.colorPrimary).toBeTruthy();
|
||||
expect(serialized.token?.borderRadius).toBe(8);
|
||||
});
|
||||
|
||||
it('merges nested component styles correctly', () => {
|
||||
const baseTheme: AnyThemeConfig = {
|
||||
token: {
|
||||
colorPrimary: '#2893B3',
|
||||
fontFamily: 'BaseFont',
|
||||
},
|
||||
components: {
|
||||
Button: {
|
||||
colorPrimary: '#basebutton',
|
||||
fontSize: 14,
|
||||
},
|
||||
Input: {
|
||||
colorBorder: '#baseinput',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const userTheme: AnyThemeConfig = {
|
||||
token: {
|
||||
borderRadius: 8,
|
||||
},
|
||||
components: {
|
||||
Button: {
|
||||
fontSize: 16, // Override Button fontSize
|
||||
},
|
||||
Select: {
|
||||
colorBorder: '#userselect', // Add new component
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const theme = Theme.fromConfig(userTheme, baseTheme);
|
||||
const serialized = theme.toSerializedConfig();
|
||||
|
||||
// Tokens should be merged
|
||||
// Note: components present may affect color transformation
|
||||
expect(serialized.token?.colorPrimary).toBeTruthy();
|
||||
expect(serialized.token?.borderRadius).toBe(8);
|
||||
expect(serialized.token?.fontFamily).toBe('BaseFont');
|
||||
|
||||
// Components should be merged (shallow merge per component)
|
||||
expect(serialized.components?.Button?.colorPrimary).toBe('#basebutton');
|
||||
expect(serialized.components?.Button?.fontSize).toBe(16); // User override
|
||||
expect(serialized.components?.Input?.colorBorder).toBe('#baseinput');
|
||||
expect(serialized.components?.Select?.colorBorder).toBe('#userselect');
|
||||
});
|
||||
|
||||
it('setConfig replaces theme config entirely (does not preserve base theme)', () => {
|
||||
const baseTheme: AnyThemeConfig = {
|
||||
token: {
|
||||
colorPrimary: '#2893B3',
|
||||
fontFamily: 'Inter',
|
||||
},
|
||||
};
|
||||
|
||||
const theme = Theme.fromConfig({}, baseTheme);
|
||||
|
||||
expect(theme.theme.colorPrimary).toBe('#2893B3');
|
||||
expect(theme.theme.fontFamily).toBe('Inter');
|
||||
|
||||
// Update config (simulating theme change)
|
||||
theme.setConfig({
|
||||
token: {
|
||||
colorPrimary: '#654321',
|
||||
},
|
||||
algorithm: antdThemeImport.darkAlgorithm,
|
||||
});
|
||||
|
||||
// setConfig replaces the entire config, so base theme is NOT preserved
|
||||
// This is expected behavior - setConfig is for complete replacement
|
||||
expect(theme.theme.colorPrimary).toBeTruthy();
|
||||
// fontFamily reverts to Ant Design default since base theme is not reapplied
|
||||
expect(theme.theme.fontFamily).not.toBe('Inter');
|
||||
});
|
||||
|
||||
it('minimal theme preserves ALL base theme tokens except overridden ones', () => {
|
||||
// Simulate a comprehensive base theme with many tokens
|
||||
const baseTheme: AnyThemeConfig = {
|
||||
token: {
|
||||
colorPrimary: '#2893B3',
|
||||
colorError: '#e04355',
|
||||
colorWarning: '#fcc700',
|
||||
colorSuccess: '#5ac189',
|
||||
colorInfo: '#66bcfe',
|
||||
fontFamily: 'Inter, Helvetica',
|
||||
fontSize: 14,
|
||||
borderRadius: 4,
|
||||
lineWidth: 1,
|
||||
controlHeight: 32,
|
||||
// Custom Superset tokens
|
||||
brandLogoAlt: 'CustomLogo',
|
||||
menuHoverBackgroundColor: '#eeeeee',
|
||||
} as Record<string, any>,
|
||||
algorithm: antdThemeImport.defaultAlgorithm,
|
||||
};
|
||||
|
||||
// Minimal theme that only overrides primary color and algorithm
|
||||
const minimalTheme: AnyThemeConfig = {
|
||||
token: {
|
||||
colorPrimary: '#ff05dd', // Only override this
|
||||
},
|
||||
algorithm: antdThemeImport.darkAlgorithm, // Change to dark
|
||||
};
|
||||
|
||||
const theme = Theme.fromConfig(minimalTheme, baseTheme);
|
||||
|
||||
// User's override should apply
|
||||
expect(theme.theme.colorPrimary).toBe('#ff05dd');
|
||||
|
||||
// ALL base theme tokens should be preserved
|
||||
expect(theme.theme.colorError).toBe('#e04355');
|
||||
expect(theme.theme.colorWarning).toBe('#fcc700');
|
||||
expect(theme.theme.colorSuccess).toBe('#5ac189');
|
||||
expect(theme.theme.colorInfo).toBe('#66bcfe');
|
||||
expect(theme.theme.fontFamily).toBe('Inter, Helvetica');
|
||||
expect(theme.theme.fontSize).toBe(14);
|
||||
expect(theme.theme.borderRadius).toBe(4);
|
||||
expect(theme.theme.lineWidth).toBe(1);
|
||||
expect(theme.theme.controlHeight).toBe(32);
|
||||
|
||||
// Custom tokens should also be preserved
|
||||
expect((theme.theme as any).brandLogoAlt).toBe('CustomLogo');
|
||||
expect((theme.theme as any).menuHoverBackgroundColor).toBe('#eeeeee');
|
||||
|
||||
// Algorithm should be updated
|
||||
const serialized = theme.toSerializedConfig();
|
||||
expect(serialized.algorithm).toBe(ThemeAlgorithm.DARK);
|
||||
});
|
||||
|
||||
it('arrays in themes are replaced entirely, not merged by index', () => {
|
||||
const baseTheme: AnyThemeConfig = {
|
||||
token: {
|
||||
colorPrimary: '#2893B3',
|
||||
},
|
||||
algorithm: [
|
||||
antdThemeImport.compactAlgorithm,
|
||||
antdThemeImport.defaultAlgorithm,
|
||||
],
|
||||
};
|
||||
|
||||
const userTheme: AnyThemeConfig = {
|
||||
algorithm: [antdThemeImport.darkAlgorithm], // Replace with single item array
|
||||
};
|
||||
|
||||
const theme = Theme.fromConfig(userTheme, baseTheme);
|
||||
const serialized = theme.toSerializedConfig();
|
||||
|
||||
// User's array should completely replace base array
|
||||
expect(Array.isArray(serialized.algorithm)).toBe(true);
|
||||
expect(serialized.algorithm).toHaveLength(1);
|
||||
expect(serialized.algorithm).toContain(ThemeAlgorithm.DARK);
|
||||
expect(serialized.algorithm).not.toContain(ThemeAlgorithm.COMPACT);
|
||||
expect(serialized.algorithm).not.toContain(ThemeAlgorithm.DEFAULT);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,31 +20,13 @@
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
import React from 'react';
|
||||
import { theme as antdThemeImport, ConfigProvider } from 'antd';
|
||||
|
||||
// @fontsource/* v5.1+ doesn't play nice with eslint-import plugin v2.31+
|
||||
/* eslint-disable import/extensions */
|
||||
import '@fontsource/inter/200.css';
|
||||
/* eslint-disable import/extensions */
|
||||
import '@fontsource/inter/400.css';
|
||||
/* eslint-disable import/extensions */
|
||||
import '@fontsource/inter/500.css';
|
||||
/* eslint-disable import/extensions */
|
||||
import '@fontsource/inter/600.css';
|
||||
/* eslint-disable import/extensions */
|
||||
import '@fontsource/fira-code/400.css';
|
||||
/* eslint-disable import/extensions */
|
||||
import '@fontsource/fira-code/500.css';
|
||||
/* eslint-disable import/extensions */
|
||||
import '@fontsource/fira-code/600.css';
|
||||
|
||||
import {
|
||||
ThemeProvider,
|
||||
CacheProvider as EmotionCacheProvider,
|
||||
} from '@emotion/react';
|
||||
import createCache from '@emotion/cache';
|
||||
import { noop } from 'lodash';
|
||||
import { noop, mergeWith } from 'lodash';
|
||||
import { GlobalStyles } from './GlobalStyles';
|
||||
|
||||
import {
|
||||
AntdThemeConfig,
|
||||
AnyThemeConfig,
|
||||
@@ -53,63 +35,16 @@ import {
|
||||
allowedAntdTokens,
|
||||
SharedAntdTokens,
|
||||
} from './types';
|
||||
|
||||
import { normalizeThemeConfig, serializeThemeConfig } from './utils';
|
||||
|
||||
/* eslint-disable theme-colors/no-literal-colors */
|
||||
|
||||
export class Theme {
|
||||
theme: SupersetTheme;
|
||||
|
||||
private static readonly defaultTokens = {
|
||||
// Brand
|
||||
brandLogoAlt: 'Apache Superset',
|
||||
brandLogoUrl: '/static/assets/images/superset-logo-horiz.png',
|
||||
brandLogoMargin: '18px',
|
||||
brandLogoHref: '/',
|
||||
brandLogoHeight: '24px',
|
||||
|
||||
// Spinner
|
||||
brandSpinnerUrl: undefined,
|
||||
brandSpinnerSvg: undefined,
|
||||
|
||||
// Default colors
|
||||
colorPrimary: '#2893B3', // NOTE: previous lighter primary color was #20a7c9
|
||||
colorLink: '#2893B3',
|
||||
colorError: '#e04355',
|
||||
colorWarning: '#fcc700',
|
||||
colorSuccess: '#5ac189',
|
||||
colorInfo: '#66bcfe',
|
||||
|
||||
// Forcing some default tokens
|
||||
fontFamily: `'Inter', Helvetica, Arial`,
|
||||
fontFamilyCode: `'Fira Code', 'Courier New', monospace`,
|
||||
|
||||
// Extra tokens
|
||||
transitionTiming: 0.3,
|
||||
brandIconMaxWidth: 37,
|
||||
fontSizeXS: '8',
|
||||
fontSizeXXL: '28',
|
||||
fontWeightNormal: '400',
|
||||
fontWeightLight: '300',
|
||||
fontWeightStrong: 500,
|
||||
};
|
||||
|
||||
private antdConfig: AntdThemeConfig;
|
||||
|
||||
private constructor({ config }: { config?: AnyThemeConfig }) {
|
||||
this.SupersetThemeProvider = this.SupersetThemeProvider.bind(this);
|
||||
|
||||
// Create a new config object with default tokens
|
||||
const newConfig: AnyThemeConfig = config ? { ...config } : {};
|
||||
|
||||
// Ensure token property exists with defaults
|
||||
newConfig.token = {
|
||||
...Theme.defaultTokens,
|
||||
...(config?.token || {}),
|
||||
};
|
||||
|
||||
this.setConfig(newConfig);
|
||||
this.setConfig(config || {});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -118,9 +53,24 @@ export class Theme {
|
||||
* If simple tokens are provided as { token: {...} }, they will be applied with defaults
|
||||
* If no config is provided, uses default tokens
|
||||
* Dark mode can be set via the algorithm property in the config
|
||||
* @param config - The theme configuration
|
||||
* @param baseTheme - Optional base theme to apply under the config
|
||||
*/
|
||||
static fromConfig(config?: AnyThemeConfig): Theme {
|
||||
return new Theme({ config });
|
||||
static fromConfig(
|
||||
config?: AnyThemeConfig,
|
||||
baseTheme?: AnyThemeConfig,
|
||||
): Theme {
|
||||
let mergedConfig: AnyThemeConfig | undefined = config;
|
||||
|
||||
if (baseTheme && config) {
|
||||
mergedConfig = mergeWith({}, baseTheme, config, (objValue, srcValue) =>
|
||||
Array.isArray(srcValue) ? srcValue : undefined,
|
||||
);
|
||||
} else if (baseTheme && !config) {
|
||||
mergedConfig = baseTheme;
|
||||
}
|
||||
|
||||
return new Theme({ config: mergedConfig });
|
||||
}
|
||||
|
||||
private static getFilteredAntdTheme(
|
||||
@@ -148,22 +98,15 @@ export class Theme {
|
||||
setConfig(config: AnyThemeConfig): void {
|
||||
const antdConfig = normalizeThemeConfig(config);
|
||||
|
||||
// Apply default tokens to token property
|
||||
antdConfig.token = {
|
||||
...Theme.defaultTokens,
|
||||
...(antdConfig.token || {}),
|
||||
};
|
||||
|
||||
// First phase: Let Ant Design compute the tokens
|
||||
const tokens = Theme.getFilteredAntdTheme(antdConfig);
|
||||
|
||||
// Set the base theme properties
|
||||
this.antdConfig = antdConfig;
|
||||
this.theme = {
|
||||
...Theme.defaultTokens,
|
||||
...antdConfig.token, // Passing through the extra, superset-specific tokens
|
||||
...tokens,
|
||||
};
|
||||
...tokens, // First apply Ant Design computed tokens
|
||||
...(antdConfig.token || {}), // Then override with our custom tokens
|
||||
} as SupersetTheme;
|
||||
|
||||
// Update the providers with the fully formed theme
|
||||
this.updateProviders(
|
||||
@@ -250,5 +193,3 @@ export class Theme {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/* eslint-enable theme-colors/no-literal-colors */
|
||||
|
||||
@@ -78,6 +78,7 @@ export type SerializableThemeConfig = {
|
||||
algorithm?: ThemeAlgorithmOption;
|
||||
hashed?: boolean;
|
||||
inherit?: boolean;
|
||||
cssVar?: boolean | { key?: string; prefix?: string };
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,7 +16,13 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { getFontSize, getColorVariants, isThemeDark } from './themeUtils';
|
||||
import { theme as antdTheme } from 'antd';
|
||||
import {
|
||||
getFontSize,
|
||||
getColorVariants,
|
||||
isThemeDark,
|
||||
isThemeConfigDark,
|
||||
} from './themeUtils';
|
||||
import { Theme } from '../Theme';
|
||||
import { ThemeAlgorithm } from '../types';
|
||||
|
||||
@@ -71,8 +77,7 @@ describe('themeUtils', () => {
|
||||
token: { fontSize: '14' },
|
||||
});
|
||||
|
||||
// Ant Design provides fontSizeXS: '8' by default
|
||||
expect(getFontSize(minimalTheme.theme, 'xs')).toBe('8');
|
||||
expect(getFontSize(minimalTheme.theme, 'xs')).toBe('14');
|
||||
expect(getFontSize(minimalTheme.theme, 'm')).toBe('14');
|
||||
});
|
||||
});
|
||||
@@ -131,4 +136,111 @@ describe('themeUtils', () => {
|
||||
expect(variants.bg).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('isThemeConfigDark', () => {
|
||||
it('returns true for config with dark algorithm', () => {
|
||||
const config = {
|
||||
algorithm: antdTheme.darkAlgorithm,
|
||||
};
|
||||
expect(isThemeConfigDark(config)).toBe(true);
|
||||
});
|
||||
|
||||
it('returns true for config with dark algorithm in array', () => {
|
||||
const config = {
|
||||
algorithm: [antdTheme.darkAlgorithm, antdTheme.compactAlgorithm],
|
||||
};
|
||||
expect(isThemeConfigDark(config)).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false for config without dark algorithm', () => {
|
||||
const config = {
|
||||
algorithm: antdTheme.defaultAlgorithm,
|
||||
};
|
||||
expect(isThemeConfigDark(config)).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false for config with no algorithm', () => {
|
||||
const config = {
|
||||
token: {
|
||||
colorPrimary: '#1890ff',
|
||||
},
|
||||
};
|
||||
expect(isThemeConfigDark(config)).toBe(false);
|
||||
});
|
||||
|
||||
it('detects manually-created dark theme without dark algorithm', () => {
|
||||
// This is the edge case: dark colors without dark algorithm
|
||||
const config = {
|
||||
token: {
|
||||
colorBgContainer: '#1a1a1a', // Dark background
|
||||
colorBgBase: '#0a0a0a', // Dark base
|
||||
colorText: '#ffffff', // Light text
|
||||
},
|
||||
};
|
||||
expect(isThemeConfigDark(config)).toBe(true);
|
||||
});
|
||||
|
||||
it('does not false-positive on light theme with custom colors', () => {
|
||||
const config = {
|
||||
token: {
|
||||
colorBgContainer: '#ffffff', // Light background
|
||||
colorBgBase: '#f5f5f5', // Light base
|
||||
colorText: '#000000', // Dark text
|
||||
},
|
||||
};
|
||||
expect(isThemeConfigDark(config)).toBe(false);
|
||||
});
|
||||
|
||||
it('handles partial color tokens gracefully', () => {
|
||||
// With actual theme computation, a dark colorBgContainer results in a dark theme
|
||||
const config = {
|
||||
token: {
|
||||
colorBgContainer: '#1a1a1a', // Dark background
|
||||
// Missing other color tokens
|
||||
},
|
||||
};
|
||||
expect(isThemeConfigDark(config)).toBe(true);
|
||||
});
|
||||
|
||||
it('respects colorBgContainer as the primary indicator', () => {
|
||||
// The computed theme uses colorBgContainer as the main background
|
||||
const darkConfig = {
|
||||
token: {
|
||||
colorBgContainer: '#1a1a1a', // Dark background
|
||||
colorText: '#000000', // Dark text (unusual but doesn't override)
|
||||
},
|
||||
};
|
||||
expect(isThemeConfigDark(darkConfig)).toBe(true);
|
||||
|
||||
const lightConfig = {
|
||||
token: {
|
||||
colorBgContainer: '#ffffff', // Light background
|
||||
colorText: '#ffffff', // Light text (unusual but doesn't override)
|
||||
},
|
||||
};
|
||||
expect(isThemeConfigDark(lightConfig)).toBe(false);
|
||||
});
|
||||
|
||||
it('handles non-string color tokens gracefully', () => {
|
||||
const config = {
|
||||
token: {
|
||||
colorBgContainer: undefined,
|
||||
colorText: null,
|
||||
colorBgBase: 123, // Invalid type
|
||||
},
|
||||
};
|
||||
expect(isThemeConfigDark(config)).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false for empty config', () => {
|
||||
expect(isThemeConfigDark({})).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false for config with empty token object', () => {
|
||||
const config = {
|
||||
token: {},
|
||||
};
|
||||
expect(isThemeConfigDark(config)).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,7 +18,14 @@
|
||||
*/
|
||||
import tinycolor from 'tinycolor2';
|
||||
import { useTheme as useEmotionTheme } from '@emotion/react';
|
||||
import type { SupersetTheme, FontSizeKey, ColorVariants } from '../types';
|
||||
import { theme as antdTheme } from 'antd';
|
||||
import type {
|
||||
SupersetTheme,
|
||||
FontSizeKey,
|
||||
ColorVariants,
|
||||
AnyThemeConfig,
|
||||
} from '../types';
|
||||
import { normalizeThemeConfig } from '../utils';
|
||||
|
||||
const fontSizeMap: Record<FontSizeKey, keyof SupersetTheme> = {
|
||||
xs: 'fontSizeXS',
|
||||
@@ -113,6 +120,22 @@ export function isThemeDark(theme: SupersetTheme): boolean {
|
||||
return tinycolor(theme.colorBgContainer).isDark();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a theme configuration results in a dark theme
|
||||
* @param config - The theme configuration to check
|
||||
* @returns true if the config results in a dark theme, false otherwise
|
||||
*/
|
||||
export function isThemeConfigDark(config: AnyThemeConfig): boolean {
|
||||
try {
|
||||
const normalizedConfig = normalizeThemeConfig(config);
|
||||
const themeConfig = antdTheme.getDesignToken(normalizedConfig);
|
||||
|
||||
return tinycolor(themeConfig.colorBgContainer).isDark();
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to determine if the current theme is dark mode
|
||||
* @returns true if theme is dark, false if light
|
||||
|
||||
@@ -122,7 +122,7 @@ describe('DeckGLPolygon bucket generation logic', () => {
|
||||
const renderWithTheme = (component: React.ReactElement) =>
|
||||
render(<ThemeProvider theme={supersetTheme}>{component}</ThemeProvider>);
|
||||
|
||||
test('should use getBuckets for linear_palette color scheme', () => {
|
||||
it('should use getBuckets for linear_palette color scheme', () => {
|
||||
const propsWithLinearPalette = {
|
||||
...mockProps,
|
||||
formData: {
|
||||
@@ -138,7 +138,7 @@ describe('DeckGLPolygon bucket generation logic', () => {
|
||||
expect(mockGetColorBreakpointsBuckets).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should use getBuckets for fixed_color color scheme', () => {
|
||||
it('should use getBuckets for fixed_color color scheme', () => {
|
||||
const propsWithFixedColor = {
|
||||
...mockProps,
|
||||
formData: {
|
||||
@@ -154,7 +154,7 @@ describe('DeckGLPolygon bucket generation logic', () => {
|
||||
expect(mockGetColorBreakpointsBuckets).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should use getColorBreakpointsBuckets for color_breakpoints scheme', () => {
|
||||
it('should use getColorBreakpointsBuckets for color_breakpoints scheme', () => {
|
||||
const propsWithBreakpoints = {
|
||||
...mockProps,
|
||||
formData: {
|
||||
@@ -187,7 +187,7 @@ describe('DeckGLPolygon bucket generation logic', () => {
|
||||
expect(mockGetBuckets).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should use getBuckets when color_scheme_type is undefined (backward compatibility)', () => {
|
||||
it('should use getBuckets when color_scheme_type is undefined (backward compatibility)', () => {
|
||||
const propsWithUndefinedScheme = {
|
||||
...mockProps,
|
||||
formData: {
|
||||
@@ -203,7 +203,7 @@ describe('DeckGLPolygon bucket generation logic', () => {
|
||||
expect(mockGetColorBreakpointsBuckets).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should use getBuckets for unsupported color schemes (categorical_palette)', () => {
|
||||
it('should use getBuckets for unsupported color schemes (categorical_palette)', () => {
|
||||
const propsWithUnsupportedScheme = {
|
||||
...mockProps,
|
||||
formData: {
|
||||
@@ -230,7 +230,7 @@ describe('DeckGLPolygon Error Handling and Edge Cases', () => {
|
||||
const renderWithTheme = (component: React.ReactElement) =>
|
||||
render(<ThemeProvider theme={supersetTheme}>{component}</ThemeProvider>);
|
||||
|
||||
test('handles empty features data gracefully', () => {
|
||||
it('handles empty features data gracefully', () => {
|
||||
const propsWithEmptyData = {
|
||||
...mockProps,
|
||||
payload: {
|
||||
@@ -249,7 +249,7 @@ describe('DeckGLPolygon Error Handling and Edge Cases', () => {
|
||||
expect(mockGetColorBreakpointsBuckets).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('handles missing color_breakpoints for color_breakpoints scheme', () => {
|
||||
it('handles missing color_breakpoints for color_breakpoints scheme', () => {
|
||||
const propsWithMissingBreakpoints = {
|
||||
...mockProps,
|
||||
formData: {
|
||||
@@ -266,7 +266,7 @@ describe('DeckGLPolygon Error Handling and Edge Cases', () => {
|
||||
expect(mockGetBuckets).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('handles null legend_position correctly', () => {
|
||||
it('handles null legend_position correctly', () => {
|
||||
const propsWithNullLegendPosition = {
|
||||
...mockProps,
|
||||
formData: {
|
||||
@@ -294,7 +294,7 @@ describe('DeckGLPolygon Legend Integration', () => {
|
||||
const renderWithTheme = (component: React.ReactElement) =>
|
||||
render(<ThemeProvider theme={supersetTheme}>{component}</ThemeProvider>);
|
||||
|
||||
test('renders legend with non-empty categories when metric and linear_palette are defined', () => {
|
||||
it('renders legend with non-empty categories when metric and linear_palette are defined', () => {
|
||||
const { container } = renderWithTheme(<DeckGLPolygon {...mockProps} />);
|
||||
|
||||
// Verify the component renders and calls the correct bucket function
|
||||
@@ -309,7 +309,7 @@ describe('DeckGLPolygon Legend Integration', () => {
|
||||
expect(Object.keys(categoriesData)).toHaveLength(2);
|
||||
});
|
||||
|
||||
test('does not render legend when metric is null', () => {
|
||||
it('does not render legend when metric is null', () => {
|
||||
const propsWithoutMetric = {
|
||||
...mockProps,
|
||||
formData: {
|
||||
@@ -326,7 +326,7 @@ describe('DeckGLPolygon Legend Integration', () => {
|
||||
});
|
||||
|
||||
describe('getPoints utility', () => {
|
||||
test('extracts points from polygon data', () => {
|
||||
it('extracts points from polygon data', () => {
|
||||
const data = [
|
||||
{
|
||||
polygon: [
|
||||
|
||||
@@ -64,7 +64,7 @@ Object.defineProperty(document, 'getElementById', {
|
||||
const mockDecode = decode as jest.MockedFunction<typeof decode>;
|
||||
|
||||
describe('spatialUtils', () => {
|
||||
test('getSpatialColumns returns correct columns for latlong type', () => {
|
||||
it('getSpatialColumns returns correct columns for latlong type', () => {
|
||||
const spatial: SpatialFormData['spatial'] = {
|
||||
type: 'latlong',
|
||||
lonCol: 'longitude',
|
||||
@@ -75,7 +75,7 @@ describe('spatialUtils', () => {
|
||||
expect(result).toEqual(['longitude', 'latitude']);
|
||||
});
|
||||
|
||||
test('getSpatialColumns returns correct columns for delimited type', () => {
|
||||
it('getSpatialColumns returns correct columns for delimited type', () => {
|
||||
const spatial: SpatialFormData['spatial'] = {
|
||||
type: 'delimited',
|
||||
lonlatCol: 'coordinates',
|
||||
@@ -85,7 +85,7 @@ describe('spatialUtils', () => {
|
||||
expect(result).toEqual(['coordinates']);
|
||||
});
|
||||
|
||||
test('getSpatialColumns returns correct columns for geohash type', () => {
|
||||
it('getSpatialColumns returns correct columns for geohash type', () => {
|
||||
const spatial: SpatialFormData['spatial'] = {
|
||||
type: 'geohash',
|
||||
geohashCol: 'geohash_code',
|
||||
@@ -95,16 +95,16 @@ describe('spatialUtils', () => {
|
||||
expect(result).toEqual(['geohash_code']);
|
||||
});
|
||||
|
||||
test('getSpatialColumns throws error when spatial is null', () => {
|
||||
it('getSpatialColumns throws error when spatial is null', () => {
|
||||
expect(() => getSpatialColumns(null as any)).toThrow('Bad spatial key');
|
||||
});
|
||||
|
||||
test('getSpatialColumns throws error when spatial type is missing', () => {
|
||||
it('getSpatialColumns throws error when spatial type is missing', () => {
|
||||
const spatial = {} as SpatialFormData['spatial'];
|
||||
expect(() => getSpatialColumns(spatial)).toThrow('Bad spatial key');
|
||||
});
|
||||
|
||||
test('getSpatialColumns throws error when latlong columns are missing', () => {
|
||||
it('getSpatialColumns throws error when latlong columns are missing', () => {
|
||||
const spatial: SpatialFormData['spatial'] = {
|
||||
type: 'latlong',
|
||||
};
|
||||
@@ -113,7 +113,7 @@ describe('spatialUtils', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('getSpatialColumns throws error when delimited column is missing', () => {
|
||||
it('getSpatialColumns throws error when delimited column is missing', () => {
|
||||
const spatial: SpatialFormData['spatial'] = {
|
||||
type: 'delimited',
|
||||
};
|
||||
@@ -122,7 +122,7 @@ describe('spatialUtils', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('getSpatialColumns throws error when geohash column is missing', () => {
|
||||
it('getSpatialColumns throws error when geohash column is missing', () => {
|
||||
const spatial: SpatialFormData['spatial'] = {
|
||||
type: 'geohash',
|
||||
};
|
||||
@@ -131,7 +131,7 @@ describe('spatialUtils', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('getSpatialColumns throws error for unknown spatial type', () => {
|
||||
it('getSpatialColumns throws error for unknown spatial type', () => {
|
||||
const spatial = {
|
||||
type: 'unknown',
|
||||
} as any;
|
||||
@@ -140,7 +140,7 @@ describe('spatialUtils', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('addSpatialNullFilters adds null filters for spatial columns', () => {
|
||||
it('addSpatialNullFilters adds null filters for spatial columns', () => {
|
||||
const spatial: SpatialFormData['spatial'] = {
|
||||
type: 'latlong',
|
||||
lonCol: 'longitude',
|
||||
@@ -159,7 +159,7 @@ describe('spatialUtils', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
test('addSpatialNullFilters returns original filters when spatial is null', () => {
|
||||
it('addSpatialNullFilters returns original filters when spatial is null', () => {
|
||||
const existingFilters: QueryObjectFilterClause[] = [
|
||||
{ col: 'test_col', op: '==', val: 'test' },
|
||||
];
|
||||
@@ -168,7 +168,7 @@ describe('spatialUtils', () => {
|
||||
expect(result).toBe(existingFilters);
|
||||
});
|
||||
|
||||
test('addSpatialNullFilters works with empty filters array', () => {
|
||||
it('addSpatialNullFilters works with empty filters array', () => {
|
||||
const spatial: SpatialFormData['spatial'] = {
|
||||
type: 'delimited',
|
||||
lonlatCol: 'coordinates',
|
||||
@@ -181,7 +181,7 @@ describe('spatialUtils', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
test('buildSpatialQuery throws error when spatial is missing', () => {
|
||||
it('buildSpatialQuery throws error when spatial is missing', () => {
|
||||
const formData = {} as SpatialFormData;
|
||||
|
||||
expect(() => buildSpatialQuery(formData)).toThrow(
|
||||
@@ -189,7 +189,7 @@ describe('spatialUtils', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('buildSpatialQuery calls buildQueryContext with correct parameters', () => {
|
||||
it('buildSpatialQuery calls buildQueryContext with correct parameters', () => {
|
||||
const mockBuildQueryContext =
|
||||
jest.requireMock('@superset-ui/core').buildQueryContext;
|
||||
const formData: SpatialFormData = {
|
||||
@@ -209,7 +209,7 @@ describe('spatialUtils', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('processSpatialData processes latlong data correctly', () => {
|
||||
it('processSpatialData processes latlong data correctly', () => {
|
||||
const records = [
|
||||
{ longitude: -122.4, latitude: 37.8, count: 10, extra: 'test1' },
|
||||
{ longitude: -122.5, latitude: 37.9, count: 20, extra: 'test2' },
|
||||
@@ -237,7 +237,7 @@ describe('spatialUtils', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('processSpatialData processes delimited data correctly', () => {
|
||||
it('processSpatialData processes delimited data correctly', () => {
|
||||
const records = [
|
||||
{ coordinates: '-122.4,37.8', count: 15 },
|
||||
{ coordinates: '-122.5,37.9', count: 25 },
|
||||
@@ -257,7 +257,7 @@ describe('spatialUtils', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('processSpatialData processes geohash data correctly', () => {
|
||||
it('processSpatialData processes geohash data correctly', () => {
|
||||
mockDecode.mockReturnValue({
|
||||
latitude: 37.8,
|
||||
longitude: -122.4,
|
||||
@@ -284,7 +284,7 @@ describe('spatialUtils', () => {
|
||||
expect(mockDecode).toHaveBeenCalledWith('dr5regw3p');
|
||||
});
|
||||
|
||||
test('processSpatialData reverses coordinates when reverseCheckbox is true', () => {
|
||||
it('processSpatialData reverses coordinates when reverseCheckbox is true', () => {
|
||||
const records = [{ longitude: -122.4, latitude: 37.8, count: 10 }];
|
||||
const spatial: SpatialFormData['spatial'] = {
|
||||
type: 'latlong',
|
||||
@@ -298,7 +298,7 @@ describe('spatialUtils', () => {
|
||||
expect(result[0].position).toEqual([37.8, -122.4]);
|
||||
});
|
||||
|
||||
test('processSpatialData handles invalid coordinates', () => {
|
||||
it('processSpatialData handles invalid coordinates', () => {
|
||||
const records = [
|
||||
{ longitude: 'invalid', latitude: 37.8, count: 10 },
|
||||
{ longitude: -122.4, latitude: NaN, count: 20 },
|
||||
@@ -318,7 +318,7 @@ describe('spatialUtils', () => {
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('processSpatialData handles missing metric values', () => {
|
||||
it('processSpatialData handles missing metric values', () => {
|
||||
const records = [
|
||||
{ longitude: -122.4, latitude: 37.8, count: null },
|
||||
{ longitude: -122.5, latitude: 37.9 },
|
||||
@@ -338,7 +338,7 @@ describe('spatialUtils', () => {
|
||||
expect(result[2].weight).toBe(1);
|
||||
});
|
||||
|
||||
test('processSpatialData returns empty array for empty records', () => {
|
||||
it('processSpatialData returns empty array for empty records', () => {
|
||||
const spatial: SpatialFormData['spatial'] = {
|
||||
type: 'latlong',
|
||||
lonCol: 'longitude',
|
||||
@@ -350,7 +350,7 @@ describe('spatialUtils', () => {
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
test('processSpatialData returns empty array when spatial is null', () => {
|
||||
it('processSpatialData returns empty array when spatial is null', () => {
|
||||
const records = [{ longitude: -122.4, latitude: 37.8 }];
|
||||
|
||||
const result = processSpatialData(records, null as any);
|
||||
@@ -358,7 +358,7 @@ describe('spatialUtils', () => {
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
test('processSpatialData handles delimited coordinate edge cases', () => {
|
||||
it('processSpatialData handles delimited coordinate edge cases', () => {
|
||||
const records = [
|
||||
{ coordinates: '', count: 10 },
|
||||
{ coordinates: null, count: 20 },
|
||||
@@ -382,7 +382,7 @@ describe('spatialUtils', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('processSpatialData copies additional properties correctly', () => {
|
||||
it('processSpatialData copies additional properties correctly', () => {
|
||||
const records = [
|
||||
{
|
||||
longitude: -122.4,
|
||||
@@ -416,7 +416,7 @@ describe('spatialUtils', () => {
|
||||
expect(result[0]).not.toHaveProperty('extra_col');
|
||||
});
|
||||
|
||||
test('transformSpatialProps transforms chart props correctly', () => {
|
||||
it('transformSpatialProps transforms chart props correctly', () => {
|
||||
const mockGetMetricLabel =
|
||||
jest.requireMock('@superset-ui/core').getMetricLabel;
|
||||
mockGetMetricLabel.mockReturnValue('count_label');
|
||||
@@ -510,7 +510,7 @@ describe('spatialUtils', () => {
|
||||
expect(result.payload.data.metricLabels).toEqual(['count_label']);
|
||||
});
|
||||
|
||||
test('transformSpatialProps handles missing hooks gracefully', () => {
|
||||
it('transformSpatialProps handles missing hooks gracefully', () => {
|
||||
const chartProps: ChartProps = {
|
||||
datasource: {
|
||||
id: 1,
|
||||
@@ -556,7 +556,7 @@ describe('spatialUtils', () => {
|
||||
expect(typeof result.setTooltip).toBe('function');
|
||||
});
|
||||
|
||||
test('transformSpatialProps handles missing metric', () => {
|
||||
it('transformSpatialProps handles missing metric', () => {
|
||||
const mockGetMetricLabel =
|
||||
jest.requireMock('@superset-ui/core').getMetricLabel;
|
||||
mockGetMetricLabel.mockReturnValue(undefined);
|
||||
|
||||
@@ -505,6 +505,7 @@ export const tooltipTemplate = {
|
||||
config: {
|
||||
type: TooltipTemplateControl,
|
||||
label: t('Customize tooltips template'),
|
||||
renderTrigger: true,
|
||||
debounceDelay: 30,
|
||||
default: '',
|
||||
description: '',
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { getColorBreakpointsBuckets } from './utils';
|
||||
import { getColorBreakpointsBuckets, getBreakPoints } from './utils';
|
||||
import { ColorBreakpointType } from './types';
|
||||
|
||||
describe('getColorBreakpointsBuckets', () => {
|
||||
@@ -44,3 +44,447 @@ describe('getColorBreakpointsBuckets', () => {
|
||||
expect(result).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getBreakPoints', () => {
|
||||
const accessor = (d: any) => d.value;
|
||||
|
||||
describe('automatic breakpoint generation', () => {
|
||||
it('generates correct number of breakpoints for given buckets', () => {
|
||||
const features = [{ value: 0 }, { value: 50 }, { value: 100 }];
|
||||
|
||||
const breakPoints = getBreakPoints(
|
||||
{ break_points: [], num_buckets: '5' },
|
||||
features,
|
||||
accessor,
|
||||
);
|
||||
|
||||
expect(breakPoints).toHaveLength(6); // n buckets = n+1 breakpoints
|
||||
expect(breakPoints.every(bp => typeof bp === 'string')).toBe(true);
|
||||
});
|
||||
|
||||
it('ensures data range is fully covered', () => {
|
||||
// Test various data ranges to ensure min/max are always included
|
||||
const testCases = [
|
||||
{ data: [0, 100], buckets: 5 },
|
||||
{ data: [0.1, 99.9], buckets: 4 },
|
||||
{ data: [-50, 50], buckets: 10 },
|
||||
{ data: [3.2, 38.7], buckets: 5 }, // Original max bug case
|
||||
{ data: [3.14, 100], buckets: 5 }, // Min rounding bug case (3.14 -> 3)
|
||||
{ data: [2.345, 10], buckets: 4 }, // Min rounding bug case (2.345 -> 2.35)
|
||||
{ data: [0.0001, 0.0009], buckets: 3 }, // Very small numbers
|
||||
{ data: [1000000, 9000000], buckets: 8 }, // Large numbers
|
||||
];
|
||||
|
||||
testCases.forEach(({ data, buckets }) => {
|
||||
const [min, max] = data;
|
||||
const features = [{ value: min }, { value: max }];
|
||||
|
||||
const breakPoints = getBreakPoints(
|
||||
{ break_points: [], num_buckets: String(buckets) },
|
||||
features,
|
||||
accessor,
|
||||
);
|
||||
|
||||
const firstBp = parseFloat(breakPoints[0]);
|
||||
const lastBp = parseFloat(breakPoints[breakPoints.length - 1]);
|
||||
|
||||
// Critical: min and max must be within the breakpoint range
|
||||
expect(firstBp).toBeLessThanOrEqual(min);
|
||||
expect(lastBp).toBeGreaterThanOrEqual(max);
|
||||
expect(breakPoints).toHaveLength(buckets + 1);
|
||||
});
|
||||
});
|
||||
|
||||
it('handles uniform distribution correctly', () => {
|
||||
const features = [
|
||||
{ value: 0 },
|
||||
{ value: 25 },
|
||||
{ value: 50 },
|
||||
{ value: 75 },
|
||||
{ value: 100 },
|
||||
];
|
||||
|
||||
const breakPoints = getBreakPoints(
|
||||
{ break_points: [], num_buckets: '4' },
|
||||
features,
|
||||
accessor,
|
||||
);
|
||||
|
||||
// Check that breakpoints are evenly spaced
|
||||
const numericBreakPoints = breakPoints.map(parseFloat);
|
||||
const deltas = [];
|
||||
for (let i = 1; i < numericBreakPoints.length; i += 1) {
|
||||
deltas.push(numericBreakPoints[i] - numericBreakPoints[i - 1]);
|
||||
}
|
||||
|
||||
// All deltas should be approximately equal
|
||||
const avgDelta = deltas.reduce((a, b) => a + b, 0) / deltas.length;
|
||||
deltas.forEach(delta => {
|
||||
expect(delta).toBeCloseTo(avgDelta, 1);
|
||||
});
|
||||
});
|
||||
|
||||
it('handles single value datasets', () => {
|
||||
const features = [{ value: 42 }, { value: 42 }, { value: 42 }];
|
||||
|
||||
const breakPoints = getBreakPoints(
|
||||
{ break_points: [], num_buckets: '5' },
|
||||
features,
|
||||
accessor,
|
||||
);
|
||||
|
||||
const firstBp = parseFloat(breakPoints[0]);
|
||||
const lastBp = parseFloat(breakPoints[breakPoints.length - 1]);
|
||||
|
||||
expect(firstBp).toBeLessThanOrEqual(42);
|
||||
expect(lastBp).toBeGreaterThanOrEqual(42);
|
||||
});
|
||||
|
||||
it('preserves appropriate precision for different scales', () => {
|
||||
const testCases = [
|
||||
{ data: [0, 1], expectedMaxPrecision: 1 }, // 0.0, 0.2, 0.4...
|
||||
{ data: [0, 0.1], expectedMaxPrecision: 2 }, // 0.00, 0.02...
|
||||
{ data: [0, 0.01], expectedMaxPrecision: 3 }, // 0.000, 0.002...
|
||||
{ data: [0, 1000], expectedMaxPrecision: 0 }, // 0, 200, 400...
|
||||
];
|
||||
|
||||
testCases.forEach(({ data, expectedMaxPrecision }) => {
|
||||
const [min, max] = data;
|
||||
const features = [{ value: min }, { value: max }];
|
||||
|
||||
const breakPoints = getBreakPoints(
|
||||
{ break_points: [], num_buckets: '5' },
|
||||
features,
|
||||
accessor,
|
||||
);
|
||||
|
||||
breakPoints.forEach(bp => {
|
||||
const decimalPlaces = (bp.split('.')[1] || '').length;
|
||||
expect(decimalPlaces).toBeLessThanOrEqual(expectedMaxPrecision);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('handles negative values correctly', () => {
|
||||
const features = [
|
||||
{ value: -100 },
|
||||
{ value: -50 },
|
||||
{ value: 0 },
|
||||
{ value: 50 },
|
||||
{ value: 100 },
|
||||
];
|
||||
|
||||
const breakPoints = getBreakPoints(
|
||||
{ break_points: [], num_buckets: '5' },
|
||||
features,
|
||||
accessor,
|
||||
);
|
||||
|
||||
const numericBreakPoints = breakPoints.map(parseFloat);
|
||||
expect(numericBreakPoints[0]).toBeLessThanOrEqual(-100);
|
||||
expect(
|
||||
numericBreakPoints[numericBreakPoints.length - 1],
|
||||
).toBeGreaterThanOrEqual(100);
|
||||
|
||||
// Verify ascending order
|
||||
for (let i = 1; i < numericBreakPoints.length; i += 1) {
|
||||
expect(numericBreakPoints[i]).toBeGreaterThan(
|
||||
numericBreakPoints[i - 1],
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('handles mixed integer and decimal values', () => {
|
||||
const features = [
|
||||
{ value: 1 },
|
||||
{ value: 2.5 },
|
||||
{ value: 3.7 },
|
||||
{ value: 5 },
|
||||
{ value: 8.2 },
|
||||
];
|
||||
|
||||
const breakPoints = getBreakPoints(
|
||||
{ break_points: [], num_buckets: '4' },
|
||||
features,
|
||||
accessor,
|
||||
);
|
||||
|
||||
const firstBp = parseFloat(breakPoints[0]);
|
||||
const lastBp = parseFloat(breakPoints[breakPoints.length - 1]);
|
||||
|
||||
expect(firstBp).toBeLessThanOrEqual(1);
|
||||
expect(lastBp).toBeGreaterThanOrEqual(8.2);
|
||||
});
|
||||
|
||||
it('uses floor/ceil for boundary breakpoints to ensure inclusion', () => {
|
||||
// Test that Math.floor and Math.ceil are used for boundaries
|
||||
// This ensures all data points fall within the breakpoint range
|
||||
|
||||
const testCases = [
|
||||
{ minValue: 3.14, maxValue: 100, buckets: 5 },
|
||||
{ minValue: 2.345, maxValue: 10.678, buckets: 4 },
|
||||
{ minValue: 1.67, maxValue: 5.33, buckets: 3 },
|
||||
{ minValue: 0.123, maxValue: 0.987, buckets: 5 },
|
||||
];
|
||||
|
||||
testCases.forEach(({ minValue, maxValue, buckets }) => {
|
||||
const features = [{ value: minValue }, { value: maxValue }];
|
||||
|
||||
const breakPoints = getBreakPoints(
|
||||
{ break_points: [], num_buckets: String(buckets) },
|
||||
features,
|
||||
accessor,
|
||||
);
|
||||
|
||||
const firstBp = parseFloat(breakPoints[0]);
|
||||
const lastBp = parseFloat(breakPoints[breakPoints.length - 1]);
|
||||
|
||||
// First breakpoint should be floored (always <= minValue)
|
||||
expect(firstBp).toBeLessThanOrEqual(minValue);
|
||||
|
||||
// Last breakpoint should be ceiled (always >= maxValue)
|
||||
expect(lastBp).toBeGreaterThanOrEqual(maxValue);
|
||||
|
||||
// All values should be within range
|
||||
expect(minValue).toBeGreaterThanOrEqual(firstBp);
|
||||
expect(maxValue).toBeLessThanOrEqual(lastBp);
|
||||
});
|
||||
});
|
||||
|
||||
it('prevents minimum value exclusion edge case', () => {
|
||||
// Specific edge case test for minimum value exclusion
|
||||
// Tests the exact scenario where rounding would exclude the min value
|
||||
|
||||
const features = [
|
||||
{ value: 3.14 }, // This would round to 3 at precision 0
|
||||
{ value: 50 },
|
||||
{ value: 100 },
|
||||
];
|
||||
|
||||
const breakPoints = getBreakPoints(
|
||||
{ break_points: [], num_buckets: '5' },
|
||||
features,
|
||||
accessor,
|
||||
);
|
||||
|
||||
const firstBp = parseFloat(breakPoints[0]);
|
||||
|
||||
// The first breakpoint must be <= 3.14 (floor behavior)
|
||||
expect(firstBp).toBeLessThanOrEqual(3.14);
|
||||
|
||||
// Verify that 3.14 is not excluded
|
||||
expect(3.14).toBeGreaterThanOrEqual(firstBp);
|
||||
|
||||
// The first breakpoint should be a clean floor value
|
||||
expect(breakPoints[0]).toMatch(/^3(\.0*)?$/);
|
||||
});
|
||||
|
||||
it('prevents maximum value exclusion edge case', () => {
|
||||
// Specific edge case test for maximum value exclusion
|
||||
// Tests the exact scenario where rounding would exclude the max value
|
||||
|
||||
const features = [
|
||||
{ value: 0 },
|
||||
{ value: 20 },
|
||||
{ value: 38.7 }, // Original bug case
|
||||
];
|
||||
|
||||
const breakPoints = getBreakPoints(
|
||||
{ break_points: [], num_buckets: '5' },
|
||||
features,
|
||||
accessor,
|
||||
);
|
||||
|
||||
const lastBp = parseFloat(breakPoints[breakPoints.length - 1]);
|
||||
|
||||
// The last breakpoint must be >= 38.7 (ceil behavior)
|
||||
expect(lastBp).toBeGreaterThanOrEqual(38.7);
|
||||
|
||||
// Verify that 38.7 is not excluded
|
||||
expect(38.7).toBeLessThanOrEqual(lastBp);
|
||||
|
||||
// The last breakpoint should be a clean ceil value
|
||||
expect(breakPoints[breakPoints.length - 1]).toMatch(/^39(\.0*)?$/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('custom breakpoints', () => {
|
||||
it('uses custom breakpoints when provided', () => {
|
||||
const features = [{ value: 5 }, { value: 15 }, { value: 25 }];
|
||||
const customBreakPoints = ['0', '10', '20', '30', '40'];
|
||||
|
||||
const breakPoints = getBreakPoints(
|
||||
{ break_points: customBreakPoints, num_buckets: '' },
|
||||
features,
|
||||
accessor,
|
||||
);
|
||||
|
||||
expect(breakPoints).toEqual(['0', '10', '20', '30', '40']);
|
||||
});
|
||||
|
||||
it('sorts custom breakpoints in ascending order', () => {
|
||||
const features = [{ value: 5 }];
|
||||
const customBreakPoints = ['30', '10', '0', '20'];
|
||||
|
||||
const breakPoints = getBreakPoints(
|
||||
{ break_points: customBreakPoints, num_buckets: '' },
|
||||
features,
|
||||
accessor,
|
||||
);
|
||||
|
||||
expect(breakPoints).toEqual(['0', '10', '20', '30']);
|
||||
});
|
||||
|
||||
it('ignores num_buckets when custom breakpoints are provided', () => {
|
||||
const features = [{ value: 5 }];
|
||||
const customBreakPoints = ['0', '50', '100'];
|
||||
|
||||
const breakPoints = getBreakPoints(
|
||||
{ break_points: customBreakPoints, num_buckets: '10' }, // num_buckets should be ignored
|
||||
features,
|
||||
accessor,
|
||||
);
|
||||
|
||||
expect(breakPoints).toEqual(['0', '50', '100']);
|
||||
expect(breakPoints).toHaveLength(3); // not 11
|
||||
});
|
||||
});
|
||||
|
||||
describe('edge cases and error handling', () => {
|
||||
it('returns empty array when features are undefined', () => {
|
||||
const breakPoints = getBreakPoints(
|
||||
{ break_points: [], num_buckets: '5' },
|
||||
undefined as any,
|
||||
accessor,
|
||||
);
|
||||
|
||||
expect(breakPoints).toEqual([]);
|
||||
});
|
||||
|
||||
it('returns empty array when features is null', () => {
|
||||
const breakPoints = getBreakPoints(
|
||||
{ break_points: [], num_buckets: '5' },
|
||||
null as any,
|
||||
accessor,
|
||||
);
|
||||
|
||||
expect(breakPoints).toEqual([]);
|
||||
});
|
||||
|
||||
it('returns empty array when all values are undefined', () => {
|
||||
const features = [
|
||||
{ value: undefined },
|
||||
{ value: undefined },
|
||||
{ value: undefined },
|
||||
];
|
||||
|
||||
const breakPoints = getBreakPoints(
|
||||
{ break_points: [], num_buckets: '5' },
|
||||
features,
|
||||
accessor,
|
||||
);
|
||||
|
||||
expect(breakPoints).toEqual([]);
|
||||
});
|
||||
|
||||
it('handles empty features array', () => {
|
||||
const breakPoints = getBreakPoints(
|
||||
{ break_points: [], num_buckets: '5' },
|
||||
[],
|
||||
accessor,
|
||||
);
|
||||
|
||||
expect(breakPoints).toEqual([]);
|
||||
});
|
||||
|
||||
it('handles string values that can be parsed as numbers', () => {
|
||||
const features = [
|
||||
{ value: '10.5' },
|
||||
{ value: '20.3' },
|
||||
{ value: '30.7' },
|
||||
];
|
||||
|
||||
const breakPoints = getBreakPoints(
|
||||
{ break_points: [], num_buckets: '3' },
|
||||
features,
|
||||
(d: any) =>
|
||||
typeof d.value === 'string' ? parseFloat(d.value) : d.value,
|
||||
);
|
||||
|
||||
const firstBp = parseFloat(breakPoints[0]);
|
||||
const lastBp = parseFloat(breakPoints[breakPoints.length - 1]);
|
||||
|
||||
expect(firstBp).toBeLessThanOrEqual(10.5);
|
||||
expect(lastBp).toBeGreaterThanOrEqual(30.7);
|
||||
});
|
||||
|
||||
it('uses default number of buckets when not specified', () => {
|
||||
const features = [{ value: 0 }, { value: 100 }];
|
||||
|
||||
const breakPoints = getBreakPoints(
|
||||
{ break_points: [], num_buckets: '' },
|
||||
features,
|
||||
accessor,
|
||||
);
|
||||
|
||||
// Should use DEFAULT_NUM_BUCKETS (10)
|
||||
expect(breakPoints).toHaveLength(11); // 10 buckets = 11 breakpoints
|
||||
});
|
||||
|
||||
it('handles Infinity and -Infinity values', () => {
|
||||
const features = [
|
||||
{ value: -Infinity },
|
||||
{ value: 0 },
|
||||
{ value: Infinity },
|
||||
];
|
||||
|
||||
const breakPoints = getBreakPoints(
|
||||
{ break_points: [], num_buckets: '5' },
|
||||
features,
|
||||
accessor,
|
||||
);
|
||||
|
||||
// Should return empty array when Infinity values are present
|
||||
expect(breakPoints).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('breakpoint boundaries validation', () => {
|
||||
it('ensures no data points fall outside breakpoint range', () => {
|
||||
// Generate random test data
|
||||
const generateRandomData = (count: number, min: number, max: number) => {
|
||||
const data = [];
|
||||
for (let i = 0; i < count; i += 1) {
|
||||
data.push({ value: Math.random() * (max - min) + min });
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
// Test with various random datasets
|
||||
for (let i = 0; i < 10; i += 1) {
|
||||
const features = generateRandomData(20, -1000, 1000);
|
||||
const minValue = Math.min(...features.map(f => f.value));
|
||||
const maxValue = Math.max(...features.map(f => f.value));
|
||||
|
||||
const breakPoints = getBreakPoints(
|
||||
{ break_points: [], num_buckets: '5' },
|
||||
features,
|
||||
accessor,
|
||||
);
|
||||
|
||||
const firstBp = parseFloat(breakPoints[0]);
|
||||
const lastBp = parseFloat(breakPoints[breakPoints.length - 1]);
|
||||
|
||||
// Every data point should fall within the breakpoint range
|
||||
features.forEach(feature => {
|
||||
expect(feature.value).toBeGreaterThanOrEqual(firstBp);
|
||||
expect(feature.value).toBeLessThanOrEqual(lastBp);
|
||||
});
|
||||
|
||||
// The range should be as tight as possible while including all data
|
||||
expect(firstBp).toBeLessThanOrEqual(minValue);
|
||||
expect(lastBp).toBeGreaterThanOrEqual(maxValue);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -75,19 +75,35 @@ export function getBreakPoints(
|
||||
if (minValue === undefined || maxValue === undefined) {
|
||||
return [];
|
||||
}
|
||||
// Handle Infinity values
|
||||
if (!Number.isFinite(minValue) || !Number.isFinite(maxValue)) {
|
||||
return [];
|
||||
}
|
||||
const delta = (maxValue - minValue) / numBuckets;
|
||||
const precision =
|
||||
delta === 0 ? 0 : Math.max(0, Math.ceil(Math.log10(1 / delta)));
|
||||
const extraBucket =
|
||||
maxValue > parseFloat(maxValue.toFixed(precision)) ? 1 : 0;
|
||||
const startValue =
|
||||
minValue < parseFloat(minValue.toFixed(precision))
|
||||
? minValue - 1
|
||||
: minValue;
|
||||
|
||||
return new Array(numBuckets + 1 + extraBucket)
|
||||
.fill(0)
|
||||
.map((_, i) => (startValue + i * delta).toFixed(precision));
|
||||
// Generate breakpoints
|
||||
const breakPoints = new Array(numBuckets + 1).fill(0).map((_, i) => {
|
||||
const value = minValue + i * delta;
|
||||
|
||||
// For the first breakpoint, floor to ensure minimum is included
|
||||
if (i === 0) {
|
||||
const scale = Math.pow(10, precision);
|
||||
return (Math.floor(minValue * scale) / scale).toFixed(precision);
|
||||
}
|
||||
|
||||
// For the last breakpoint, ceil to ensure maximum is included
|
||||
if (i === numBuckets) {
|
||||
const scale = Math.pow(10, precision);
|
||||
return (Math.ceil(maxValue * scale) / scale).toFixed(precision);
|
||||
}
|
||||
|
||||
// For middle breakpoints, use standard rounding
|
||||
return value.toFixed(precision);
|
||||
});
|
||||
|
||||
return breakPoints;
|
||||
}
|
||||
|
||||
return formDataBreakPoints.sort(
|
||||
@@ -146,7 +162,10 @@ export function getBreakPointColorScaler(
|
||||
scaler = scaleThreshold<number, string>()
|
||||
.domain(points)
|
||||
.range(bucketedColors);
|
||||
maskPoint = value => !!value && (value > points[n] || value < points[0]);
|
||||
// Only mask values that are strictly outside the min/max bounds
|
||||
// Include values equal to the max breakpoint
|
||||
maskPoint = value =>
|
||||
!!value && (value > points[points.length - 1] || value < points[0]);
|
||||
} else {
|
||||
// interpolate colors linearly
|
||||
const linearScaleDomain = extent(features, accessor);
|
||||
|
||||
@@ -131,10 +131,7 @@ const AgGridDataTable: FunctionComponent<AgGridTableProps> = memo(
|
||||
|
||||
const defaultColDef = useMemo<ColDef>(
|
||||
() => ({
|
||||
flex: 1,
|
||||
filter: true,
|
||||
enableRowGroup: true,
|
||||
enableValue: true,
|
||||
sortable: true,
|
||||
resizable: true,
|
||||
minWidth: 100,
|
||||
@@ -251,6 +248,12 @@ const AgGridDataTable: FunctionComponent<AgGridTableProps> = memo(
|
||||
}
|
||||
}, [hasServerPageLengthChanged]);
|
||||
|
||||
useEffect(() => {
|
||||
if (gridRef.current?.api) {
|
||||
gridRef.current.api.sizeColumnsToFit();
|
||||
}
|
||||
}, [width]);
|
||||
|
||||
const onGridReady = (params: GridReadyEvent) => {
|
||||
// This will make columns fill the grid width
|
||||
params.api.sizeColumnsToFit();
|
||||
@@ -312,7 +315,6 @@ const AgGridDataTable: FunctionComponent<AgGridTableProps> = memo(
|
||||
onCellClicked={handleCrossFilter}
|
||||
initialState={gridInitialState}
|
||||
suppressAggFuncInHeader
|
||||
rowGroupPanelShow="always"
|
||||
enableCellTextSelection
|
||||
quickFilterText={serverPagination ? '' : quickFilterText}
|
||||
suppressMovableColumns={!allowRearrangeColumns}
|
||||
|
||||
@@ -322,12 +322,6 @@ export default function transformProps(
|
||||
primarySeries.add(seriesOption.id as string);
|
||||
}
|
||||
};
|
||||
rawSeriesA.forEach(seriesOption =>
|
||||
mapSeriesIdToAxis(seriesOption, yAxisIndex),
|
||||
);
|
||||
rawSeriesB.forEach(seriesOption =>
|
||||
mapSeriesIdToAxis(seriesOption, yAxisIndexB),
|
||||
);
|
||||
const showValueIndexesA = extractShowValueIndexes(rawSeriesA, {
|
||||
stack,
|
||||
onlyTotal,
|
||||
@@ -460,7 +454,11 @@ export default function transformProps(
|
||||
theme,
|
||||
},
|
||||
);
|
||||
if (transformedSeries) series.push(transformedSeries);
|
||||
|
||||
if (transformedSeries) {
|
||||
series.push(transformedSeries);
|
||||
mapSeriesIdToAxis(transformedSeries, yAxisIndex);
|
||||
}
|
||||
});
|
||||
|
||||
rawSeriesB.forEach(entry => {
|
||||
@@ -528,7 +526,11 @@ export default function transformProps(
|
||||
theme,
|
||||
},
|
||||
);
|
||||
if (transformedSeries) series.push(transformedSeries);
|
||||
|
||||
if (transformedSeries) {
|
||||
series.push(transformedSeries);
|
||||
mapSeriesIdToAxis(transformedSeries, yAxisIndexB);
|
||||
}
|
||||
});
|
||||
|
||||
// default to 0-100% range when doing row-level contribution chart
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
import { mergeReplaceArrays } from '@superset-ui/core';
|
||||
|
||||
describe('Theme Override Deep Merge Behavior', () => {
|
||||
test('should merge nested objects correctly', () => {
|
||||
it('should merge nested objects correctly', () => {
|
||||
const baseOptions = {
|
||||
grid: {
|
||||
left: '5%',
|
||||
@@ -67,7 +67,7 @@ describe('Theme Override Deep Merge Behavior', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('should replace arrays instead of merging them', () => {
|
||||
it('should replace arrays instead of merging them', () => {
|
||||
const baseOptions = {
|
||||
series: [
|
||||
{ name: 'Series 1', type: 'line' },
|
||||
@@ -86,7 +86,7 @@ describe('Theme Override Deep Merge Behavior', () => {
|
||||
expect(result.series).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('should handle null overrides correctly', () => {
|
||||
it('should handle null overrides correctly', () => {
|
||||
const baseOptions = {
|
||||
grid: {
|
||||
left: '5%',
|
||||
@@ -127,7 +127,7 @@ describe('Theme Override Deep Merge Behavior', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('should handle override precedence correctly', () => {
|
||||
it('should handle override precedence correctly', () => {
|
||||
const baseTheme = {
|
||||
textStyle: { color: '#000', fontSize: 12 },
|
||||
};
|
||||
@@ -167,7 +167,7 @@ describe('Theme Override Deep Merge Behavior', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('should preserve deep nested structures', () => {
|
||||
it('should preserve deep nested structures', () => {
|
||||
const baseOptions = {
|
||||
xAxis: {
|
||||
axisLabel: {
|
||||
@@ -215,7 +215,7 @@ describe('Theme Override Deep Merge Behavior', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('should handle function values correctly', () => {
|
||||
it('should handle function values correctly', () => {
|
||||
const formatFunction = (value: any) => `${value}%`;
|
||||
const overrideFunction = (value: any) => `$${value}`;
|
||||
|
||||
@@ -241,7 +241,7 @@ describe('Theme Override Deep Merge Behavior', () => {
|
||||
expect(result.yAxis.axisLabel.formatter('100')).toBe('$100');
|
||||
});
|
||||
|
||||
test('should handle empty objects and arrays', () => {
|
||||
it('should handle empty objects and arrays', () => {
|
||||
const baseOptions = {
|
||||
series: [{ name: 'Test', data: [1, 2, 3] }],
|
||||
grid: { left: '5%' },
|
||||
|
||||
@@ -117,7 +117,7 @@ const chartPropsConfig = {
|
||||
theme: supersetTheme,
|
||||
};
|
||||
|
||||
it('should transform chart props for viz with showQueryIdentifiers=false', () => {
|
||||
test('should transform chart props for viz with showQueryIdentifiers=false', () => {
|
||||
const chartPropsConfigWithoutIdentifiers = {
|
||||
...chartPropsConfig,
|
||||
formData: {
|
||||
@@ -158,7 +158,7 @@ it('should transform chart props for viz with showQueryIdentifiers=false', () =>
|
||||
]);
|
||||
});
|
||||
|
||||
it('should transform chart props for viz with showQueryIdentifiers=true', () => {
|
||||
test('should transform chart props for viz with showQueryIdentifiers=true', () => {
|
||||
const chartPropsConfigWithIdentifiers = {
|
||||
...chartPropsConfig,
|
||||
formData: {
|
||||
@@ -259,7 +259,7 @@ describe('legend sorting', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('legend margin: top orientation sets grid.top correctly', () => {
|
||||
test('legend margin: top orientation sets grid.top correctly', () => {
|
||||
const chartPropsConfigWithoutIdentifiers = {
|
||||
...chartPropsConfig,
|
||||
formData: {
|
||||
@@ -274,7 +274,7 @@ it('legend margin: top orientation sets grid.top correctly', () => {
|
||||
expect((transformed.echartOptions.grid as any).top).toEqual(270);
|
||||
});
|
||||
|
||||
it('legend margin: bottom orientation sets grid.bottom correctly', () => {
|
||||
test('legend margin: bottom orientation sets grid.bottom correctly', () => {
|
||||
const chartPropsConfigWithoutIdentifiers = {
|
||||
...chartPropsConfig,
|
||||
formData: {
|
||||
@@ -290,7 +290,7 @@ it('legend margin: bottom orientation sets grid.bottom correctly', () => {
|
||||
expect((transformed.echartOptions.grid as any).bottom).toEqual(270);
|
||||
});
|
||||
|
||||
it('legend margin: left orientation sets grid.left correctly', () => {
|
||||
test('legend margin: left orientation sets grid.left correctly', () => {
|
||||
const chartPropsConfigWithoutIdentifiers = {
|
||||
...chartPropsConfig,
|
||||
formData: {
|
||||
@@ -306,7 +306,7 @@ it('legend margin: left orientation sets grid.left correctly', () => {
|
||||
expect((transformed.echartOptions.grid as any).left).toEqual(270);
|
||||
});
|
||||
|
||||
it('legend margin: right orientation sets grid.right correctly', () => {
|
||||
test('legend margin: right orientation sets grid.right correctly', () => {
|
||||
const chartPropsConfigWithoutIdentifiers = {
|
||||
...chartPropsConfig,
|
||||
formData: {
|
||||
|
||||
@@ -32,7 +32,7 @@ const mockColorScale = jest.fn(
|
||||
describe('transformSeries', () => {
|
||||
const series = { name: 'test-series' };
|
||||
|
||||
test('should use the colorScaleKey if timeShiftColor is enabled', () => {
|
||||
it('should use the colorScaleKey if timeShiftColor is enabled', () => {
|
||||
const opts = {
|
||||
timeShiftColor: true,
|
||||
colorScaleKey: 'test-key',
|
||||
@@ -44,7 +44,7 @@ describe('transformSeries', () => {
|
||||
expect((result as any)?.itemStyle.color).toBe('color-for-test-key-1');
|
||||
});
|
||||
|
||||
test('should use seriesKey if timeShiftColor is not enabled', () => {
|
||||
it('should use seriesKey if timeShiftColor is not enabled', () => {
|
||||
const opts = {
|
||||
timeShiftColor: false,
|
||||
seriesKey: 'series-key',
|
||||
@@ -56,7 +56,7 @@ describe('transformSeries', () => {
|
||||
expect((result as any)?.itemStyle.color).toBe('color-for-series-key-2');
|
||||
});
|
||||
|
||||
test('should apply border styles for bar series with connectNulls', () => {
|
||||
it('should apply border styles for bar series with connectNulls', () => {
|
||||
const opts = {
|
||||
seriesType: EchartsTimeseriesSeriesType.Bar,
|
||||
connectNulls: true,
|
||||
@@ -72,7 +72,7 @@ describe('transformSeries', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('should not apply border styles for non-bar series', () => {
|
||||
it('should not apply border styles for non-bar series', () => {
|
||||
const opts = {
|
||||
seriesType: EchartsTimeseriesSeriesType.Line,
|
||||
connectNulls: true,
|
||||
@@ -88,7 +88,7 @@ describe('transformSeries', () => {
|
||||
});
|
||||
|
||||
describe('transformNegativeLabelsPosition', () => {
|
||||
test('label position bottom of negative value no Horizontal', () => {
|
||||
it('label position bottom of negative value no Horizontal', () => {
|
||||
const isHorizontal = false;
|
||||
const series: SeriesOption = {
|
||||
data: [
|
||||
@@ -112,7 +112,7 @@ describe('transformNegativeLabelsPosition', () => {
|
||||
expect((result as any)[4].label).toBe(undefined);
|
||||
});
|
||||
|
||||
test('label position left of negative value is Horizontal', () => {
|
||||
it('label position left of negative value is Horizontal', () => {
|
||||
const isHorizontal = true;
|
||||
const series: SeriesOption = {
|
||||
data: [
|
||||
@@ -137,7 +137,7 @@ describe('transformNegativeLabelsPosition', () => {
|
||||
expect((result as any)[4].label.position).toBe('outside');
|
||||
});
|
||||
|
||||
test('label position to line type', () => {
|
||||
it('label position to line type', () => {
|
||||
const isHorizontal = false;
|
||||
const series: SeriesOption = {
|
||||
data: [
|
||||
@@ -165,7 +165,7 @@ describe('transformNegativeLabelsPosition', () => {
|
||||
expect((result as any)[4].label).toBe(undefined);
|
||||
});
|
||||
|
||||
test('label position to bar type and stack', () => {
|
||||
it('label position to bar type and stack', () => {
|
||||
const isHorizontal = false;
|
||||
const series: SeriesOption = {
|
||||
data: [
|
||||
|
||||
@@ -1,40 +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.
|
||||
*/
|
||||
{
|
||||
"plugins": ["jest", "jest-dom", "no-only-tests", "testing-library"],
|
||||
"env": {
|
||||
"jest/globals": true
|
||||
},
|
||||
"settings": {
|
||||
"jest": {
|
||||
"version": "detect"
|
||||
}
|
||||
},
|
||||
"extends": [
|
||||
"plugin:jest/recommended",
|
||||
"plugin:jest-dom/recommended",
|
||||
"plugin:testing-library/react"
|
||||
],
|
||||
"rules": {
|
||||
"import/no-extraneous-dependencies": ["error", { "devDependencies": true }],
|
||||
"jest/consistent-test-it": "error",
|
||||
"no-only-tests/no-only-tests": "error",
|
||||
"prefer-promise-reject-errors": 0
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,6 @@ export default {
|
||||
dash_edit_perm: true,
|
||||
dash_save_perm: true,
|
||||
common: {
|
||||
flash_messages: [],
|
||||
conf: { SUPERSET_WEBSERVER_TIMEOUT: 60 },
|
||||
},
|
||||
filterBarOrientation: FilterBarOrientation.Vertical,
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"extends": "prettier",
|
||||
"plugins": ["prettier"],
|
||||
"rules": {
|
||||
"prefer-template": 2,
|
||||
"new-cap": 2,
|
||||
"no-restricted-syntax": 2,
|
||||
"guard-for-in": 2,
|
||||
"prefer-arrow-callback": 2,
|
||||
"func-names": 2,
|
||||
"react/jsx-no-bind": 2,
|
||||
"no-confusing-arrow": 2,
|
||||
"jsx-a11y/no-static-element-interactions": 2,
|
||||
"jsx-a11y/anchor-has-content": 2,
|
||||
"react/require-default-props": 2,
|
||||
"no-plusplus": 2,
|
||||
"no-mixed-operators": 0,
|
||||
"no-continue": 2,
|
||||
"no-bitwise": 2,
|
||||
"no-multi-assign": 2,
|
||||
"no-restricted-properties": 2,
|
||||
"no-prototype-builtins": 2,
|
||||
"class-methods-use-this": 2,
|
||||
"import/no-named-as-default": 2,
|
||||
"react/no-unescaped-entities": 2,
|
||||
"react/no-string-refs": 2,
|
||||
"react/jsx-indent": 0,
|
||||
"prettier/prettier": "error"
|
||||
}
|
||||
}
|
||||
11
superset-frontend/src/.eslintrc.json
Normal file
11
superset-frontend/src/.eslintrc.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.test.ts", "*.test.tsx", "*.test.js", "*.test.jsx"],
|
||||
"rules": {
|
||||
"jest/consistent-test-it": ["error", {"fn": "test"}],
|
||||
"no-restricted-globals": ["error", "describe", "it"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -338,7 +338,6 @@ export function runQuery(query, runPreviewOnly) {
|
||||
const postPayload = {
|
||||
client_id: query.id,
|
||||
database_id: query.dbId,
|
||||
json: true,
|
||||
runAsync: query.runAsync,
|
||||
catalog: query.catalog,
|
||||
schema: query.schema,
|
||||
@@ -956,7 +955,7 @@ export function addTable(queryEditor, tableName, catalogName, schemaName) {
|
||||
const { dbId } = getUpToDateQuery(getState(), queryEditor, queryEditor.id);
|
||||
const table = {
|
||||
dbId,
|
||||
queryEditorId: queryEditor.id,
|
||||
queryEditorId: queryEditor.tabViewId ?? queryEditor.id,
|
||||
catalog: catalogName,
|
||||
schema: schemaName,
|
||||
name: tableName,
|
||||
@@ -1018,12 +1017,13 @@ export function runTablePreviewQuery(newTable, runPreviewOnly) {
|
||||
};
|
||||
}
|
||||
|
||||
export function syncTable(table, tableMetadata) {
|
||||
export function syncTable(table, tableMetadata, finalQueryEditorId) {
|
||||
return function (dispatch) {
|
||||
const finalTable = { ...table, queryEditorId: finalQueryEditorId };
|
||||
const sync = isFeatureEnabled(FeatureFlag.SqllabBackendPersistence)
|
||||
? SupersetClient.post({
|
||||
endpoint: encodeURI('/tableschemaview/'),
|
||||
postPayload: { table: { ...tableMetadata, ...table } },
|
||||
postPayload: { table: { ...tableMetadata, ...finalTable } },
|
||||
})
|
||||
: Promise.resolve({ json: { id: table.id } });
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ jest.mock('@superset-ui/core', () => ({
|
||||
isFeatureEnabled: jest.fn(),
|
||||
}));
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('getUpToDateQuery', () => {
|
||||
test('should return the up to date query editor state', () => {
|
||||
const outOfUpdatedQueryEditor = {
|
||||
@@ -72,6 +73,7 @@ describe('getUpToDateQuery', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('async actions', () => {
|
||||
const mockBigNumber = '9223372036854775807';
|
||||
const queryEditor = {
|
||||
@@ -100,6 +102,7 @@ describe('async actions', () => {
|
||||
const runQueryEndpoint = 'glob:*/api/v1/sqllab/execute/';
|
||||
fetchMock.post(runQueryEndpoint, `{ "data": ${mockBigNumber} }`);
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('saveQuery', () => {
|
||||
const saveQueryEndpoint = 'glob:*/api/v1/saved_query/';
|
||||
fetchMock.post(saveQueryEndpoint, { results: { json: {} } });
|
||||
@@ -109,7 +112,7 @@ describe('async actions', () => {
|
||||
return request(dispatch, () => initialState);
|
||||
};
|
||||
|
||||
it('posts to the correct url', () => {
|
||||
test('posts to the correct url', () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const store = mockStore(initialState);
|
||||
@@ -118,7 +121,7 @@ describe('async actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('posts the correct query object', () => {
|
||||
test('posts the correct query object', () => {
|
||||
const store = mockStore(initialState);
|
||||
return store.dispatch(actions.saveQuery(query, queryId)).then(() => {
|
||||
const call = fetchMock.calls(saveQueryEndpoint)[0];
|
||||
@@ -131,7 +134,7 @@ describe('async actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('calls 3 dispatch actions', () => {
|
||||
test('calls 3 dispatch actions', () => {
|
||||
expect.assertions(1);
|
||||
|
||||
return makeRequest().then(() => {
|
||||
@@ -139,7 +142,7 @@ describe('async actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('calls QUERY_EDITOR_SAVED after making a request', () => {
|
||||
test('calls QUERY_EDITOR_SAVED after making a request', () => {
|
||||
expect.assertions(1);
|
||||
|
||||
return makeRequest().then(() => {
|
||||
@@ -147,7 +150,7 @@ describe('async actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('onSave calls QUERY_EDITOR_SAVED and QUERY_EDITOR_SET_TITLE', () => {
|
||||
test('onSave calls QUERY_EDITOR_SAVED and QUERY_EDITOR_SET_TITLE', () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const store = mockStore(initialState);
|
||||
@@ -163,6 +166,7 @@ describe('async actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('formatQuery', () => {
|
||||
const formatQueryEndpoint = 'glob:*/api/v1/sqllab/format_sql/';
|
||||
const expectedSql = 'SELECT 1';
|
||||
@@ -179,6 +183,7 @@ describe('async actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('fetchQueryResults', () => {
|
||||
const makeRequest = () => {
|
||||
const store = mockStore(initialState);
|
||||
@@ -186,7 +191,7 @@ describe('async actions', () => {
|
||||
return request(dispatch, store.getState);
|
||||
};
|
||||
|
||||
it('makes the fetch request', () => {
|
||||
test('makes the fetch request', () => {
|
||||
expect.assertions(1);
|
||||
|
||||
return makeRequest().then(() => {
|
||||
@@ -194,7 +199,7 @@ describe('async actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('calls requestQueryResults', () => {
|
||||
test('calls requestQueryResults', () => {
|
||||
expect.assertions(1);
|
||||
|
||||
return makeRequest().then(() => {
|
||||
@@ -202,7 +207,7 @@ describe('async actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it.skip('parses large number result without losing precision', () =>
|
||||
test.skip('parses large number result without losing precision', () =>
|
||||
makeRequest().then(() => {
|
||||
expect(fetchMock.calls(fetchQueryEndpoint)).toHaveLength(1);
|
||||
expect(dispatch.callCount).toBe(2);
|
||||
@@ -211,7 +216,7 @@ describe('async actions', () => {
|
||||
);
|
||||
}));
|
||||
|
||||
it('calls querySuccess on fetch success', () => {
|
||||
test('calls querySuccess on fetch success', () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const store = mockStore({});
|
||||
@@ -226,7 +231,7 @@ describe('async actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('calls queryFailed on fetch error', () => {
|
||||
test('calls queryFailed on fetch error', () => {
|
||||
expect.assertions(1);
|
||||
|
||||
fetchMock.get(
|
||||
@@ -248,13 +253,14 @@ describe('async actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('runQuery without query params', () => {
|
||||
const makeRequest = () => {
|
||||
const request = actions.runQuery(query);
|
||||
return request(dispatch, () => initialState);
|
||||
};
|
||||
|
||||
it('makes the fetch request', () => {
|
||||
test('makes the fetch request', () => {
|
||||
expect.assertions(1);
|
||||
|
||||
return makeRequest().then(() => {
|
||||
@@ -262,7 +268,7 @@ describe('async actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('calls startQuery', () => {
|
||||
test('calls startQuery', () => {
|
||||
expect.assertions(1);
|
||||
|
||||
return makeRequest().then(() => {
|
||||
@@ -270,7 +276,7 @@ describe('async actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it.skip('parses large number result without losing precision', () =>
|
||||
test.skip('parses large number result without losing precision', () =>
|
||||
makeRequest().then(() => {
|
||||
expect(fetchMock.calls(runQueryEndpoint)).toHaveLength(1);
|
||||
expect(dispatch.callCount).toBe(2);
|
||||
@@ -279,7 +285,7 @@ describe('async actions', () => {
|
||||
);
|
||||
}));
|
||||
|
||||
it('calls querySuccess on fetch success', () => {
|
||||
test('calls querySuccess on fetch success', () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const store = mockStore({});
|
||||
@@ -293,7 +299,7 @@ describe('async actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('calls queryFailed on fetch error and logs the error details', () => {
|
||||
test('calls queryFailed on fetch error and logs the error details', () => {
|
||||
expect.assertions(2);
|
||||
|
||||
fetchMock.post(
|
||||
@@ -324,6 +330,7 @@ describe('async actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('runQuery with query params', () => {
|
||||
const { location } = window;
|
||||
|
||||
@@ -342,7 +349,7 @@ describe('async actions', () => {
|
||||
return request(dispatch, () => initialState);
|
||||
};
|
||||
|
||||
it('makes the fetch request', async () => {
|
||||
test('makes the fetch request', async () => {
|
||||
const runQueryEndpointWithParams =
|
||||
'glob:*/api/v1/sqllab/execute/?foo=bar';
|
||||
fetchMock.post(
|
||||
@@ -355,8 +362,9 @@ describe('async actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('reRunQuery', () => {
|
||||
it('creates new query with a new id', () => {
|
||||
test('creates new query with a new id', () => {
|
||||
const id = 'id';
|
||||
const state = {
|
||||
sqlLab: {
|
||||
@@ -372,6 +380,7 @@ describe('async actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('postStopQuery', () => {
|
||||
const stopQueryEndpoint = 'glob:*/api/v1/query/stop';
|
||||
fetchMock.post(stopQueryEndpoint, {});
|
||||
@@ -385,7 +394,7 @@ describe('async actions', () => {
|
||||
return request(dispatch);
|
||||
};
|
||||
|
||||
it('makes the fetch request', () => {
|
||||
test('makes the fetch request', () => {
|
||||
expect.assertions(1);
|
||||
|
||||
return makeRequest().then(() => {
|
||||
@@ -393,7 +402,7 @@ describe('async actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('calls stopQuery', () => {
|
||||
test('calls stopQuery', () => {
|
||||
expect.assertions(1);
|
||||
|
||||
return makeRequest().then(() => {
|
||||
@@ -401,7 +410,7 @@ describe('async actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('sends the correct data', () => {
|
||||
test('sends the correct data', () => {
|
||||
expect.assertions(1);
|
||||
|
||||
return makeRequest().then(() => {
|
||||
@@ -412,8 +421,9 @@ describe('async actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('cloneQueryToNewTab', () => {
|
||||
it('creates new query editor', () => {
|
||||
test('creates new query editor', () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const id = 'id';
|
||||
@@ -455,6 +465,7 @@ describe('async actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('popSavedQuery', () => {
|
||||
const supersetClientGetSpy = jest.spyOn(SupersetClient, 'get');
|
||||
const store = mockStore({});
|
||||
@@ -508,7 +519,7 @@ describe('async actions', () => {
|
||||
supersetClientGetSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('calls API endpint with correct params', async () => {
|
||||
test('calls API endpint with correct params', async () => {
|
||||
supersetClientGetSpy.mockResolvedValue({
|
||||
json: { result: mockSavedQueryApiResponse },
|
||||
});
|
||||
@@ -520,7 +531,7 @@ describe('async actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('dispatches addQueryEditor with correct params on successful API call', async () => {
|
||||
test('dispatches addQueryEditor with correct params on successful API call', async () => {
|
||||
supersetClientGetSpy.mockResolvedValue({
|
||||
json: { result: mockSavedQueryApiResponse },
|
||||
});
|
||||
@@ -547,7 +558,7 @@ describe('async actions', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should dispatch addDangerToast on API error', async () => {
|
||||
test('should dispatch addDangerToast on API error', async () => {
|
||||
supersetClientGetSpy.mockResolvedValue(new Error());
|
||||
|
||||
await makeRequest(1);
|
||||
@@ -561,8 +572,9 @@ describe('async actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('addQueryEditor', () => {
|
||||
it('creates new query editor', () => {
|
||||
test('creates new query editor', () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const store = mockStore(initialState);
|
||||
@@ -582,8 +594,9 @@ describe('async actions', () => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('addNewQueryEditor', () => {
|
||||
it('creates new query editor with new tab name', () => {
|
||||
test('creates new query editor with new tab name', () => {
|
||||
const store = mockStore({
|
||||
...initialState,
|
||||
sqlLab: {
|
||||
@@ -621,7 +634,7 @@ describe('async actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('set current query editor', () => {
|
||||
test('set current query editor', () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const store = mockStore(initialState);
|
||||
@@ -636,8 +649,9 @@ describe('async actions', () => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('swithQueryEditor', () => {
|
||||
it('switch to the next tab editor', () => {
|
||||
test('switch to the next tab editor', () => {
|
||||
const store = mockStore(initialState);
|
||||
const expectedActions = [
|
||||
{
|
||||
@@ -650,7 +664,7 @@ describe('async actions', () => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
|
||||
it('switch to the first tab editor once it reaches the rightmost tab', () => {
|
||||
test('switch to the first tab editor once it reaches the rightmost tab', () => {
|
||||
const store = mockStore({
|
||||
...initialState,
|
||||
sqlLab: {
|
||||
@@ -673,7 +687,7 @@ describe('async actions', () => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
|
||||
it('switch to the previous tab editor', () => {
|
||||
test('switch to the previous tab editor', () => {
|
||||
const store = mockStore({
|
||||
...initialState,
|
||||
sqlLab: {
|
||||
@@ -692,7 +706,7 @@ describe('async actions', () => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
|
||||
it('switch to the last tab editor once it reaches the leftmost tab', () => {
|
||||
test('switch to the last tab editor once it reaches the leftmost tab', () => {
|
||||
const store = mockStore({
|
||||
...initialState,
|
||||
sqlLab: {
|
||||
@@ -715,6 +729,7 @@ describe('async actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('backend sync', () => {
|
||||
const updateTabStateEndpoint = 'glob:*/tabstateview/*';
|
||||
fetchMock.put(updateTabStateEndpoint, {});
|
||||
@@ -745,8 +760,9 @@ describe('async actions', () => {
|
||||
|
||||
afterEach(() => fetchMock.resetHistory());
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('addQueryEditor', () => {
|
||||
it('creates the tab state in the local storage', () => {
|
||||
test('creates the tab state in the local storage', () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const store = mockStore({});
|
||||
@@ -769,8 +785,9 @@ describe('async actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('removeQueryEditor', () => {
|
||||
it('updates the tab state in the backend', () => {
|
||||
test('updates the tab state in the backend', () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const store = mockStore({});
|
||||
@@ -785,8 +802,9 @@ describe('async actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('queryEditorSetDb', () => {
|
||||
it('updates the tab state in the backend', () => {
|
||||
test('updates the tab state in the backend', () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const dbId = 42;
|
||||
@@ -803,8 +821,9 @@ describe('async actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('queryEditorSetCatalog', () => {
|
||||
it('updates the tab state in the backend', () => {
|
||||
test('updates the tab state in the backend', () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const catalog = 'public';
|
||||
@@ -821,8 +840,9 @@ describe('async actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('queryEditorSetSchema', () => {
|
||||
it('updates the tab state in the backend', () => {
|
||||
test('updates the tab state in the backend', () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const schema = 'schema';
|
||||
@@ -839,8 +859,9 @@ describe('async actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('queryEditorSetAutorun', () => {
|
||||
it('updates the tab state in the backend', () => {
|
||||
test('updates the tab state in the backend', () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const autorun = true;
|
||||
@@ -857,8 +878,9 @@ describe('async actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('queryEditorSetTitle', () => {
|
||||
it('updates the tab state in the backend', () => {
|
||||
test('updates the tab state in the backend', () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const name = 'name';
|
||||
@@ -877,6 +899,7 @@ describe('async actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('queryEditorSetAndSaveSql', () => {
|
||||
const sql = 'SELECT * ';
|
||||
const expectedActions = [
|
||||
@@ -886,8 +909,9 @@ describe('async actions', () => {
|
||||
sql,
|
||||
},
|
||||
];
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('with backend persistence flag on', () => {
|
||||
it('updates the tab state in the backend', () => {
|
||||
test('updates the tab state in the backend', () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const store = mockStore({
|
||||
@@ -904,8 +928,9 @@ describe('async actions', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('with backend persistence flag off', () => {
|
||||
it('does not update the tab state in the backend', () => {
|
||||
test('does not update the tab state in the backend', () => {
|
||||
isFeatureEnabled.mockImplementation(
|
||||
feature => !(feature === 'SQLLAB_BACKEND_PERSISTENCE'),
|
||||
);
|
||||
@@ -927,8 +952,9 @@ describe('async actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('queryEditorSetQueryLimit', () => {
|
||||
it('updates the tab state in the backend', () => {
|
||||
test('updates the tab state in the backend', () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const queryLimit = 10;
|
||||
@@ -947,8 +973,9 @@ describe('async actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('queryEditorSetTemplateParams', () => {
|
||||
it('updates the tab state in the backend', () => {
|
||||
test('updates the tab state in the backend', () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const templateParams = '{"foo": "bar"}';
|
||||
@@ -968,8 +995,9 @@ describe('async actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('addTable', () => {
|
||||
it('dispatches table state from unsaved change', () => {
|
||||
test('dispatches table state from unsaved change', () => {
|
||||
const tableName = 'table';
|
||||
const catalogName = null;
|
||||
const schemaName = 'schema';
|
||||
@@ -1002,10 +1030,90 @@ describe('async actions', () => {
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('uses tabViewId when available', () => {
|
||||
const tableName = 'table';
|
||||
const catalogName = null;
|
||||
const schemaName = 'schema';
|
||||
const expectedDbId = 473892;
|
||||
const tabViewId = '123';
|
||||
const queryWithTabViewId = { ...query, tabViewId };
|
||||
|
||||
const store = mockStore({
|
||||
...initialState,
|
||||
sqlLab: {
|
||||
...initialState.sqlLab,
|
||||
unsavedQueryEditor: {
|
||||
id: query.id,
|
||||
dbId: expectedDbId,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const request = actions.addTable(
|
||||
queryWithTabViewId,
|
||||
tableName,
|
||||
catalogName,
|
||||
schemaName,
|
||||
);
|
||||
request(store.dispatch, store.getState);
|
||||
|
||||
expect(store.getActions()[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
table: expect.objectContaining({
|
||||
name: tableName,
|
||||
catalog: catalogName,
|
||||
schema: schemaName,
|
||||
dbId: expectedDbId,
|
||||
queryEditorId: tabViewId, // Should use tabViewId, not id
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('falls back to id when tabViewId is not available', () => {
|
||||
const tableName = 'table';
|
||||
const catalogName = null;
|
||||
const schemaName = 'schema';
|
||||
const expectedDbId = 473892;
|
||||
const queryWithoutTabViewId = { ...query, tabViewId: undefined };
|
||||
|
||||
const store = mockStore({
|
||||
...initialState,
|
||||
sqlLab: {
|
||||
...initialState.sqlLab,
|
||||
unsavedQueryEditor: {
|
||||
id: query.id,
|
||||
dbId: expectedDbId,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const request = actions.addTable(
|
||||
queryWithoutTabViewId,
|
||||
tableName,
|
||||
catalogName,
|
||||
schemaName,
|
||||
);
|
||||
request(store.dispatch, store.getState);
|
||||
|
||||
expect(store.getActions()[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
table: expect.objectContaining({
|
||||
name: tableName,
|
||||
catalog: catalogName,
|
||||
schema: schemaName,
|
||||
dbId: expectedDbId,
|
||||
queryEditorId: query.id, // Should use id when tabViewId is not available
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('syncTable', () => {
|
||||
it('updates the table schema state in the backend', () => {
|
||||
test('updates the table schema state in the backend', () => {
|
||||
expect.assertions(4);
|
||||
|
||||
const tableName = 'table';
|
||||
@@ -1028,6 +1136,7 @@ describe('async actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('runTablePreviewQuery', () => {
|
||||
const results = {
|
||||
data: mockBigNumber,
|
||||
@@ -1058,7 +1167,7 @@ describe('async actions', () => {
|
||||
fetchMock.resetHistory();
|
||||
});
|
||||
|
||||
it('updates and runs data preview query when configured', () => {
|
||||
test('updates and runs data preview query when configured', () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const expectedActionTypes = [
|
||||
@@ -1082,7 +1191,7 @@ describe('async actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('runs data preview query only', () => {
|
||||
test('runs data preview query only', () => {
|
||||
const expectedActionTypes = [
|
||||
actions.START_QUERY, // runQuery (data preview)
|
||||
actions.QUERY_SUCCESS, // querySuccess
|
||||
@@ -1107,8 +1216,9 @@ describe('async actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('expandTable', () => {
|
||||
it('updates the table schema state in the backend', () => {
|
||||
test('updates the table schema state in the backend', () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const table = { id: 1 };
|
||||
@@ -1126,8 +1236,9 @@ describe('async actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('collapseTable', () => {
|
||||
it('updates the table schema state in the backend', () => {
|
||||
test('updates the table schema state in the backend', () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const table = { id: 1 };
|
||||
@@ -1145,8 +1256,9 @@ describe('async actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('removeTables', () => {
|
||||
it('updates the table schema state in the backend', () => {
|
||||
test('updates the table schema state in the backend', () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const table = { id: 1, initialized: true };
|
||||
@@ -1163,7 +1275,7 @@ describe('async actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('deletes multiple tables and updates the table schema state in the backend', () => {
|
||||
test('deletes multiple tables and updates the table schema state in the backend', () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const tables = [
|
||||
@@ -1183,7 +1295,7 @@ describe('async actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('only updates the initialized table schema state in the backend', () => {
|
||||
test('only updates the initialized table schema state in the backend', () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const tables = [{ id: 1 }, { id: 2, initialized: true }];
|
||||
@@ -1201,8 +1313,9 @@ describe('async actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('syncQueryEditor', () => {
|
||||
it('updates the tab state in the backend', () => {
|
||||
test('updates the tab state in the backend', () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const results = {
|
||||
|
||||
@@ -68,12 +68,13 @@ const setup = (queryEditor: QueryEditor, store?: Store) =>
|
||||
},
|
||||
);
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('AceEditorWrapper', () => {
|
||||
beforeEach(() => {
|
||||
(FullSQLEditor as any as jest.Mock).mockClear();
|
||||
});
|
||||
|
||||
it('renders ace editor including sql value', async () => {
|
||||
test('renders ace editor including sql value', async () => {
|
||||
const store = createStore(initialState, reducerIndex);
|
||||
const { getByTestId } = setup(defaultQueryEditor, store);
|
||||
await waitFor(() => expect(getByTestId('react-ace')).toBeInTheDocument());
|
||||
@@ -83,7 +84,7 @@ describe('AceEditorWrapper', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('renders current sql for unrelated unsaved changes', () => {
|
||||
test('renders current sql for unrelated unsaved changes', () => {
|
||||
const expectedSql = 'SELECT updated_column\nFROM updated_table\nWHERE';
|
||||
const store = createStore(
|
||||
{
|
||||
@@ -108,7 +109,7 @@ describe('AceEditorWrapper', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('skips rerendering for updating cursor position', () => {
|
||||
test('skips rerendering for updating cursor position', () => {
|
||||
const store = createStore(initialState, reducerIndex);
|
||||
setup(defaultQueryEditor, store);
|
||||
|
||||
|
||||
@@ -65,6 +65,7 @@ const AceEditorWrapper = ({
|
||||
'catalog',
|
||||
'schema',
|
||||
'templateParams',
|
||||
'tabViewId',
|
||||
]);
|
||||
// Prevent a maximum update depth exceeded error
|
||||
// by skipping access the unsaved query editor state
|
||||
@@ -172,6 +173,7 @@ const AceEditorWrapper = ({
|
||||
dbId: queryEditor.dbId,
|
||||
catalog: queryEditor.catalog,
|
||||
schema: queryEditor.schema,
|
||||
tabViewId: queryEditor.tabViewId,
|
||||
},
|
||||
!autocomplete,
|
||||
);
|
||||
@@ -191,6 +193,11 @@ const AceEditorWrapper = ({
|
||||
width: ${theme.sizeUnit * 130}px !important;
|
||||
}
|
||||
|
||||
.ace_completion-highlight {
|
||||
color: ${theme.colorPrimaryText} !important;
|
||||
background-color: ${theme.colorPrimaryBgHover};
|
||||
}
|
||||
|
||||
.ace_tooltip {
|
||||
max-width: ${SQL_EDITOR_LEFTBAR_WIDTH}px;
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ type Params = {
|
||||
dbId?: string | number;
|
||||
catalog?: string | null;
|
||||
schema?: string;
|
||||
tabViewId?: string;
|
||||
};
|
||||
|
||||
const EMPTY_LIST = [] as typeof sqlKeywords;
|
||||
@@ -59,7 +60,7 @@ const getHelperText = (value: string) =>
|
||||
const extensionsRegistry = getExtensionsRegistry();
|
||||
|
||||
export function useKeywords(
|
||||
{ queryEditorId, dbId, catalog, schema }: Params,
|
||||
{ queryEditorId, dbId, catalog, schema, tabViewId }: Params,
|
||||
skip = false,
|
||||
) {
|
||||
const useCustomKeywords = extensionsRegistry.get(
|
||||
@@ -147,7 +148,12 @@ export function useKeywords(
|
||||
const insertMatch = useEffectEvent((editor: Editor, data: any) => {
|
||||
if (data.meta === 'table') {
|
||||
dispatch(
|
||||
addTable({ id: queryEditorId, dbId }, data.value, catalog, schema),
|
||||
addTable(
|
||||
{ id: queryEditorId, dbId, tabViewId },
|
||||
data.value,
|
||||
catalog,
|
||||
schema,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ const sqlLabReducer = combineReducers({
|
||||
});
|
||||
const mockAction = {} as AnyAction;
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('SqlLab App', () => {
|
||||
const middlewares = [thunk];
|
||||
const mockStore = configureStore(middlewares);
|
||||
@@ -60,23 +61,23 @@ describe('SqlLab App', () => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('is valid', () => {
|
||||
test('is valid', () => {
|
||||
expect(isValidElement(<App />)).toBe(true);
|
||||
});
|
||||
|
||||
it('should render', () => {
|
||||
test('should render', () => {
|
||||
const { getByTestId } = render(<App />, { useRedux: true, store });
|
||||
expect(getByTestId('SqlLabApp')).toBeInTheDocument();
|
||||
expect(getByTestId('mock-tabbed-sql-editors')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('reset hotkey events on unmount', () => {
|
||||
test('reset hotkey events on unmount', () => {
|
||||
const { unmount } = render(<App />, { useRedux: true, store });
|
||||
unmount();
|
||||
expect(Mousetrap.reset).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('logs current usage warning', () => {
|
||||
test('logs current usage warning', () => {
|
||||
const localStorageUsageInKilobytes = LOCALSTORAGE_MAX_USAGE_KB + 10;
|
||||
const initialState = {
|
||||
localStorageUsageInKilobytes,
|
||||
@@ -100,7 +101,7 @@ describe('SqlLab App', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('logs current local storage usage', async () => {
|
||||
test('logs current local storage usage', async () => {
|
||||
const localStorageUsageInKilobytes = LOCALSTORAGE_MAX_USAGE_KB - 10;
|
||||
const storeExceedLocalStorage = mockStore(
|
||||
sqlLabReducer(
|
||||
|
||||
@@ -21,22 +21,23 @@ import { render } from 'spec/helpers/testing-library';
|
||||
import ColumnElement from 'src/SqlLab/components/ColumnElement';
|
||||
import { mockedActions, table } from 'src/SqlLab/fixtures';
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('ColumnElement', () => {
|
||||
const mockedProps = {
|
||||
actions: mockedActions,
|
||||
column: table.columns[0],
|
||||
};
|
||||
it('is valid with props', () => {
|
||||
test('is valid with props', () => {
|
||||
expect(isValidElement(<ColumnElement {...mockedProps} />)).toBe(true);
|
||||
});
|
||||
it('renders a proper primary key', () => {
|
||||
test('renders a proper primary key', () => {
|
||||
const { container } = render(<ColumnElement column={table.columns[0]} />);
|
||||
expect(container.querySelector('i.fa-key')).toBeInTheDocument();
|
||||
expect(
|
||||
container.querySelector('[data-test="col-name"]')?.firstChild,
|
||||
).toHaveTextContent('id');
|
||||
});
|
||||
it('renders a multi-key column', () => {
|
||||
test('renders a multi-key column', () => {
|
||||
const { container } = render(<ColumnElement column={table.columns[1]} />);
|
||||
expect(container.querySelector('i.fa-link')).toBeInTheDocument();
|
||||
expect(container.querySelector('i.fa-bookmark')).toBeInTheDocument();
|
||||
@@ -44,7 +45,7 @@ describe('ColumnElement', () => {
|
||||
container.querySelector('[data-test="col-name"]')?.firstChild,
|
||||
).toHaveTextContent('first_name');
|
||||
});
|
||||
it('renders a column with no keys', () => {
|
||||
test('renders a column with no keys', () => {
|
||||
const { container } = render(<ColumnElement column={table.columns[2]} />);
|
||||
expect(container.querySelector('i')).not.toBeInTheDocument();
|
||||
expect(
|
||||
|
||||
@@ -53,14 +53,15 @@ const setup = (props: Partial<EstimateQueryCostButtonProps>, store?: Store) =>
|
||||
},
|
||||
);
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('EstimateQueryCostButton', () => {
|
||||
it('renders EstimateQueryCostButton', async () => {
|
||||
test('renders EstimateQueryCostButton', async () => {
|
||||
const { queryByText } = setup({}, mockStore(initialState));
|
||||
|
||||
expect(queryByText('Estimate cost')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders label for selected query', async () => {
|
||||
test('renders label for selected query', async () => {
|
||||
const { queryByText } = setup(
|
||||
{ queryEditorId: extraQueryEditor1.id },
|
||||
mockStore(initialState),
|
||||
@@ -69,7 +70,7 @@ describe('EstimateQueryCostButton', () => {
|
||||
expect(queryByText('Estimate selected query cost')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders label for selected query from unsaved', async () => {
|
||||
test('renders label for selected query from unsaved', async () => {
|
||||
const { queryByText } = setup(
|
||||
{},
|
||||
mockStore({
|
||||
@@ -87,7 +88,7 @@ describe('EstimateQueryCostButton', () => {
|
||||
expect(queryByText('Estimate selected query cost')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders estimation error result', async () => {
|
||||
test('renders estimation error result', async () => {
|
||||
const { queryByText, getByText } = setup(
|
||||
{},
|
||||
mockStore({
|
||||
@@ -109,7 +110,7 @@ describe('EstimateQueryCostButton', () => {
|
||||
expect(queryByText('Estimate error')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders estimation success result', async () => {
|
||||
test('renders estimation success result', async () => {
|
||||
const { queryByText, getByText } = setup(
|
||||
{},
|
||||
mockStore({
|
||||
|
||||
@@ -47,17 +47,18 @@ const setup = (props: Partial<ExploreCtasResultsButtonProps>, store?: Store) =>
|
||||
},
|
||||
);
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('ExploreCtasResultsButton', () => {
|
||||
const postFormSpy = jest.spyOn(SupersetClientClass.prototype, 'postForm');
|
||||
postFormSpy.mockImplementation(jest.fn());
|
||||
|
||||
it('renders', async () => {
|
||||
test('renders', async () => {
|
||||
const { queryByText } = setup({}, mockStore(initialState));
|
||||
|
||||
expect(queryByText('Explore')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('visualize results', async () => {
|
||||
test('visualize results', async () => {
|
||||
const { getByText } = setup({}, mockStore(initialState));
|
||||
|
||||
postFormSpy.mockClear();
|
||||
@@ -75,7 +76,7 @@ describe('ExploreCtasResultsButton', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('visualize results fails', async () => {
|
||||
test('visualize results fails', async () => {
|
||||
const { getByText } = setup({}, mockStore(initialState));
|
||||
|
||||
postFormSpy.mockClear();
|
||||
|
||||
@@ -31,8 +31,9 @@ const setup = (
|
||||
useRedux: true,
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('ExploreResultsButton', () => {
|
||||
it('renders', async () => {
|
||||
test('renders', async () => {
|
||||
const { queryByText } = setup(jest.fn(), {
|
||||
database: { allows_subquery: true },
|
||||
});
|
||||
@@ -41,7 +42,7 @@ describe('ExploreResultsButton', () => {
|
||||
expect(screen.getByRole('button', { name: /Create chart/i })).toBeEnabled();
|
||||
});
|
||||
|
||||
it('renders disabled if subquery not allowed', async () => {
|
||||
test('renders disabled if subquery not allowed', async () => {
|
||||
const { queryByText } = setup(jest.fn());
|
||||
expect(queryByText('Create chart')).toBeInTheDocument();
|
||||
// Updated line to match the actual button name that includes the icon
|
||||
|
||||
@@ -43,6 +43,7 @@ const mockState = {
|
||||
databases: mockDatabases,
|
||||
};
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('QueryAutoRefresh', () => {
|
||||
const runningQueries: QueryDictionary = { [runningQuery.id]: runningQuery };
|
||||
const successfulQueries: QueryDictionary = {
|
||||
@@ -62,15 +63,15 @@ describe('QueryAutoRefresh', () => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('isQueryRunning returns true for valid running query', () => {
|
||||
test('isQueryRunning returns true for valid running query', () => {
|
||||
expect(isQueryRunning(runningQuery)).toBe(true);
|
||||
});
|
||||
|
||||
it('isQueryRunning returns false for valid not-running query', () => {
|
||||
test('isQueryRunning returns false for valid not-running query', () => {
|
||||
expect(isQueryRunning(successfulQuery)).toBe(false);
|
||||
});
|
||||
|
||||
it('isQueryRunning returns false for invalid query', () => {
|
||||
test('isQueryRunning returns false for invalid query', () => {
|
||||
// @ts-ignore
|
||||
expect(isQueryRunning(null)).toBe(false);
|
||||
// @ts-ignore
|
||||
@@ -81,15 +82,15 @@ describe('QueryAutoRefresh', () => {
|
||||
expect(isQueryRunning({ state: { badFormat: true } })).toBe(false);
|
||||
});
|
||||
|
||||
it('shouldCheckForQueries is true for valid running query', () => {
|
||||
test('shouldCheckForQueries is true for valid running query', () => {
|
||||
expect(shouldCheckForQueries(runningQueries)).toBe(true);
|
||||
});
|
||||
|
||||
it('shouldCheckForQueries is false for valid completed query', () => {
|
||||
test('shouldCheckForQueries is false for valid completed query', () => {
|
||||
expect(shouldCheckForQueries(successfulQueries)).toBe(false);
|
||||
});
|
||||
|
||||
it('shouldCheckForQueries is false for invalid inputs', () => {
|
||||
test('shouldCheckForQueries is false for invalid inputs', () => {
|
||||
// @ts-ignore
|
||||
expect(shouldCheckForQueries(null)).toBe(false);
|
||||
// @ts-ignore
|
||||
@@ -109,7 +110,7 @@ describe('QueryAutoRefresh', () => {
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('Attempts to refresh when given pending query', async () => {
|
||||
test('Attempts to refresh when given pending query', async () => {
|
||||
const store = mockStore({ sqlLab: { ...mockState } });
|
||||
|
||||
fetchMock.get(refreshApi, {
|
||||
@@ -135,7 +136,7 @@ describe('QueryAutoRefresh', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('Attempts to clear inactive queries when updated queries are empty', async () => {
|
||||
test('Attempts to clear inactive queries when updated queries are empty', async () => {
|
||||
const store = mockStore({ sqlLab: { ...mockState } });
|
||||
|
||||
fetchMock.get(refreshApi, { result: [] });
|
||||
@@ -164,7 +165,7 @@ describe('QueryAutoRefresh', () => {
|
||||
expect(fetchMock.calls(refreshApi)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('Does not fail and attempts to refresh with mixed valid/invalid queries', async () => {
|
||||
test('Does not fail and attempts to refresh with mixed valid/invalid queries', async () => {
|
||||
const store = mockStore({ sqlLab: { ...mockState } });
|
||||
|
||||
fetchMock.get(refreshApi, {
|
||||
@@ -191,7 +192,7 @@ describe('QueryAutoRefresh', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('Does NOT Attempt to refresh when given only completed queries', async () => {
|
||||
test('Does NOT Attempt to refresh when given only completed queries', async () => {
|
||||
const store = mockStore({ sqlLab: { ...mockState } });
|
||||
|
||||
fetchMock.get(refreshApi, {
|
||||
@@ -219,7 +220,7 @@ describe('QueryAutoRefresh', () => {
|
||||
expect(fetchMock.calls(refreshApi)).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('logs the failed error for async queries', async () => {
|
||||
test('logs the failed error for async queries', async () => {
|
||||
const store = mockStore({ sqlLab: { ...mockState } });
|
||||
|
||||
fetchMock.get(refreshApi, {
|
||||
|
||||
@@ -58,8 +58,9 @@ const setup = (props?: Partial<QueryLimitSelectProps>, store?: Store) =>
|
||||
},
|
||||
);
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('QueryLimitSelect', () => {
|
||||
it('renders current query limit size', () => {
|
||||
test('renders current query limit size', () => {
|
||||
const queryLimit = 10;
|
||||
const { getByText } = setup(
|
||||
{
|
||||
@@ -81,12 +82,12 @@ describe('QueryLimitSelect', () => {
|
||||
expect(getByText(queryLimit)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders default query limit for initial queryEditor', () => {
|
||||
test('renders default query limit for initial queryEditor', () => {
|
||||
const { getByText } = setup({}, mockStore(initialState));
|
||||
expect(getByText(defaultQueryLimit)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders queryLimit from unsavedQueryEditor', () => {
|
||||
test('renders queryLimit from unsavedQueryEditor', () => {
|
||||
const queryLimit = 10000;
|
||||
const { getByText } = setup(
|
||||
{},
|
||||
@@ -104,7 +105,7 @@ describe('QueryLimitSelect', () => {
|
||||
expect(getByText(convertToNumWithSpaces(queryLimit))).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders dropdown select', async () => {
|
||||
test('renders dropdown select', async () => {
|
||||
const { baseElement, getAllByRole, getByRole } = setup(
|
||||
{ maxRow: 50000 },
|
||||
mockStore(initialState),
|
||||
@@ -126,7 +127,7 @@ describe('QueryLimitSelect', () => {
|
||||
expect(actualLabels).toEqual(expectedLabels);
|
||||
});
|
||||
|
||||
it('renders dropdown select correctly when maxRow is less than 10', async () => {
|
||||
test('renders dropdown select correctly when maxRow is less than 10', async () => {
|
||||
const { baseElement, getAllByRole, getByRole } = setup(
|
||||
{ maxRow: 5 },
|
||||
mockStore(initialState),
|
||||
@@ -146,7 +147,7 @@ describe('QueryLimitSelect', () => {
|
||||
expect(actualLabels).toEqual(expectedLabels);
|
||||
});
|
||||
|
||||
it('renders dropdown select correctly when maxRow is a multiple of 10', async () => {
|
||||
test('renders dropdown select correctly when maxRow is a multiple of 10', async () => {
|
||||
const { baseElement, getAllByRole, getByRole } = setup(
|
||||
{ maxRow: 10000 },
|
||||
mockStore(initialState),
|
||||
@@ -168,7 +169,7 @@ describe('QueryLimitSelect', () => {
|
||||
expect(actualLabels).toEqual(expectedLabels);
|
||||
});
|
||||
|
||||
it('dispatches QUERY_EDITOR_SET_QUERY_LIMIT action on dropdown menu click', async () => {
|
||||
test('dispatches QUERY_EDITOR_SET_QUERY_LIMIT action on dropdown menu click', async () => {
|
||||
const store = mockStore(initialState);
|
||||
const expectedIndex = 1;
|
||||
const { baseElement, getAllByRole, getByRole } = setup({}, store);
|
||||
|
||||
@@ -29,6 +29,7 @@ const mockedProps = {
|
||||
latestQueryId: 'ryhMUZCGb',
|
||||
};
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('QueryTable', () => {
|
||||
test('is valid', () => {
|
||||
expect(isValidElement(<QueryTable displayLimit={100} />)).toBe(true);
|
||||
|
||||
@@ -150,6 +150,7 @@ const setup = (props?: any, store?: Store) =>
|
||||
...(store && { store }),
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('ResultSet', () => {
|
||||
beforeAll(() => {
|
||||
setupAGGridModules();
|
||||
|
||||
@@ -53,17 +53,17 @@ const setup = (props?: Partial<RunQueryActionButtonProps>, store?: Store) =>
|
||||
...(store && { store }),
|
||||
});
|
||||
|
||||
it('renders a single Button', () => {
|
||||
test('renders a single Button', () => {
|
||||
const { getByRole } = setup({}, mockStore(initialState));
|
||||
expect(getByRole('button')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders a label for Run Query', () => {
|
||||
test('renders a label for Run Query', () => {
|
||||
const { getByText } = setup({}, mockStore(initialState));
|
||||
expect(getByText('Run')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders a label for Selected Query', () => {
|
||||
test('renders a label for Selected Query', () => {
|
||||
const { getByText } = setup(
|
||||
{},
|
||||
mockStore({
|
||||
@@ -80,7 +80,7 @@ it('renders a label for Selected Query', () => {
|
||||
expect(getByText('Run selection')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('disable button when sql from unsaved changes is empty', () => {
|
||||
test('disable button when sql from unsaved changes is empty', () => {
|
||||
const { getByRole } = setup(
|
||||
{},
|
||||
mockStore({
|
||||
@@ -98,7 +98,7 @@ it('disable button when sql from unsaved changes is empty', () => {
|
||||
expect(button).toBeDisabled();
|
||||
});
|
||||
|
||||
it('disable button when selectedText only contains blank contents', () => {
|
||||
test('disable button when selectedText only contains blank contents', () => {
|
||||
const { getByRole } = setup(
|
||||
{},
|
||||
mockStore({
|
||||
@@ -116,7 +116,7 @@ it('disable button when selectedText only contains blank contents', () => {
|
||||
expect(button).toBeDisabled();
|
||||
});
|
||||
|
||||
it('enable default button for unrelated unsaved changes', () => {
|
||||
test('enable default button for unrelated unsaved changes', () => {
|
||||
const { getByRole } = setup(
|
||||
{},
|
||||
mockStore({
|
||||
@@ -134,7 +134,7 @@ it('enable default button for unrelated unsaved changes', () => {
|
||||
expect(button).toBeEnabled();
|
||||
});
|
||||
|
||||
it('dispatch runQuery on click', async () => {
|
||||
test('dispatch runQuery on click', async () => {
|
||||
const runQuery = jest.fn();
|
||||
const { getByRole } = setup({ runQuery }, mockStore(initialState));
|
||||
const button = getByRole('button');
|
||||
@@ -143,7 +143,7 @@ it('dispatch runQuery on click', async () => {
|
||||
await waitFor(() => expect(runQuery).toHaveBeenCalledTimes(1));
|
||||
});
|
||||
|
||||
it('dispatch stopQuery on click while running state', async () => {
|
||||
test('dispatch stopQuery on click while running state', async () => {
|
||||
const stopQuery = jest.fn();
|
||||
const { getByRole } = setup(
|
||||
{ queryState: 'running', stopQuery },
|
||||
|
||||
@@ -24,6 +24,7 @@ const overlayMenu = (
|
||||
<Menu items={[{ label: 'Save dataset', key: 'save-dataset' }]} />
|
||||
);
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('SaveDatasetActionButton', () => {
|
||||
test('renders a split save button', async () => {
|
||||
render(
|
||||
|
||||
@@ -62,8 +62,9 @@ jest.mock('src/explore/exploreUtils/formData', () => ({
|
||||
postFormData: jest.fn(),
|
||||
}));
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('SaveDatasetModal', () => {
|
||||
it('renders a "Save as new" field', () => {
|
||||
test('renders a "Save as new" field', () => {
|
||||
render(<SaveDatasetModal {...mockedProps} />, { useRedux: true });
|
||||
|
||||
const saveRadioBtn = screen.getByRole('radio', {
|
||||
@@ -80,7 +81,7 @@ describe('SaveDatasetModal', () => {
|
||||
expect(inputFieldText).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders an "Overwrite existing" field', () => {
|
||||
test('renders an "Overwrite existing" field', () => {
|
||||
render(<SaveDatasetModal {...mockedProps} />, { useRedux: true });
|
||||
|
||||
const overwriteRadioBtn = screen.getByRole('radio', {
|
||||
@@ -96,20 +97,20 @@ describe('SaveDatasetModal', () => {
|
||||
expect(placeholderText).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders a close button', () => {
|
||||
test('renders a close button', () => {
|
||||
render(<SaveDatasetModal {...mockedProps} />, { useRedux: true });
|
||||
|
||||
expect(screen.getByRole('button', { name: /close/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders a save button when "Save as new" is selected', () => {
|
||||
test('renders a save button when "Save as new" is selected', () => {
|
||||
render(<SaveDatasetModal {...mockedProps} />, { useRedux: true });
|
||||
|
||||
// "Save as new" is selected when the modal opens by default
|
||||
expect(screen.getByRole('button', { name: /save/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders an overwrite button when "Overwrite existing" is selected', () => {
|
||||
test('renders an overwrite button when "Overwrite existing" is selected', () => {
|
||||
render(<SaveDatasetModal {...mockedProps} />, { useRedux: true });
|
||||
|
||||
// Click the overwrite radio button to reveal the overwrite confirmation and back buttons
|
||||
@@ -123,7 +124,7 @@ describe('SaveDatasetModal', () => {
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the overwrite button as disabled until an existing dataset is selected', async () => {
|
||||
test('renders the overwrite button as disabled until an existing dataset is selected', async () => {
|
||||
useSelectorMock.mockReturnValue({ ...user });
|
||||
render(<SaveDatasetModal {...mockedProps} />, { useRedux: true });
|
||||
|
||||
@@ -155,7 +156,7 @@ describe('SaveDatasetModal', () => {
|
||||
expect(overwriteConfirmationBtn).toBeEnabled();
|
||||
});
|
||||
|
||||
it('renders a confirm overwrite screen when overwrite is clicked', async () => {
|
||||
test('renders a confirm overwrite screen when overwrite is clicked', async () => {
|
||||
useSelectorMock.mockReturnValue({ ...user });
|
||||
render(<SaveDatasetModal {...mockedProps} />, { useRedux: true });
|
||||
|
||||
@@ -196,7 +197,7 @@ describe('SaveDatasetModal', () => {
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('sends the schema when creating the dataset', async () => {
|
||||
test('sends the schema when creating the dataset', async () => {
|
||||
const dummyDispatch = jest.fn().mockResolvedValue({});
|
||||
useDispatchMock.mockReturnValue(dummyDispatch);
|
||||
useSelectorMock.mockReturnValue({ ...user });
|
||||
@@ -221,7 +222,7 @@ describe('SaveDatasetModal', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('sends the catalog when creating the dataset', async () => {
|
||||
test('sends the catalog when creating the dataset', async () => {
|
||||
const dummyDispatch = jest.fn().mockResolvedValue({});
|
||||
useDispatchMock.mockReturnValue(dummyDispatch);
|
||||
useSelectorMock.mockReturnValue({ ...user });
|
||||
@@ -252,12 +253,12 @@ describe('SaveDatasetModal', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('does not renders a checkbox button when template processing is disabled', () => {
|
||||
test('does not renders a checkbox button when template processing is disabled', () => {
|
||||
render(<SaveDatasetModal {...mockedProps} />, { useRedux: true });
|
||||
expect(screen.queryByRole('checkbox')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders a checkbox button when template processing is enabled', () => {
|
||||
test('renders a checkbox button when template processing is enabled', () => {
|
||||
// @ts-ignore
|
||||
global.featureFlags = {
|
||||
[FeatureFlag.EnableTemplateProcessing]: true,
|
||||
@@ -266,7 +267,7 @@ describe('SaveDatasetModal', () => {
|
||||
expect(screen.getByRole('checkbox')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('correctly includes template parameters when template processing is enabled', () => {
|
||||
test('correctly includes template parameters when template processing is enabled', () => {
|
||||
// @ts-ignore
|
||||
global.featureFlags = {
|
||||
[FeatureFlag.EnableTemplateProcessing]: true,
|
||||
@@ -301,7 +302,7 @@ describe('SaveDatasetModal', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('correctly excludes template parameters when template processing is enabled', () => {
|
||||
test('correctly excludes template parameters when template processing is enabled', () => {
|
||||
// @ts-ignore
|
||||
global.featureFlags = {
|
||||
[FeatureFlag.EnableTemplateProcessing]: true,
|
||||
|
||||
@@ -64,8 +64,9 @@ const splitSaveBtnProps = {
|
||||
const middlewares = [thunk];
|
||||
const mockStore = configureStore(middlewares);
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('SavedQuery', () => {
|
||||
it('doesnt render save button when allows_virtual_table_explore is undefined', async () => {
|
||||
test('doesnt render save button when allows_virtual_table_explore is undefined', async () => {
|
||||
const noRenderProps = {
|
||||
...mockedProps,
|
||||
database: {
|
||||
@@ -84,7 +85,7 @@ describe('SavedQuery', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('renders a non-split save button when allows_virtual_table_explore is not enabled', () => {
|
||||
test('renders a non-split save button when allows_virtual_table_explore is not enabled', () => {
|
||||
render(<SaveQuery {...mockedProps} />, {
|
||||
useRedux: true,
|
||||
store: mockStore(mockState),
|
||||
@@ -95,7 +96,7 @@ describe('SavedQuery', () => {
|
||||
expect(saveBtn).toBeVisible();
|
||||
});
|
||||
|
||||
it('renders a save query modal when user clicks save button', () => {
|
||||
test('renders a save query modal when user clicks save button', () => {
|
||||
render(<SaveQuery {...mockedProps} />, {
|
||||
useRedux: true,
|
||||
store: mockStore(mockState),
|
||||
@@ -111,7 +112,7 @@ describe('SavedQuery', () => {
|
||||
expect(saveQueryModalHeader).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the save query modal UI', () => {
|
||||
test('renders the save query modal UI', () => {
|
||||
render(<SaveQuery {...mockedProps} />, {
|
||||
useRedux: true,
|
||||
store: mockStore(mockState),
|
||||
@@ -146,7 +147,7 @@ describe('SavedQuery', () => {
|
||||
expect(cancelBtn).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders a "save as new" and "update" button if query already exists', () => {
|
||||
test('renders a "save as new" and "update" button if query already exists', () => {
|
||||
render(<SaveQuery {...mockedProps} />, {
|
||||
useRedux: true,
|
||||
store: mockStore({
|
||||
@@ -171,7 +172,7 @@ describe('SavedQuery', () => {
|
||||
expect(updateBtn).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders a split save button when allows_virtual_table_explore is enabled', async () => {
|
||||
test('renders a split save button when allows_virtual_table_explore is enabled', async () => {
|
||||
render(<SaveQuery {...splitSaveBtnProps} />, {
|
||||
useRedux: true,
|
||||
store: mockStore(mockState),
|
||||
@@ -186,7 +187,7 @@ describe('SavedQuery', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('renders a save dataset modal when user clicks "save dataset" menu item', async () => {
|
||||
test('renders a save dataset modal when user clicks "save dataset" menu item', async () => {
|
||||
render(<SaveQuery {...splitSaveBtnProps} />, {
|
||||
useRedux: true,
|
||||
store: mockStore(mockState),
|
||||
@@ -205,7 +206,7 @@ describe('SavedQuery', () => {
|
||||
expect(saveDatasetHeader).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the save dataset modal UI', async () => {
|
||||
test('renders the save dataset modal UI', async () => {
|
||||
render(<SaveQuery {...splitSaveBtnProps} />, {
|
||||
useRedux: true,
|
||||
store: mockStore(mockState),
|
||||
@@ -246,7 +247,7 @@ describe('SavedQuery', () => {
|
||||
expect(overwritePlaceholderText).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('modal stays open while save is in progress and closes after completion', async () => {
|
||||
test('modal stays open while save is in progress and closes after completion', async () => {
|
||||
let resolveSave: () => void;
|
||||
const savePromise = new Promise<void>(resolve => {
|
||||
resolveSave = resolve;
|
||||
@@ -290,7 +291,7 @@ describe('SavedQuery', () => {
|
||||
expect(mockOnSave).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('handles save with a new tab that has no changes', async () => {
|
||||
test('handles save with a new tab that has no changes', async () => {
|
||||
const mockOnSave = jest.fn().mockResolvedValue(undefined);
|
||||
|
||||
// Mock state for a new tab with default SQL
|
||||
|
||||
@@ -76,6 +76,7 @@ const unsavedQueryEditor = {
|
||||
templateParams: '{ "my_value": "foo" }',
|
||||
};
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('ShareSqlLabQuery', () => {
|
||||
const storeQueryUrl = 'glob:*/api/v1/sqllab/permalink';
|
||||
const storeQueryMockId = 'ci39c3';
|
||||
@@ -94,6 +95,7 @@ describe('ShareSqlLabQuery', () => {
|
||||
|
||||
afterAll(() => fetchMock.reset());
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('via permalink api', () => {
|
||||
beforeAll(() => {
|
||||
mockedIsFeatureEnabled.mockImplementation(() => true);
|
||||
@@ -103,7 +105,7 @@ describe('ShareSqlLabQuery', () => {
|
||||
mockedIsFeatureEnabled.mockReset();
|
||||
});
|
||||
|
||||
it('calls storeQuery() with the query when getCopyUrl() is called', async () => {
|
||||
test('calls storeQuery() with the query when getCopyUrl() is called', async () => {
|
||||
await act(async () => {
|
||||
render(<ShareSqlLabQuery {...defaultProps} />, {
|
||||
useRedux: true,
|
||||
@@ -121,7 +123,7 @@ describe('ShareSqlLabQuery', () => {
|
||||
).toEqual(expected);
|
||||
});
|
||||
|
||||
it('calls storeQuery() with unsaved changes', async () => {
|
||||
test('calls storeQuery() with unsaved changes', async () => {
|
||||
await act(async () => {
|
||||
render(<ShareSqlLabQuery {...defaultProps} />, {
|
||||
useRedux: true,
|
||||
|
||||
@@ -149,6 +149,7 @@ const createStore = (initState: object) =>
|
||||
getDefaultMiddleware().concat(api.middleware, logAction),
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('SqlEditor', () => {
|
||||
beforeAll(() => {
|
||||
jest.setTimeout(30000);
|
||||
@@ -192,7 +193,7 @@ describe('SqlEditor', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('does not render SqlEditor if no db selected', async () => {
|
||||
test('does not render SqlEditor if no db selected', async () => {
|
||||
const queryEditor = initialState.sqlLab.queryEditors[2];
|
||||
const { findByText } = setup({ ...mockedProps, queryEditor }, store);
|
||||
expect(
|
||||
@@ -200,7 +201,7 @@ describe('SqlEditor', () => {
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders db unavailable message', async () => {
|
||||
test('renders db unavailable message', async () => {
|
||||
const queryEditor = initialState.sqlLab.queryEditors[1];
|
||||
const { findByText } = setup({ ...mockedProps, queryEditor }, store);
|
||||
expect(
|
||||
@@ -210,7 +211,7 @@ describe('SqlEditor', () => {
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('render a SqlEditorLeftBar', async () => {
|
||||
test('render a SqlEditorLeftBar', async () => {
|
||||
const { getByTestId, unmount } = setup(mockedProps, store);
|
||||
|
||||
await waitFor(
|
||||
@@ -222,7 +223,7 @@ describe('SqlEditor', () => {
|
||||
}, 15000);
|
||||
|
||||
// Update other similar tests with timeouts
|
||||
it('render an AceEditorWrapper', async () => {
|
||||
test('render an AceEditorWrapper', async () => {
|
||||
const { findByTestId, unmount } = setup(mockedProps, store);
|
||||
|
||||
await waitFor(
|
||||
@@ -233,7 +234,7 @@ describe('SqlEditor', () => {
|
||||
unmount();
|
||||
}, 15000);
|
||||
|
||||
it('skip rendering an AceEditorWrapper when the current tab is inactive', async () => {
|
||||
test('skip rendering an AceEditorWrapper when the current tab is inactive', async () => {
|
||||
const { findByTestId, queryByTestId } = setup(
|
||||
{
|
||||
...mockedProps,
|
||||
@@ -245,7 +246,7 @@ describe('SqlEditor', () => {
|
||||
expect(queryByTestId('react-ace')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('avoids rerendering EditorLeftBar and ResultSet while typing', async () => {
|
||||
test('avoids rerendering EditorLeftBar and ResultSet while typing', async () => {
|
||||
const { findByTestId } = setup(mockedProps, store);
|
||||
const editor = await findByTestId('react-ace');
|
||||
const sql = 'select *';
|
||||
@@ -260,7 +261,7 @@ describe('SqlEditor', () => {
|
||||
expect(ResultSet).toHaveBeenCalledTimes(renderCountForSouthPane);
|
||||
});
|
||||
|
||||
it('renders sql from unsaved change', async () => {
|
||||
test('renders sql from unsaved change', async () => {
|
||||
const expectedSql = 'SELECT updated_column\nFROM updated_table\nWHERE';
|
||||
store = createStore({
|
||||
...initialState,
|
||||
@@ -293,12 +294,12 @@ describe('SqlEditor', () => {
|
||||
expect(editor).toHaveValue(expectedSql);
|
||||
});
|
||||
|
||||
it('render a SouthPane', async () => {
|
||||
test('render a SouthPane', async () => {
|
||||
const { findByTestId } = setup(mockedProps, store);
|
||||
expect(await findByTestId('mock-result-set')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('runs query action with ctas false', async () => {
|
||||
test('runs query action with ctas false', async () => {
|
||||
store = createStore({
|
||||
...initialState,
|
||||
sqlLab: {
|
||||
@@ -338,7 +339,7 @@ describe('SqlEditor', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('render a Limit Dropdown', async () => {
|
||||
test('render a Limit Dropdown', async () => {
|
||||
const defaultQueryLimit = 101;
|
||||
const updatedProps = { ...mockedProps, defaultQueryLimit };
|
||||
const { findByText } = setup(updatedProps, store);
|
||||
@@ -346,7 +347,7 @@ describe('SqlEditor', () => {
|
||||
expect(await findByText('10 000')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders an Extension if provided', async () => {
|
||||
test('renders an Extension if provided', async () => {
|
||||
const extensionsRegistry = getExtensionsRegistry();
|
||||
|
||||
extensionsRegistry.set('sqleditor.extension.form', () => (
|
||||
@@ -360,6 +361,7 @@ describe('SqlEditor', () => {
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('with EstimateQueryCost enabled', () => {
|
||||
beforeEach(() => {
|
||||
mockIsFeatureEnabled.mockImplementation(
|
||||
@@ -370,7 +372,7 @@ describe('SqlEditor', () => {
|
||||
mockIsFeatureEnabled.mockClear();
|
||||
});
|
||||
|
||||
it('sends the catalog and schema to the endpoint', async () => {
|
||||
test('sends the catalog and schema to the endpoint', async () => {
|
||||
const estimateApi = 'http://localhost/api/v1/sqllab/estimate/';
|
||||
fetchMock.post(estimateApi, {});
|
||||
|
||||
@@ -436,6 +438,7 @@ describe('SqlEditor', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('with SqllabBackendPersistence enabled', () => {
|
||||
beforeEach(() => {
|
||||
mockIsFeatureEnabled.mockImplementation(
|
||||
@@ -446,7 +449,7 @@ describe('SqlEditor', () => {
|
||||
mockIsFeatureEnabled.mockClear();
|
||||
});
|
||||
|
||||
it('should render loading state when its Editor is not loaded', async () => {
|
||||
test('should render loading state when its Editor is not loaded', async () => {
|
||||
const switchTabApi = `glob:*/tabstateview/${defaultQueryEditor.id}/activate`;
|
||||
fetchMock.post(switchTabApi, {});
|
||||
const { getByTestId } = setup(
|
||||
|
||||
@@ -168,6 +168,9 @@ const StyledToolbar = styled.div`
|
||||
|
||||
const StyledSidebar = styled.div`
|
||||
padding: ${({ theme }) => theme.sizeUnit * 2.5}px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
const StyledSqlEditor = styled.div`
|
||||
|
||||
@@ -47,7 +47,6 @@ import TableElement from '../TableElement';
|
||||
|
||||
export interface SqlEditorLeftBarProps {
|
||||
queryEditorId: string;
|
||||
height?: number;
|
||||
database?: DatabaseObject;
|
||||
}
|
||||
|
||||
@@ -72,7 +71,6 @@ const LeftBarStyles = styled.div`
|
||||
const SqlEditorLeftBar = ({
|
||||
database,
|
||||
queryEditorId,
|
||||
height = 500,
|
||||
}: SqlEditorLeftBarProps) => {
|
||||
const allSelectedTables = useSelector<SqlLabRootState, Table[]>(
|
||||
({ sqlLab }) =>
|
||||
@@ -84,6 +82,7 @@ const SqlEditorLeftBar = ({
|
||||
'dbId',
|
||||
'catalog',
|
||||
'schema',
|
||||
'tabViewId',
|
||||
]);
|
||||
|
||||
const [_emptyResultsWithSearch, setEmptyResultsWithSearch] = useState(false);
|
||||
@@ -170,7 +169,6 @@ const SqlEditorLeftBar = ({
|
||||
};
|
||||
|
||||
const shouldShowReset = window.location.search === '?reset=1';
|
||||
const tableMetaDataHeight = height - 130; // 130 is the height of the selects above
|
||||
|
||||
const handleCatalogChange = useCallback(
|
||||
(catalog: string | null) => {
|
||||
@@ -227,22 +225,16 @@ const SqlEditorLeftBar = ({
|
||||
/>
|
||||
<div className="divider" />
|
||||
<StyledScrollbarContainer>
|
||||
<div
|
||||
css={css`
|
||||
height: ${tableMetaDataHeight}px;
|
||||
`}
|
||||
>
|
||||
{tables.map(table => (
|
||||
<TableElement
|
||||
table={table}
|
||||
key={table.id}
|
||||
activeKey={tables
|
||||
.filter(({ expanded }) => expanded)
|
||||
.map(({ id }) => id)}
|
||||
onChange={onToggleTable}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{tables.map(table => (
|
||||
<TableElement
|
||||
table={table}
|
||||
key={table.id}
|
||||
activeKey={tables
|
||||
.filter(({ expanded }) => expanded)
|
||||
.map(({ id }) => id)}
|
||||
onChange={onToggleTable}
|
||||
/>
|
||||
))}
|
||||
</StyledScrollbarContainer>
|
||||
{shouldShowReset && (
|
||||
<Button
|
||||
|
||||
@@ -56,15 +56,16 @@ const setup = (queryEditor: QueryEditor, store?: Store) =>
|
||||
...(store && { store }),
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('SqlEditorTabHeader', () => {
|
||||
it('renders name', () => {
|
||||
test('renders name', () => {
|
||||
const { queryByText } = setup(defaultQueryEditor, mockStore(initialState));
|
||||
expect(queryByText(defaultQueryEditor.name)).toBeInTheDocument();
|
||||
expect(queryByText(extraQueryEditor1.name)).not.toBeInTheDocument();
|
||||
expect(queryByText(extraQueryEditor2.name)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders name from unsaved changes', () => {
|
||||
test('renders name from unsaved changes', () => {
|
||||
const expectedTitle = 'updated title';
|
||||
const { queryByText } = setup(
|
||||
defaultQueryEditor,
|
||||
@@ -85,7 +86,7 @@ describe('SqlEditorTabHeader', () => {
|
||||
expect(queryByText(extraQueryEditor2.name)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders current name for unrelated unsaved changes', () => {
|
||||
test('renders current name for unrelated unsaved changes', () => {
|
||||
const unrelatedTitle = 'updated title';
|
||||
const { queryByText } = setup(
|
||||
defaultQueryEditor,
|
||||
@@ -106,6 +107,7 @@ describe('SqlEditorTabHeader', () => {
|
||||
expect(queryByText(extraQueryEditor2.name)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('with dropdown menus', () => {
|
||||
let store = mockStore();
|
||||
beforeEach(async () => {
|
||||
@@ -116,7 +118,7 @@ describe('SqlEditorTabHeader', () => {
|
||||
userEvent.click(dropdown);
|
||||
});
|
||||
|
||||
it('should dispatch removeQueryEditor action', async () => {
|
||||
test('should dispatch removeQueryEditor action', async () => {
|
||||
await waitFor(() =>
|
||||
expect(screen.getByTestId('close-tab-menu-option')).toBeInTheDocument(),
|
||||
);
|
||||
@@ -132,7 +134,7 @@ describe('SqlEditorTabHeader', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should dispatch queryEditorSetTitle action', async () => {
|
||||
test('should dispatch queryEditorSetTitle action', async () => {
|
||||
await waitFor(() =>
|
||||
expect(screen.getByTestId('close-tab-menu-option')).toBeInTheDocument(),
|
||||
);
|
||||
@@ -155,7 +157,7 @@ describe('SqlEditorTabHeader', () => {
|
||||
mockPrompt.mockClear();
|
||||
});
|
||||
|
||||
it('should dispatch toggleLeftBar action', async () => {
|
||||
test('should dispatch toggleLeftBar action', async () => {
|
||||
await waitFor(() =>
|
||||
expect(screen.getByTestId('close-tab-menu-option')).toBeInTheDocument(),
|
||||
);
|
||||
@@ -173,7 +175,7 @@ describe('SqlEditorTabHeader', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should dispatch removeAllOtherQueryEditors action', async () => {
|
||||
test('should dispatch removeAllOtherQueryEditors action', async () => {
|
||||
await waitFor(() =>
|
||||
expect(screen.getByTestId('close-tab-menu-option')).toBeInTheDocument(),
|
||||
);
|
||||
@@ -194,7 +196,7 @@ describe('SqlEditorTabHeader', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should dispatch cloneQueryToNewTab action', async () => {
|
||||
test('should dispatch cloneQueryToNewTab action', async () => {
|
||||
await waitFor(() =>
|
||||
expect(screen.getByTestId('close-tab-menu-option')).toBeInTheDocument(),
|
||||
);
|
||||
|
||||
@@ -56,6 +56,7 @@ afterEach(() => {
|
||||
pathStub.mockReset();
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('componentDidMount', () => {
|
||||
let uriStub = jest.spyOn(URI.prototype, 'search');
|
||||
let replaceState = jest.spyOn(window.history, 'replaceState');
|
||||
|
||||
@@ -22,6 +22,8 @@ import { FeatureFlag, isFeatureEnabled } from '@superset-ui/core';
|
||||
import TableElement, { Column } from 'src/SqlLab/components/TableElement';
|
||||
import { table, initialState } from 'src/SqlLab/fixtures';
|
||||
import { render, waitFor, fireEvent } from 'spec/helpers/testing-library';
|
||||
import * as sqlLabActions from 'src/SqlLab/actions/sqlLab';
|
||||
import { QueryEditor } from 'src/SqlLab/types';
|
||||
|
||||
jest.mock('@superset-ui/core', () => ({
|
||||
...jest.requireActual('@superset-ui/core'),
|
||||
@@ -79,6 +81,27 @@ const mockedProps = {
|
||||
activeKey: [table.id],
|
||||
};
|
||||
|
||||
const createStateWithQueryEditor = (queryEditor: Partial<QueryEditor>) => ({
|
||||
...initialState,
|
||||
sqlLab: {
|
||||
...initialState.sqlLab,
|
||||
queryEditors: [queryEditor],
|
||||
},
|
||||
});
|
||||
|
||||
const setupSyncTableTest = () => {
|
||||
const spy = jest.spyOn(sqlLabActions, 'syncTable');
|
||||
mockedIsFeatureEnabled.mockImplementation(
|
||||
featureFlag => featureFlag === FeatureFlag.SqllabBackendPersistence,
|
||||
);
|
||||
fetchMock.post(
|
||||
updateTableSchemaEndpoint,
|
||||
{ id: 100 },
|
||||
{ overwriteRoutes: true },
|
||||
);
|
||||
return spy;
|
||||
};
|
||||
|
||||
test('renders', () => {
|
||||
expect(isValidElement(<TableElement table={table} />)).toBe(true);
|
||||
});
|
||||
@@ -207,3 +230,212 @@ test('refreshes table metadata when triggered', async () => {
|
||||
expect(fetchMock.calls(updateTableSchemaEndpoint)).toHaveLength(1),
|
||||
);
|
||||
});
|
||||
|
||||
test('calls syncTable with valid backend ID when query editor has tabViewId', async () => {
|
||||
const syncTableSpy = setupSyncTableTest();
|
||||
const testTable = {
|
||||
...table,
|
||||
initialized: false,
|
||||
queryEditorId: 'temp-id-123',
|
||||
};
|
||||
|
||||
const state = createStateWithQueryEditor({
|
||||
id: 'temp-id-123',
|
||||
tabViewId: '42',
|
||||
inLocalStorage: false,
|
||||
name: 'Test Editor',
|
||||
});
|
||||
|
||||
render(<TableElement table={testTable} activeKey={[testTable.id]} />, {
|
||||
useRedux: true,
|
||||
initialState: state,
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(syncTableSpy).toHaveBeenCalledWith(
|
||||
expect.any(Object),
|
||||
expect.any(Object),
|
||||
'42', // finalQueryEditorId
|
||||
);
|
||||
});
|
||||
|
||||
syncTableSpy.mockRestore();
|
||||
});
|
||||
|
||||
test('does not call syncTable when query editor is in localStorage', async () => {
|
||||
const syncTableSpy = setupSyncTableTest();
|
||||
const testTable = {
|
||||
...table,
|
||||
initialized: false,
|
||||
queryEditorId: 'local-id',
|
||||
};
|
||||
|
||||
const state = createStateWithQueryEditor({
|
||||
id: 'local-id',
|
||||
tabViewId: undefined,
|
||||
inLocalStorage: true,
|
||||
name: 'Local Editor',
|
||||
});
|
||||
|
||||
render(<TableElement table={testTable} activeKey={[testTable.id]} />, {
|
||||
useRedux: true,
|
||||
initialState: state,
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(fetchMock.calls(getTableMetadataEndpoint)).toHaveLength(1);
|
||||
});
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
expect(syncTableSpy).not.toHaveBeenCalled();
|
||||
|
||||
syncTableSpy.mockRestore();
|
||||
});
|
||||
|
||||
test('does not call syncTable with non-numeric queryEditorId', async () => {
|
||||
const syncTableSpy = setupSyncTableTest();
|
||||
const testTable = {
|
||||
...table,
|
||||
initialized: false,
|
||||
queryEditorId: 'not-a-number',
|
||||
};
|
||||
|
||||
const state = createStateWithQueryEditor({
|
||||
id: 'not-a-number',
|
||||
tabViewId: 'also-not-a-number',
|
||||
inLocalStorage: false,
|
||||
name: 'Invalid Editor',
|
||||
});
|
||||
|
||||
render(<TableElement table={testTable} activeKey={[testTable.id]} />, {
|
||||
useRedux: true,
|
||||
initialState: state,
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(fetchMock.calls(getTableMetadataEndpoint)).toHaveLength(1);
|
||||
});
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
expect(syncTableSpy).not.toHaveBeenCalled();
|
||||
|
||||
syncTableSpy.mockRestore();
|
||||
});
|
||||
|
||||
test('does not call syncTable for already initialized tables', async () => {
|
||||
const syncTableSpy = setupSyncTableTest();
|
||||
const testTable = {
|
||||
...table,
|
||||
initialized: true, // Already initialized
|
||||
queryEditorId: '789',
|
||||
};
|
||||
|
||||
const state = createStateWithQueryEditor({
|
||||
id: '789',
|
||||
tabViewId: '789',
|
||||
inLocalStorage: false,
|
||||
name: 'Initialized Editor',
|
||||
});
|
||||
|
||||
render(<TableElement table={testTable} activeKey={[testTable.id]} />, {
|
||||
useRedux: true,
|
||||
initialState: state,
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(fetchMock.calls(getTableMetadataEndpoint)).toHaveLength(1);
|
||||
});
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
expect(syncTableSpy).not.toHaveBeenCalled();
|
||||
|
||||
syncTableSpy.mockRestore();
|
||||
});
|
||||
|
||||
test('calls syncTable after query editor is migrated from localStorage', async () => {
|
||||
const syncTableSpy = setupSyncTableTest();
|
||||
const testTable = {
|
||||
...table,
|
||||
initialized: false,
|
||||
queryEditorId: 'temp-editor-id',
|
||||
};
|
||||
|
||||
// Start with editor in localStorage
|
||||
const localState = createStateWithQueryEditor({
|
||||
id: 'temp-editor-id',
|
||||
tabViewId: undefined,
|
||||
inLocalStorage: true,
|
||||
name: 'Temp Editor',
|
||||
});
|
||||
|
||||
const { rerender } = render(
|
||||
<TableElement table={testTable} activeKey={[testTable.id]} />,
|
||||
{
|
||||
useRedux: true,
|
||||
initialState: localState,
|
||||
},
|
||||
);
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
expect(syncTableSpy).not.toHaveBeenCalled();
|
||||
|
||||
const migratedState = createStateWithQueryEditor({
|
||||
id: 'temp-editor-id',
|
||||
tabViewId: '999',
|
||||
inLocalStorage: false,
|
||||
name: 'Temp Editor',
|
||||
});
|
||||
|
||||
rerender(<TableElement table={testTable} activeKey={[testTable.id]} />);
|
||||
|
||||
const { unmount } = render(
|
||||
<TableElement table={testTable} activeKey={[testTable.id]} />,
|
||||
{
|
||||
useRedux: true,
|
||||
initialState: migratedState,
|
||||
},
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(syncTableSpy).toHaveBeenCalledWith(
|
||||
expect.any(Object),
|
||||
expect.any(Object),
|
||||
'999',
|
||||
);
|
||||
});
|
||||
|
||||
unmount();
|
||||
syncTableSpy.mockRestore();
|
||||
});
|
||||
|
||||
test('passes numeric queryEditorId validation', async () => {
|
||||
const syncTableSpy = setupSyncTableTest();
|
||||
const testTable = {
|
||||
...table,
|
||||
initialized: false,
|
||||
queryEditorId: 'editor-123',
|
||||
};
|
||||
|
||||
const state = createStateWithQueryEditor({
|
||||
id: 'editor-123',
|
||||
tabViewId: '456',
|
||||
inLocalStorage: false,
|
||||
name: 'Valid Editor',
|
||||
});
|
||||
|
||||
render(<TableElement table={testTable} activeKey={[testTable.id]} />, {
|
||||
useRedux: true,
|
||||
initialState: state,
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(syncTableSpy).toHaveBeenCalled();
|
||||
const [, , finalQueryEditorId] = syncTableSpy.mock.calls[0];
|
||||
// Verify it's a valid numeric string
|
||||
expect(Number.isNaN(Number(finalQueryEditorId))).toBe(false);
|
||||
expect(typeof finalQueryEditorId).toBe('string');
|
||||
expect(finalQueryEditorId).toMatch(/^\d+$/);
|
||||
});
|
||||
|
||||
syncTableSpy.mockRestore();
|
||||
});
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
* under the License.
|
||||
*/
|
||||
import { useState, useRef, useEffect } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import type { Table } from 'src/SqlLab/types';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import type { QueryEditor, SqlLabRootState, Table } from 'src/SqlLab/types';
|
||||
import {
|
||||
ButtonGroup,
|
||||
Card,
|
||||
@@ -103,6 +103,19 @@ const TableElement = ({ table, ...props }: TableElementProps) => {
|
||||
},
|
||||
{ skip: !expanded },
|
||||
);
|
||||
const tableData = {
|
||||
...tableMetadata,
|
||||
...tableExtendedMetadata,
|
||||
};
|
||||
const queryEditors = useSelector<SqlLabRootState, QueryEditor[]>(
|
||||
state => state.sqlLab.queryEditors,
|
||||
);
|
||||
const currentTable = { ...tableData, ...table };
|
||||
const { queryEditorId } = currentTable;
|
||||
const queryEditor = queryEditors.find(
|
||||
qe => qe.id === queryEditorId || qe.tabViewId === queryEditorId,
|
||||
);
|
||||
const currentQueryEditorId = queryEditor?.tabViewId || queryEditorId;
|
||||
|
||||
useEffect(() => {
|
||||
if (hasMetadataError || hasExtendedMetadataError) {
|
||||
@@ -112,16 +125,16 @@ const TableElement = ({ table, ...props }: TableElementProps) => {
|
||||
}
|
||||
}, [hasMetadataError, hasExtendedMetadataError, dispatch]);
|
||||
|
||||
const tableData = {
|
||||
...tableMetadata,
|
||||
...tableExtendedMetadata,
|
||||
};
|
||||
|
||||
// TODO: migrate syncTable logic by SIP-93
|
||||
const syncTableMetadata = useEffectEvent(() => {
|
||||
const { initialized } = table;
|
||||
if (!initialized) {
|
||||
dispatch(syncTable(table, tableData));
|
||||
// if not a valid number, wait for backend to assign one
|
||||
const hasFinalQueryEditorId =
|
||||
currentQueryEditorId &&
|
||||
!Number.isNaN(Number(currentQueryEditorId)) &&
|
||||
currentTable.queryEditorId !== currentQueryEditorId;
|
||||
if (!initialized && hasFinalQueryEditorId) {
|
||||
dispatch(syncTable(currentTable, tableData, currentQueryEditorId));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -129,7 +142,12 @@ const TableElement = ({ table, ...props }: TableElementProps) => {
|
||||
if (isMetadataSuccess && isExtraMetadataSuccess) {
|
||||
syncTableMetadata();
|
||||
}
|
||||
}, [isMetadataSuccess, isExtraMetadataSuccess, syncTableMetadata]);
|
||||
}, [
|
||||
isMetadataSuccess,
|
||||
isExtraMetadataSuccess,
|
||||
currentQueryEditorId,
|
||||
syncTableMetadata,
|
||||
]);
|
||||
|
||||
const [sortColumns, setSortColumns] = useState(false);
|
||||
const [hovered, setHovered] = useState(false);
|
||||
|
||||
@@ -135,6 +135,7 @@ test('renders preview', async () => {
|
||||
);
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('table actions', () => {
|
||||
test('refreshes table metadata when triggered', async () => {
|
||||
const { getByRole, getByText } = render(<TablePreview {...mockedProps} />, {
|
||||
|
||||
@@ -64,13 +64,14 @@ const setup = (
|
||||
},
|
||||
);
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('TemplateParamsEditor', () => {
|
||||
it('should render with a title', () => {
|
||||
test('should render with a title', () => {
|
||||
const { container } = setup();
|
||||
expect(container.querySelector('div[role="button"]')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should open a modal with the ace editor', async () => {
|
||||
test('should open a modal with the ace editor', async () => {
|
||||
const { container, getByTestId } = setup();
|
||||
fireEvent.click(getByText(container, 'Parameters'));
|
||||
await waitFor(() => {
|
||||
@@ -78,7 +79,7 @@ describe('TemplateParamsEditor', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('renders templateParams', async () => {
|
||||
test('renders templateParams', async () => {
|
||||
const { container, getByTestId } = setup();
|
||||
fireEvent.click(getByText(container, 'Parameters'));
|
||||
await waitFor(() => {
|
||||
@@ -89,7 +90,7 @@ describe('TemplateParamsEditor', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('renders code from unsaved changes', async () => {
|
||||
test('renders code from unsaved changes', async () => {
|
||||
const expectedCode = 'custom code value';
|
||||
const { container, getByTestId } = setup(
|
||||
{},
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user