Compare commits
196 Commits
fix/check-
...
yousoph-ec
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a05c06b3a | ||
|
|
62dc237014 | ||
|
|
823eb905d3 | ||
|
|
966e97989b | ||
|
|
8b0e63b58c | ||
|
|
64dae07675 | ||
|
|
e56883baef | ||
|
|
a62bf2b0bb | ||
|
|
01224007da | ||
|
|
d1e9a5df06 | ||
|
|
48530cb888 | ||
|
|
676979643f | ||
|
|
21e62d594e | ||
|
|
5bad4f55fb | ||
|
|
17a5f69339 | ||
|
|
d690aa7eb4 | ||
|
|
d6c458abd4 | ||
|
|
c233bf6171 | ||
|
|
992f561ab9 | ||
|
|
d7fa9301cc | ||
|
|
958d4aa3de | ||
|
|
2a1dcb79e3 | ||
|
|
817814d4f6 | ||
|
|
1a7a14c357 | ||
|
|
85c4411041 | ||
|
|
a50de459ae | ||
|
|
6216e57490 | ||
|
|
cdddb99e9a | ||
|
|
803fed28b8 | ||
|
|
8074ae2e38 | ||
|
|
577085eece | ||
|
|
5d40d8aeac | ||
|
|
b4cb780e74 | ||
|
|
aebc6fbf34 | ||
|
|
9e749da93c | ||
|
|
2c7e418d7b | ||
|
|
6a1305fe53 | ||
|
|
726d83d758 | ||
|
|
6cebba49ca | ||
|
|
940779ad5f | ||
|
|
c59ab8bffd | ||
|
|
e2a8a88d36 | ||
|
|
0d9ecb7664 | ||
|
|
1d220f7172 | ||
|
|
af4dc3a9aa | ||
|
|
fa06989ed7 | ||
|
|
4d0cc1d7a6 | ||
|
|
d8b2c5872b | ||
|
|
86ba63b072 | ||
|
|
4c14e16e58 | ||
|
|
fe22e06011 | ||
|
|
9160da0d27 | ||
|
|
43a89f8710 | ||
|
|
a77fec68d4 | ||
|
|
e94465208f | ||
|
|
f2eee4ef46 | ||
|
|
7445105735 | ||
|
|
2392c8e624 | ||
|
|
39ad6b200f | ||
|
|
3363b48180 | ||
|
|
c9fb1bc10f | ||
|
|
658907a0a6 | ||
|
|
4a79896bb2 | ||
|
|
b0c5b061c5 | ||
|
|
c394405fc1 | ||
|
|
d2ae5fb275 | ||
|
|
460992d89b | ||
|
|
85935b0b88 | ||
|
|
fa168fcc8a | ||
|
|
a6ad0bf169 | ||
|
|
fed29b3017 | ||
|
|
24d76b4249 | ||
|
|
5ab8583cd0 | ||
|
|
e66fbc91c2 | ||
|
|
187bb416e7 | ||
|
|
cfb704dbeb | ||
|
|
e77f6ece92 | ||
|
|
785a08c7d5 | ||
|
|
d90d3a2dea | ||
|
|
6ee4d694bc | ||
|
|
006a1800be | ||
|
|
2fe6269c22 | ||
|
|
26ef4b7ed3 | ||
|
|
a7aa854968 | ||
|
|
db0c5b32da | ||
|
|
96ad20318d | ||
|
|
516bb19e10 | ||
|
|
2cc20d3284 | ||
|
|
3e3c5c36c3 | ||
|
|
eed7098093 | ||
|
|
1d1a0e6fec | ||
|
|
494c29f5bf | ||
|
|
ad7075d2aa | ||
|
|
3e1cfc6d69 | ||
|
|
fcf3f6c0d5 | ||
|
|
14ba666594 | ||
|
|
1c795418d2 | ||
|
|
6271272e60 | ||
|
|
2cf4a2c31f | ||
|
|
2adb6f64eb | ||
|
|
5a453fe95d | ||
|
|
245fffca79 | ||
|
|
372b50e19d | ||
|
|
d83b0c5ce3 | ||
|
|
f81821086a | ||
|
|
f67dd4a8f3 | ||
|
|
68fa8e2733 | ||
|
|
a60860c969 | ||
|
|
d023fe1703 | ||
|
|
547660dcc4 | ||
|
|
e934f2af92 | ||
|
|
cfb0b6e811 | ||
|
|
ff7dc53853 | ||
|
|
dce3317bc9 | ||
|
|
dc22b82d88 | ||
|
|
0250092378 | ||
|
|
4311a15eb2 | ||
|
|
b899556130 | ||
|
|
2f82236b29 | ||
|
|
5bde86785f | ||
|
|
69fbbfd7ce | ||
|
|
d3784879c2 | ||
|
|
ad5e3170dd | ||
|
|
aa710672ed | ||
|
|
8c80caefa3 | ||
|
|
8088c5d1de | ||
|
|
9b520312a1 | ||
|
|
9ac4711ac8 | ||
|
|
7593d2a164 | ||
|
|
d3c44e311e | ||
|
|
b5186d1c65 | ||
|
|
5b5dd01028 | ||
|
|
4aa4415d8f | ||
|
|
e667ceb6cf | ||
|
|
9aaa12c7d4 | ||
|
|
adfbbf1433 | ||
|
|
d7663a9a1c | ||
|
|
7290d3c452 | ||
|
|
d7beffcec1 | ||
|
|
f018b67895 | ||
|
|
5e2c6d8c9e | ||
|
|
b305c8681c | ||
|
|
d578fa1949 | ||
|
|
14d28c34fd | ||
|
|
c06aee8513 | ||
|
|
d0ef19953a | ||
|
|
3745e37182 | ||
|
|
4b17ac2629 | ||
|
|
4a21a5365f | ||
|
|
9459bc7bf4 | ||
|
|
cb53745d43 | ||
|
|
9e91ae8cff | ||
|
|
5b5f23d127 | ||
|
|
8173cfe9e3 | ||
|
|
586de12a05 | ||
|
|
d6188374b4 | ||
|
|
2edae162f0 | ||
|
|
e80207218b | ||
|
|
76955017eb | ||
|
|
5325b87e73 | ||
|
|
e76318633e | ||
|
|
c2725e86f3 | ||
|
|
2f605724e7 | ||
|
|
ebb02d0ecf | ||
|
|
319b8a1124 | ||
|
|
2be971ce77 | ||
|
|
812f4ae080 | ||
|
|
af8d15fdfc | ||
|
|
673634f7af | ||
|
|
41a22d7918 | ||
|
|
28239c18d4 | ||
|
|
6205afbaa0 | ||
|
|
dc1c0f6ba1 | ||
|
|
ad73395c89 | ||
|
|
867e173427 | ||
|
|
c90c8612ad | ||
|
|
b14cca15f6 | ||
|
|
9d4384e49e | ||
|
|
d8dd2d99b3 | ||
|
|
dbe26d81ce | ||
|
|
a17b0840ef | ||
|
|
98eaaaa6d6 | ||
|
|
cb74438865 | ||
|
|
e77fb5e3fc | ||
|
|
1ac113fd44 | ||
|
|
6bfdee98cd | ||
|
|
de45f3a928 | ||
|
|
2ec53c0694 | ||
|
|
d23b0cad92 | ||
|
|
e585406fff | ||
|
|
957b298ae1 | ||
|
|
f29d82b3b1 | ||
|
|
3f550f166f | ||
|
|
86eb6176d1 | ||
|
|
4244ae87bf | ||
|
|
dbe8a877a2 |
4
.github/CODEOWNERS
vendored
@@ -36,6 +36,10 @@
|
||||
**/*.geojson @villebro @rusackas
|
||||
/superset-frontend/plugins/legacy-plugin-chart-country-map/ @villebro @rusackas
|
||||
|
||||
# Notify translation maintainers of changes to translations
|
||||
|
||||
/superset/translations/ @sfirke
|
||||
|
||||
# Notify PMC members of changes to extension-related files
|
||||
|
||||
/docs/developer_portal/extensions/ @michael-s-molina @villebro @rusackas
|
||||
|
||||
4
.github/workflows/check-python-deps.yml
vendored
@@ -8,6 +8,10 @@ on:
|
||||
pull_request:
|
||||
types: [synchronize, opened, reopened, ready_for_review]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
# cancel previous workflow jobs for PRs
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
|
||||
10
.github/workflows/claude.yml
vendored
@@ -17,13 +17,12 @@ jobs:
|
||||
steps:
|
||||
- name: Check if user is allowed
|
||||
id: check
|
||||
env:
|
||||
COMMENTER: ${{ github.event.comment.user.login }}
|
||||
run: |
|
||||
# List of allowed users
|
||||
ALLOWED_USERS="mistercrunch,rusackas"
|
||||
|
||||
# Get the commenter's username
|
||||
COMMENTER="${{ github.event.comment.user.login }}"
|
||||
|
||||
echo "Checking permissions for user: $COMMENTER"
|
||||
|
||||
# Check if user is in allowed list
|
||||
@@ -45,9 +44,12 @@ jobs:
|
||||
steps:
|
||||
- name: Comment access denied
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
COMMENTER_LOGIN: ${{ github.event.comment.user.login || github.event.review.user.login || github.event.issue.user.login }}
|
||||
with:
|
||||
script: |
|
||||
const message = `👋 Hi @${{ github.event.comment.user.login || github.event.review.user.login || github.event.issue.user.login }}!
|
||||
const commenter = process.env.COMMENTER_LOGIN;
|
||||
const message = `👋 Hi @${commenter}!
|
||||
|
||||
Thanks for trying to use Claude Code, but currently only certain team members have access to this feature.
|
||||
|
||||
|
||||
4
.github/workflows/codeql-analysis.yml
vendored
@@ -41,7 +41,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v4
|
||||
uses: github/codeql-action/init@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -53,6 +53,6 @@ jobs:
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend
|
||||
uses: github/codeql-action/analyze@v4
|
||||
uses: github/codeql-action/analyze@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
2
.github/workflows/dependency-review.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
- name: "Checkout Repository"
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- name: "Dependency Review"
|
||||
uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4.9.0
|
||||
uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0
|
||||
continue-on-error: true
|
||||
with:
|
||||
fail-on-severity: critical
|
||||
|
||||
4
.github/workflows/docker.yml
vendored
@@ -9,6 +9,10 @@ on:
|
||||
branches:
|
||||
- "master"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
3
.github/workflows/embedded-sdk-release.yml
vendored
@@ -6,6 +6,9 @@ on:
|
||||
- "master"
|
||||
- "[0-9].[0-9]*"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
config:
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
3
.github/workflows/embedded-sdk-test.yml
vendored
@@ -6,6 +6,9 @@ on:
|
||||
- "superset-embedded-sdk/**"
|
||||
types: [synchronize, opened, reopened, ready_for_review]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
# cancel previous workflow jobs for PRs
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
|
||||
2
.github/workflows/ephemeral-env-pr-close.yml
vendored
@@ -58,7 +58,7 @@ jobs:
|
||||
- name: Login to Amazon ECR
|
||||
if: steps.describe-services.outputs.active == 'true'
|
||||
id: login-ecr
|
||||
uses: aws-actions/amazon-ecr-login@19d944daaa35f0fa1d3f7f8af1d3f2e5de25c5b7 # v2
|
||||
uses: aws-actions/amazon-ecr-login@fa648b43de3d4d023bcb3f89ed6940096949c419 # v2
|
||||
|
||||
- name: Delete ECR image tag
|
||||
if: steps.describe-services.outputs.active == 'true'
|
||||
|
||||
8
.github/workflows/ephemeral-env.yml
vendored
@@ -199,7 +199,7 @@ jobs:
|
||||
|
||||
- name: Login to Amazon ECR
|
||||
id: login-ecr
|
||||
uses: aws-actions/amazon-ecr-login@19d944daaa35f0fa1d3f7f8af1d3f2e5de25c5b7 # v2
|
||||
uses: aws-actions/amazon-ecr-login@fa648b43de3d4d023bcb3f89ed6940096949c419 # v2
|
||||
|
||||
- name: Load, tag and push image to ECR
|
||||
id: push-image
|
||||
@@ -235,7 +235,7 @@ jobs:
|
||||
|
||||
- name: Login to Amazon ECR
|
||||
id: login-ecr
|
||||
uses: aws-actions/amazon-ecr-login@19d944daaa35f0fa1d3f7f8af1d3f2e5de25c5b7 # v2
|
||||
uses: aws-actions/amazon-ecr-login@fa648b43de3d4d023bcb3f89ed6940096949c419 # v2
|
||||
|
||||
- name: Check target image exists in ECR
|
||||
id: check-image
|
||||
@@ -265,7 +265,7 @@ jobs:
|
||||
|
||||
- name: Fill in the new image ID in the Amazon ECS task definition
|
||||
id: task-def
|
||||
uses: aws-actions/amazon-ecs-render-task-definition@77954e213ba1f9f9cb016b86a1d4f6fcdea0d57e # v1
|
||||
uses: aws-actions/amazon-ecs-render-task-definition@6853cfae8c3a7d978fbf68b5a55453395541dfbb # v1
|
||||
with:
|
||||
task-definition: .github/workflows/ecs-task-definition.json
|
||||
container-name: superset-ci
|
||||
@@ -300,7 +300,7 @@ jobs:
|
||||
--tags key=pr,value=$PR_NUMBER key=github_user,value=${{ github.actor }}
|
||||
- name: Deploy Amazon ECS task definition
|
||||
id: deploy-task
|
||||
uses: aws-actions/amazon-ecs-deploy-task-definition@fc8fc60f3a60ffd500fcb13b209c59d221ac8c8c # v2
|
||||
uses: aws-actions/amazon-ecs-deploy-task-definition@a310a830f5c14e583e35d84e4e1ec7dd177c3c9c # v2
|
||||
with:
|
||||
task-definition: ${{ steps.task-def.outputs.task-definition }}
|
||||
service: pr-${{ github.event.inputs.issue_number || github.event.pull_request.number }}-service
|
||||
|
||||
3
.github/workflows/generate-FOSSA-report.yml
vendored
@@ -6,6 +6,9 @@ on:
|
||||
- "master"
|
||||
- "[0-9].[0-9]*"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
config:
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
@@ -8,6 +8,9 @@ on:
|
||||
pull_request:
|
||||
types: [synchronize, opened, reopened, ready_for_review]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
|
||||
validate-all-ghas:
|
||||
|
||||
2
.github/workflows/latest-release-tag.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
|
||||
- name: Run latest-tag
|
||||
uses: ./.github/actions/latest-tag
|
||||
if: (! ${{ steps.latest-tag.outputs.SKIP_TAG }} )
|
||||
if: steps.latest-tag.outputs.SKIP_TAG != 'true'
|
||||
with:
|
||||
description: Superset latest release
|
||||
tag-name: latest
|
||||
|
||||
3
.github/workflows/license-check.yml
vendored
@@ -4,6 +4,9 @@ on:
|
||||
pull_request:
|
||||
types: [synchronize, opened, reopened, ready_for_review]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
# cancel previous workflow jobs for PRs
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
|
||||
4
.github/workflows/superset-app-cli.yml
vendored
@@ -8,6 +8,10 @@ on:
|
||||
pull_request:
|
||||
types: [synchronize, opened, reopened, ready_for_review]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
# cancel previous workflow jobs for PRs
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
|
||||
10
.github/workflows/superset-docs-deploy.yml
vendored
@@ -17,6 +17,16 @@ on:
|
||||
|
||||
workflow_dispatch: {}
|
||||
|
||||
# Serialize deploys: the action pushes to apache/superset-site without
|
||||
# rebasing, so concurrent runs race on the final push and the loser fails
|
||||
# with `! [rejected] asf-site -> asf-site (fetch first)`. Cancel any
|
||||
# in-progress run as soon as a newer one starts — the destination repo
|
||||
# isn't touched until the final push step, so canceling mid-build is safe,
|
||||
# and the freshest content always wins.
|
||||
concurrency:
|
||||
group: docs-deploy-asf-site
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
config:
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
@@ -8,6 +8,10 @@ on:
|
||||
pull_request:
|
||||
types: [synchronize, opened, reopened, ready_for_review]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
# cancel previous workflow jobs for PRs
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
|
||||
3
.github/workflows/superset-helm-lint.yml
vendored
@@ -6,6 +6,9 @@ on:
|
||||
paths:
|
||||
- "helm/**"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
# cancel previous workflow jobs for PRs
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
|
||||
@@ -54,6 +54,7 @@ jobs:
|
||||
SUPERSET_SECRET_KEY: not-a-secret
|
||||
run: |
|
||||
pytest --durations-min=0.5 --cov=superset/sql/ ./tests/unit_tests/sql/ --cache-clear --cov-fail-under=100
|
||||
pytest --durations-min=0.5 --cov=superset/semantic_layers/ ./tests/unit_tests/semantic_layers/ --cache-clear --cov-fail-under=100
|
||||
- name: Upload code coverage
|
||||
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v5
|
||||
with:
|
||||
|
||||
4
.github/workflows/superset-translations.yml
vendored
@@ -8,6 +8,10 @@ on:
|
||||
pull_request:
|
||||
types: [synchronize, opened, reopened, ready_for_review]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
# cancel previous workflow jobs for PRs
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
|
||||
3
.github/workflows/superset-websocket.yml
vendored
@@ -11,6 +11,9 @@ on:
|
||||
- "superset-websocket/**"
|
||||
types: [synchronize, opened, reopened, ready_for_review]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
# cancel previous workflow jobs for PRs
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
|
||||
@@ -43,6 +43,9 @@ _build/*
|
||||
_static/*
|
||||
.buildinfo
|
||||
searchindex.js
|
||||
# auto-generated by docs/scripts/convert-api-sidebar.mjs from openapi.json
|
||||
sidebar.js
|
||||
sidebar.ts
|
||||
# auto generated
|
||||
requirements/*
|
||||
# vendorized
|
||||
|
||||
@@ -56,8 +56,33 @@ def verify_sha512(filename: str) -> str:
|
||||
# Part 2: Verify RSA key - this is the same as running `gpg --verify {release}.asc {release}` and comparing the RSA key and email address against the KEYS file # noqa: E501
|
||||
|
||||
|
||||
KEYS_URL = "https://downloads.apache.org/superset/KEYS"
|
||||
|
||||
|
||||
def ensure_keys_imported() -> None:
|
||||
"""Import the Apache Superset KEYS file into the local GPG keyring.
|
||||
|
||||
Without this, `gpg --verify` returns "No public key" and the signature
|
||||
cannot actually be verified — only the key ID in the signature metadata
|
||||
is visible.
|
||||
"""
|
||||
try:
|
||||
keys = requests.get(KEYS_URL, timeout=30)
|
||||
except requests.RequestException as exc:
|
||||
print(f"Warning: could not fetch KEYS file for import: {exc}")
|
||||
return
|
||||
if keys.status_code != 200:
|
||||
print(f"Warning: could not fetch KEYS file (HTTP {keys.status_code})")
|
||||
return
|
||||
subprocess.run( # noqa: S603
|
||||
["gpg", "--import"], # noqa: S607
|
||||
input=keys.content,
|
||||
capture_output=True,
|
||||
)
|
||||
|
||||
|
||||
def get_gpg_info(filename: str) -> tuple[Optional[str], Optional[str]]:
|
||||
"""Run the GPG verify command and extract RSA key and email address."""
|
||||
"""Run the GPG verify command and extract RSA/EDDSA key and email address."""
|
||||
asc_filename = filename + ".asc"
|
||||
result = subprocess.run( # noqa: S603
|
||||
["gpg", "--verify", asc_filename, filename], # noqa: S607
|
||||
@@ -65,25 +90,50 @@ def get_gpg_info(filename: str) -> tuple[Optional[str], Optional[str]]:
|
||||
)
|
||||
output = result.stderr.decode()
|
||||
|
||||
# If no public key was available, import KEYS and retry so that
|
||||
# `Good signature from "Name <email>"` appears in the output.
|
||||
if "No public key" in output:
|
||||
ensure_keys_imported()
|
||||
result = subprocess.run( # noqa: S603
|
||||
["gpg", "--verify", asc_filename, filename], # noqa: S607
|
||||
capture_output=True, # noqa: S607
|
||||
)
|
||||
output = result.stderr.decode()
|
||||
|
||||
rsa_key = re.search(r"RSA key ([0-9A-F]+)", output)
|
||||
eddsa_key = re.search(r"EDDSA key ([0-9A-F]+)", output)
|
||||
email = re.search(r'issuer "([^"]+)"', output)
|
||||
|
||||
# Try multiple patterns — `Good signature from` is the most reliable
|
||||
# source of the email; `issuer` is a fallback for older gpg output.
|
||||
email_patterns = (
|
||||
r'Good signature from ".*?<([^>]+)>"',
|
||||
r'aka ".*?<([^>]+)>"',
|
||||
r'issuer "([^"]+)"',
|
||||
)
|
||||
email_result: Optional[str] = None
|
||||
for pattern in email_patterns:
|
||||
match = re.search(pattern, output)
|
||||
if match:
|
||||
email_result = match.group(1)
|
||||
break
|
||||
|
||||
rsa_key_result = rsa_key.group(1) if rsa_key else None
|
||||
eddsa_key_result = eddsa_key.group(1) if eddsa_key else None
|
||||
email_result = email.group(1) if email else None
|
||||
|
||||
key_result = rsa_key_result or eddsa_key_result
|
||||
|
||||
# Debugging:
|
||||
if key_result:
|
||||
print("RSA or EDDSA Key found")
|
||||
else:
|
||||
print("Warning: No RSA or EDDSA key found in GPG verification output.")
|
||||
if email_result:
|
||||
print("email found")
|
||||
print(f"Email found: {email_result}")
|
||||
else:
|
||||
print("Warning: No email address found in GPG verification output.")
|
||||
if "No public key" in output:
|
||||
print(
|
||||
"Hint: public key is not in your keyring. Import it with:\n"
|
||||
f" curl -s {KEYS_URL} | gpg --import"
|
||||
)
|
||||
|
||||
return key_result, email_result
|
||||
|
||||
|
||||
15
UPDATING.md
@@ -46,6 +46,13 @@ The Deck.gl MapBox chart's **Opacity**, **Default longitude**, **Default latitud
|
||||
|
||||
**To restore fit-to-data behavior:** Open the chart in Explore, clear the **Default longitude**, **Default latitude**, and **Zoom** fields in the Viewport section, and re-save the chart.
|
||||
|
||||
### Combined datasource list endpoint
|
||||
|
||||
Added a new combined datasource list endpoint at `GET /api/v1/datasource/` to serve datasets and semantic views in one response.
|
||||
|
||||
- The endpoint is available to users with at least one of `can_read` on `Dataset` or `SemanticView`.
|
||||
- Semantic views are included only when the `SEMANTIC_LAYERS` feature flag is enabled.
|
||||
- The endpoint enforces strict `order_column` validation and returns `400` for invalid sort columns.
|
||||
### ClickHouse minimum driver version bump
|
||||
|
||||
The minimum required version of `clickhouse-connect` has been raised to `>=0.13.0`. If you are using the ClickHouse connector, please upgrade your `clickhouse-connect` package. The `_mutate_label` workaround that appended hash suffixes to column aliases has also been removed, as it is no longer needed with modern versions of the driver.
|
||||
@@ -321,7 +328,7 @@ Note: Pillow is now a required dependency (previously optional) to support image
|
||||
- [33116](https://github.com/apache/superset/pull/33116) In Echarts Series charts (e.g. Line, Area, Bar, etc.) charts, the `x_axis_sort_series` and `x_axis_sort_series_ascending` form data items have been renamed with `x_axis_sort` and `x_axis_sort_asc`.
|
||||
There's a migration added that can potentially affect a significant number of existing charts.
|
||||
- [32317](https://github.com/apache/superset/pull/32317) The horizontal filter bar feature is now out of testing/beta development and its feature flag `HORIZONTAL_FILTER_BAR` has been removed.
|
||||
- [31590](https://github.com/apache/superset/pull/31590) Marks the begining of intricate work around supporting dynamic Theming, and breaks support for [THEME_OVERRIDES](https://github.com/apache/superset/blob/732de4ac7fae88e29b7f123b6cbb2d7cd411b0e4/superset/config.py#L671) in favor of a new theming system based on AntD V5. Likely this will be in disrepair until settling over the 5.x lifecycle.
|
||||
- [31590](https://github.com/apache/superset/pull/31590) Marks the beginning of intricate work around supporting dynamic Theming, and breaks support for [THEME_OVERRIDES](https://github.com/apache/superset/blob/732de4ac7fae88e29b7f123b6cbb2d7cd411b0e4/superset/config.py#L671) in favor of a new theming system based on AntD V5. Likely this will be in disrepair until settling over the 5.x lifecycle.
|
||||
- [32432](https://github.com/apache/superset/pull/32432) Moves the List Roles FAB view to the frontend and requires `FAB_ADD_SECURITY_API` to be enabled in the configuration and `superset init` to be executed.
|
||||
- [34319](https://github.com/apache/superset/pull/34319) Drill to Detail and Drill By is now supported in Embedded mode, and also with the `DASHBOARD_RBAC` FF. If you don't want to expose these features in Embedded / `DASHBOARD_RBAC`, make sure the roles used for Embedded / `DASHBOARD_RBAC`don't have the required permissions to perform D2D actions.
|
||||
|
||||
@@ -336,7 +343,7 @@ Note: Pillow is now a required dependency (previously optional) to support image
|
||||
- [31774](https://github.com/apache/superset/pull/31774): Fixes the spelling of the `USE-ANALAGOUS-COLORS` feature flag. Please update any scripts/configuration item to use the new/corrected `USE-ANALOGOUS-COLORS` flag spelling.
|
||||
- [31582](https://github.com/apache/superset/pull/31582) Removed the legacy Area, Bar, Event Flow, Heatmap, Histogram, Line, Sankey, and Sankey Loop charts. They were all automatically migrated to their ECharts counterparts with the exception of the Event Flow and Sankey Loop charts which were removed as they were not actively maintained and not widely used. If you were using the Event Flow or Sankey Loop charts, you will need to find an alternative solution.
|
||||
- [31198](https://github.com/apache/superset/pull/31198) Disallows by default the use of the following ClickHouse functions: "version", "currentDatabase", "hostName".
|
||||
- [29798](https://github.com/apache/superset/pull/29798) Since 3.1.0, the intial schedule for an alert or report was mistakenly offset by the specified timezone's relation to UTC. The initial schedule should now begin at the correct time.
|
||||
- [29798](https://github.com/apache/superset/pull/29798) Since 3.1.0, the initial schedule for an alert or report was mistakenly offset by the specified timezone's relation to UTC. The initial schedule should now begin at the correct time.
|
||||
- [30021](https://github.com/apache/superset/pull/30021) The `dev` layer in our Dockerfile no long includes firefox binaries, only Chromium to reduce bloat/docker-build-time.
|
||||
- [30099](https://github.com/apache/superset/pull/30099) Translations are no longer included in the default docker image builds. If your environment requires translations, you'll want to set the docker build arg `BUILD_TRANSLATIONS=true`.
|
||||
- [31262](https://github.com/apache/superset/pull/31262) NOTE: deprecated `pylint` in favor of `ruff` as our only python linter. Only affect development workflows positively (not the release itself). It should cover most important rules, be much faster, but some things linting rules that were enforced before may not be enforce in the exact same way as before.
|
||||
@@ -349,7 +356,7 @@ Note: Pillow is now a required dependency (previously optional) to support image
|
||||
- [25166](https://github.com/apache/superset/pull/25166) Changed the default configuration of `UPLOAD_FOLDER` from `/app/static/uploads/` to `/static/uploads/`. It also removed the unused `IMG_UPLOAD_FOLDER` and `IMG_UPLOAD_URL` configuration options.
|
||||
- [30284](https://github.com/apache/superset/pull/30284) Deprecated GLOBAL_ASYNC_QUERIES_REDIS_CONFIG in favor of the new GLOBAL_ASYNC_QUERIES_CACHE_BACKEND configuration. To leverage Redis Sentinel, set CACHE_TYPE to RedisSentinelCache, or use RedisCache for standalone Redis
|
||||
- [31961](https://github.com/apache/superset/pull/31961) Upgraded React from version 16.13.1 to 17.0.2. If you are using custom frontend extensions or plugins, you may need to update them to be compatible with React 17.
|
||||
- [31260](https://github.com/apache/superset/pull/31260) Docker images now use `uv pip install` instead of `pip install` to manage the python envrionment. Most docker-based deployments will be affected, whether you derive one of the published images, or have custom bootstrap script that install python libraries (drivers)
|
||||
- [31260](https://github.com/apache/superset/pull/31260) Docker images now use `uv pip install` instead of `pip install` to manage the python environment. Most docker-based deployments will be affected, whether you derive one of the published images, or have custom bootstrap script that install python libraries (drivers)
|
||||
|
||||
### Potential Downtime
|
||||
|
||||
@@ -426,7 +433,7 @@ Note: Pillow is now a required dependency (previously optional) to support image
|
||||
- [26462](https://github.com/apache/superset/issues/26462): Removes the Profile feature given that it's not actively maintained and not widely used.
|
||||
- [26377](https://github.com/apache/superset/pull/26377): Removes the deprecated Redirect API that supported short URLs used before the permalink feature.
|
||||
- [26329](https://github.com/apache/superset/issues/26329): Removes the deprecated `DASHBOARD_NATIVE_FILTERS` feature flag. The previous value of the feature flag was `True` and now the feature is permanently enabled.
|
||||
- [25510](https://github.com/apache/superset/pull/25510): Reenforces that any newly defined Python data format (other than epoch) must adhere to the ISO 8601 standard (enforced by way of validation at the API and database level) after a previous relaxation to include slashes in addition to dashes. From now on when specifying new columns, dataset owners will need to use a SQL expression instead to convert their string columns of the form %Y/%m/%d etc. to a `DATE`, `DATETIME`, etc. type.
|
||||
- [25510](https://github.com/apache/superset/pull/25510): Reinforces that any newly defined Python data format (other than epoch) must adhere to the ISO 8601 standard (enforced by way of validation at the API and database level) after a previous relaxation to include slashes in addition to dashes. From now on when specifying new columns, dataset owners will need to use a SQL expression instead to convert their string columns of the form %Y/%m/%d etc. to a `DATE`, `DATETIME`, etc. type.
|
||||
- [26372](https://github.com/apache/superset/issues/26372): Removes the deprecated `GENERIC_CHART_AXES` feature flag. The previous value of the feature flag was `True` and now the feature is permanently enabled.
|
||||
|
||||
### Potential Downtime
|
||||
|
||||
@@ -105,7 +105,13 @@ class CeleryConfig:
|
||||
|
||||
CELERY_CONFIG = CeleryConfig
|
||||
|
||||
FEATURE_FLAGS = {"ALERT_REPORTS": True, "DATASET_FOLDERS": True}
|
||||
FEATURE_FLAGS = {
|
||||
"ALERT_REPORTS": True,
|
||||
"DATASET_FOLDERS": True,
|
||||
"ENABLE_EXTENSIONS": True,
|
||||
"SEMANTIC_LAYERS": True,
|
||||
}
|
||||
EXTENSIONS_PATH = "/app/docker/extensions"
|
||||
ALERT_REPORTS_NOTIFICATION_DRY_RUN = True
|
||||
WEBDRIVER_BASEURL = f"http://superset_app{os.environ.get('SUPERSET_APP_ROOT', '/')}/" # When using docker compose baseurl should be http://superset_nginx{ENV{BASEPATH}}/ # noqa: E501
|
||||
# The base URL for the email report hyperlinks.
|
||||
|
||||
@@ -31,8 +31,9 @@ You are currently in the `/docs` subdirectory of the Apache Superset repository.
|
||||
├── superset-frontend/ # React/TypeScript frontend
|
||||
└── docs/ # Documentation site (YOU ARE HERE)
|
||||
├── docs/ # Main documentation content
|
||||
├── developer_portal/ # Developer guides (currently disabled)
|
||||
├── components/ # Component playground (currently disabled)
|
||||
├── admin_docs/ # Admin-focused guides
|
||||
├── developer_docs/ # Developer guides
|
||||
├── components/ # Component playground
|
||||
└── docusaurus.config.ts # Site configuration
|
||||
```
|
||||
|
||||
@@ -46,12 +47,19 @@ yarn build # Build production site
|
||||
yarn serve # Serve built site locally
|
||||
|
||||
# Version Management (USE THESE, NOT docusaurus commands)
|
||||
# The add scripts auto-run `generate:smart` so auto-gen content (database
|
||||
# pages, API reference, component pages) is fresh before snapshotting.
|
||||
# For maximum-detail databases.json, drop the `database-diagnostics`
|
||||
# artifact from Python-Integration CI at src/data/databases.json before
|
||||
# cutting. See README.md "Before You Cut".
|
||||
yarn version:add:docs <version> # Add new docs version
|
||||
yarn version:add:developer_portal <version> # Add developer portal version
|
||||
yarn version:add:admin_docs <version> # Add admin docs version
|
||||
yarn version:add:developer_docs <version> # Add developer docs version
|
||||
yarn version:add:components <version> # Add components version
|
||||
yarn version:remove:docs <version> # Remove docs version
|
||||
yarn version:remove:developer_portal <version> # Remove developer portal version
|
||||
yarn version:remove:components <version> # Remove components version
|
||||
yarn version:remove:admin_docs <version> # Remove admin docs version
|
||||
yarn version:remove:developer_docs <version> # Remove developer docs version
|
||||
yarn version:remove:components <version> # Remove components version
|
||||
|
||||
# Quality Checks
|
||||
yarn typecheck # TypeScript validation
|
||||
@@ -95,15 +103,14 @@ docs/
|
||||
└── [security guides]
|
||||
```
|
||||
|
||||
### Developer Portal (`/developer_portal`) - Currently Disabled
|
||||
When enabled, contains developer-focused content:
|
||||
- API documentation
|
||||
- Architecture guides
|
||||
- CLI tools
|
||||
- Code examples
|
||||
### Admin Docs (`/admin_docs`)
|
||||
Admin-focused content: installation, configuration, security.
|
||||
|
||||
### Component Playground (`/components`) - Currently Disabled
|
||||
When enabled, provides interactive component examples for UI development.
|
||||
### Developer Docs (`/developer_docs`)
|
||||
Developer-focused content: API documentation, architecture guides, CLI tools, code examples.
|
||||
|
||||
### Component Playground (`/components`)
|
||||
Interactive component examples for UI development.
|
||||
|
||||
## 📝 Documentation Standards
|
||||
|
||||
|
||||
@@ -37,23 +37,45 @@ Each section maintains its own version history and can be versioned independentl
|
||||
|
||||
To create a new version for any section, use the Docusaurus version command with the appropriate plugin ID or use our automated scripts:
|
||||
|
||||
#### Before You Cut
|
||||
|
||||
The cut snapshots whatever's on disk into a frozen historical version, including auto-generated content (database pages from `superset/db_engine_specs/`, API reference from `static/resources/openapi.json`, component pages from Storybook stories). The cut script refreshes these via `generate:smart` before snapshotting, but the **`databases.json` diagnostics file** needs special care to capture full detail:
|
||||
|
||||
1. **Canonical release cut**: download the `database-diagnostics` artifact from a green `Python-Integration` run on master, place it at `docs/src/data/databases.json`, then run the cut script with `--skip-generate` to preserve it. This is what the production deploy uses and includes full Flask-context diagnostics (driver versions, feature support matrix, etc.).
|
||||
2. **Local dev cut**: just run the script normally. `generate:smart` will regenerate `databases.json` using your local Flask environment — accurate to whatever drivers/extras you have installed, but typically less complete than the CI artifact.
|
||||
3. **No Flask available**: also fine — the database generator falls back to AST parsing of engine spec files. The MDX pages are still correct; only the diagnostics JSON is leaner.
|
||||
|
||||
Also: confirm `master` CI is green, and that your local checkout matches the SHA you intend to cut from.
|
||||
|
||||
#### Using Automated Scripts (Required)
|
||||
|
||||
**⚠️ Important:** Always use these custom commands instead of the native Docusaurus commands. These scripts ensure that both the Docusaurus versioning system AND the `versions-config.json` file are updated correctly.
|
||||
**⚠️ Important:** Always use these custom commands instead of the native Docusaurus commands. These scripts ensure that both the Docusaurus versioning system AND the `versions-config.json` file are updated correctly, AND that auto-generated content is refreshed before snapshotting.
|
||||
|
||||
```bash
|
||||
# Main Documentation
|
||||
yarn version:add:docs 1.2.0
|
||||
|
||||
# Developer Portal
|
||||
yarn version:add:developer_portal 1.2.0
|
||||
# Admin Docs
|
||||
yarn version:add:admin_docs 1.2.0
|
||||
|
||||
# Component Playground (when enabled)
|
||||
# Developer Docs
|
||||
yarn version:add:developer_docs 1.2.0
|
||||
|
||||
# Component Playground
|
||||
yarn version:add:components 1.2.0
|
||||
```
|
||||
|
||||
What the script does:
|
||||
1. Refreshes auto-generated content via `generate:smart` (database pages, API reference, component pages).
|
||||
2. Calls `yarn docusaurus docs:version` (or the per-section equivalent) to snapshot the section.
|
||||
3. Freezes any data-file imports (`@site/static/*.json`, `../../data/*.json`) into a snapshot-local `_versioned_data/` dir so the historical version doesn't silently mutate when the source files change.
|
||||
4. Adjusts relative import paths (`../../src/...` → `../../../src/...`) for files now one directory deeper.
|
||||
5. Updates `versions-config.json` and `<section>_versions.json`.
|
||||
|
||||
**Do NOT use** the native Docusaurus commands directly (`yarn docusaurus docs:version`), as they will:
|
||||
- ❌ Create version files but NOT update `versions-config.json`
|
||||
- ❌ Skip auto-gen refresh, freezing whatever was on disk
|
||||
- ❌ Skip data-import freezing, leaving the snapshot pointed at live data
|
||||
- ❌ Cause versions to not appear in dropdown menus
|
||||
- ❌ Require manual fixes to synchronize the configuration
|
||||
|
||||
@@ -91,8 +113,11 @@ If creating versions manually, you'll need to:
|
||||
# Main Documentation
|
||||
yarn version:remove:docs 1.0.0
|
||||
|
||||
# Developer Portal
|
||||
yarn version:remove:developer_portal 1.0.0
|
||||
# Admin Docs
|
||||
yarn version:remove:admin_docs 1.0.0
|
||||
|
||||
# Developer Docs
|
||||
yarn version:remove:developer_docs 1.0.0
|
||||
|
||||
# Component Playground
|
||||
yarn version:remove:components 1.0.0
|
||||
@@ -103,17 +128,20 @@ To manually remove a version:
|
||||
|
||||
1. **Delete the version folder** from the appropriate location:
|
||||
- Main docs: `versioned_docs/version-X.X.X/` (no prefix for main)
|
||||
- Developer Portal: `developer_portal_versioned_docs/version-X.X.X/`
|
||||
- Admin Docs: `admin_docs_versioned_docs/version-X.X.X/`
|
||||
- Developer Docs: `developer_docs_versioned_docs/version-X.X.X/`
|
||||
- Components: `components_versioned_docs/version-X.X.X/`
|
||||
|
||||
2. **Delete the version metadata file**:
|
||||
- Main docs: `versioned_sidebars/version-X.X.X-sidebars.json` (no prefix)
|
||||
- Developer Portal: `developer_portal_versioned_sidebars/version-X.X.X-sidebars.json`
|
||||
- Admin Docs: `admin_docs_versioned_sidebars/version-X.X.X-sidebars.json`
|
||||
- Developer Docs: `developer_docs_versioned_sidebars/version-X.X.X-sidebars.json`
|
||||
- Components: `components_versioned_sidebars/version-X.X.X-sidebars.json`
|
||||
|
||||
3. **Update the versions list file**:
|
||||
- Main docs: `versions.json`
|
||||
- Developer Portal: `developer_portal_versions.json`
|
||||
- Admin Docs: `admin_docs_versions.json`
|
||||
- Developer Docs: `developer_docs_versions.json`
|
||||
- Components: `components_versions.json`
|
||||
|
||||
4. **Update configuration**:
|
||||
@@ -145,12 +173,12 @@ docs: {
|
||||
}
|
||||
```
|
||||
|
||||
#### Developer Portal & Components (custom plugins)
|
||||
#### Developer Docs & Components (custom plugins)
|
||||
```typescript
|
||||
{
|
||||
id: 'developer_portal',
|
||||
path: 'developer_portal',
|
||||
routeBasePath: 'developer_portal',
|
||||
id: 'developer_docs',
|
||||
path: 'developer_docs',
|
||||
routeBasePath: 'developer-docs',
|
||||
includeCurrentVersion: true,
|
||||
lastVersion: '1.1.0', // Default version
|
||||
onlyIncludeVersions: ['current', '1.1.0', '1.0.0'],
|
||||
@@ -194,7 +222,7 @@ For other issues:
|
||||
|
||||
#### Broken Links in Versioned Documentation
|
||||
When creating a new version, links in the documentation are preserved as-is. Common issues:
|
||||
- **Cross-section links**: Links between sections (e.g., from developer_portal to docs) need to be version-aware
|
||||
- **Cross-section links**: Links between sections (e.g., from developer_docs to docs) need to be version-aware
|
||||
- **Absolute vs relative paths**: Use relative paths within the same section
|
||||
- **Version-specific URLs**: Update hardcoded URLs to use version variables
|
||||
|
||||
|
||||
@@ -81,6 +81,87 @@ SLACK_CACHE_TIMEOUT = int(timedelta(days=2).total_seconds())
|
||||
SLACK_API_RATE_LIMIT_RETRY_COUNT = 5
|
||||
```
|
||||
|
||||
### Webhook integration
|
||||
|
||||
Superset can send alert and report notifications to any HTTP endpoint — useful for chat platforms, incident management tools, or custom automation.
|
||||
|
||||
#### Enabling Webhooks
|
||||
|
||||
Enable the feature flag in `superset_config.py`:
|
||||
|
||||
```python
|
||||
FEATURE_FLAGS = {
|
||||
"ALERT_REPORTS": True,
|
||||
"ALERT_REPORT_WEBHOOK": True,
|
||||
}
|
||||
```
|
||||
|
||||
#### Configuring a Webhook Recipient
|
||||
|
||||
When creating or editing an alert or report, select **Webhook** as the notification method and enter your endpoint URL.
|
||||
|
||||
#### Payload Format
|
||||
|
||||
Superset sends an HTTP POST with `Content-Type: application/json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "My Alert",
|
||||
"header": {
|
||||
"notification_format": "JSON",
|
||||
"notification_type": "Alert",
|
||||
"notification_source": "Alert",
|
||||
"chart_id": 42,
|
||||
"dashboard_id": null
|
||||
},
|
||||
"text": "Alert condition met: value exceeded threshold",
|
||||
"description": "Monthly revenue dropped below target",
|
||||
"url": "https://your-superset-host/superset/dashboard/1/"
|
||||
}
|
||||
```
|
||||
|
||||
When a report includes file attachments (CSV, PDF, or PNG screenshots), the request is sent as `multipart/form-data` instead. In that case, each top-level payload field (`name`, `text`, `description`, `url`) becomes its own form field, and nested structures like `header` are serialized as a JSON-encoded string in their own field. Every attachment is added as a repeated form field named `files`:
|
||||
|
||||
```
|
||||
POST /webhook HTTP/1.1
|
||||
Content-Type: multipart/form-data; boundary=...
|
||||
|
||||
--...
|
||||
Content-Disposition: form-data; name="name"
|
||||
|
||||
My Alert
|
||||
--...
|
||||
Content-Disposition: form-data; name="header"
|
||||
|
||||
{"notification_format": "JSON", "notification_type": "Alert", ...}
|
||||
--...
|
||||
Content-Disposition: form-data; name="text"
|
||||
|
||||
Alert condition met: value exceeded threshold
|
||||
--...
|
||||
Content-Disposition: form-data; name="files"; filename="report.csv"
|
||||
Content-Type: text/csv
|
||||
|
||||
<file bytes>
|
||||
--...
|
||||
```
|
||||
|
||||
Webhook consumers should branch on `Content-Type`: parse the body as JSON when `application/json`, or read the individual form fields (decoding `header` as JSON) when `multipart/form-data`.
|
||||
|
||||
#### HTTPS Enforcement
|
||||
|
||||
To require HTTPS webhook URLs (recommended for production), set:
|
||||
|
||||
```python
|
||||
ALERT_REPORTS_WEBHOOK_HTTPS_ONLY = True
|
||||
```
|
||||
|
||||
When enabled, Superset rejects webhook configurations that use `http://` URLs.
|
||||
|
||||
#### Retry Behavior
|
||||
|
||||
Superset automatically retries webhook deliveries on `429 Too Many Requests` and `5xx` server errors using exponential backoff.
|
||||
|
||||
### Kubernetes-specific
|
||||
|
||||
- You must have a `celery beat` pod running. If you're using the chart included in the GitHub repository under [helm/superset](https://github.com/apache/superset/tree/master/helm/superset), you need to put `supersetCeleryBeat.enabled = true` in your values override.
|
||||
|
||||
@@ -472,6 +472,38 @@ FEATURE_FLAGS = {
|
||||
|
||||
A current list of feature flags can be found in the [Feature Flags](/admin-docs/configuration/feature-flags) documentation.
|
||||
|
||||
## Security Configuration
|
||||
|
||||
### HASH_ALGORITHM
|
||||
|
||||
Controls the hashing algorithm used for internal checksums and cache keys (thumbnails, cache keys, etc.). The default is `sha256`, which satisfies environments with stricter compliance requirements (e.g., FedRAMP). Set it to `md5` to retain the legacy behavior from older Superset deployments:
|
||||
|
||||
```python
|
||||
HASH_ALGORITHM = "sha256" # default; set to "md5" for legacy behavior
|
||||
```
|
||||
|
||||
A companion `HASH_ALGORITHM_FALLBACKS` list (default: `["md5"]`) lets UUID lookups fall back to older algorithms, which enables gradual migration without breaking existing entries. Set it to `[]` for strict mode (use only `HASH_ALGORITHM`).
|
||||
|
||||
:::note
|
||||
This setting affects internal Superset operations only, not user passwords or authentication tokens. Changing it in an existing deployment may invalidate cached values but does not require a database migration.
|
||||
:::
|
||||
|
||||
## SQL Lab Query History Pruning
|
||||
|
||||
SQL Lab query history is stored in the metadata database and is **not** pruned by default. To trim older rows, enable the `prune_query` Celery beat task by uncommenting it in `CELERY_BEAT_SCHEDULE` and choosing a retention window:
|
||||
|
||||
```python
|
||||
CELERY_BEAT_SCHEDULE = {
|
||||
"prune_query": {
|
||||
"task": "prune_query",
|
||||
"schedule": crontab(minute=0, hour=0, day_of_month=1),
|
||||
"kwargs": {"retention_period_days": 180},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Adjust `retention_period_days` to control how long query rows are kept. Companion opt-in tasks (`prune_logs`, `prune_tasks`) exist for pruning the logs and tasks tables; see the commented-out examples in `superset/config.py`. Without enabling these tasks, the metadata database will grow unbounded over time.
|
||||
|
||||
:::resources
|
||||
- [Blog: Feature Flags in Apache Superset](https://preset.io/blog/feature-flags-in-apache-superset-and-preset/)
|
||||
:::
|
||||
|
||||
@@ -122,6 +122,17 @@ When `ENABLE_UI_THEME_ADMINISTRATION = True`:
|
||||
3. Administrators can change system themes without restarting Superset
|
||||
4. Configuration file themes serve as fallbacks when no UI themes are set
|
||||
|
||||
### Theme Validation and Fallback
|
||||
|
||||
Superset validates theme JSON when it is saved, either through the UI or via configuration. If a theme contains invalid tokens or an unrecognized structure, Superset logs a warning and falls back to the built-in default theme rather than applying a broken configuration. This prevents a bad theme from rendering the application unusable.
|
||||
|
||||
The fallback order is:
|
||||
1. **UI-configured system theme** (highest priority, if `ENABLE_UI_THEME_ADMINISTRATION = True`)
|
||||
2. **`THEME_DEFAULT` / `THEME_DARK`** from `superset_config.py`
|
||||
3. **Built-in Superset default theme** (always present as a safety net)
|
||||
|
||||
If you see unexpected styling after a config change, check the Superset server logs for theme validation warnings.
|
||||
|
||||
### Copying Themes Between Systems
|
||||
|
||||
To export a theme for use in configuration files or another instance:
|
||||
@@ -143,7 +154,11 @@ Superset supports custom fonts through the theme configuration, allowing you to
|
||||
|
||||
### Default Fonts
|
||||
|
||||
By default, Superset uses Inter and Fira Code fonts which are bundled with the application via `@fontsource` packages. These fonts work offline and require no external network calls.
|
||||
By default, Superset uses **Inter** for UI text and **IBM Plex Mono** for code (SQL editors, JSON fields, and other monospace contexts). Both fonts are bundled with the application via `@fontsource` packages and work offline without any external network calls.
|
||||
|
||||
:::note
|
||||
IBM Plex Mono replaced Fira Code as the default code font in Superset 6.1. If you have an existing theme that explicitly sets `fontFamilyCode: "Fira Code, ..."`, you may want to update it.
|
||||
:::
|
||||
|
||||
### Configuring Custom Fonts
|
||||
|
||||
@@ -213,32 +228,31 @@ Apply settings to all ECharts visualizations using `echartsOptionsOverrides`:
|
||||
|
||||
```python
|
||||
THEME_DEFAULT = {
|
||||
"token": {
|
||||
"colorPrimary": "#2893B3",
|
||||
# ... other Ant Design tokens
|
||||
},
|
||||
"token": {
|
||||
"colorPrimary": "#2893B3",
|
||||
# ... other Ant Design tokens
|
||||
"echartsOptionsOverrides": {
|
||||
"grid": {
|
||||
"left": "10%",
|
||||
"right": "10%",
|
||||
"top": "15%",
|
||||
"bottom": "15%"
|
||||
},
|
||||
"tooltip": {
|
||||
"backgroundColor": "rgba(0, 0, 0, 0.8)",
|
||||
"borderColor": "#ccc",
|
||||
"textStyle": {
|
||||
"color": "#fff"
|
||||
}
|
||||
},
|
||||
"legend": {
|
||||
"textStyle": {
|
||||
"fontSize": 14,
|
||||
"fontWeight": "bold"
|
||||
}
|
||||
"grid": {
|
||||
"left": "10%",
|
||||
"right": "10%",
|
||||
"top": "15%",
|
||||
"bottom": "15%"
|
||||
},
|
||||
"tooltip": {
|
||||
"backgroundColor": "rgba(0, 0, 0, 0.8)",
|
||||
"borderColor": "#ccc",
|
||||
"textStyle": {
|
||||
"color": "#fff"
|
||||
}
|
||||
},
|
||||
"legend": {
|
||||
"textStyle": {
|
||||
"fontSize": 14,
|
||||
"fontWeight": "bold"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Chart-Specific Overrides
|
||||
@@ -249,38 +263,45 @@ Target specific chart types using `echartsOptionsOverridesByChartType`:
|
||||
THEME_DEFAULT = {
|
||||
"token": {
|
||||
"colorPrimary": "#2893B3",
|
||||
# ... other tokens
|
||||
},
|
||||
"echartsOptionsOverridesByChartType": {
|
||||
"echarts_pie": {
|
||||
"legend": {
|
||||
"orient": "vertical",
|
||||
"right": 10,
|
||||
"top": "center"
|
||||
}
|
||||
},
|
||||
"echarts_timeseries": {
|
||||
"xAxis": {
|
||||
"axisLabel": {
|
||||
"rotate": 45,
|
||||
"fontSize": 12
|
||||
}
|
||||
"echartsOptionsOverridesByChartType": {
|
||||
"pie": {
|
||||
"tooltip": {
|
||||
"backgroundColor": "rgba(0, 0, 0, 0.8)",
|
||||
"borderColor": "#ccc",
|
||||
"textStyle": {
|
||||
"color": "#fff"
|
||||
}
|
||||
},
|
||||
"dataZoom": [{
|
||||
"legend": {
|
||||
"orient": "vertical",
|
||||
"right": 10,
|
||||
"top": "center"
|
||||
}
|
||||
},
|
||||
"echarts_timeseries": {
|
||||
"xAxis": {
|
||||
"axisLabel": {
|
||||
"rotate": 45,
|
||||
"fontSize": 12
|
||||
}
|
||||
},
|
||||
"dataZoom": [
|
||||
{
|
||||
"type": "slider",
|
||||
"show": True,
|
||||
"show": "True",
|
||||
"start": 0,
|
||||
"end": 100
|
||||
}]
|
||||
},
|
||||
"echarts_bubble": {
|
||||
}
|
||||
]
|
||||
},
|
||||
"echarts_bubble": {
|
||||
"grid": {
|
||||
"left": "15%",
|
||||
"bottom": "20%"
|
||||
"left": "15%",
|
||||
"bottom": "20%"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### UI Configuration
|
||||
@@ -290,22 +311,22 @@ You can also configure ECharts overrides through the theme CRUD interface:
|
||||
```json
|
||||
{
|
||||
"token": {
|
||||
"colorPrimary": "#2893B3"
|
||||
},
|
||||
"echartsOptionsOverrides": {
|
||||
"grid": {
|
||||
"left": "10%",
|
||||
"right": "10%"
|
||||
"colorPrimary": "#2893B3",
|
||||
"echartsOptionsOverrides": {
|
||||
"grid": {
|
||||
"left": "10%",
|
||||
"right": "10%"
|
||||
},
|
||||
"tooltip": {
|
||||
"backgroundColor": "rgba(0, 0, 0, 0.8)"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"backgroundColor": "rgba(0, 0, 0, 0.8)"
|
||||
}
|
||||
},
|
||||
"echartsOptionsOverridesByChartType": {
|
||||
"echarts_pie": {
|
||||
"legend": {
|
||||
"orient": "vertical",
|
||||
"right": 10
|
||||
"echartsOptionsOverridesByChartType": {
|
||||
"pie": {
|
||||
"legend": {
|
||||
"orient": "vertical",
|
||||
"right": 10
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -328,7 +349,7 @@ This ensures chart-specific overrides take precedence over global ones.
|
||||
Available chart types for `echartsOptionsOverridesByChartType`:
|
||||
|
||||
- `echarts_timeseries` - Time series/line charts
|
||||
- `echarts_pie` - Pie and donut charts
|
||||
- `pie` - Pie and donut charts
|
||||
- `echarts_bubble` - Bubble/scatter charts
|
||||
- `echarts_funnel` - Funnel charts
|
||||
- `echarts_gauge` - Gauge charts
|
||||
@@ -368,47 +389,48 @@ THEME_DEFAULT = {
|
||||
```python
|
||||
# Complete corporate theme with ECharts customization
|
||||
THEME_DEFAULT = {
|
||||
"token": {
|
||||
"colorPrimary": "#1B4D3E",
|
||||
"fontFamily": "Corporate Sans, Arial, sans-serif"
|
||||
},
|
||||
"token": {
|
||||
"colorPrimary": "#1B4D3E",
|
||||
"fontFamily": "Corporate Sans, Arial, sans-serif",
|
||||
"echartsOptionsOverrides": {
|
||||
"grid": {
|
||||
"left": "8%",
|
||||
"right": "8%",
|
||||
"top": "12%",
|
||||
"bottom": "12%"
|
||||
},
|
||||
"grid": {
|
||||
"left": "8%",
|
||||
"right": "8%",
|
||||
"top": "12%",
|
||||
"bottom": "12%"
|
||||
},
|
||||
"textStyle": {
|
||||
"fontFamily": "Corporate Sans, Arial, sans-serif"
|
||||
},
|
||||
"title": {
|
||||
"textStyle": {
|
||||
"fontFamily": "Corporate Sans, Arial, sans-serif"
|
||||
},
|
||||
"title": {
|
||||
"textStyle": {
|
||||
"color": "#1B4D3E",
|
||||
"fontSize": 18,
|
||||
"fontWeight": "bold"
|
||||
}
|
||||
"color": "#1B4D3E",
|
||||
"fontSize": 18,
|
||||
"fontWeight": "bold"
|
||||
}
|
||||
}
|
||||
},
|
||||
"echartsOptionsOverridesByChartType": {
|
||||
"echarts_timeseries": {
|
||||
"xAxis": {
|
||||
"axisLabel": {
|
||||
"color": "#666",
|
||||
"fontSize": 11
|
||||
}
|
||||
}
|
||||
},
|
||||
"echarts_pie": {
|
||||
"legend": {
|
||||
"textStyle": {
|
||||
"fontSize": 12
|
||||
},
|
||||
"itemGap": 20
|
||||
}
|
||||
"echarts_timeseries": {
|
||||
"xAxis": {
|
||||
"axisLabel": {
|
||||
"color": "#666",
|
||||
"fontSize": 11
|
||||
}
|
||||
}
|
||||
},
|
||||
"pie": {
|
||||
"legend": {
|
||||
"textStyle": {
|
||||
"fontSize": 12
|
||||
},
|
||||
"itemGap": 20
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
This feature provides powerful theming capabilities while maintaining the flexibility of ECharts' extensive configuration options.
|
||||
|
||||
@@ -205,6 +205,57 @@ FAB_ADD_SECURITY_API = True
|
||||
|
||||
Once configured, the documentation for additional "Security" endpoints will be visible in Swagger for you to explore.
|
||||
|
||||
### API Key Authentication
|
||||
|
||||
Superset supports long-lived API keys for service accounts, CI/CD pipelines, and programmatic integrations (including MCP clients).
|
||||
|
||||
#### Enabling API Key Authentication
|
||||
|
||||
API key authentication is **disabled by default**. To turn it on, set the Flask-AppBuilder config value in `superset_config.py` and also enable the matching feature flag so the management UI is exposed:
|
||||
|
||||
```python
|
||||
FAB_API_KEY_ENABLED = True
|
||||
|
||||
FEATURE_FLAGS = {
|
||||
"FAB_API_KEY_ENABLED": True,
|
||||
}
|
||||
```
|
||||
|
||||
The config value registers the `ApiKeyApi` blueprint on the backend; the feature flag controls whether the UI for managing keys appears for the user. See the [Feature Flags](/admin-docs/configuration/feature-flags) documentation for more on feature flag configuration.
|
||||
|
||||
#### Creating an API Key
|
||||
|
||||
Once enabled, each user manages their own keys from their profile page:
|
||||
|
||||
1. Open the user menu (top-right) and click **Info** to navigate to the User Info page
|
||||
2. Expand the **API Keys** section
|
||||
3. Click **+ API Key**
|
||||
4. Enter a name and (optionally) an expiration date
|
||||
5. Copy the generated token — it is shown only once
|
||||
|
||||
Only users with the `can_read` and `can_write` permissions on `ApiKey` (granted by default to Admins) can manage API keys.
|
||||
|
||||
#### Using an API Key
|
||||
|
||||
Pass the key as a Bearer token in the `Authorization` header:
|
||||
|
||||
```
|
||||
Authorization: Bearer <your-api-key>
|
||||
```
|
||||
|
||||
This works for all REST API endpoints and the MCP server. The request is executed with the permissions of the user who created the key.
|
||||
|
||||
#### Use Cases
|
||||
|
||||
- **CI/CD pipelines** — automated chart/dashboard exports and imports
|
||||
- **MCP integrations** — connect AI assistants without interactive login
|
||||
- **External services** — dashboards embedded in other applications
|
||||
- **Service accounts** — long-lived credentials that don't expire with session cookies
|
||||
|
||||
:::caution
|
||||
Store API keys securely. Anyone with a valid key can make requests on behalf of the creating user. Revoke keys promptly if they are compromised by deleting them from the **API Keys** section of your User Info page.
|
||||
:::
|
||||
|
||||
### Customizing Permissions
|
||||
|
||||
The permissions exposed by FAB are very granular and allow for a great level of
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
[]
|
||||
@@ -224,3 +224,52 @@ async def analysis_guide(ctx: Context) -> str:
|
||||
```
|
||||
|
||||
See [MCP Integration](./mcp) for implementation details.
|
||||
|
||||
### Semantic Layers
|
||||
|
||||
Extensions can register custom semantic layer implementations that allow Superset to connect to external data modeling frameworks. Each semantic layer defines how to authenticate, discover semantic views (tables/metrics/dimensions), and execute queries against the external system.
|
||||
|
||||
```python
|
||||
from superset_core.semantic_layers.decorators import semantic_layer
|
||||
from superset_core.semantic_layers.layer import SemanticLayer
|
||||
|
||||
from my_extension.config import MyConfig
|
||||
from my_extension.view import MySemanticView
|
||||
|
||||
|
||||
@semantic_layer(
|
||||
id="my_platform",
|
||||
name="My Data Platform",
|
||||
description="Connect to My Data Platform's semantic layer",
|
||||
)
|
||||
class MySemanticLayer(SemanticLayer[MyConfig, MySemanticView]):
|
||||
configuration_class = MyConfig
|
||||
|
||||
@classmethod
|
||||
def from_configuration(cls, configuration: dict) -> "MySemanticLayer":
|
||||
config = MyConfig.model_validate(configuration)
|
||||
return cls(config)
|
||||
|
||||
@classmethod
|
||||
def get_configuration_schema(cls, configuration=None) -> dict:
|
||||
return MyConfig.model_json_schema()
|
||||
|
||||
@classmethod
|
||||
def get_runtime_schema(cls, configuration=None, runtime_data=None) -> dict:
|
||||
return {"type": "object", "properties": {}}
|
||||
|
||||
def get_semantic_views(self, runtime_configuration: dict) -> set[MySemanticView]:
|
||||
# Return available views from the external platform
|
||||
...
|
||||
|
||||
def get_semantic_view(self, name: str, additional_configuration: dict) -> MySemanticView:
|
||||
# Return a specific view by name
|
||||
...
|
||||
```
|
||||
|
||||
**Note**: The `@semantic_layer` decorator automatically detects context and applies appropriate ID prefixing:
|
||||
|
||||
- **Extension context**: ID prefixed as `extensions.{publisher}.{name}.{id}`
|
||||
- **Host context**: Original ID used as-is
|
||||
|
||||
The decorator registers the class in the semantic layers registry, making it available in the UI for users to create connections. The `configuration_class` should be a Pydantic model that defines the fields needed to connect (credentials, project, database, etc.). Superset uses the model's JSON schema to render the configuration form dynamically.
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
[]
|
||||
@@ -1 +0,0 @@
|
||||
[]
|
||||
143
docs/docs/using-superset/handlebars-chart.mdx
Normal file
@@ -0,0 +1,143 @@
|
||||
---
|
||||
title: Handlebars Chart
|
||||
hide_title: true
|
||||
sidebar_position: 10
|
||||
version: 1
|
||||
---
|
||||
|
||||
## Handlebars Chart
|
||||
|
||||
The Handlebars chart lets you render query results using a custom [Handlebars](https://handlebarsjs.com/) template. This gives you full control over how your data is displayed — from simple tables to rich HTML layouts.
|
||||
|
||||
### Basic Usage
|
||||
|
||||
In the chart editor, write a Handlebars template in the **Template** field. Your query results are available as `data`, an array of row objects.
|
||||
|
||||
```handlebars
|
||||
{{#each data}}
|
||||
<p>{{this.name}}: {{this.value}}</p>
|
||||
{{/each}}
|
||||
```
|
||||
|
||||
### Built-in Helpers
|
||||
|
||||
Superset registers several custom helpers on top of the standard Handlebars built-ins.
|
||||
|
||||
#### `dateFormat`
|
||||
|
||||
Formats a date value using [Day.js](https://day.js.org/) format strings.
|
||||
|
||||
```handlebars
|
||||
{{dateFormat my_date format="MMMM YYYY"}}
|
||||
```
|
||||
|
||||
| Option | Default | Description |
|
||||
|--------|---------|-------------|
|
||||
| `format` | `YYYY-MM-DD` | A Day.js-compatible format string |
|
||||
|
||||
---
|
||||
|
||||
#### `stringify`
|
||||
|
||||
Converts an object to a JSON string, or any other value to its string representation.
|
||||
|
||||
```handlebars
|
||||
{{stringify myObj}}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `formatNumber`
|
||||
|
||||
Formats a number using locale-aware formatting.
|
||||
|
||||
```handlebars
|
||||
{{formatNumber myNumber "en-US"}}
|
||||
```
|
||||
|
||||
| Option | Default | Description |
|
||||
|--------|---------|-------------|
|
||||
| `locale` | `en-US` | A BCP 47 language tag |
|
||||
|
||||
---
|
||||
|
||||
#### `parseJson`
|
||||
|
||||
Parses a JSON string into an object that can be used in your template.
|
||||
|
||||
```handlebars
|
||||
{{parseJson myJsonString}}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `groupBy`
|
||||
|
||||
Groups an array of objects by a key, powered by [handlebars-group-by](https://github.com/nicktindall/handlebars-group-by).
|
||||
|
||||
```handlebars
|
||||
{{#groupBy data "department"}}
|
||||
<h3>{{value}}</h3>
|
||||
{{#each items}}
|
||||
<p>{{this.name}}</p>
|
||||
{{/each}}
|
||||
{{/groupBy}}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Helpers from just-handlebars-helpers
|
||||
|
||||
Superset also registers all helpers from the [just-handlebars-helpers](https://github.com/leapfrogtechnology/just-handlebars-helpers) library. These include a wide range of comparison, math, string, and conditional helpers. Commonly used ones include:
|
||||
|
||||
#### Comparison
|
||||
|
||||
| Helper | Description | Example |
|
||||
|--------|-------------|---------|
|
||||
| `eq` | Strict equality | `{{#if (eq status "active")}}` |
|
||||
| `eqw` | Weak equality | `{{#if (eqw count "5")}}` |
|
||||
| `neq` | Strict inequality | `{{#if (neq role "admin")}}` |
|
||||
| `lt` | Less than | `{{#if (lt score 50)}}` |
|
||||
| `lte` | Less than or equal | `{{#if (lte score 100)}}` |
|
||||
| `gt` | Greater than | `{{#if (gt price 0)}}` |
|
||||
| `gte` | Greater than or equal | `{{#if (gte age 18)}}` |
|
||||
|
||||
#### Logical
|
||||
|
||||
| Helper | Description | Example |
|
||||
|--------|-------------|---------|
|
||||
| `and` | Logical AND | `{{#if (and isActive isVerified)}}` |
|
||||
| `or` | Logical OR | `{{#if (or isAdmin isMod)}}` |
|
||||
| `not` | Logical NOT | `{{#if (not isDisabled)}}` |
|
||||
| `ifx` | Inline conditional | `{{ifx isActive "Yes" "No"}}` |
|
||||
| `coalesce` | Returns first non-falsy value | `{{coalesce nickname name "Anonymous"}}` |
|
||||
|
||||
#### String
|
||||
|
||||
| Helper | Description | Example |
|
||||
|--------|-------------|---------|
|
||||
| `capitalize` | Capitalizes first letter | `{{capitalize name}}` |
|
||||
| `uppercase` | Converts to uppercase | `{{uppercase status}}` |
|
||||
| `lowercase` | Converts to lowercase | `{{lowercase email}}` |
|
||||
| `truncate` | Truncates a string | `{{truncate description 100}}` |
|
||||
| `contains` | Checks if string contains substring | `{{#if (contains tag "urgent")}}` |
|
||||
|
||||
#### Math
|
||||
|
||||
| Helper | Description | Example |
|
||||
|--------|-------------|---------|
|
||||
| `add` | Addition | `{{add a b}}` |
|
||||
| `subtract` | Subtraction | `{{subtract total discount}}` |
|
||||
| `multiply` | Multiplication | `{{multiply price quantity}}` |
|
||||
| `divide` | Division | `{{divide total count}}` |
|
||||
| `ceil` | Ceiling | `{{ceil value}}` |
|
||||
| `floor` | Floor | `{{floor value}}` |
|
||||
| `round` | Round | `{{round value}}` |
|
||||
|
||||
For the full list of available helpers, see the [just-handlebars-helpers documentation](https://github.com/leapfrogtechnology/just-handlebars-helpers).
|
||||
|
||||
### Tips
|
||||
|
||||
- Use raw blocks to escape Handlebars syntax if you need to display double curly braces literally.
|
||||
- Comparison helpers like `eq` must be wrapped in a subexpression when used with `#if`: `{{#if (eq myVal "foo")}}`.
|
||||
- HTML output is sanitized by default based on your Superset configuration (`HTML_SANITIZATION`).
|
||||
@@ -33,20 +33,22 @@
|
||||
"version:add": "node scripts/manage-versions.mjs add",
|
||||
"version:remove": "node scripts/manage-versions.mjs remove",
|
||||
"version:add:docs": "node scripts/manage-versions.mjs add docs",
|
||||
"version:add:developer_portal": "node scripts/manage-versions.mjs add developer_portal",
|
||||
"version:add:admin_docs": "node scripts/manage-versions.mjs add admin_docs",
|
||||
"version:add:developer_docs": "node scripts/manage-versions.mjs add developer_docs",
|
||||
"version:add:components": "node scripts/manage-versions.mjs add components",
|
||||
"version:remove:docs": "node scripts/manage-versions.mjs remove docs",
|
||||
"version:remove:developer_portal": "node scripts/manage-versions.mjs remove developer_portal",
|
||||
"version:remove:admin_docs": "node scripts/manage-versions.mjs remove admin_docs",
|
||||
"version:remove:developer_docs": "node scripts/manage-versions.mjs remove developer_docs",
|
||||
"version:remove:components": "node scripts/manage-versions.mjs remove components"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^6.2.2",
|
||||
"@docusaurus/core": "^3.10.0",
|
||||
"@docusaurus/faster": "^3.10.0",
|
||||
"@docusaurus/plugin-client-redirects": "^3.10.0",
|
||||
"@docusaurus/preset-classic": "3.10.0",
|
||||
"@docusaurus/theme-live-codeblock": "^3.10.0",
|
||||
"@docusaurus/theme-mermaid": "^3.10.0",
|
||||
"@ant-design/icons": "^6.2.3",
|
||||
"@docusaurus/core": "^3.10.1",
|
||||
"@docusaurus/faster": "^3.10.1",
|
||||
"@docusaurus/plugin-client-redirects": "^3.10.1",
|
||||
"@docusaurus/preset-classic": "3.10.1",
|
||||
"@docusaurus/theme-live-codeblock": "^3.10.1",
|
||||
"@docusaurus/theme-mermaid": "^3.10.1",
|
||||
"@emotion/core": "^11.0.0",
|
||||
"@emotion/react": "^11.13.3",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
@@ -67,12 +69,12 @@
|
||||
"@storybook/preview-api": "^8.6.18",
|
||||
"@storybook/theming": "^8.6.15",
|
||||
"@superset-ui/core": "^0.20.4",
|
||||
"@swc/core": "^1.15.32",
|
||||
"@swc/core": "^1.15.33",
|
||||
"antd": "^6.3.7",
|
||||
"baseline-browser-mapping": "^2.10.24",
|
||||
"caniuse-lite": "^1.0.30001791",
|
||||
"docusaurus-plugin-openapi-docs": "^5.0.1",
|
||||
"docusaurus-theme-openapi-docs": "^5.0.1",
|
||||
"baseline-browser-mapping": "^2.10.29",
|
||||
"caniuse-lite": "^1.0.30001792",
|
||||
"docusaurus-plugin-openapi-docs": "^5.0.2",
|
||||
"docusaurus-theme-openapi-docs": "^5.0.2",
|
||||
"js-yaml": "^4.1.1",
|
||||
"js-yaml-loader": "^1.2.2",
|
||||
"json-bigint": "^1.0.0",
|
||||
@@ -92,21 +94,21 @@
|
||||
"unist-util-visit": "^5.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "^3.10.0",
|
||||
"@docusaurus/tsconfig": "^3.10.0",
|
||||
"@docusaurus/module-type-aliases": "^3.10.1",
|
||||
"@docusaurus/tsconfig": "^3.10.1",
|
||||
"@eslint/js": "^9.39.2",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/react": "^19.1.8",
|
||||
"@typescript-eslint/eslint-plugin": "^8.52.0",
|
||||
"@typescript-eslint/parser": "^8.59.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.59.3",
|
||||
"@typescript-eslint/parser": "^8.59.3",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-prettier": "^5.5.5",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"globals": "^17.5.0",
|
||||
"globals": "^17.6.0",
|
||||
"prettier": "^3.8.3",
|
||||
"typescript": "~6.0.3",
|
||||
"typescript-eslint": "^8.59.1",
|
||||
"typescript-eslint": "^8.59.3",
|
||||
"webpack": "^5.106.2"
|
||||
},
|
||||
"browserslist": {
|
||||
@@ -124,8 +126,7 @@
|
||||
"resolutions": {
|
||||
"react-redux": "^9.2.0",
|
||||
"@reduxjs/toolkit": "^2.5.0",
|
||||
"baseline-browser-mapping": "^2.9.19",
|
||||
"webpackbar": "^7.0.0"
|
||||
"baseline-browser-mapping": "^2.9.19"
|
||||
},
|
||||
"packageManager": "yarn@1.22.22+sha1.ac34549e6aa8e7ead463a7407e1c7390f61a6610"
|
||||
}
|
||||
|
||||
@@ -141,6 +141,47 @@ def eval_node(node):
|
||||
return "<f-string>"
|
||||
return None
|
||||
|
||||
def static_return_bool(func_node):
|
||||
"""
|
||||
Statically resolve a method's return value to a bool when possible.
|
||||
|
||||
Returns True/False for functions whose body is (effectively) a single
|
||||
\`return True\` / \`return False\` — allowing a leading docstring and
|
||||
ignoring pure-comment/pass statements. Returns None for anything more
|
||||
complex (conditional returns, computed values, no return, etc.).
|
||||
|
||||
Used by \`has_implicit_cancel\` handling: \`diagnose()\` in lib.py calls
|
||||
the method and checks the return value, so an override that explicitly
|
||||
returns False must NOT be treated as enabling query cancelation.
|
||||
"""
|
||||
returns = []
|
||||
other_logic = False
|
||||
docstring_skipped = False
|
||||
for stmt in func_node.body:
|
||||
# Skip docstring (only the FIRST expression statement that is a
|
||||
# string constant — later bare string literals are not docstrings
|
||||
# and should count as non-trivial logic).
|
||||
if (not docstring_skipped
|
||||
and isinstance(stmt, ast.Expr)
|
||||
and isinstance(stmt.value, ast.Constant)
|
||||
and isinstance(stmt.value.value, str)):
|
||||
docstring_skipped = True
|
||||
continue
|
||||
if isinstance(stmt, ast.Pass):
|
||||
continue
|
||||
if isinstance(stmt, ast.Return):
|
||||
returns.append(stmt)
|
||||
continue
|
||||
# Any other statement (if/for/assign/etc.) means control flow is
|
||||
# non-trivial; bail out to be conservative.
|
||||
other_logic = True
|
||||
break
|
||||
if other_logic or len(returns) != 1:
|
||||
return None
|
||||
val = eval_node(returns[0].value)
|
||||
return val if isinstance(val, bool) else None
|
||||
|
||||
|
||||
def deep_merge(base, override):
|
||||
"""Deep merge two dictionaries. Override values take precedence."""
|
||||
if base is None:
|
||||
@@ -186,8 +227,55 @@ if not os.path.isdir(specs_dir):
|
||||
print(json.dumps({"error": f"Directory not found: {specs_dir}", "cwd": os.getcwd()}))
|
||||
sys.exit(1)
|
||||
|
||||
# First pass: collect all class info (name, bases, metadata)
|
||||
class_info = {} # class_name -> {bases: [], metadata: {}, engine_name: str, filename: str}
|
||||
# Capability flag attributes with their defaults from BaseEngineSpec
|
||||
CAP_ATTR_DEFAULTS = {
|
||||
'supports_dynamic_schema': False,
|
||||
'supports_catalog': False,
|
||||
'supports_dynamic_catalog': False,
|
||||
'disable_ssh_tunneling': False,
|
||||
'supports_file_upload': True,
|
||||
'allows_joins': True,
|
||||
'allows_subqueries': True,
|
||||
}
|
||||
|
||||
# Maps source capability attribute -> output field name used in databases.json.
|
||||
# When a cap attr is assigned an unevaluable expression (e.g.
|
||||
# allows_joins = is_feature_enabled("DRUID_JOINS")), the JS layer uses this
|
||||
# mapping to preserve the corresponding field from the previously-generated
|
||||
# JSON rather than silently inheriting an incorrect parent default.
|
||||
CAP_ATTR_TO_OUTPUT_FIELD = {
|
||||
'allows_joins': 'joins',
|
||||
'allows_subqueries': 'subqueries',
|
||||
'supports_dynamic_schema': 'supports_dynamic_schema',
|
||||
'supports_catalog': 'supports_catalog',
|
||||
'supports_dynamic_catalog': 'supports_dynamic_catalog',
|
||||
'disable_ssh_tunneling': 'ssh_tunneling',
|
||||
'supports_file_upload': 'supports_file_upload',
|
||||
}
|
||||
|
||||
# Methods that indicate a capability when overridden by a non-BaseEngineSpec class.
|
||||
# Mirrors the has_custom_method checks in superset/db_engine_specs/lib.py.
|
||||
# cancel_query / has_implicit_cancel -> query_cancelation
|
||||
# (diagnose() checks cancel_query override OR has_implicit_cancel() == True;
|
||||
# base has_implicit_cancel returns False, so overriding it is the static
|
||||
# equivalent of that method returning True. get_cancel_query_id is NOT
|
||||
# part of the diagnose() heuristic and is intentionally excluded.)
|
||||
# estimate_statement_cost / estimate_query_cost -> query_cost_estimation
|
||||
# impersonate_user / update_impersonation_config / get_url_for_impersonation -> user_impersonation
|
||||
# validate_sql -> sql_validation (not used yet; validation is engine-based)
|
||||
CAP_METHODS = {
|
||||
'cancel_query', 'has_implicit_cancel',
|
||||
'estimate_statement_cost', 'estimate_query_cost',
|
||||
'impersonate_user', 'update_impersonation_config', 'get_url_for_impersonation',
|
||||
'validate_sql',
|
||||
}
|
||||
|
||||
# Only the literal BaseEngineSpec is excluded from method-override tracking.
|
||||
# Intermediate base classes (e.g. PrestoBaseEngineSpec) do count as overrides.
|
||||
TRUE_BASE_CLASS = 'BaseEngineSpec'
|
||||
|
||||
# First pass: collect all class info (name, bases, metadata, cap_attrs, direct_methods)
|
||||
class_info = {} # class_name -> {bases: [], metadata: {}, engine_name: str, filename: str, ...}
|
||||
|
||||
for filename in sorted(os.listdir(specs_dir)):
|
||||
if not filename.endswith('.py') or filename in ('__init__.py', 'lib.py', 'lint_metadata.py'):
|
||||
@@ -218,30 +306,54 @@ for filename in sorted(os.listdir(specs_dir)):
|
||||
|
||||
# Extract class attributes
|
||||
engine_name = None
|
||||
engine_attr = None
|
||||
metadata = None
|
||||
cap_attrs = {} # capability flag attributes defined directly in this class
|
||||
# Cap attrs assigned via expressions we can't statically resolve
|
||||
# (e.g. is_feature_enabled("FLAG")). Tracked so the JS layer can
|
||||
# fall back to the previously-generated databases.json value
|
||||
# rather than inherit a parent default that would be wrong.
|
||||
unresolved_cap_attrs = set()
|
||||
direct_methods = set() # capability methods defined directly in this class
|
||||
|
||||
for item in node.body:
|
||||
if isinstance(item, ast.Assign):
|
||||
for target in item.targets:
|
||||
if isinstance(target, ast.Name):
|
||||
if target.id == 'engine_name':
|
||||
val = eval_node(item.value)
|
||||
if isinstance(val, str):
|
||||
engine_name = val
|
||||
elif target.id == 'metadata':
|
||||
metadata = eval_node(item.value)
|
||||
if not isinstance(target, ast.Name):
|
||||
continue
|
||||
if target.id == 'engine_name':
|
||||
val = eval_node(item.value)
|
||||
if isinstance(val, str):
|
||||
engine_name = val
|
||||
elif target.id == 'engine':
|
||||
val = eval_node(item.value)
|
||||
if isinstance(val, str):
|
||||
engine_attr = val
|
||||
elif target.id == 'metadata':
|
||||
metadata = eval_node(item.value)
|
||||
elif target.id in CAP_ATTR_DEFAULTS:
|
||||
val = eval_node(item.value)
|
||||
if isinstance(val, bool):
|
||||
cap_attrs[target.id] = val
|
||||
else:
|
||||
# Unevaluable expression — defer to JS fallback.
|
||||
unresolved_cap_attrs.add(target.id)
|
||||
elif isinstance(item, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
||||
if item.name in CAP_METHODS:
|
||||
# has_implicit_cancel is special: diagnose() uses the
|
||||
# method's RETURN VALUE, not just its presence. If the
|
||||
# override statically returns False, treat it as if
|
||||
# the method weren't overridden so query_cancelation
|
||||
# matches diagnose(). Unresolvable / True / anything
|
||||
# else falls through as an override (conservative).
|
||||
if item.name == 'has_implicit_cancel':
|
||||
if static_return_bool(item) is False:
|
||||
continue
|
||||
direct_methods.add(item.name)
|
||||
|
||||
# Check for engine attribute with non-empty value to distinguish
|
||||
# true base classes from product classes like OceanBaseEngineSpec
|
||||
has_non_empty_engine = False
|
||||
for item in node.body:
|
||||
if isinstance(item, ast.Assign):
|
||||
for target in item.targets:
|
||||
if isinstance(target, ast.Name) and target.id == 'engine':
|
||||
# Check if engine value is non-empty string
|
||||
if isinstance(item.value, ast.Constant):
|
||||
has_non_empty_engine = bool(item.value.value)
|
||||
break
|
||||
has_non_empty_engine = engine_attr is not None and bool(engine_attr)
|
||||
|
||||
# True base classes: end with BaseEngineSpec AND don't define engine
|
||||
# or have empty engine (like PostgresBaseEngineSpec with engine = "")
|
||||
@@ -254,13 +366,18 @@ for filename in sorted(os.listdir(specs_dir)):
|
||||
'bases': base_names,
|
||||
'metadata': metadata,
|
||||
'engine_name': engine_name,
|
||||
'engine': engine_attr,
|
||||
'filename': filename,
|
||||
'is_base_or_mixin': is_true_base,
|
||||
'cap_attrs': cap_attrs,
|
||||
'unresolved_cap_attrs': unresolved_cap_attrs,
|
||||
'direct_methods': direct_methods,
|
||||
}
|
||||
except Exception as e:
|
||||
errors.append(f"{filename}: {str(e)}")
|
||||
|
||||
# Second pass: resolve inheritance and build final metadata
|
||||
# Second pass: resolve inheritance and build final metadata + capability flags
|
||||
|
||||
def get_inherited_metadata(class_name, visited=None):
|
||||
"""Recursively get metadata from parent classes."""
|
||||
if visited is None:
|
||||
@@ -286,6 +403,64 @@ def get_inherited_metadata(class_name, visited=None):
|
||||
|
||||
return inherited
|
||||
|
||||
def get_resolved_caps(class_name, visited=None):
|
||||
"""
|
||||
Resolve capability flags and method overrides with inheritance.
|
||||
|
||||
Returns (attr_values, unresolved, methods):
|
||||
- attr_values: {attr: bool} for attrs where the nearest MRO assignment
|
||||
was a literal bool. Defaults are applied at the call site.
|
||||
- unresolved: attrs where the nearest MRO assignment was an unevaluable
|
||||
expression (e.g. is_feature_enabled("FLAG")). The JS layer falls
|
||||
back to the previously-generated JSON value for these.
|
||||
- methods: capability methods defined directly in some non-base ancestor,
|
||||
matching the has_custom_method() logic in db_engine_specs/lib.py.
|
||||
|
||||
attr_values and unresolved are disjoint — an attr is in at most one.
|
||||
"""
|
||||
if visited is None:
|
||||
visited = set()
|
||||
if class_name in visited:
|
||||
return {}, set(), set()
|
||||
visited.add(class_name)
|
||||
|
||||
info = class_info.get(class_name)
|
||||
if not info:
|
||||
return {}, set(), set()
|
||||
|
||||
attr_values = {}
|
||||
unresolved = set()
|
||||
resolved_methods = set()
|
||||
|
||||
# Collect from parents, iterating right-to-left so leftmost bases win
|
||||
# (matches Python MRO: for class C(A, B), A's attributes take precedence).
|
||||
for base_name in reversed(info['bases']):
|
||||
p_vals, p_unres, p_meth = get_resolved_caps(base_name, visited.copy())
|
||||
# A parent's literal assignments overwrite whatever we inherited so far.
|
||||
for attr, val in p_vals.items():
|
||||
attr_values[attr] = val
|
||||
unresolved.discard(attr)
|
||||
# A parent's unresolved assignments likewise take precedence.
|
||||
for attr in p_unres:
|
||||
unresolved.add(attr)
|
||||
attr_values.pop(attr, None)
|
||||
resolved_methods.update(p_meth)
|
||||
|
||||
# Apply this class's own assignments (override parents).
|
||||
for attr, val in info['cap_attrs'].items():
|
||||
attr_values[attr] = val
|
||||
unresolved.discard(attr)
|
||||
for attr in info['unresolved_cap_attrs']:
|
||||
unresolved.add(attr)
|
||||
attr_values.pop(attr, None)
|
||||
|
||||
# Accumulate method overrides, but skip the literal BaseEngineSpec
|
||||
# (its implementations are stubs; only non-base overrides count).
|
||||
if class_name != TRUE_BASE_CLASS:
|
||||
resolved_methods.update(info['direct_methods'])
|
||||
|
||||
return attr_values, unresolved, resolved_methods
|
||||
|
||||
for class_name, info in class_info.items():
|
||||
# Skip base classes and mixins
|
||||
if info['is_base_or_mixin']:
|
||||
@@ -310,7 +485,14 @@ for class_name, info in class_info.items():
|
||||
|
||||
if final_metadata and isinstance(final_metadata, dict) and display_name:
|
||||
debug_info["classes_with_metadata"] += 1
|
||||
databases[display_name] = {
|
||||
|
||||
# Resolve capability flags from Python source
|
||||
attr_values, unresolved_caps, cap_methods = get_resolved_caps(class_name)
|
||||
cap_attrs = dict(CAP_ATTR_DEFAULTS)
|
||||
cap_attrs.update(attr_values)
|
||||
engine_attr = info.get('engine') or ''
|
||||
|
||||
entry = {
|
||||
'engine': display_name.lower().replace(' ', '_'),
|
||||
'engine_name': display_name,
|
||||
'module': info['filename'][:-3], # Remove .py extension
|
||||
@@ -318,19 +500,40 @@ for class_name, info in class_info.items():
|
||||
'time_grains': {},
|
||||
'score': 0,
|
||||
'max_score': 0,
|
||||
'joins': True,
|
||||
'subqueries': True,
|
||||
'supports_dynamic_schema': False,
|
||||
'supports_catalog': False,
|
||||
'supports_dynamic_catalog': False,
|
||||
'ssh_tunneling': False,
|
||||
'query_cancelation': False,
|
||||
'supports_file_upload': False,
|
||||
'user_impersonation': False,
|
||||
'query_cost_estimation': False,
|
||||
'sql_validation': False,
|
||||
# Capability flags read from engine spec class attributes/methods
|
||||
'joins': cap_attrs['allows_joins'],
|
||||
'subqueries': cap_attrs['allows_subqueries'],
|
||||
'supports_dynamic_schema': cap_attrs['supports_dynamic_schema'],
|
||||
'supports_catalog': cap_attrs['supports_catalog'],
|
||||
'supports_dynamic_catalog': cap_attrs['supports_dynamic_catalog'],
|
||||
'ssh_tunneling': not cap_attrs['disable_ssh_tunneling'],
|
||||
'supports_file_upload': cap_attrs['supports_file_upload'],
|
||||
# Method-based flags: True only when a non-base class overrides them.
|
||||
# Matches diagnose() in lib.py: cancel_query override OR
|
||||
# has_implicit_cancel() returning True (which, given the base
|
||||
# returns False, is equivalent to overriding has_implicit_cancel).
|
||||
'query_cancelation': bool({'cancel_query', 'has_implicit_cancel'} & cap_methods),
|
||||
'query_cost_estimation': bool({'estimate_statement_cost', 'estimate_query_cost'} & cap_methods),
|
||||
# SQL validation is implemented in external validator classes keyed by engine name
|
||||
'sql_validation': engine_attr in {'presto', 'postgresql'},
|
||||
'user_impersonation': bool(
|
||||
{'impersonate_user', 'update_impersonation_config', 'get_url_for_impersonation'} & cap_methods
|
||||
),
|
||||
}
|
||||
|
||||
# Tell the JS layer which output fields were populated from the
|
||||
# BaseEngineSpec default because the source assignment was an
|
||||
# unevaluable expression; those get overridden from existing JSON.
|
||||
unresolved_fields = sorted(
|
||||
CAP_ATTR_TO_OUTPUT_FIELD[attr]
|
||||
for attr in unresolved_caps
|
||||
if attr in CAP_ATTR_TO_OUTPUT_FIELD
|
||||
)
|
||||
if unresolved_fields:
|
||||
entry['_unresolved_cap_fields'] = unresolved_fields
|
||||
|
||||
databases[display_name] = entry
|
||||
|
||||
if errors and not databases:
|
||||
print(json.dumps({"error": "Parse errors", "details": errors, "debug": debug_info}), file=sys.stderr)
|
||||
|
||||
@@ -851,24 +1054,52 @@ function loadExistingData() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fall back to the previously-generated databases.json for capability flags
|
||||
* whose source assignment couldn't be statically resolved (e.g.
|
||||
* `allows_joins = is_feature_enabled("DRUID_JOINS")`). The Python extractor
|
||||
* flags these via the internal `_unresolved_cap_fields` marker; without this
|
||||
* fallback those fields would silently inherit the BaseEngineSpec default
|
||||
* and disagree with runtime behavior. The marker is stripped before output.
|
||||
*/
|
||||
function fallbackUnresolvedCaps(newDatabases, existingData) {
|
||||
for (const [name, db] of Object.entries(newDatabases)) {
|
||||
const unresolved = db._unresolved_cap_fields;
|
||||
if (!unresolved || unresolved.length === 0) {
|
||||
delete db._unresolved_cap_fields;
|
||||
continue;
|
||||
}
|
||||
const existingDb = existingData?.databases?.[name];
|
||||
if (existingDb) {
|
||||
for (const field of unresolved) {
|
||||
if (existingDb[field] !== undefined) {
|
||||
db[field] = existingDb[field];
|
||||
}
|
||||
}
|
||||
}
|
||||
delete db._unresolved_cap_fields;
|
||||
}
|
||||
return newDatabases;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge new documentation with existing diagnostics
|
||||
* Preserves score, time_grains, and feature flags from existing data
|
||||
* Preserves score, max_score, and time_grains from existing data (these require
|
||||
* Flask context to generate and cannot be derived from static source analysis).
|
||||
* Capability flags (joins, supports_catalog, etc.) are NOT preserved here — they
|
||||
* are read fresh from the Python engine spec source by extractEngineSpecMetadata(),
|
||||
* with a separate fallback for expression-based assignments (see fallbackUnresolvedCaps).
|
||||
*/
|
||||
function mergeWithExistingDiagnostics(newDatabases, existingData) {
|
||||
if (!existingData?.databases) return newDatabases;
|
||||
|
||||
const diagnosticFields = [
|
||||
'score', 'max_score', 'time_grains', 'joins', 'subqueries',
|
||||
'supports_dynamic_schema', 'supports_catalog', 'supports_dynamic_catalog',
|
||||
'ssh_tunneling', 'query_cancelation', 'supports_file_upload',
|
||||
'user_impersonation', 'query_cost_estimation', 'sql_validation'
|
||||
];
|
||||
// Only preserve fields that require Flask/runtime context to generate
|
||||
const diagnosticFields = ['score', 'max_score', 'time_grains'];
|
||||
|
||||
for (const [name, db] of Object.entries(newDatabases)) {
|
||||
const existingDb = existingData.databases[name];
|
||||
if (existingDb && existingDb.score > 0) {
|
||||
// Preserve diagnostics from existing data
|
||||
// Preserve score/time_grain diagnostics from existing data
|
||||
for (const field of diagnosticFields) {
|
||||
if (existingDb[field] !== undefined) {
|
||||
db[field] = existingDb[field];
|
||||
@@ -879,7 +1110,7 @@ function mergeWithExistingDiagnostics(newDatabases, existingData) {
|
||||
|
||||
const preserved = Object.values(newDatabases).filter(d => d.score > 0).length;
|
||||
if (preserved > 0) {
|
||||
console.log(`Preserved diagnostics for ${preserved} databases from existing data`);
|
||||
console.log(`Preserved score/time_grains for ${preserved} databases from existing data`);
|
||||
}
|
||||
|
||||
return newDatabases;
|
||||
@@ -927,6 +1158,12 @@ async function main() {
|
||||
databases = mergeWithExistingDiagnostics(databases, existingData);
|
||||
}
|
||||
|
||||
// For cap flags assigned via unevaluable expressions (e.g.
|
||||
// `is_feature_enabled(...)`), prefer the value from a previously-generated
|
||||
// JSON. Runs regardless of scores since it addresses static-analysis gaps,
|
||||
// not missing Flask diagnostics. Always strips the internal marker.
|
||||
databases = fallbackUnresolvedCaps(databases, existingData);
|
||||
|
||||
// Extract and merge custom_errors for troubleshooting documentation
|
||||
const customErrors = extractCustomErrors();
|
||||
mergeCustomErrors(databases, customErrors);
|
||||
|
||||
@@ -30,9 +30,11 @@ const __dirname = path.dirname(__filename);
|
||||
const CONFIG_FILE = path.join(__dirname, '..', 'versions-config.json');
|
||||
|
||||
// Parse command line arguments
|
||||
const args = process.argv.slice(2);
|
||||
const rawArgs = process.argv.slice(2);
|
||||
const skipGenerate = rawArgs.includes('--skip-generate');
|
||||
const args = rawArgs.filter((a) => a !== '--skip-generate');
|
||||
const command = args[0]; // 'add' or 'remove'
|
||||
const section = args[1]; // 'docs', 'developer_portal', or 'components'
|
||||
const section = args[1]; // 'docs', 'admin_docs', 'developer_docs', or 'components'
|
||||
const version = args[2]; // version string like '1.2.0'
|
||||
|
||||
function loadConfig() {
|
||||
@@ -43,36 +45,158 @@ function saveConfig(config) {
|
||||
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + '\n');
|
||||
}
|
||||
|
||||
function fixVersionedImports(version) {
|
||||
const versionedDocsPath = path.join(__dirname, '..', 'versioned_docs', `version-${version}`);
|
||||
function freezeDataImports(section, version) {
|
||||
// MDX files can `import` JSON/YAML data from outside the section, either
|
||||
// via escaping relative paths (e.g. country-map-tools.mdx imports
|
||||
// `../../data/countries.json`) or via the `@site/` alias (e.g.
|
||||
// feature-flags.mdx imports `@site/static/feature-flags.json`). Without
|
||||
// intervention the snapshot keeps reading the live file, so the
|
||||
// historical version's content silently changes whenever the data file
|
||||
// is updated. Copy each escaping data import into a snapshot-local
|
||||
// `_versioned_data/` dir and rewrite the import to point there.
|
||||
const sectionRoot = section === 'docs'
|
||||
? path.join(__dirname, '..', 'docs')
|
||||
: path.join(__dirname, '..', section);
|
||||
const docsRoot = path.join(__dirname, '..');
|
||||
const versionedDocsDir = section === 'docs'
|
||||
? `versioned_docs/version-${version}`
|
||||
: `${section}_versioned_docs/version-${version}`;
|
||||
const versionedDocsPath = path.join(__dirname, '..', versionedDocsDir);
|
||||
const frozenDataDir = path.join(versionedDocsPath, '_versioned_data');
|
||||
|
||||
// Files that need import path fixes
|
||||
const filesToFix = [
|
||||
'contributing/resources.mdx',
|
||||
'configuration/country-map-tools.mdx'
|
||||
];
|
||||
if (!fs.existsSync(versionedDocsPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(` Fixing relative imports in versioned docs...`);
|
||||
console.log(` Freezing data imports in ${versionedDocsDir}...`);
|
||||
|
||||
filesToFix.forEach(filePath => {
|
||||
const fullPath = path.join(versionedDocsPath, filePath);
|
||||
if (fs.existsSync(fullPath)) {
|
||||
let content = fs.readFileSync(fullPath, 'utf8');
|
||||
// Matches data file imports in two flavors:
|
||||
// `from '../../foo/bar.json'` (relative, must escape one or more dirs)
|
||||
// `from '@site/static/foo.json'` (Docusaurus site-root alias)
|
||||
const dataImportRe = /(from\s+['"])((?:\.\.\/)+|@site\/)([^'"\s]+\.(?:json|ya?ml))(['"])/g;
|
||||
|
||||
// Fix imports that go up two directories to go up three instead
|
||||
content = content.replace(
|
||||
/from ['"]\.\.\/\.\.\/src\//g,
|
||||
"from '../../../src/"
|
||||
);
|
||||
content = content.replace(
|
||||
/from ['"]\.\.\/\.\.\/data\//g,
|
||||
"from '../../../data/"
|
||||
);
|
||||
|
||||
fs.writeFileSync(fullPath, content);
|
||||
console.log(` Fixed imports in ${filePath}`);
|
||||
function freezeOne(fullPath, depth, prefix, pathSpec, importPath, suffix) {
|
||||
let resolvedSource;
|
||||
if (pathSpec === '@site/') {
|
||||
// `@site/...` always resolves relative to the docs root.
|
||||
resolvedSource = path.join(docsRoot, importPath);
|
||||
} else {
|
||||
// Relative path — must escape the file's depth within the section
|
||||
// to point at content outside the section. Imports that stay inside
|
||||
// are copied wholesale by Docusaurus, so we leave them alone.
|
||||
const upCount = pathSpec.match(/\.\.\//g).length;
|
||||
if (upCount <= depth) return null;
|
||||
const relativeFromVersioned = path.relative(versionedDocsPath, fullPath);
|
||||
const originalDir = path.dirname(path.join(sectionRoot, relativeFromVersioned));
|
||||
resolvedSource = path.resolve(originalDir, pathSpec + importPath);
|
||||
}
|
||||
});
|
||||
// Skip imports that land inside the section root — those get copied
|
||||
// with the section snapshot already.
|
||||
const relFromSection = path.relative(sectionRoot, resolvedSource);
|
||||
if (!relFromSection.startsWith('..')) return null;
|
||||
const relFromDocsRoot = path.relative(docsRoot, resolvedSource);
|
||||
if (relFromDocsRoot.startsWith('..') || !fs.existsSync(resolvedSource)) {
|
||||
return null;
|
||||
}
|
||||
const destPath = path.join(frozenDataDir, relFromDocsRoot);
|
||||
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
||||
fs.copyFileSync(resolvedSource, destPath);
|
||||
const rewritten = path
|
||||
.relative(path.dirname(fullPath), destPath)
|
||||
.split(path.sep)
|
||||
.join('/');
|
||||
const finalImport = rewritten.startsWith('.') ? rewritten : `./${rewritten}`;
|
||||
return `${prefix}${finalImport}${suffix}`;
|
||||
}
|
||||
|
||||
function walk(dir, depth) {
|
||||
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
if (entry.name.startsWith('_')) continue;
|
||||
walk(fullPath, depth + 1);
|
||||
} else if (entry.isFile() && /\.(md|mdx)$/.test(entry.name)) {
|
||||
const original = fs.readFileSync(fullPath, 'utf8');
|
||||
let inFence = false;
|
||||
let mutated = false;
|
||||
const updated = original.split('\n').map(line => {
|
||||
if (/^\s*(```|~~~)/.test(line)) {
|
||||
inFence = !inFence;
|
||||
return line;
|
||||
}
|
||||
if (inFence) return line;
|
||||
return line.replace(dataImportRe, (match, prefix, pathSpec, importPath, suffix) => {
|
||||
const rewritten = freezeOne(fullPath, depth, prefix, pathSpec, importPath, suffix);
|
||||
if (rewritten === null) return match;
|
||||
mutated = true;
|
||||
return rewritten;
|
||||
});
|
||||
}).join('\n');
|
||||
if (mutated) {
|
||||
fs.writeFileSync(fullPath, updated);
|
||||
const rel = path.relative(versionedDocsPath, fullPath);
|
||||
console.log(` Froze data imports in ${rel}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
walk(versionedDocsPath, 0);
|
||||
}
|
||||
|
||||
function fixVersionedImports(section, version) {
|
||||
// Versioned content lands one directory deeper than the source content,
|
||||
// so any `../../src/` or `../../data/` imports in .md/.mdx files need
|
||||
// an extra `../` to keep reaching docs/src and docs/data.
|
||||
const versionedDocsDir = section === 'docs'
|
||||
? `versioned_docs/version-${version}`
|
||||
: `${section}_versioned_docs/version-${version}`;
|
||||
const versionedDocsPath = path.join(__dirname, '..', versionedDocsDir);
|
||||
|
||||
if (!fs.existsSync(versionedDocsPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(` Fixing relative imports in ${versionedDocsDir}...`);
|
||||
|
||||
// Imports whose `../` count exceeds the file's depth within the section
|
||||
// escape the section root, so they need one extra `../` once the file
|
||||
// lives one level deeper inside the snapshot dir. Imports that stay
|
||||
// inside the section are unaffected (the section copies wholesale).
|
||||
function walk(dir, depth) {
|
||||
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
walk(fullPath, depth + 1);
|
||||
} else if (entry.isFile() && /\.(md|mdx)$/.test(entry.name)) {
|
||||
const original = fs.readFileSync(fullPath, 'utf8');
|
||||
// Track fenced code blocks so we don't rewrite import samples inside
|
||||
// ```ts / ```js (etc.) blocks that are documentation, not real imports.
|
||||
let inFence = false;
|
||||
const updated = original.split('\n').map(line => {
|
||||
if (/^\s*(```|~~~)/.test(line)) {
|
||||
inFence = !inFence;
|
||||
return line;
|
||||
}
|
||||
if (inFence) return line;
|
||||
return line.replace(
|
||||
/(from\s+['"])((?:\.\.\/)+)/g,
|
||||
(match, prefix, dots) => {
|
||||
const upCount = dots.match(/\.\.\//g).length;
|
||||
return upCount > depth ? `${prefix}../${dots}` : match;
|
||||
},
|
||||
);
|
||||
}).join('\n');
|
||||
if (updated !== original) {
|
||||
fs.writeFileSync(fullPath, updated);
|
||||
const rel = path.relative(versionedDocsPath, fullPath);
|
||||
console.log(` Fixed imports in ${rel}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
walk(versionedDocsPath, 0);
|
||||
}
|
||||
|
||||
function addVersion(section, version) {
|
||||
@@ -91,6 +215,28 @@ function addVersion(section, version) {
|
||||
|
||||
console.log(`Creating version ${version} for ${section}...`);
|
||||
|
||||
// Refresh auto-generated content (database pages, API reference,
|
||||
// component playground) so the snapshot captures the current state of
|
||||
// master rather than whatever happened to be on disk. `generate:smart`
|
||||
// hashes its inputs and skips unchanged generators, so this is cheap
|
||||
// when the dev already has fresh output.
|
||||
//
|
||||
// Use --skip-generate if you've placed a CI-artifact databases.json
|
||||
// (the `database-diagnostics` artifact from Python-Integration) and
|
||||
// want to preserve it instead of letting the local env regenerate it.
|
||||
// See docs/README.md "Before You Cut" for the canonical release flow.
|
||||
if (skipGenerate) {
|
||||
console.log(` Skipping auto-gen refresh (--skip-generate set)`);
|
||||
} else {
|
||||
console.log(` Refreshing auto-generated docs...`);
|
||||
try {
|
||||
execSync('yarn run generate:smart', { stdio: 'inherit' });
|
||||
} catch (error) {
|
||||
console.error(`Failed to refresh auto-generated docs: ${error.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Run Docusaurus version command
|
||||
const docusaurusCommand = section === 'docs'
|
||||
? `yarn docusaurus docs:version ${version}`
|
||||
@@ -103,10 +249,12 @@ function addVersion(section, version) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Fix relative imports in versioned docs (for main docs section only)
|
||||
if (section === 'docs') {
|
||||
fixVersionedImports(version);
|
||||
}
|
||||
// Freeze data imports BEFORE adjusting paths, so the depth-aware rewriter
|
||||
// doesn't process the now-local imports we just rewrote.
|
||||
freezeDataImports(section, version);
|
||||
|
||||
// Fix relative imports in versioned content
|
||||
fixVersionedImports(section, version);
|
||||
|
||||
// Update config
|
||||
// Add to onlyIncludeVersions array (after 'current')
|
||||
@@ -121,10 +269,15 @@ function addVersion(section, version) {
|
||||
banner: 'none'
|
||||
};
|
||||
|
||||
// Optionally update lastVersion if this is the first non-current version
|
||||
if (config[section].onlyIncludeVersions.length === 2) {
|
||||
config[section].lastVersion = version;
|
||||
}
|
||||
// Note: we deliberately do NOT auto-bump `lastVersion` to the new
|
||||
// version. Superset's docs site keeps `lastVersion: 'current'` so
|
||||
// the canonical URLs (`/user-docs/...`, `/admin-docs/...`,
|
||||
// `/developer-docs/...`, `/components/...`) always render master
|
||||
// content; cut versions are accessed only via their explicit version
|
||||
// segment. (`/docs/...` paths are legacy and handled via per-page
|
||||
// redirects in docusaurus.config.ts — not a current canonical
|
||||
// form.) If you want a different policy, edit versions-config.json
|
||||
// after cutting.
|
||||
|
||||
saveConfig(config);
|
||||
console.log(`✅ Version ${version} added successfully to ${section}`);
|
||||
@@ -185,8 +338,17 @@ function removeVersion(section, version) {
|
||||
const versionIndex = versions.indexOf(version);
|
||||
if (versionIndex > -1) {
|
||||
versions.splice(versionIndex, 1);
|
||||
fs.writeFileSync(versionsJsonPath, JSON.stringify(versions, null, 2) + '\n');
|
||||
console.log(` Updated ${versionsJsonFile}`);
|
||||
if (versions.length === 0) {
|
||||
// Sections with no versions shouldn't carry an empty versions file
|
||||
// on disk — Docusaurus doesn't require it, and an empty `[]` file
|
||||
// gets picked up by `docusaurus version` and snapshotted into the
|
||||
// next cut.
|
||||
fs.unlinkSync(versionsJsonPath);
|
||||
console.log(` Removed empty ${versionsJsonFile}`);
|
||||
} else {
|
||||
fs.writeFileSync(versionsJsonPath, JSON.stringify(versions, null, 2) + '\n');
|
||||
console.log(` Updated ${versionsJsonFile}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,17 +373,20 @@ function removeVersion(section, version) {
|
||||
function printUsage() {
|
||||
console.log(`
|
||||
Usage:
|
||||
node scripts/manage-versions.js add <section> <version>
|
||||
node scripts/manage-versions.js remove <section> <version>
|
||||
node scripts/manage-versions.mjs add <section> <version> [--skip-generate]
|
||||
node scripts/manage-versions.mjs remove <section> <version>
|
||||
|
||||
Where:
|
||||
- section: 'docs', 'developer_portal', or 'components'
|
||||
- section: 'docs', 'developer_docs', 'admin_docs', or 'components'
|
||||
- version: version string (e.g., '1.2.0', '2.0.0')
|
||||
- --skip-generate: skip refreshing auto-generated docs before snapshotting
|
||||
(use when you've already placed a fresh databases.json
|
||||
from CI and want to preserve it)
|
||||
|
||||
Examples:
|
||||
node scripts/manage-versions.js add docs 2.0.0
|
||||
node scripts/manage-versions.js add developer_portal 1.3.0
|
||||
node scripts/manage-versions.js remove components 1.0.0
|
||||
node scripts/manage-versions.mjs add docs 2.0.0
|
||||
node scripts/manage-versions.mjs add developer_docs 1.3.0
|
||||
node scripts/manage-versions.mjs remove components 1.0.0
|
||||
`);
|
||||
}
|
||||
|
||||
|
||||
@@ -30,19 +30,30 @@ import { DownOutlined } from '@ant-design/icons';
|
||||
|
||||
import styles from './styles.module.css';
|
||||
|
||||
// Map each versioned plugin id to the URL prefix it actually serves
|
||||
// content from. Three of the four routeBasePath values differ from
|
||||
// their pluginId — the default preset-classic docs plugin lives at
|
||||
// `/user-docs`, and admin_docs / developer_docs use hyphens in their
|
||||
// URLs even though the plugin ids use underscores. Without this map
|
||||
// the basePath derivation below would mis-split the pathname for
|
||||
// those sections and the version dropdown would jump to the section
|
||||
// root instead of preserving the current page.
|
||||
//
|
||||
// Keep in sync with the `routeBasePath` values in docusaurus.config.ts.
|
||||
const PLUGIN_ID_TO_BASE_PATH = {
|
||||
default: '/user-docs',
|
||||
components: '/components',
|
||||
admin_docs: '/admin-docs',
|
||||
developer_docs: '/developer-docs',
|
||||
};
|
||||
|
||||
export default function DocVersionBadge() {
|
||||
const activePlugin = useActivePlugin();
|
||||
const { pathname } = useLocation();
|
||||
const pluginId = activePlugin?.pluginId;
|
||||
const [versionedPath, setVersionedPath] = React.useState('');
|
||||
|
||||
// Show version selector for all versioned sections
|
||||
const isVersioned = [
|
||||
'default', // main docs
|
||||
'components',
|
||||
'tutorials',
|
||||
'developer_portal',
|
||||
].includes(pluginId);
|
||||
const isVersioned = pluginId && pluginId in PLUGIN_ID_TO_BASE_PATH;
|
||||
|
||||
const { preferredVersion } = useDocsPreferredVersion(pluginId);
|
||||
const versions = useVersions(pluginId);
|
||||
@@ -53,7 +64,8 @@ export default function DocVersionBadge() {
|
||||
if (!pathname || !version || !pluginId) return;
|
||||
|
||||
let relativePath = '';
|
||||
const basePath = pluginId === 'default' ? '/docs' : `/${pluginId}`;
|
||||
const basePath = PLUGIN_ID_TO_BASE_PATH[pluginId];
|
||||
if (!basePath) return;
|
||||
|
||||
// Handle different version path patterns
|
||||
if (pathname.includes(basePath)) {
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import DocVersionBanner from '@theme-original/DocVersionBanner';
|
||||
import {
|
||||
useActivePlugin,
|
||||
useDocsVersion,
|
||||
useVersions,
|
||||
} from '@docusaurus/plugin-content-docs/client';
|
||||
import { useLocation } from '@docusaurus/router';
|
||||
import { useDocsPreferredVersion } from '@docusaurus/theme-common';
|
||||
import { Dropdown } from 'antd';
|
||||
import { DownOutlined } from '@ant-design/icons';
|
||||
|
||||
import styles from './styles.module.css';
|
||||
|
||||
export default function DocVersionBannerWrapper(props) {
|
||||
const activePlugin = useActivePlugin();
|
||||
const { pathname } = useLocation();
|
||||
const pluginId = activePlugin?.pluginId;
|
||||
const [versionedPath, setVersionedPath] = useState('');
|
||||
|
||||
// Only show version selector for tutorials
|
||||
// Main docs, components, and developer_portal use the DocVersionBadge component instead
|
||||
const isVersioned = pluginId && ['tutorials'].includes(pluginId);
|
||||
|
||||
const { preferredVersion } = useDocsPreferredVersion(pluginId);
|
||||
const versions = useVersions(pluginId);
|
||||
const version = useDocsVersion();
|
||||
|
||||
// Early return if required data is not available
|
||||
if (!isVersioned || !versions || !version) {
|
||||
return <DocVersionBanner {...props} />;
|
||||
}
|
||||
|
||||
// Extract the current page path relative to the version
|
||||
useEffect(() => {
|
||||
if (!pathname || !version || !pluginId) return;
|
||||
|
||||
let relativePath = '';
|
||||
|
||||
// Handle different version path patterns
|
||||
if (pathname.includes(`/${pluginId}/`)) {
|
||||
// Extract the part after the version
|
||||
// Example: /components/1.1.0/ui-components/button -> /ui-components/button
|
||||
const parts = pathname.split(`/${pluginId}/`);
|
||||
if (parts.length > 1) {
|
||||
const afterPluginId = parts[1];
|
||||
// Find where the version part ends
|
||||
const versionParts = afterPluginId.split('/');
|
||||
if (versionParts.length > 1) {
|
||||
// Remove the version part and join the rest
|
||||
relativePath = '/' + versionParts.slice(1).join('/');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setVersionedPath(relativePath);
|
||||
}, [pathname, version, pluginId]);
|
||||
|
||||
// Create dropdown items for version selection
|
||||
const items = versions.map(v => {
|
||||
// Construct the URL for this version, preserving the current page
|
||||
// v.path is the version-specific path like "1.0.0" or "next"
|
||||
let versionUrl = v.path;
|
||||
|
||||
if (versionedPath) {
|
||||
// Construct the full URL with the version and the current page path
|
||||
versionUrl = v.path + versionedPath;
|
||||
}
|
||||
|
||||
return {
|
||||
key: v.name,
|
||||
label: (
|
||||
<a href={versionUrl}>
|
||||
{v.label}
|
||||
{v.name === version.name && ' (current)'}
|
||||
{v.name === preferredVersion?.name && ' (preferred)'}
|
||||
</a>
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<DocVersionBanner {...props} />
|
||||
{isVersioned && (
|
||||
<div className={styles.versionBanner}>
|
||||
<div className={styles.versionContainer}>
|
||||
<span className={styles.versionLabel}>Version:</span>
|
||||
<Dropdown menu={{ items }} trigger={['click']}>
|
||||
<a
|
||||
onClick={e => e.preventDefault()}
|
||||
className={styles.versionSelector}
|
||||
>
|
||||
{version.label} <DownOutlined />
|
||||
</a>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
8
docs/static/feature-flags.json
vendored
@@ -81,6 +81,12 @@
|
||||
"lifecycle": "development",
|
||||
"description": "Expand nested types in Presto into extra columns/arrays. Experimental, doesn't work with all nested types."
|
||||
},
|
||||
{
|
||||
"name": "SEMANTIC_LAYERS",
|
||||
"default": false,
|
||||
"lifecycle": "development",
|
||||
"description": "Enable semantic layers and show semantic views alongside datasets"
|
||||
},
|
||||
{
|
||||
"name": "TABLE_V2_TIME_COMPARISON_ENABLED",
|
||||
"default": false,
|
||||
@@ -168,7 +174,7 @@
|
||||
"default": false,
|
||||
"lifecycle": "testing",
|
||||
"description": "Allows users to add a superset:// DB that can query across databases. Experimental with potential security/performance risks. See SUPERSET_META_DB_LIMIT.",
|
||||
"docs": "https://superset.apache.org/docs/configuration/databases/#querying-across-databases"
|
||||
"docs": "https://superset.apache.org/user-docs/databases/supported/superset-meta-database"
|
||||
},
|
||||
{
|
||||
"name": "ESTIMATE_QUERY_COST",
|
||||
|
||||
BIN
docs/static/img/screenshots/dashboard.jpg
vendored
|
Before Width: | Height: | Size: 134 KiB After Width: | Height: | Size: 132 KiB |
BIN
docs/static/img/screenshots/explore.jpg
vendored
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 116 KiB |
BIN
docs/static/img/screenshots/gallery.jpg
vendored
|
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 118 KiB |
BIN
docs/static/img/screenshots/sql_lab.jpg
vendored
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 97 KiB |
BIN
docs/static/img/tutorial/create_pivot.png
vendored
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 240 KiB |
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 131 KiB |
BIN
docs/static/img/tutorial/tutorial_chart_resize.png
vendored
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 12 KiB |
BIN
docs/static/img/tutorial/tutorial_edit_button.png
vendored
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 37 KiB |
BIN
docs/static/img/tutorial/tutorial_save_slice.png
vendored
|
Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 21 KiB |
@@ -1,3 +0,0 @@
|
||||
[
|
||||
"1.0.0"
|
||||
]
|
||||
1239
docs/yarn.lock
@@ -29,7 +29,7 @@ maintainers:
|
||||
- name: craig-rueda
|
||||
email: craig@craigrueda.com
|
||||
url: https://github.com/craig-rueda
|
||||
version: 0.15.4 # See [README](https://github.com/apache/superset/blob/master/helm/superset/README.md#versioning) for version details.
|
||||
version: 0.15.5 # See [README](https://github.com/apache/superset/blob/master/helm/superset/README.md#versioning) for version details.
|
||||
dependencies:
|
||||
- name: postgresql
|
||||
version: 16.7.27
|
||||
|
||||
@@ -23,7 +23,7 @@ NOTE: This file is generated by helm-docs: https://github.com/norwoodj/helm-docs
|
||||
|
||||
# superset
|
||||
|
||||

|
||||

|
||||
|
||||
Apache Superset is a modern, enterprise-ready business intelligence web application
|
||||
|
||||
|
||||
@@ -844,6 +844,8 @@ postgresql:
|
||||
database: superset
|
||||
|
||||
image:
|
||||
registry: docker.io
|
||||
repository: bitnamilegacy/postgresql
|
||||
tag: "14.17.0-debian-12-r3"
|
||||
|
||||
## PostgreSQL Primary parameters
|
||||
@@ -918,6 +920,11 @@ redis:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
|
||||
image:
|
||||
registry: docker.io
|
||||
repository: bitnamilegacy/redis
|
||||
tag: 7.0.10-debian-11-r4
|
||||
|
||||
nodeSelector: {}
|
||||
|
||||
tolerations: []
|
||||
|
||||
@@ -71,7 +71,7 @@ dependencies = [
|
||||
"marshmallow>=3.0, <4",
|
||||
"marshmallow-union>=0.1",
|
||||
"msgpack>=1.0.0, <1.2",
|
||||
"nh3>=0.2.11, <0.3",
|
||||
"nh3>=0.2.11, <0.4",
|
||||
"numpy>1.23.5, <2.3",
|
||||
"packaging",
|
||||
# --------------------------
|
||||
@@ -95,7 +95,7 @@ dependencies = [
|
||||
"redis>=5.0.0, <6.0",
|
||||
"rison>=2.0.0, <3.0",
|
||||
"selenium>=4.14.0, <5.0",
|
||||
"shillelagh[gsheetsapi]>=1.4.3, <2.0",
|
||||
"shillelagh[gsheetsapi]>=1.4.4, <2.0",
|
||||
"sshtunnel>=0.4.0, <0.5",
|
||||
"simplejson>=3.15.0",
|
||||
"slack_sdk>=3.19.0, <4",
|
||||
@@ -114,7 +114,7 @@ dependencies = [
|
||||
|
||||
[project.optional-dependencies]
|
||||
|
||||
athena = ["pyathena[pandas]>=2, <3"]
|
||||
athena = ["pyathena[pandas]>=2, <4"]
|
||||
aurora-data-api = ["preset-sqlalchemy-aurora-data-api>=0.2.8,<0.3"]
|
||||
bigquery = [
|
||||
"pandas-gbq>=0.19.1",
|
||||
@@ -131,25 +131,31 @@ d1 = [
|
||||
]
|
||||
databend = ["databend-sqlalchemy>=0.3.2, <1.0"]
|
||||
databricks = [
|
||||
"databricks-sql-connector==4.1.2",
|
||||
"databricks-sql-connector==4.2.6",
|
||||
"databricks-sqlalchemy==1.0.5",
|
||||
]
|
||||
db2 = ["ibm-db-sa>0.3.8, <=0.4.0"]
|
||||
denodo = ["denodo-sqlalchemy~=1.0.6"]
|
||||
db2 = ["ibm-db-sa>0.3.8, <=0.4.4"]
|
||||
denodo = ["denodo-sqlalchemy>=1.0.6,<2.1.0"]
|
||||
dremio = ["sqlalchemy-dremio>=1.2.1, <4"]
|
||||
drill = ["sqlalchemy-drill>=1.1.4, <2"]
|
||||
druid = ["pydruid>=0.6.5,<0.7"]
|
||||
duckdb = ["duckdb>=1.4.2,<2", "duckdb-engine>=0.17.0"]
|
||||
dynamodb = ["pydynamodb>=0.4.2"]
|
||||
solr = ["sqlalchemy-solr >= 0.2.0"]
|
||||
elasticsearch = ["elasticsearch-dbapi>=0.2.12, <0.3.0"]
|
||||
elasticsearch = ["elasticsearch-dbapi>=0.2.13, <0.3.0"]
|
||||
exasol = ["sqlalchemy-exasol >= 2.4.0, <3.0"]
|
||||
excel = ["xlrd>=1.2.0, <1.3"]
|
||||
fastmcp = ["fastmcp>=3.2.4,<4.0"]
|
||||
firebird = ["sqlalchemy-firebird>=0.7.0, <0.8"]
|
||||
fastmcp = [
|
||||
"fastmcp>=3.2.4,<4.0",
|
||||
# tiktoken backs the response-size-guard token estimator. Without
|
||||
# it, the middleware falls back to a coarser character-based
|
||||
# heuristic that under-counts JSON-heavy MCP responses.
|
||||
"tiktoken>=0.7.0,<1.0",
|
||||
]
|
||||
firebird = ["sqlalchemy-firebird>=0.7.0, <2.2"]
|
||||
firebolt = ["firebolt-sqlalchemy>=1.0.0, <2"]
|
||||
gevent = ["gevent>=23.9.1"]
|
||||
gsheets = ["shillelagh[gsheetsapi]>=1.4.3, <2"]
|
||||
gsheets = ["shillelagh[gsheetsapi]>=1.4.4, <2"]
|
||||
hana = ["hdbcli==2.4.162", "sqlalchemy_hana==0.4.0"]
|
||||
hive = [
|
||||
"pyhive[hive]>=0.6.5;python_version<'3.11'",
|
||||
@@ -158,7 +164,7 @@ hive = [
|
||||
"thrift>=0.14.1, <1.0.0",
|
||||
"thrift_sasl>=0.4.3, < 1.0.0",
|
||||
]
|
||||
impala = ["impyla>0.16.2, <0.17"]
|
||||
impala = ["impyla>0.16.2, <0.23"]
|
||||
kusto = ["sqlalchemy-kusto>=3.0.0, <4"]
|
||||
kylin = ["kylinpy>=2.8.1, <2.9"]
|
||||
mssql = ["pymssql>=2.2.8, <3"]
|
||||
@@ -171,9 +177,9 @@ ocient = [
|
||||
"shapely",
|
||||
"geojson",
|
||||
]
|
||||
oracle = ["cx-Oracle>8.0.0, <8.1"]
|
||||
oracle = ["cx-Oracle>8.0.0, <8.4"]
|
||||
parseable = ["sqlalchemy-parseable>=0.1.3,<0.2.0"]
|
||||
pinot = ["pinotdb>=5.0.0, <6.0.0"]
|
||||
pinot = ["pinotdb>=5.0.0, <10.0.0"]
|
||||
playwright = ["playwright>=1.37.0, <2"]
|
||||
postgres = ["psycopg2-binary==2.9.12"]
|
||||
presto = ["pyhive[presto]>=0.6.5"]
|
||||
@@ -181,7 +187,7 @@ trino = ["trino>=0.328.0"]
|
||||
prophet = ["prophet>=1.1.6, <2"]
|
||||
redshift = ["sqlalchemy-redshift>=0.8.1, <0.9"]
|
||||
risingwave = ["sqlalchemy-risingwave"]
|
||||
shillelagh = ["shillelagh[all]>=1.4.3, <2"]
|
||||
shillelagh = ["shillelagh[all]>=1.4.4, <2"]
|
||||
singlestore = ["sqlalchemy-singlestoredb>=1.1.1, <2"]
|
||||
snowflake = ["snowflake-sqlalchemy>=1.2.4, <2"]
|
||||
sqlite = ["syntaqlite>=0.1.0"]
|
||||
@@ -197,7 +203,7 @@ tdengine = [
|
||||
]
|
||||
teradata = ["teradatasql>=16.20.0.23"]
|
||||
thumbnails = [] # deprecated, will be removed in 7.0
|
||||
vertica = ["sqlalchemy-vertica-python>=0.5.9, < 0.6"]
|
||||
vertica = ["sqlalchemy-vertica-python>= 0.5.9, < 0.7"]
|
||||
netezza = ["nzalchemy>=11.0.2"]
|
||||
starrocks = ["starrocks>=1.0.0"]
|
||||
doris = ["pydoris>=1.0.0, <2.0.0"]
|
||||
@@ -218,7 +224,7 @@ development = [
|
||||
"progress>=1.5,<2",
|
||||
"psutil",
|
||||
"pyfakefs",
|
||||
"pyinstrument>=4.0.2,<5",
|
||||
"pyinstrument>=4.0.2,<6",
|
||||
"pylint",
|
||||
"pytest<8.0.0", # hairy issue with pytest >=8 where current_app proxies are not set in time
|
||||
"pytest-asyncio",
|
||||
@@ -288,6 +294,7 @@ module = [
|
||||
"superset.tags.filters",
|
||||
"superset.commands.security.update",
|
||||
"superset.commands.security.create",
|
||||
"superset.semantic_layers.api",
|
||||
]
|
||||
warn_unused_ignores = false
|
||||
|
||||
@@ -376,6 +383,7 @@ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
||||
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
"superset/mcp_service/app.py" = ["S608", "E501"] # LLM instruction text: SQL examples (S608) and long lines in multiline string (E501)
|
||||
"superset/mcp_service/*/tool/list_*.py" = ["E501"] # LLM docstring examples show full request shapes which exceed line length
|
||||
"scripts/*" = ["TID251"]
|
||||
"setup.py" = ["TID251"]
|
||||
"superset/config.py" = ["TID251"]
|
||||
|
||||
@@ -183,7 +183,9 @@ idna==3.10
|
||||
# trio
|
||||
# url-normalize
|
||||
isodate==0.7.2
|
||||
# via apache-superset (pyproject.toml)
|
||||
# via
|
||||
# apache-superset (pyproject.toml)
|
||||
# apache-superset-core
|
||||
itsdangerous==2.2.0
|
||||
# via
|
||||
# flask
|
||||
@@ -296,6 +298,7 @@ pyarrow==20.0.0
|
||||
# via
|
||||
# -r requirements/base.in
|
||||
# apache-superset (pyproject.toml)
|
||||
# apache-superset-core
|
||||
pyasn1==0.6.3
|
||||
# via
|
||||
# pyasn1-modules
|
||||
@@ -381,7 +384,7 @@ selenium==4.32.0
|
||||
# via apache-superset (pyproject.toml)
|
||||
setuptools==80.9.0
|
||||
# via -r requirements/base.in
|
||||
shillelagh==1.4.3
|
||||
shillelagh==1.4.4
|
||||
# via apache-superset (pyproject.toml)
|
||||
simplejson==3.20.1
|
||||
# via apache-superset (pyproject.toml)
|
||||
|
||||
@@ -442,6 +442,7 @@ isodate==0.7.2
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# apache-superset
|
||||
# apache-superset-core
|
||||
isort==6.0.1
|
||||
# via pylint
|
||||
itsdangerous==2.2.0
|
||||
@@ -715,6 +716,7 @@ pyarrow==20.0.0
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# apache-superset
|
||||
# apache-superset-core
|
||||
# db-dtypes
|
||||
# pandas-gbq
|
||||
pyasn1==0.6.3
|
||||
@@ -866,6 +868,8 @@ referencing==0.36.2
|
||||
# jsonschema
|
||||
# jsonschema-path
|
||||
# jsonschema-specifications
|
||||
regex==2026.4.4
|
||||
# via tiktoken
|
||||
requests==2.33.0
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
@@ -878,6 +882,7 @@ requests==2.33.0
|
||||
# requests-cache
|
||||
# requests-oauthlib
|
||||
# shillelagh
|
||||
# tiktoken
|
||||
# trino
|
||||
requests-cache==1.2.1
|
||||
# via
|
||||
@@ -931,7 +936,7 @@ setuptools==80.9.0
|
||||
# pydata-google-auth
|
||||
# zope-event
|
||||
# zope-interface
|
||||
shillelagh==1.4.3
|
||||
shillelagh==1.4.4
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# apache-superset
|
||||
@@ -1003,6 +1008,8 @@ tabulate==0.9.0
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# apache-superset
|
||||
tiktoken==0.12.0
|
||||
# via apache-superset
|
||||
tomli-w==1.2.0
|
||||
# via apache-superset-extensions-cli
|
||||
tomlkit==0.13.3
|
||||
|
||||
@@ -20,7 +20,7 @@ import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from typing import List
|
||||
from typing import List, Optional
|
||||
from urllib.request import Request, urlopen
|
||||
|
||||
# Define patterns for each group of files you're interested in
|
||||
@@ -111,7 +111,7 @@ def main(event_type: str, sha: str, repo: str) -> None:
|
||||
"""Main function to check for file changes based on event context."""
|
||||
print("SHA:", sha)
|
||||
print("EVENT_TYPE", event_type)
|
||||
files = []
|
||||
files: Optional[List[str]] = []
|
||||
if event_type == "pull_request":
|
||||
pr_number = os.getenv("GITHUB_REF", "").split("/")[-2]
|
||||
if is_int(pr_number):
|
||||
@@ -124,11 +124,15 @@ def main(event_type: str, sha: str, repo: str) -> None:
|
||||
print("Files touched since previous commit:")
|
||||
print_files(files)
|
||||
|
||||
elif event_type == "workflow_dispatch":
|
||||
print("Workflow dispatched, assuming all changed")
|
||||
elif event_type in ("workflow_dispatch", "schedule"):
|
||||
# Manual or cron-triggered runs aren't tied to a specific diff, so
|
||||
# treat every group as changed. `files = None` makes the loop below
|
||||
# short-circuit to True for every group via `files is None or ...`.
|
||||
print(f"{event_type} run, assuming all changed")
|
||||
files = None
|
||||
|
||||
else:
|
||||
raise ValueError("Unsupported event type")
|
||||
raise ValueError(f"Unsupported event type: {event_type}")
|
||||
|
||||
changes_detected = {}
|
||||
for group, regex_patterns in PATTERNS.items():
|
||||
@@ -144,7 +148,7 @@ def main(event_type: str, sha: str, repo: str) -> None:
|
||||
# NOTE: as noted above, we assume that if 100 files are touched, we should
|
||||
# trigger all checks. This is a workaround for the GitHub API limit of 100
|
||||
# files. Using >= 99 because off-by-one errors are not uncommon
|
||||
if changed or len(files) >= 99:
|
||||
if changed or (files is not None and len(files) >= 99):
|
||||
print(f"{check}=true", file=f)
|
||||
print(f"Triggering group: {check}")
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
[project]
|
||||
name = "apache-superset-core"
|
||||
version = "0.1.0rc2"
|
||||
version = "0.1.0"
|
||||
description = "Core Python package for building Apache Superset backend extensions and integrations"
|
||||
readme = "README.md"
|
||||
authors = [
|
||||
@@ -43,6 +43,8 @@ classifiers = [
|
||||
]
|
||||
dependencies = [
|
||||
"flask-appbuilder>=5.0.2,<6",
|
||||
"isodate>=0.7.0",
|
||||
"pyarrow>=16.0.0",
|
||||
"pydantic>=2.8.0",
|
||||
"sqlalchemy>=1.4.0,<2.0",
|
||||
"sqlalchemy-utils>=0.38.0, <0.43", # expanding lowerbound to work with pydoris
|
||||
|
||||
73
superset-core/src/superset_core/semantic_layers/config.py
Normal file
@@ -0,0 +1,73 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
def build_configuration_schema(
|
||||
config_class: type[BaseModel],
|
||||
configuration: BaseModel | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Build a JSON schema from a Pydantic configuration class.
|
||||
|
||||
Handles generic boilerplate that any semantic layer with dynamic fields needs:
|
||||
|
||||
- Reorders properties to match model field order (Pydantic sorts alphabetically)
|
||||
- When ``configuration`` is None, sets ``enum: []`` on all ``x-dynamic`` properties
|
||||
so the frontend renders them as empty dropdowns
|
||||
|
||||
Semantic layer implementations call this instead of
|
||||
``model_json_schema()`` directly,
|
||||
then only need to add their own dynamic population logic.
|
||||
"""
|
||||
schema = config_class.model_json_schema()
|
||||
|
||||
# Pydantic sorts properties alphabetically; restore model field order
|
||||
field_order = [
|
||||
field.alias or name for name, field in config_class.model_fields.items()
|
||||
]
|
||||
schema["properties"] = {
|
||||
key: schema["properties"][key]
|
||||
for key in field_order
|
||||
if key in schema["properties"]
|
||||
}
|
||||
|
||||
if configuration is None:
|
||||
for prop_schema in schema["properties"].values():
|
||||
if prop_schema.get("x-dynamic"):
|
||||
prop_schema["enum"] = []
|
||||
|
||||
return schema
|
||||
|
||||
|
||||
def check_dependencies(
|
||||
prop_schema: dict[str, Any],
|
||||
configuration: BaseModel,
|
||||
) -> bool:
|
||||
"""
|
||||
Check whether a dynamic property's dependencies are satisfied.
|
||||
|
||||
Reads the ``x-dependsOn`` list from the property schema and returns ``True``
|
||||
when every referenced attribute on ``configuration`` is truthy.
|
||||
"""
|
||||
dependencies = prop_schema.get("x-dependsOn", [])
|
||||
return all(getattr(configuration, dep, None) for dep in dependencies)
|
||||
169
superset-core/src/superset_core/semantic_layers/daos.py
Normal file
@@ -0,0 +1,169 @@
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Semantic layer DAO interfaces for superset-core.
|
||||
|
||||
Provides abstract DAO classes for semantic layers and views that define the
|
||||
interface contract. Host implementations replace these with concrete classes
|
||||
backed by SQLAlchemy during initialization.
|
||||
|
||||
Usage:
|
||||
from superset_core.semantic_layers.daos import (
|
||||
AbstractSemanticLayerDAO,
|
||||
AbstractSemanticViewDAO,
|
||||
)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import abstractmethod
|
||||
from typing import Any, ClassVar
|
||||
|
||||
from superset_core.common.daos import BaseDAO
|
||||
from superset_core.semantic_layers.models import SemanticLayerModel, SemanticViewModel
|
||||
|
||||
|
||||
class AbstractSemanticLayerDAO(BaseDAO[SemanticLayerModel]):
|
||||
"""
|
||||
Abstract DAO interface for SemanticLayer.
|
||||
|
||||
Host implementations will replace this class during initialization
|
||||
with a concrete DAO providing actual database access.
|
||||
"""
|
||||
|
||||
model_cls: ClassVar[type[Any] | None] = None
|
||||
base_filter = None
|
||||
id_column_name = "uuid"
|
||||
uuid_column_name = "uuid"
|
||||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def validate_uniqueness(cls, name: str) -> bool:
|
||||
"""
|
||||
Validate that a semantic layer name is unique.
|
||||
|
||||
:param name: Semantic layer name to validate
|
||||
:return: True if the name is unique, False otherwise
|
||||
"""
|
||||
...
|
||||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def validate_update_uniqueness(cls, layer_uuid: str, name: str) -> bool:
|
||||
"""
|
||||
Validate that a semantic layer name is unique for an update operation,
|
||||
excluding the layer being updated.
|
||||
|
||||
:param layer_uuid: UUID of the semantic layer being updated
|
||||
:param name: New name to validate
|
||||
:return: True if the name is unique, False otherwise
|
||||
"""
|
||||
...
|
||||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def find_by_name(cls, name: str) -> SemanticLayerModel | None:
|
||||
"""
|
||||
Find a semantic layer by name.
|
||||
|
||||
:param name: Semantic layer name
|
||||
:return: SemanticLayerModel instance or None
|
||||
"""
|
||||
...
|
||||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def get_semantic_views(cls, layer_uuid: str) -> list[SemanticViewModel]:
|
||||
"""
|
||||
Get all semantic views associated with a semantic layer.
|
||||
|
||||
:param layer_uuid: UUID of the semantic layer
|
||||
:return: List of SemanticViewModel instances
|
||||
"""
|
||||
...
|
||||
|
||||
|
||||
class AbstractSemanticViewDAO(BaseDAO[SemanticViewModel]):
|
||||
"""
|
||||
Abstract DAO interface for SemanticView.
|
||||
|
||||
Host implementations will replace this class during initialization
|
||||
with a concrete DAO providing actual database access.
|
||||
"""
|
||||
|
||||
model_cls: ClassVar[type[Any] | None] = None
|
||||
base_filter = None
|
||||
id_column_name = "id"
|
||||
uuid_column_name = "uuid"
|
||||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def validate_uniqueness(
|
||||
cls,
|
||||
name: str,
|
||||
layer_uuid: str,
|
||||
configuration: dict[str, Any],
|
||||
) -> bool:
|
||||
"""
|
||||
Validate that a semantic view is unique within a semantic layer.
|
||||
|
||||
Uniqueness is determined by the combination of name, layer UUID, and
|
||||
configuration.
|
||||
|
||||
:param name: View name
|
||||
:param layer_uuid: UUID of the parent semantic layer
|
||||
:param configuration: Configuration dict to compare
|
||||
:return: True if unique, False otherwise
|
||||
"""
|
||||
...
|
||||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def validate_update_uniqueness(
|
||||
cls,
|
||||
view_uuid: str,
|
||||
name: str,
|
||||
layer_uuid: str,
|
||||
configuration: dict[str, Any],
|
||||
) -> bool:
|
||||
"""
|
||||
Validate that a semantic view is unique within a semantic layer for an
|
||||
update operation, excluding the view being updated.
|
||||
|
||||
:param view_uuid: UUID of the view being updated
|
||||
:param name: New name to validate
|
||||
:param layer_uuid: UUID of the parent semantic layer
|
||||
:param configuration: Configuration dict to compare
|
||||
:return: True if unique, False otherwise
|
||||
"""
|
||||
...
|
||||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def find_by_name(cls, name: str, layer_uuid: str) -> SemanticViewModel | None:
|
||||
"""
|
||||
Find a semantic view by name within a semantic layer.
|
||||
|
||||
:param name: View name
|
||||
:param layer_uuid: UUID of the parent semantic layer
|
||||
:return: SemanticViewModel instance or None
|
||||
"""
|
||||
...
|
||||
|
||||
|
||||
__all__ = ["AbstractSemanticLayerDAO", "AbstractSemanticViewDAO"]
|
||||
102
superset-core/src/superset_core/semantic_layers/decorators.py
Normal file
@@ -0,0 +1,102 @@
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Semantic layer registration decorator for Superset.
|
||||
|
||||
This module provides a decorator interface to register semantic layer
|
||||
implementations with the host application, enabling automatic discovery
|
||||
by the extensions framework.
|
||||
|
||||
Usage:
|
||||
from superset_core.semantic_layers.decorators import semantic_layer
|
||||
|
||||
@semantic_layer(
|
||||
id="snowflake",
|
||||
name="Snowflake Cortex",
|
||||
description="Snowflake semantic layer via Cortex Analyst",
|
||||
)
|
||||
class SnowflakeSemanticLayer(SemanticLayer[SnowflakeConfig, SnowflakeView]):
|
||||
...
|
||||
|
||||
# Or with minimal arguments:
|
||||
@semantic_layer(id="dbt", name="dbt Semantic Layer")
|
||||
class DbtSemanticLayer(SemanticLayer[DbtConfig, DbtView]):
|
||||
...
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Callable, TypeVar
|
||||
|
||||
# Type variable for decorated semantic layer classes
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
def semantic_layer(
|
||||
id: str,
|
||||
name: str,
|
||||
description: str | None = None,
|
||||
) -> Callable[[T], T]:
|
||||
"""
|
||||
Decorator to register a semantic layer implementation.
|
||||
|
||||
Automatically detects extension context and applies appropriate
|
||||
namespacing to prevent ID conflicts between host and extension
|
||||
semantic layers.
|
||||
|
||||
Host implementations will replace this function during initialization
|
||||
with a concrete implementation providing actual functionality.
|
||||
|
||||
Args:
|
||||
id: Unique semantic layer type identifier (e.g., "snowflake",
|
||||
"dbt"). Used as the key in the semantic layers registry and
|
||||
stored in the ``type`` column of the ``SemanticLayer`` model.
|
||||
name: Human-readable display name (e.g., "Snowflake Cortex").
|
||||
Shown in the UI when listing available semantic layer types.
|
||||
description: Optional description for documentation and UI
|
||||
tooltips.
|
||||
|
||||
Returns:
|
||||
Decorated semantic layer class registered with the host
|
||||
application.
|
||||
|
||||
Raises:
|
||||
NotImplementedError: If called before host implementation is
|
||||
initialized.
|
||||
|
||||
Example:
|
||||
from superset_core.semantic_layers.decorators import semantic_layer
|
||||
from superset_core.semantic_layers.layer import SemanticLayer
|
||||
|
||||
@semantic_layer(
|
||||
id="snowflake",
|
||||
name="Snowflake Cortex",
|
||||
description="Connect to Snowflake Cortex Analyst",
|
||||
)
|
||||
class SnowflakeSemanticLayer(
|
||||
SemanticLayer[SnowflakeConfig, SnowflakeView]
|
||||
):
|
||||
...
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
"Semantic layer decorator not initialized. "
|
||||
"This decorator should be replaced during Superset startup."
|
||||
)
|
||||
|
||||
|
||||
__all__ = ["semantic_layer"]
|
||||
129
superset-core/src/superset_core/semantic_layers/layer.py
Normal file
@@ -0,0 +1,129 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Any, Generic, TypeVar
|
||||
|
||||
from pydantic import BaseModel
|
||||
from superset_core.semantic_layers.view import SemanticView
|
||||
|
||||
ConfigT = TypeVar("ConfigT", bound=BaseModel)
|
||||
SemanticViewT = TypeVar("SemanticViewT", bound="SemanticView")
|
||||
|
||||
|
||||
class SemanticLayer(ABC, Generic[ConfigT, SemanticViewT]):
|
||||
"""
|
||||
Abstract base class for semantic layers.
|
||||
"""
|
||||
|
||||
configuration_class: type[BaseModel]
|
||||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def from_configuration(
|
||||
cls,
|
||||
configuration: dict[str, Any],
|
||||
) -> SemanticLayer[ConfigT, SemanticViewT]:
|
||||
"""
|
||||
Create a semantic layer from its configuration.
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
"Semantic layers must implement the from_configuration method"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def get_configuration_schema(
|
||||
cls,
|
||||
configuration: ConfigT | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Get the JSON schema for the configuration needed to add the semantic layer.
|
||||
|
||||
A partial configuration `configuration` can be sent to improve the schema,
|
||||
allowing for progressive validation and better UX. For example, a semantic
|
||||
layer might require:
|
||||
|
||||
- auth information
|
||||
- a database
|
||||
|
||||
If the user provides the auth information, a client can send the partial
|
||||
configuration to this method, and the resulting JSON schema would include
|
||||
the list of databases the user has access to, allowing a dropdown to be
|
||||
populated.
|
||||
|
||||
The Snowflake semantic layer has an example implementation of this method, where
|
||||
database and schema names are populated based on the provided connection info.
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
"Semantic layers must implement the get_configuration_schema method"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def get_runtime_schema(
|
||||
cls,
|
||||
configuration: ConfigT,
|
||||
runtime_data: dict[str, Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Get the JSON schema for the runtime parameters needed to load semantic views.
|
||||
|
||||
This returns the schema needed to connect to a semantic view given the
|
||||
configuration for the semantic layer. For example, a semantic layer might
|
||||
be configured by:
|
||||
|
||||
- auth information
|
||||
- an optional database
|
||||
|
||||
If the user does not provide a database when creating the semantic layer, the
|
||||
runtime schema would require the database name to be provided before loading any
|
||||
semantic views. This allows users to create semantic layers that connect to a
|
||||
specific database (or project, account, etc.), or that allow users to select it
|
||||
at query time.
|
||||
|
||||
The Snowflake semantic layer has an example implementation of this method, where
|
||||
database and schema names are required if they were not provided in the initial
|
||||
configuration.
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
"Semantic layers must implement the get_runtime_schema method"
|
||||
)
|
||||
|
||||
@abstractmethod
|
||||
def get_semantic_views(
|
||||
self,
|
||||
runtime_configuration: dict[str, Any],
|
||||
) -> set[SemanticViewT]:
|
||||
"""
|
||||
Get the semantic views available in the semantic layer.
|
||||
|
||||
The runtime configuration can provide information like a given project or
|
||||
schema, used to restrict the semantic views returned.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def get_semantic_view(
|
||||
self,
|
||||
name: str,
|
||||
additional_configuration: dict[str, Any],
|
||||
) -> SemanticViewT:
|
||||
"""
|
||||
Get a specific semantic view by its name and additional configuration.
|
||||
"""
|
||||
85
superset-core/src/superset_core/semantic_layers/models.py
Normal file
@@ -0,0 +1,85 @@
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Semantic layer model interfaces for superset-core.
|
||||
|
||||
Provides abstract model classes for semantic layers and views that will be
|
||||
replaced by the host implementation's concrete SQLAlchemy models during
|
||||
initialization.
|
||||
|
||||
Usage:
|
||||
from superset_core.semantic_layers.models import (
|
||||
SemanticLayerModel,
|
||||
SemanticViewModel,
|
||||
)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from uuid import UUID
|
||||
|
||||
from superset_core.common.models import CoreModel
|
||||
|
||||
|
||||
class SemanticLayerModel(CoreModel):
|
||||
"""
|
||||
Abstract interface for the SemanticLayer database model.
|
||||
|
||||
Host implementations will replace this class during initialization
|
||||
with a concrete SQLAlchemy model providing actual persistence.
|
||||
"""
|
||||
|
||||
__abstract__ = True
|
||||
|
||||
# Type hints for expected column attributes
|
||||
uuid: UUID
|
||||
name: str
|
||||
description: str | None
|
||||
type: str
|
||||
configuration: str
|
||||
configuration_version: int
|
||||
cache_timeout: int | None
|
||||
created_on: datetime | None
|
||||
changed_on: datetime | None
|
||||
|
||||
|
||||
class SemanticViewModel(CoreModel):
|
||||
"""
|
||||
Abstract interface for the SemanticView database model.
|
||||
|
||||
Host implementations will replace this class during initialization
|
||||
with a concrete SQLAlchemy model providing actual persistence.
|
||||
"""
|
||||
|
||||
__abstract__ = True
|
||||
|
||||
# Type hints for expected column attributes
|
||||
id: int
|
||||
uuid: UUID
|
||||
name: str
|
||||
description: str | None
|
||||
configuration: str
|
||||
configuration_version: int
|
||||
cache_timeout: int | None
|
||||
semantic_layer_uuid: UUID
|
||||
created_on: datetime | None
|
||||
changed_on: datetime | None
|
||||
|
||||
|
||||
__all__ = ["SemanticLayerModel", "SemanticViewModel"]
|
||||
209
superset-core/src/superset_core/semantic_layers/types.py
Normal file
@@ -0,0 +1,209 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import enum
|
||||
from dataclasses import dataclass
|
||||
from datetime import date, datetime, time, timedelta
|
||||
|
||||
import isodate
|
||||
import pyarrow as pa
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Grain:
|
||||
"""
|
||||
Represents a time grain (e.g., day, month, year).
|
||||
|
||||
Attributes:
|
||||
name: Human-readable name of the grain (e.g., "Second")
|
||||
representation: ISO 8601 duration (e.g., "PT1S", "P1D", "P1M")
|
||||
"""
|
||||
|
||||
name: str
|
||||
representation: str
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
isodate.parse_duration(self.representation)
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if isinstance(other, Grain):
|
||||
return self.representation == other.representation
|
||||
return NotImplemented
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(self.representation)
|
||||
|
||||
|
||||
class Grains:
|
||||
"""Pre-defined common grains and factory for custom ones."""
|
||||
|
||||
SECOND = Grain("Second", "PT1S")
|
||||
MINUTE = Grain("Minute", "PT1M")
|
||||
HOUR = Grain("Hour", "PT1H")
|
||||
DAY = Grain("Day", "P1D")
|
||||
WEEK = Grain("Week", "P1W")
|
||||
MONTH = Grain("Month", "P1M")
|
||||
QUARTER = Grain("Quarter", "P3M")
|
||||
YEAR = Grain("Year", "P1Y")
|
||||
|
||||
_REGISTRY: dict[str, Grain] = {
|
||||
"PT1S": SECOND,
|
||||
"PT1M": MINUTE,
|
||||
"PT1H": HOUR,
|
||||
"P1D": DAY,
|
||||
"P1W": WEEK,
|
||||
"P1M": MONTH,
|
||||
"P3M": QUARTER,
|
||||
"P1Y": YEAR,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get(cls, representation: str, name: str | None = None) -> Grain:
|
||||
"""Return a pre-defined grain or create a custom one."""
|
||||
if grain := cls._REGISTRY.get(representation):
|
||||
return grain
|
||||
return Grain(name or representation, representation)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Dimension:
|
||||
id: str
|
||||
name: str
|
||||
type: pa.DataType
|
||||
|
||||
definition: str | None = None
|
||||
description: str | None = None
|
||||
grain: Grain | None = None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Metric:
|
||||
id: str
|
||||
name: str
|
||||
type: pa.DataType
|
||||
|
||||
definition: str
|
||||
description: str | None = None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class AdhocExpression:
|
||||
id: str
|
||||
definition: str
|
||||
|
||||
|
||||
class Operator(str, enum.Enum):
|
||||
EQUALS = "="
|
||||
NOT_EQUALS = "!="
|
||||
GREATER_THAN = ">"
|
||||
LESS_THAN = "<"
|
||||
GREATER_THAN_OR_EQUAL = ">="
|
||||
LESS_THAN_OR_EQUAL = "<="
|
||||
IN = "IN"
|
||||
NOT_IN = "NOT IN"
|
||||
LIKE = "LIKE"
|
||||
NOT_LIKE = "NOT LIKE"
|
||||
IS_NULL = "IS NULL"
|
||||
IS_NOT_NULL = "IS NOT NULL"
|
||||
ADHOC = "ADHOC"
|
||||
|
||||
|
||||
FilterValues = str | int | float | bool | datetime | date | time | timedelta | None
|
||||
|
||||
|
||||
class PredicateType(enum.Enum):
|
||||
WHERE = "WHERE"
|
||||
HAVING = "HAVING"
|
||||
|
||||
|
||||
@dataclass(frozen=True, order=True)
|
||||
class Filter:
|
||||
type: PredicateType
|
||||
column: Dimension | Metric | None
|
||||
operator: Operator
|
||||
value: FilterValues | frozenset[FilterValues]
|
||||
|
||||
|
||||
class OrderDirection(enum.Enum):
|
||||
ASC = "ASC"
|
||||
DESC = "DESC"
|
||||
|
||||
|
||||
OrderTuple = tuple[Metric | Dimension | AdhocExpression, OrderDirection]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class GroupLimit:
|
||||
"""
|
||||
Limit query to top/bottom N combinations of specified dimensions.
|
||||
|
||||
The `filters` parameter allows specifying separate filter constraints for the
|
||||
group limit subquery. This is useful when you want to determine the top N groups
|
||||
using different criteria (e.g., a different time range) than the main query.
|
||||
|
||||
For example, you might want to find the top 10 products by sales over the last
|
||||
30 days, but then show daily sales for those products over the last 7 days.
|
||||
"""
|
||||
|
||||
dimensions: list[Dimension]
|
||||
top: int
|
||||
metric: Metric | None
|
||||
direction: OrderDirection = OrderDirection.DESC
|
||||
group_others: bool = False
|
||||
filters: set[Filter] | None = None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SemanticRequest:
|
||||
"""
|
||||
Represents a request made to obtain semantic results.
|
||||
|
||||
This could be a SQL query, an HTTP request, etc.
|
||||
"""
|
||||
|
||||
type: str
|
||||
definition: str
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SemanticResult:
|
||||
"""
|
||||
Represents the results of a semantic query.
|
||||
|
||||
This includes any requests (SQL queries, HTTP requests) that were performed in order
|
||||
to obtain the results, in order to help troubleshooting.
|
||||
"""
|
||||
|
||||
requests: list[SemanticRequest]
|
||||
results: pa.Table
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SemanticQuery:
|
||||
"""
|
||||
Represents a semantic query.
|
||||
"""
|
||||
|
||||
metrics: list[Metric]
|
||||
dimensions: list[Dimension]
|
||||
filters: set[Filter] | None = None
|
||||
order: list[OrderTuple] | None = None
|
||||
limit: int | None = None
|
||||
offset: int | None = None
|
||||
group_limit: GroupLimit | None = None
|
||||
113
superset-core/src/superset_core/semantic_layers/view.py
Normal file
@@ -0,0 +1,113 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import enum
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from superset_core.semantic_layers.types import (
|
||||
Dimension,
|
||||
Filter,
|
||||
Metric,
|
||||
SemanticQuery,
|
||||
SemanticResult,
|
||||
)
|
||||
|
||||
|
||||
# TODO (betodealmeida): move to the extension JSON
|
||||
class SemanticViewFeature(enum.Enum):
|
||||
"""
|
||||
Custom features supported by semantic layers.
|
||||
"""
|
||||
|
||||
ADHOC_EXPRESSIONS_IN_ORDERBY = "ADHOC_EXPRESSIONS_IN_ORDERBY"
|
||||
GROUP_LIMIT = "GROUP_LIMIT"
|
||||
GROUP_OTHERS = "GROUP_OTHERS"
|
||||
|
||||
|
||||
class SemanticView(ABC):
|
||||
"""
|
||||
Abstract base class for semantic views.
|
||||
"""
|
||||
|
||||
features: frozenset[SemanticViewFeature]
|
||||
|
||||
# Implementations must expose a display name for the view.
|
||||
# Declared here as a type annotation (not abstract) so that existing
|
||||
# implementations are not required to add a formal @abstractmethod.
|
||||
name: str
|
||||
|
||||
@abstractmethod
|
||||
def uid(self) -> str:
|
||||
"""
|
||||
Returns a unique identifier for the semantic view.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def get_dimensions(self) -> set[Dimension]:
|
||||
"""
|
||||
Get the dimensions defined in the semantic view.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def get_metrics(self) -> set[Metric]:
|
||||
"""
|
||||
Get the metrics defined in the semantic view.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def get_values(
|
||||
self,
|
||||
dimension: Dimension,
|
||||
filters: set[Filter] | None = None,
|
||||
) -> SemanticResult:
|
||||
"""
|
||||
Return distinct values for a dimension.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def get_table(self, query: SemanticQuery) -> SemanticResult:
|
||||
"""
|
||||
Execute a semantic query and return the results.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def get_row_count(self, query: SemanticQuery) -> SemanticResult:
|
||||
"""
|
||||
Execute a query and return the number of rows the result would have.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def get_compatible_metrics(
|
||||
self,
|
||||
selected_metrics: set[Metric],
|
||||
selected_dimensions: set[Dimension],
|
||||
) -> set[Metric]:
|
||||
"""
|
||||
Return metrics compatible with the selected dimensions.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def get_compatible_dimensions(
|
||||
self,
|
||||
selected_metrics: set[Metric],
|
||||
selected_dimensions: set[Dimension],
|
||||
) -> set[Dimension]:
|
||||
"""
|
||||
Return dimensions compatible with the selected metrics.
|
||||
"""
|
||||
568
superset-embedded-sdk/package-lock.json
generated
@@ -71,14 +71,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz",
|
||||
"integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==",
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
|
||||
"integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/highlight": "^7.24.7",
|
||||
"picocolors": "^1.0.0"
|
||||
"@babel/helper-validator-identifier": "^7.28.5",
|
||||
"js-tokens": "^4.0.0",
|
||||
"picocolors": "^1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -126,16 +126,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/generator": {
|
||||
"version": "7.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.6.tgz",
|
||||
"integrity": "sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==",
|
||||
"version": "7.29.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
|
||||
"integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.25.6",
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
"@jridgewell/trace-mapping": "^0.3.25",
|
||||
"jsesc": "^2.5.1"
|
||||
"@babel/parser": "^7.29.0",
|
||||
"@babel/types": "^7.29.0",
|
||||
"@jridgewell/gen-mapping": "^0.3.12",
|
||||
"@jridgewell/trace-mapping": "^0.3.28",
|
||||
"jsesc": "^3.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -242,6 +242,15 @@
|
||||
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-globals": {
|
||||
"version": "7.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
|
||||
"integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-member-expression-to-functions": {
|
||||
"version": "7.24.8",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.8.tgz",
|
||||
@@ -257,30 +266,27 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-module-imports": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz",
|
||||
"integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==",
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
|
||||
"integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/traverse": "^7.24.7",
|
||||
"@babel/types": "^7.24.7"
|
||||
"@babel/traverse": "^7.28.6",
|
||||
"@babel/types": "^7.28.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-module-transforms": {
|
||||
"version": "7.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz",
|
||||
"integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==",
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
|
||||
"integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-module-imports": "^7.24.7",
|
||||
"@babel/helper-simple-access": "^7.24.7",
|
||||
"@babel/helper-validator-identifier": "^7.24.7",
|
||||
"@babel/traverse": "^7.25.2"
|
||||
"@babel/helper-module-imports": "^7.28.6",
|
||||
"@babel/helper-validator-identifier": "^7.28.5",
|
||||
"@babel/traverse": "^7.28.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -303,11 +309,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-plugin-utils": {
|
||||
"version": "7.24.8",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz",
|
||||
"integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==",
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
|
||||
"integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
@@ -377,21 +382,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-string-parser": {
|
||||
"version": "7.24.8",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz",
|
||||
"integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==",
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
|
||||
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-validator-identifier": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz",
|
||||
"integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==",
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
|
||||
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
@@ -435,30 +438,13 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/highlight": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz",
|
||||
"integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-validator-identifier": "^7.24.7",
|
||||
"chalk": "^2.4.2",
|
||||
"js-tokens": "^4.0.0",
|
||||
"picocolors": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz",
|
||||
"integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==",
|
||||
"version": "7.29.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz",
|
||||
"integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.25.6"
|
||||
"@babel/types": "^7.29.0"
|
||||
},
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
@@ -1262,16 +1248,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-modules-systemjs": {
|
||||
"version": "7.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.0.tgz",
|
||||
"integrity": "sha512-YPJfjQPDXxyQWg/0+jHKj1llnY5f/R6a0p/vP4lPymxLu7Lvl4k2WMitqi08yxwQcCVUUdG9LCUj4TNEgAp3Jw==",
|
||||
"version": "7.29.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.4.tgz",
|
||||
"integrity": "sha512-N7QmZ0xRZfjHOfZeQLJjwgX2zS9pdGHSVl/cjSGlo4dXMqvurfxXDMKY4RqEKzPozV78VMcd0lxyG13mlbKc4w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-module-transforms": "^7.25.0",
|
||||
"@babel/helper-plugin-utils": "^7.24.8",
|
||||
"@babel/helper-validator-identifier": "^7.24.7",
|
||||
"@babel/traverse": "^7.25.0"
|
||||
"@babel/helper-module-transforms": "^7.28.6",
|
||||
"@babel/helper-plugin-utils": "^7.28.6",
|
||||
"@babel/helper-validator-identifier": "^7.28.5",
|
||||
"@babel/traverse": "^7.29.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -1858,49 +1843,45 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/template": {
|
||||
"version": "7.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz",
|
||||
"integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==",
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
|
||||
"integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.24.7",
|
||||
"@babel/parser": "^7.25.0",
|
||||
"@babel/types": "^7.25.0"
|
||||
"@babel/code-frame": "^7.28.6",
|
||||
"@babel/parser": "^7.28.6",
|
||||
"@babel/types": "^7.28.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/traverse": {
|
||||
"version": "7.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.6.tgz",
|
||||
"integrity": "sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==",
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
|
||||
"integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.24.7",
|
||||
"@babel/generator": "^7.25.6",
|
||||
"@babel/parser": "^7.25.6",
|
||||
"@babel/template": "^7.25.0",
|
||||
"@babel/types": "^7.25.6",
|
||||
"debug": "^4.3.1",
|
||||
"globals": "^11.1.0"
|
||||
"@babel/code-frame": "^7.29.0",
|
||||
"@babel/generator": "^7.29.0",
|
||||
"@babel/helper-globals": "^7.28.0",
|
||||
"@babel/parser": "^7.29.0",
|
||||
"@babel/template": "^7.28.6",
|
||||
"@babel/types": "^7.29.0",
|
||||
"debug": "^4.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz",
|
||||
"integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==",
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
|
||||
"integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.24.8",
|
||||
"@babel/helper-validator-identifier": "^7.24.7",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
"@babel/helper-string-parser": "^7.27.1",
|
||||
"@babel/helper-validator-identifier": "^7.28.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -2659,17 +2640,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/gen-mapping": {
|
||||
"version": "0.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
|
||||
"integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
|
||||
"version": "0.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/set-array": "^1.2.1",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.10",
|
||||
"@jridgewell/sourcemap-codec": "^1.5.0",
|
||||
"@jridgewell/trace-mapping": "^0.3.24"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/resolve-uri": {
|
||||
@@ -2681,15 +2658,6 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/set-array": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
|
||||
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/source-map": {
|
||||
"version": "0.3.11",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz",
|
||||
@@ -2701,15 +2669,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.4.14",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
|
||||
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@jridgewell/trace-mapping": {
|
||||
"version": "0.3.25",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
|
||||
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
|
||||
"version": "0.3.31",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
||||
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/resolve-uri": "^3.1.0",
|
||||
@@ -3199,19 +3167,6 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-styles": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-convert": "^1.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/anymatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
|
||||
@@ -3617,21 +3572,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^3.2.1",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"supports-color": "^5.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/char-regex": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz",
|
||||
@@ -3741,23 +3681,6 @@
|
||||
"integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "1.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/color-name": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/colorette": {
|
||||
"version": "2.0.16",
|
||||
"resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz",
|
||||
@@ -4037,16 +3960,6 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-scope": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
|
||||
@@ -4301,11 +4214,20 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fast-uri": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.1.tgz",
|
||||
"integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==",
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz",
|
||||
"integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/fastify"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/fastify"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/fastest-levenshtein": {
|
||||
"version": "1.0.12",
|
||||
@@ -4610,16 +4532,6 @@
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/html-escaper": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
|
||||
@@ -6626,8 +6538,7 @@
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "3.14.2",
|
||||
@@ -6643,15 +6554,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/jsesc": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
|
||||
"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
|
||||
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"jsesc": "bin/jsesc"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/json-parse-even-better-errors": {
|
||||
@@ -7495,19 +7406,6 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-flag": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-preserve-symlinks-flag": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||
@@ -7610,15 +7508,6 @@
|
||||
"integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/to-fast-properties": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
|
||||
"integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
@@ -8122,13 +8011,14 @@
|
||||
}
|
||||
},
|
||||
"@babel/code-frame": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz",
|
||||
"integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==",
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
|
||||
"integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/highlight": "^7.24.7",
|
||||
"picocolors": "^1.0.0"
|
||||
"@babel/helper-validator-identifier": "^7.28.5",
|
||||
"js-tokens": "^4.0.0",
|
||||
"picocolors": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"@babel/compat-data": {
|
||||
@@ -8161,15 +8051,16 @@
|
||||
}
|
||||
},
|
||||
"@babel/generator": {
|
||||
"version": "7.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.6.tgz",
|
||||
"integrity": "sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==",
|
||||
"version": "7.29.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
|
||||
"integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.25.6",
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
"@jridgewell/trace-mapping": "^0.3.25",
|
||||
"jsesc": "^2.5.1"
|
||||
"@babel/parser": "^7.29.0",
|
||||
"@babel/types": "^7.29.0",
|
||||
"@jridgewell/gen-mapping": "^0.3.12",
|
||||
"@jridgewell/trace-mapping": "^0.3.28",
|
||||
"jsesc": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"@babel/helper-annotate-as-pure": {
|
||||
@@ -8243,6 +8134,12 @@
|
||||
"resolve": "^1.14.2"
|
||||
}
|
||||
},
|
||||
"@babel/helper-globals": {
|
||||
"version": "7.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
|
||||
"integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/helper-member-expression-to-functions": {
|
||||
"version": "7.24.8",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.8.tgz",
|
||||
@@ -8254,25 +8151,24 @@
|
||||
}
|
||||
},
|
||||
"@babel/helper-module-imports": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz",
|
||||
"integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==",
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
|
||||
"integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/traverse": "^7.24.7",
|
||||
"@babel/types": "^7.24.7"
|
||||
"@babel/traverse": "^7.28.6",
|
||||
"@babel/types": "^7.28.6"
|
||||
}
|
||||
},
|
||||
"@babel/helper-module-transforms": {
|
||||
"version": "7.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz",
|
||||
"integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==",
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
|
||||
"integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-module-imports": "^7.24.7",
|
||||
"@babel/helper-simple-access": "^7.24.7",
|
||||
"@babel/helper-validator-identifier": "^7.24.7",
|
||||
"@babel/traverse": "^7.25.2"
|
||||
"@babel/helper-module-imports": "^7.28.6",
|
||||
"@babel/helper-validator-identifier": "^7.28.5",
|
||||
"@babel/traverse": "^7.28.6"
|
||||
}
|
||||
},
|
||||
"@babel/helper-optimise-call-expression": {
|
||||
@@ -8285,9 +8181,9 @@
|
||||
}
|
||||
},
|
||||
"@babel/helper-plugin-utils": {
|
||||
"version": "7.24.8",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz",
|
||||
"integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==",
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
|
||||
"integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/helper-remap-async-to-generator": {
|
||||
@@ -8333,15 +8229,15 @@
|
||||
}
|
||||
},
|
||||
"@babel/helper-string-parser": {
|
||||
"version": "7.24.8",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz",
|
||||
"integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==",
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
|
||||
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/helper-validator-identifier": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz",
|
||||
"integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==",
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
|
||||
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/helper-validator-option": {
|
||||
@@ -8371,25 +8267,13 @@
|
||||
"@babel/types": "^7.25.6"
|
||||
}
|
||||
},
|
||||
"@babel/highlight": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz",
|
||||
"integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.24.7",
|
||||
"chalk": "^2.4.2",
|
||||
"js-tokens": "^4.0.0",
|
||||
"picocolors": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz",
|
||||
"integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==",
|
||||
"version": "7.29.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz",
|
||||
"integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.25.6"
|
||||
"@babel/types": "^7.29.0"
|
||||
}
|
||||
},
|
||||
"@babel/plugin-bugfix-firefox-class-in-computed-class-key": {
|
||||
@@ -8882,15 +8766,15 @@
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-modules-systemjs": {
|
||||
"version": "7.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.0.tgz",
|
||||
"integrity": "sha512-YPJfjQPDXxyQWg/0+jHKj1llnY5f/R6a0p/vP4lPymxLu7Lvl4k2WMitqi08yxwQcCVUUdG9LCUj4TNEgAp3Jw==",
|
||||
"version": "7.29.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.4.tgz",
|
||||
"integrity": "sha512-N7QmZ0xRZfjHOfZeQLJjwgX2zS9pdGHSVl/cjSGlo4dXMqvurfxXDMKY4RqEKzPozV78VMcd0lxyG13mlbKc4w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-module-transforms": "^7.25.0",
|
||||
"@babel/helper-plugin-utils": "^7.24.8",
|
||||
"@babel/helper-validator-identifier": "^7.24.7",
|
||||
"@babel/traverse": "^7.25.0"
|
||||
"@babel/helper-module-transforms": "^7.28.6",
|
||||
"@babel/helper-plugin-utils": "^7.28.6",
|
||||
"@babel/helper-validator-identifier": "^7.28.5",
|
||||
"@babel/traverse": "^7.29.0"
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-modules-umd": {
|
||||
@@ -9273,40 +9157,39 @@
|
||||
}
|
||||
},
|
||||
"@babel/template": {
|
||||
"version": "7.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz",
|
||||
"integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==",
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
|
||||
"integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.24.7",
|
||||
"@babel/parser": "^7.25.0",
|
||||
"@babel/types": "^7.25.0"
|
||||
"@babel/code-frame": "^7.28.6",
|
||||
"@babel/parser": "^7.28.6",
|
||||
"@babel/types": "^7.28.6"
|
||||
}
|
||||
},
|
||||
"@babel/traverse": {
|
||||
"version": "7.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.6.tgz",
|
||||
"integrity": "sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==",
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
|
||||
"integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.24.7",
|
||||
"@babel/generator": "^7.25.6",
|
||||
"@babel/parser": "^7.25.6",
|
||||
"@babel/template": "^7.25.0",
|
||||
"@babel/types": "^7.25.6",
|
||||
"debug": "^4.3.1",
|
||||
"globals": "^11.1.0"
|
||||
"@babel/code-frame": "^7.29.0",
|
||||
"@babel/generator": "^7.29.0",
|
||||
"@babel/helper-globals": "^7.28.0",
|
||||
"@babel/parser": "^7.29.0",
|
||||
"@babel/template": "^7.28.6",
|
||||
"@babel/types": "^7.29.0",
|
||||
"debug": "^4.3.1"
|
||||
}
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz",
|
||||
"integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==",
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
|
||||
"integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-string-parser": "^7.24.8",
|
||||
"@babel/helper-validator-identifier": "^7.24.7",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
"@babel/helper-string-parser": "^7.27.1",
|
||||
"@babel/helper-validator-identifier": "^7.28.5"
|
||||
}
|
||||
},
|
||||
"@bcoe/v8-coverage": {
|
||||
@@ -9879,13 +9762,12 @@
|
||||
}
|
||||
},
|
||||
"@jridgewell/gen-mapping": {
|
||||
"version": "0.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
|
||||
"integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
|
||||
"version": "0.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@jridgewell/set-array": "^1.2.1",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.10",
|
||||
"@jridgewell/sourcemap-codec": "^1.5.0",
|
||||
"@jridgewell/trace-mapping": "^0.3.24"
|
||||
}
|
||||
},
|
||||
@@ -9895,12 +9777,6 @@
|
||||
"integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
|
||||
"dev": true
|
||||
},
|
||||
"@jridgewell/set-array": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
|
||||
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
|
||||
"dev": true
|
||||
},
|
||||
"@jridgewell/source-map": {
|
||||
"version": "0.3.11",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz",
|
||||
@@ -9912,15 +9788,15 @@
|
||||
}
|
||||
},
|
||||
"@jridgewell/sourcemap-codec": {
|
||||
"version": "1.4.14",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
|
||||
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
||||
"dev": true
|
||||
},
|
||||
"@jridgewell/trace-mapping": {
|
||||
"version": "0.3.25",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
|
||||
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
|
||||
"version": "0.3.31",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
||||
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@jridgewell/resolve-uri": "^3.1.0",
|
||||
@@ -10346,15 +10222,6 @@
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"dev": true
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-convert": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"anymatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
|
||||
@@ -10640,17 +10507,6 @@
|
||||
"integrity": "sha512-qY3aDRZC5nWPgHUgIB84WL+nySuo19wk0VJpp/XI9T34lrvkyhRvNVOFJOp2kxClQhiFBu+TaUSudf6oa3vkSA==",
|
||||
"dev": true
|
||||
},
|
||||
"chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^3.2.1",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"supports-color": "^5.3.0"
|
||||
}
|
||||
},
|
||||
"char-regex": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz",
|
||||
@@ -10726,21 +10582,6 @@
|
||||
"integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==",
|
||||
"dev": true
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-name": "1.1.3"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
|
||||
"dev": true
|
||||
},
|
||||
"colorette": {
|
||||
"version": "2.0.16",
|
||||
"resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz",
|
||||
@@ -10940,12 +10781,6 @@
|
||||
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
|
||||
"dev": true
|
||||
},
|
||||
"escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
|
||||
"dev": true
|
||||
},
|
||||
"eslint-scope": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
|
||||
@@ -11133,9 +10968,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"fast-uri": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.1.tgz",
|
||||
"integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==",
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz",
|
||||
"integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==",
|
||||
"dev": true
|
||||
},
|
||||
"fastest-levenshtein": {
|
||||
@@ -11342,12 +11177,6 @@
|
||||
"function-bind": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
||||
"dev": true
|
||||
},
|
||||
"html-escaper": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
|
||||
@@ -12835,9 +12664,9 @@
|
||||
}
|
||||
},
|
||||
"jsesc": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
|
||||
"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
|
||||
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
|
||||
"dev": true
|
||||
},
|
||||
"json-parse-even-better-errors": {
|
||||
@@ -13458,15 +13287,6 @@
|
||||
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
|
||||
"dev": true
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-flag": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"supports-preserve-symlinks-flag": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||
@@ -13528,12 +13348,6 @@
|
||||
"integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==",
|
||||
"dev": true
|
||||
},
|
||||
"to-fast-properties": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
|
||||
"integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=",
|
||||
"dev": true
|
||||
},
|
||||
"to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
|
||||
@@ -66,7 +66,7 @@ export type EmbedDashboardParams = {
|
||||
iframeTitle?: string;
|
||||
/** additional iframe sandbox attributes ex (allow-top-navigation, allow-popups-to-escape-sandbox) **/
|
||||
iframeSandboxExtras?: string[];
|
||||
/** iframe allow attribute for Permissions Policy (e.g., ['clipboard-write', 'fullscreen']) **/
|
||||
/** Additional Permissions Policy features for the iframe's `allow` attribute (e.g., ['camera', 'microphone']). `fullscreen` and `clipboard-write` are granted by default. **/
|
||||
iframeAllowExtras?: string[];
|
||||
/** force a specific refererPolicy to be used in the iframe request **/
|
||||
referrerPolicy?: ReferrerPolicy;
|
||||
@@ -233,9 +233,14 @@ export async function embedDashboard({
|
||||
iframe.src = `${supersetDomain}/embedded/${id}${urlParamsString}`;
|
||||
iframe.title = iframeTitle;
|
||||
iframe.style.background = 'transparent';
|
||||
if (iframeAllowExtras.length > 0) {
|
||||
iframe.setAttribute('allow', iframeAllowExtras.join('; '));
|
||||
}
|
||||
// Permissions Policy features the embedded dashboard relies on. Modern
|
||||
// browsers gate these APIs on the iframe's `allow` attribute regardless
|
||||
// of sandbox flags, so we include them by default. Host apps can extend
|
||||
// the list via `iframeAllowExtras`.
|
||||
const allowFeatures = Array.from(
|
||||
new Set(['fullscreen', 'clipboard-write', ...iframeAllowExtras]),
|
||||
);
|
||||
iframe.setAttribute('allow', allowFeatures.join('; '));
|
||||
//@ts-ignore
|
||||
mountPoint.replaceChildren(iframe);
|
||||
log('placed the iframe');
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
[project]
|
||||
name = "apache-superset-extensions-cli"
|
||||
version = "0.1.0rc2"
|
||||
version = "0.1.0"
|
||||
description = "Official command-line interface for building, bundling, and managing Apache Superset extensions"
|
||||
readme = "README.md"
|
||||
authors = [
|
||||
|
||||
@@ -77,6 +77,10 @@ const restrictedImportsRules = {
|
||||
name: 'query-string',
|
||||
message: 'Please use the URLSearchParams API instead of query-string.',
|
||||
},
|
||||
'no-jest-mock-console': {
|
||||
name: 'jest-mock-console',
|
||||
message: 'Please use native Jest spies, i.e. jest.spyOn(console, "warn")',
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
|
||||
423
superset-frontend/cypress-base/package-lock.json
generated
@@ -96,14 +96,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/generator": {
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz",
|
||||
"integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==",
|
||||
"version": "7.29.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
|
||||
"integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.23.0",
|
||||
"@jridgewell/gen-mapping": "^0.3.2",
|
||||
"@jridgewell/trace-mapping": "^0.3.17",
|
||||
"jsesc": "^2.5.1"
|
||||
"@babel/parser": "^7.29.0",
|
||||
"@babel/types": "^7.29.0",
|
||||
"@jridgewell/gen-mapping": "^0.3.12",
|
||||
"@jridgewell/trace-mapping": "^0.3.28",
|
||||
"jsesc": "^3.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -224,6 +225,7 @@
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz",
|
||||
"integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
@@ -244,6 +246,7 @@
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz",
|
||||
"integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/template": "^7.22.15",
|
||||
"@babel/types": "^7.23.0"
|
||||
@@ -252,13 +255,10 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-hoist-variables": {
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz",
|
||||
"integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.22.5"
|
||||
},
|
||||
"node_modules/@babel/helper-globals": {
|
||||
"version": "7.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
|
||||
"integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
@@ -276,32 +276,31 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-module-imports": {
|
||||
"version": "7.21.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz",
|
||||
"integrity": "sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==",
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
|
||||
"integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.21.4"
|
||||
"@babel/traverse": "^7.28.6",
|
||||
"@babel/types": "^7.28.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-module-transforms": {
|
||||
"version": "7.21.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz",
|
||||
"integrity": "sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==",
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
|
||||
"integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
|
||||
"dependencies": {
|
||||
"@babel/helper-environment-visitor": "^7.18.9",
|
||||
"@babel/helper-module-imports": "^7.18.6",
|
||||
"@babel/helper-simple-access": "^7.20.2",
|
||||
"@babel/helper-split-export-declaration": "^7.18.6",
|
||||
"@babel/helper-validator-identifier": "^7.19.1",
|
||||
"@babel/template": "^7.20.7",
|
||||
"@babel/traverse": "^7.21.2",
|
||||
"@babel/types": "^7.21.2"
|
||||
"@babel/helper-module-imports": "^7.28.6",
|
||||
"@babel/helper-validator-identifier": "^7.28.5",
|
||||
"@babel/traverse": "^7.28.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-optimise-call-expression": {
|
||||
@@ -317,9 +316,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-plugin-utils": {
|
||||
"version": "7.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz",
|
||||
"integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==",
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
|
||||
"integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -364,6 +363,7 @@
|
||||
"version": "7.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz",
|
||||
"integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.20.2"
|
||||
},
|
||||
@@ -387,6 +387,7 @@
|
||||
"version": "7.22.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz",
|
||||
"integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.22.5"
|
||||
},
|
||||
@@ -395,19 +396,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-string-parser": {
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
|
||||
"integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
|
||||
"license": "MIT",
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
|
||||
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-validator-identifier": {
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
|
||||
"integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
|
||||
"license": "MIT",
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
|
||||
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
@@ -462,12 +461,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.26.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.10.tgz",
|
||||
"integrity": "sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA==",
|
||||
"license": "MIT",
|
||||
"version": "7.29.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz",
|
||||
"integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.26.10"
|
||||
"@babel/types": "^7.29.0"
|
||||
},
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
@@ -1223,15 +1221,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-modules-systemjs": {
|
||||
"version": "7.20.11",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.20.11.tgz",
|
||||
"integrity": "sha512-vVu5g9BPQKSFEmvt2TA4Da5N+QVS66EX21d8uoOihC+OCpUoGvzVsXeqFdtAEfVa5BILAeFt+U7yVmLbQnAJmw==",
|
||||
"version": "7.29.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.4.tgz",
|
||||
"integrity": "sha512-N7QmZ0xRZfjHOfZeQLJjwgX2zS9pdGHSVl/cjSGlo4dXMqvurfxXDMKY4RqEKzPozV78VMcd0lxyG13mlbKc4w==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-hoist-variables": "^7.18.6",
|
||||
"@babel/helper-module-transforms": "^7.20.11",
|
||||
"@babel/helper-plugin-utils": "^7.20.2",
|
||||
"@babel/helper-validator-identifier": "^7.19.1"
|
||||
"@babel/helper-module-transforms": "^7.28.6",
|
||||
"@babel/helper-plugin-utils": "^7.28.6",
|
||||
"@babel/helper-validator-identifier": "^7.28.5",
|
||||
"@babel/traverse": "^7.29.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -1595,73 +1593,68 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/template": {
|
||||
"version": "7.26.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz",
|
||||
"integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==",
|
||||
"license": "MIT",
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
|
||||
"integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.26.2",
|
||||
"@babel/parser": "^7.26.9",
|
||||
"@babel/types": "^7.26.9"
|
||||
"@babel/code-frame": "^7.28.6",
|
||||
"@babel/parser": "^7.28.6",
|
||||
"@babel/types": "^7.28.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/template/node_modules/@babel/code-frame": {
|
||||
"version": "7.26.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
|
||||
"integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==",
|
||||
"license": "MIT",
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
|
||||
"integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
|
||||
"dependencies": {
|
||||
"@babel/helper-validator-identifier": "^7.25.9",
|
||||
"@babel/helper-validator-identifier": "^7.28.5",
|
||||
"js-tokens": "^4.0.0",
|
||||
"picocolors": "^1.0.0"
|
||||
"picocolors": "^1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/traverse": {
|
||||
"version": "7.23.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz",
|
||||
"integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==",
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
|
||||
"integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.22.13",
|
||||
"@babel/generator": "^7.23.0",
|
||||
"@babel/helper-environment-visitor": "^7.22.20",
|
||||
"@babel/helper-function-name": "^7.23.0",
|
||||
"@babel/helper-hoist-variables": "^7.22.5",
|
||||
"@babel/helper-split-export-declaration": "^7.22.6",
|
||||
"@babel/parser": "^7.23.0",
|
||||
"@babel/types": "^7.23.0",
|
||||
"debug": "^4.1.0",
|
||||
"globals": "^11.1.0"
|
||||
"@babel/code-frame": "^7.29.0",
|
||||
"@babel/generator": "^7.29.0",
|
||||
"@babel/helper-globals": "^7.28.0",
|
||||
"@babel/parser": "^7.29.0",
|
||||
"@babel/template": "^7.28.6",
|
||||
"@babel/types": "^7.29.0",
|
||||
"debug": "^4.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/traverse/node_modules/@babel/code-frame": {
|
||||
"version": "7.22.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
|
||||
"integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
|
||||
"integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
|
||||
"dependencies": {
|
||||
"@babel/highlight": "^7.22.13",
|
||||
"chalk": "^2.4.2"
|
||||
"@babel/helper-validator-identifier": "^7.28.5",
|
||||
"js-tokens": "^4.0.0",
|
||||
"picocolors": "^1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.26.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.10.tgz",
|
||||
"integrity": "sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==",
|
||||
"license": "MIT",
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
|
||||
"integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.25.9",
|
||||
"@babel/helper-validator-identifier": "^7.25.9"
|
||||
"@babel/helper-string-parser": "^7.27.1",
|
||||
"@babel/helper-validator-identifier": "^7.28.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -2080,16 +2073,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/gen-mapping": {
|
||||
"version": "0.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
|
||||
"integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
|
||||
"version": "0.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
||||
"dependencies": {
|
||||
"@jridgewell/set-array": "^1.2.1",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.10",
|
||||
"@jridgewell/sourcemap-codec": "^1.5.0",
|
||||
"@jridgewell/trace-mapping": "^0.3.24"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/resolve-uri": {
|
||||
@@ -2100,14 +2089,6 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/set-array": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
|
||||
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/source-map": {
|
||||
"version": "0.3.11",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz",
|
||||
@@ -2119,14 +2100,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.4.14",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
|
||||
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw=="
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="
|
||||
},
|
||||
"node_modules/@jridgewell/trace-mapping": {
|
||||
"version": "0.3.25",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
|
||||
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
|
||||
"version": "0.3.31",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
||||
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
||||
"dependencies": {
|
||||
"@jridgewell/resolve-uri": "^3.1.0",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
@@ -4550,9 +4531,9 @@
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/fast-uri": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
|
||||
"integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz",
|
||||
"integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -4932,6 +4913,7 @@
|
||||
"version": "11.12.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
|
||||
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
@@ -5641,14 +5623,14 @@
|
||||
"integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg=="
|
||||
},
|
||||
"node_modules/jsesc": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
|
||||
"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
|
||||
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
|
||||
"bin": {
|
||||
"jsesc": "bin/jsesc"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/json-parse-even-better-errors": {
|
||||
@@ -8758,14 +8740,15 @@
|
||||
}
|
||||
},
|
||||
"@babel/generator": {
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz",
|
||||
"integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==",
|
||||
"version": "7.29.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
|
||||
"integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
|
||||
"requires": {
|
||||
"@babel/types": "^7.23.0",
|
||||
"@jridgewell/gen-mapping": "^0.3.2",
|
||||
"@jridgewell/trace-mapping": "^0.3.17",
|
||||
"jsesc": "^2.5.1"
|
||||
"@babel/parser": "^7.29.0",
|
||||
"@babel/types": "^7.29.0",
|
||||
"@jridgewell/gen-mapping": "^0.3.12",
|
||||
"@jridgewell/trace-mapping": "^0.3.28",
|
||||
"jsesc": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"@babel/helper-annotate-as-pure": {
|
||||
@@ -8857,7 +8840,8 @@
|
||||
"@babel/helper-environment-visitor": {
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz",
|
||||
"integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA=="
|
||||
"integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==",
|
||||
"peer": true
|
||||
},
|
||||
"@babel/helper-explode-assignable-expression": {
|
||||
"version": "7.18.6",
|
||||
@@ -8872,18 +8856,16 @@
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz",
|
||||
"integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==",
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"@babel/template": "^7.22.15",
|
||||
"@babel/types": "^7.23.0"
|
||||
}
|
||||
},
|
||||
"@babel/helper-hoist-variables": {
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz",
|
||||
"integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==",
|
||||
"requires": {
|
||||
"@babel/types": "^7.22.5"
|
||||
}
|
||||
"@babel/helper-globals": {
|
||||
"version": "7.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
|
||||
"integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="
|
||||
},
|
||||
"@babel/helper-member-expression-to-functions": {
|
||||
"version": "7.21.0",
|
||||
@@ -8895,26 +8877,22 @@
|
||||
}
|
||||
},
|
||||
"@babel/helper-module-imports": {
|
||||
"version": "7.21.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz",
|
||||
"integrity": "sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==",
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
|
||||
"integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
|
||||
"requires": {
|
||||
"@babel/types": "^7.21.4"
|
||||
"@babel/traverse": "^7.28.6",
|
||||
"@babel/types": "^7.28.6"
|
||||
}
|
||||
},
|
||||
"@babel/helper-module-transforms": {
|
||||
"version": "7.21.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz",
|
||||
"integrity": "sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==",
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
|
||||
"integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
|
||||
"requires": {
|
||||
"@babel/helper-environment-visitor": "^7.18.9",
|
||||
"@babel/helper-module-imports": "^7.18.6",
|
||||
"@babel/helper-simple-access": "^7.20.2",
|
||||
"@babel/helper-split-export-declaration": "^7.18.6",
|
||||
"@babel/helper-validator-identifier": "^7.19.1",
|
||||
"@babel/template": "^7.20.7",
|
||||
"@babel/traverse": "^7.21.2",
|
||||
"@babel/types": "^7.21.2"
|
||||
"@babel/helper-module-imports": "^7.28.6",
|
||||
"@babel/helper-validator-identifier": "^7.28.5",
|
||||
"@babel/traverse": "^7.28.6"
|
||||
}
|
||||
},
|
||||
"@babel/helper-optimise-call-expression": {
|
||||
@@ -8927,9 +8905,9 @@
|
||||
}
|
||||
},
|
||||
"@babel/helper-plugin-utils": {
|
||||
"version": "7.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz",
|
||||
"integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==",
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
|
||||
"integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
|
||||
"peer": true
|
||||
},
|
||||
"@babel/helper-remap-async-to-generator": {
|
||||
@@ -8962,6 +8940,7 @@
|
||||
"version": "7.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz",
|
||||
"integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==",
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.20.2"
|
||||
}
|
||||
@@ -8979,19 +8958,20 @@
|
||||
"version": "7.22.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz",
|
||||
"integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==",
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.22.5"
|
||||
}
|
||||
},
|
||||
"@babel/helper-string-parser": {
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
|
||||
"integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA=="
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
|
||||
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="
|
||||
},
|
||||
"@babel/helper-validator-identifier": {
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
|
||||
"integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
|
||||
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="
|
||||
},
|
||||
"@babel/helper-validator-option": {
|
||||
"version": "7.21.0",
|
||||
@@ -9030,11 +9010,11 @@
|
||||
}
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.26.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.10.tgz",
|
||||
"integrity": "sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA==",
|
||||
"version": "7.29.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz",
|
||||
"integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==",
|
||||
"requires": {
|
||||
"@babel/types": "^7.26.10"
|
||||
"@babel/types": "^7.29.0"
|
||||
}
|
||||
},
|
||||
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": {
|
||||
@@ -9523,15 +9503,15 @@
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-modules-systemjs": {
|
||||
"version": "7.20.11",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.20.11.tgz",
|
||||
"integrity": "sha512-vVu5g9BPQKSFEmvt2TA4Da5N+QVS66EX21d8uoOihC+OCpUoGvzVsXeqFdtAEfVa5BILAeFt+U7yVmLbQnAJmw==",
|
||||
"version": "7.29.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.4.tgz",
|
||||
"integrity": "sha512-N7QmZ0xRZfjHOfZeQLJjwgX2zS9pdGHSVl/cjSGlo4dXMqvurfxXDMKY4RqEKzPozV78VMcd0lxyG13mlbKc4w==",
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"@babel/helper-hoist-variables": "^7.18.6",
|
||||
"@babel/helper-module-transforms": "^7.20.11",
|
||||
"@babel/helper-plugin-utils": "^7.20.2",
|
||||
"@babel/helper-validator-identifier": "^7.19.1"
|
||||
"@babel/helper-module-transforms": "^7.28.6",
|
||||
"@babel/helper-plugin-utils": "^7.28.6",
|
||||
"@babel/helper-validator-identifier": "^7.28.5",
|
||||
"@babel/traverse": "^7.29.0"
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-modules-umd": {
|
||||
@@ -9786,62 +9766,60 @@
|
||||
}
|
||||
},
|
||||
"@babel/template": {
|
||||
"version": "7.26.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz",
|
||||
"integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==",
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
|
||||
"integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.26.2",
|
||||
"@babel/parser": "^7.26.9",
|
||||
"@babel/types": "^7.26.9"
|
||||
"@babel/code-frame": "^7.28.6",
|
||||
"@babel/parser": "^7.28.6",
|
||||
"@babel/types": "^7.28.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/code-frame": {
|
||||
"version": "7.26.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
|
||||
"integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==",
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
|
||||
"integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.25.9",
|
||||
"@babel/helper-validator-identifier": "^7.28.5",
|
||||
"js-tokens": "^4.0.0",
|
||||
"picocolors": "^1.0.0"
|
||||
"picocolors": "^1.1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@babel/traverse": {
|
||||
"version": "7.23.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz",
|
||||
"integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==",
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
|
||||
"integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.22.13",
|
||||
"@babel/generator": "^7.23.0",
|
||||
"@babel/helper-environment-visitor": "^7.22.20",
|
||||
"@babel/helper-function-name": "^7.23.0",
|
||||
"@babel/helper-hoist-variables": "^7.22.5",
|
||||
"@babel/helper-split-export-declaration": "^7.22.6",
|
||||
"@babel/parser": "^7.23.0",
|
||||
"@babel/types": "^7.23.0",
|
||||
"debug": "^4.1.0",
|
||||
"globals": "^11.1.0"
|
||||
"@babel/code-frame": "^7.29.0",
|
||||
"@babel/generator": "^7.29.0",
|
||||
"@babel/helper-globals": "^7.28.0",
|
||||
"@babel/parser": "^7.29.0",
|
||||
"@babel/template": "^7.28.6",
|
||||
"@babel/types": "^7.29.0",
|
||||
"debug": "^4.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/code-frame": {
|
||||
"version": "7.22.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
|
||||
"integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
|
||||
"integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
|
||||
"requires": {
|
||||
"@babel/highlight": "^7.22.13",
|
||||
"chalk": "^2.4.2"
|
||||
"@babel/helper-validator-identifier": "^7.28.5",
|
||||
"js-tokens": "^4.0.0",
|
||||
"picocolors": "^1.1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.26.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.10.tgz",
|
||||
"integrity": "sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==",
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
|
||||
"integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
|
||||
"requires": {
|
||||
"@babel/helper-string-parser": "^7.25.9",
|
||||
"@babel/helper-validator-identifier": "^7.25.9"
|
||||
"@babel/helper-string-parser": "^7.27.1",
|
||||
"@babel/helper-validator-identifier": "^7.28.5"
|
||||
}
|
||||
},
|
||||
"@colors/colors": {
|
||||
@@ -10180,12 +10158,11 @@
|
||||
"integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw=="
|
||||
},
|
||||
"@jridgewell/gen-mapping": {
|
||||
"version": "0.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
|
||||
"integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
|
||||
"version": "0.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
||||
"requires": {
|
||||
"@jridgewell/set-array": "^1.2.1",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.10",
|
||||
"@jridgewell/sourcemap-codec": "^1.5.0",
|
||||
"@jridgewell/trace-mapping": "^0.3.24"
|
||||
}
|
||||
},
|
||||
@@ -10194,11 +10171,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
|
||||
"integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w=="
|
||||
},
|
||||
"@jridgewell/set-array": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
|
||||
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A=="
|
||||
},
|
||||
"@jridgewell/source-map": {
|
||||
"version": "0.3.11",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz",
|
||||
@@ -10210,14 +10182,14 @@
|
||||
}
|
||||
},
|
||||
"@jridgewell/sourcemap-codec": {
|
||||
"version": "1.4.14",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
|
||||
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw=="
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="
|
||||
},
|
||||
"@jridgewell/trace-mapping": {
|
||||
"version": "0.3.25",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
|
||||
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
|
||||
"version": "0.3.31",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
||||
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
||||
"requires": {
|
||||
"@jridgewell/resolve-uri": "^3.1.0",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
@@ -12164,9 +12136,9 @@
|
||||
"peer": true
|
||||
},
|
||||
"fast-uri": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
|
||||
"integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz",
|
||||
"integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==",
|
||||
"peer": true
|
||||
},
|
||||
"fastq": {
|
||||
@@ -12441,7 +12413,8 @@
|
||||
"globals": {
|
||||
"version": "11.12.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
|
||||
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="
|
||||
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
|
||||
"peer": true
|
||||
},
|
||||
"globby": {
|
||||
"version": "11.0.4",
|
||||
@@ -12945,9 +12918,9 @@
|
||||
"integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg=="
|
||||
},
|
||||
"jsesc": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
|
||||
"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA=="
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
|
||||
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="
|
||||
},
|
||||
"json-parse-even-better-errors": {
|
||||
"version": "2.3.1",
|
||||
|
||||
10601
superset-frontend/package-lock.json
generated
@@ -117,7 +117,14 @@
|
||||
"@luma.gl/gltf": "~9.2.5",
|
||||
"@luma.gl/shadertools": "~9.2.5",
|
||||
"@luma.gl/webgl": "~9.2.5",
|
||||
"@fontsource/fira-code": "^5.2.7",
|
||||
"@fontsource/inter": "^5.2.8",
|
||||
"@great-expectations/jsonforms-antd-renderers": "^2.2.10",
|
||||
"@jsonforms/core": "^3.7.0",
|
||||
"@jsonforms/react": "^3.7.0",
|
||||
"@jsonforms/vanilla-renderers": "^3.7.0",
|
||||
"@reduxjs/toolkit": "^1.9.3",
|
||||
"@rjsf/antd": "^5.24.13",
|
||||
"@rjsf/core": "^5.24.13",
|
||||
"@rjsf/utils": "^5.24.3",
|
||||
"@rjsf/validator-ajv8": "^5.24.13",
|
||||
@@ -157,7 +164,7 @@
|
||||
"ag-grid-community": "35.2.1",
|
||||
"ag-grid-react": "35.2.1",
|
||||
"antd": "^5.26.0",
|
||||
"chrono-node": "^2.9.0",
|
||||
"chrono-node": "^2.9.1",
|
||||
"classnames": "^2.2.5",
|
||||
"content-disposition": "^1.1.0",
|
||||
"d3-color": "^3.1.0",
|
||||
@@ -170,37 +177,37 @@
|
||||
"fs-extra": "^11.3.4",
|
||||
"fuse.js": "^7.3.0",
|
||||
"geolib": "^3.3.14",
|
||||
"geostyler": "^18.5.0",
|
||||
"geostyler": "^18.5.1",
|
||||
"geostyler-data": "^1.1.0",
|
||||
"geostyler-openlayers-parser": "^5.7.0",
|
||||
"geostyler-style": "11.0.2",
|
||||
"geostyler-wfs-parser": "^3.0.1",
|
||||
"google-auth-library": "^10.6.2",
|
||||
"immer": "^11.1.4",
|
||||
"immer": "^11.1.7",
|
||||
"interweave": "^13.1.1",
|
||||
"jquery": "^4.0.0",
|
||||
"js-levenshtein": "^1.1.6",
|
||||
"json-bigint": "^1.0.0",
|
||||
"json-stringify-pretty-compact": "^2.0.0",
|
||||
"lodash": "^4.18.1",
|
||||
"mapbox-gl": "^3.22.0",
|
||||
"markdown-to-jsx": "^9.7.16",
|
||||
"mapbox-gl": "^3.23.1",
|
||||
"markdown-to-jsx": "^9.8.0",
|
||||
"match-sorter": "^8.3.0",
|
||||
"memoize-one": "^5.2.1",
|
||||
"mousetrap": "^1.6.5",
|
||||
"mustache": "^4.2.0",
|
||||
"nanoid": "^5.1.9",
|
||||
"nanoid": "^5.1.11",
|
||||
"ol": "^10.9.0",
|
||||
"pretty-ms": "^9.3.0",
|
||||
"query-string": "9.3.1",
|
||||
"re-resizable": "^6.11.2",
|
||||
"react": "^17.0.2",
|
||||
"react": "^18.2.0",
|
||||
"react-arborist": "^3.5.0",
|
||||
"react-checkbox-tree": "^1.8.0",
|
||||
"react-diff-viewer-continued": "^4.2.2",
|
||||
"react-dnd": "^11.1.3",
|
||||
"react-dnd-html5-backend": "^11.1.3",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-google-recaptcha": "^3.1.0",
|
||||
"react-intersection-observer": "^10.0.3",
|
||||
"react-json-tree": "^0.20.0",
|
||||
@@ -211,7 +218,6 @@
|
||||
"react-reverse-portal": "^2.3.0",
|
||||
"react-router-dom": "^5.3.4",
|
||||
"react-search-input": "^0.11.3",
|
||||
"react-sortable-hoc": "^2.0.0",
|
||||
"react-split": "^2.0.9",
|
||||
"react-table": "^7.8.0",
|
||||
"react-transition-group": "^4.4.5",
|
||||
@@ -244,19 +250,18 @@
|
||||
"@babel/plugin-transform-export-namespace-from": "^7.27.1",
|
||||
"@babel/plugin-transform-modules-commonjs": "^7.28.6",
|
||||
"@babel/plugin-transform-runtime": "^7.29.0",
|
||||
"@babel/preset-env": "^7.29.2",
|
||||
"@babel/preset-env": "^7.29.5",
|
||||
"@babel/preset-react": "^7.28.5",
|
||||
"@babel/preset-typescript": "^7.28.5",
|
||||
"@babel/register": "^7.23.7",
|
||||
"@babel/register": "^7.29.3",
|
||||
"@babel/runtime": "^7.29.2",
|
||||
"@babel/runtime-corejs3": "^7.29.2",
|
||||
"@babel/types": "^7.28.6",
|
||||
"@cypress/react": "^8.0.2",
|
||||
"@emotion/babel-plugin": "^11.13.5",
|
||||
"@emotion/jest": "^11.14.2",
|
||||
"@istanbuljs/nyc-config-typescript": "^1.0.1",
|
||||
"@mihkeleidast/storybook-addon-source": "^1.0.1",
|
||||
"@playwright/test": "^1.59.1",
|
||||
"@playwright/test": "^1.60.0",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.6.2",
|
||||
"@storybook/addon-actions": "^8.6.18",
|
||||
"@storybook/addon-controls": "^8.6.18",
|
||||
@@ -270,13 +275,12 @@
|
||||
"@storybook/test": "^8.6.18",
|
||||
"@storybook/test-runner": "^0.17.0",
|
||||
"@svgr/webpack": "^8.1.0",
|
||||
"@swc/core": "^1.15.32",
|
||||
"@swc/core": "^1.15.33",
|
||||
"@swc/plugin-emotion": "^14.9.0",
|
||||
"@swc/plugin-transform-imports": "^12.5.0",
|
||||
"@testing-library/dom": "^8.20.1",
|
||||
"@testing-library/dom": "^9.3.4",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@testing-library/react": "^12.1.5",
|
||||
"@testing-library/react-hooks": "^8.0.1",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/user-event": "^12.8.3",
|
||||
"@types/content-disposition": "^0.5.9",
|
||||
"@types/dom-to-image": "^2.6.7",
|
||||
@@ -286,8 +290,8 @@
|
||||
"@types/json-bigint": "^1.0.4",
|
||||
"@types/mousetrap": "^1.6.15",
|
||||
"@types/node": "^25.6.0",
|
||||
"@types/react": "^17.0.83",
|
||||
"@types/react-dom": "^17.0.26",
|
||||
"@types/react": "^18.2.0",
|
||||
"@types/react-dom": "^18.2.0",
|
||||
"@types/react-loadable": "^5.5.11",
|
||||
"@types/react-redux": "^7.1.10",
|
||||
"@types/react-resizable": "^3.0.8",
|
||||
@@ -299,14 +303,14 @@
|
||||
"@types/rison": "0.1.0",
|
||||
"@types/tinycolor2": "^1.4.3",
|
||||
"@types/unzipper": "^0.10.11",
|
||||
"@typescript-eslint/eslint-plugin": "^8.59.1",
|
||||
"@typescript-eslint/parser": "^8.58.2",
|
||||
"@typescript-eslint/eslint-plugin": "^8.59.3",
|
||||
"@typescript-eslint/parser": "^8.59.3",
|
||||
"babel-jest": "^30.0.2",
|
||||
"babel-loader": "^10.1.1",
|
||||
"babel-plugin-dynamic-import-node": "^2.3.3",
|
||||
"babel-plugin-jsx-remove-data-test-id": "^3.0.0",
|
||||
"babel-plugin-lodash": "^3.3.4",
|
||||
"baseline-browser-mapping": "^2.10.21",
|
||||
"baseline-browser-mapping": "^2.10.29",
|
||||
"cheerio": "1.2.0",
|
||||
"concurrently": "^9.2.1",
|
||||
"copy-webpack-plugin": "^14.0.0",
|
||||
@@ -326,7 +330,7 @@
|
||||
"eslint-plugin-no-only-tests": "^3.4.0",
|
||||
"eslint-plugin-prettier": "^5.5.5",
|
||||
"eslint-plugin-react-prefer-function-component": "^5.0.0",
|
||||
"eslint-plugin-react-you-might-not-need-an-effect": "^0.9.3",
|
||||
"eslint-plugin-react-you-might-not-need-an-effect": "^0.10.0",
|
||||
"eslint-plugin-storybook": "^0.8.0",
|
||||
"eslint-plugin-testing-library": "^7.16.2",
|
||||
"eslint-plugin-theme-colors": "file:eslint-rules/eslint-plugin-theme-colors",
|
||||
@@ -341,12 +345,12 @@
|
||||
"jest-html-reporter": "^4.4.0",
|
||||
"jest-websocket-mock": "^2.5.0",
|
||||
"js-yaml-loader": "^1.2.2",
|
||||
"jsdom": "^29.1.0",
|
||||
"jsdom": "^29.1.1",
|
||||
"lerna": "^9.0.4",
|
||||
"lightningcss": "^1.32.0",
|
||||
"mini-css-extract-plugin": "^2.10.2",
|
||||
"open-cli": "^9.0.0",
|
||||
"oxlint": "^1.62.0",
|
||||
"oxlint": "^1.63.0",
|
||||
"po2json": "^0.4.5",
|
||||
"prettier": "3.8.3",
|
||||
"prettier-plugin-packagejson": "^3.0.2",
|
||||
@@ -360,20 +364,20 @@
|
||||
"storybook": "8.6.18",
|
||||
"style-loader": "^4.0.0",
|
||||
"swc-loader": "^0.2.7",
|
||||
"terser-webpack-plugin": "^5.5.0",
|
||||
"terser-webpack-plugin": "^5.6.0",
|
||||
"ts-jest": "^29.4.9",
|
||||
"tscw-config": "^1.1.2",
|
||||
"tsx": "^4.21.0",
|
||||
"typescript": "5.4.5",
|
||||
"unzipper": "^0.12.3",
|
||||
"vm-browserify": "^1.1.2",
|
||||
"wait-on": "^9.0.5",
|
||||
"wait-on": "^9.0.10",
|
||||
"webpack": "^5.106.2",
|
||||
"webpack-bundle-analyzer": "^5.3.0",
|
||||
"webpack-cli": "^6.0.1",
|
||||
"webpack-dev-server": "^5.2.3",
|
||||
"webpack-manifest-plugin": "^5.0.1",
|
||||
"webpack-sources": "^3.4.0",
|
||||
"webpack-sources": "^3.4.1",
|
||||
"webpack-visualizer-plugin2": "^2.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -35,9 +35,9 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"cross-env": "^10.1.0",
|
||||
"fs-extra": "^11.3.4",
|
||||
"fs-extra": "^11.3.5",
|
||||
"jest": "^30.3.0",
|
||||
"yeoman-test": "^11.3.1"
|
||||
"yeoman-test": "^11.5.2"
|
||||
},
|
||||
"engines": {
|
||||
"npm": ">= 4.0.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@apache-superset/core",
|
||||
"version": "0.1.0-rc2",
|
||||
"version": "0.1.0",
|
||||
"description": "This package contains UI elements, APIs, and utility functions used by Superset.",
|
||||
"sideEffects": false,
|
||||
"main": "lib/index.js",
|
||||
@@ -75,16 +75,15 @@
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.28.6",
|
||||
"@babel/core": "^7.29.0",
|
||||
"@babel/preset-env": "^7.29.2",
|
||||
"@babel/preset-env": "^7.29.5",
|
||||
"@babel/preset-react": "^7.28.5",
|
||||
"@babel/preset-typescript": "^7.28.5",
|
||||
"typescript": "^5.0.0",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
"@types/lodash": "^4.17.24",
|
||||
"@testing-library/dom": "^8.20.1",
|
||||
"@testing-library/dom": "^9.3.4",
|
||||
"@testing-library/jest-dom": "*",
|
||||
"@testing-library/react": "^12.1.5",
|
||||
"@testing-library/react-hooks": "*",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/user-event": "*",
|
||||
"@types/react": "*",
|
||||
"@types/react-loadable": "*",
|
||||
@@ -98,8 +97,8 @@
|
||||
"@fontsource/ibm-plex-mono": "^5.2.7",
|
||||
"@fontsource/inter": "^5.2.6",
|
||||
"nanoid": "^5.0.9",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-loadable": "^5.5.0",
|
||||
"tinycolor2": "*",
|
||||
"lodash": "^4.18.1",
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
*/
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { ReactElement } from 'react';
|
||||
import { render, RenderOptions } from '@testing-library/react';
|
||||
import { render, RenderOptions, RenderResult } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
import { themeObject } from './theme';
|
||||
|
||||
@@ -33,7 +33,7 @@ const Providers = ({ children }: { children: React.ReactNode }) => (
|
||||
const customRender = (
|
||||
ui: ReactElement,
|
||||
options?: Omit<RenderOptions, 'wrapper'>,
|
||||
) => render(ui, { wrapper: Providers, ...options });
|
||||
): RenderResult => render(ui, { wrapper: Providers, ...options });
|
||||
|
||||
export {
|
||||
createEvent,
|
||||
|
||||
@@ -38,9 +38,17 @@ import {
|
||||
import { normalizeThemeConfig, serializeThemeConfig } from './utils';
|
||||
|
||||
export class Theme {
|
||||
theme: SupersetTheme;
|
||||
// Forward-compat: TS 6.0 enforces strictPropertyInitialization here;
|
||||
// both fields are assigned via setConfig() during construction, so we
|
||||
// use a definite-assignment assertion rather than hoisting the logic
|
||||
// out of setConfig().
|
||||
//
|
||||
// Assigned via setConfig() in the constructor; TypeScript 6.0's
|
||||
// strictPropertyInitialization can't trace that call chain, so we use
|
||||
// a definite-assignment assertion.
|
||||
theme!: SupersetTheme;
|
||||
|
||||
private antdConfig: AntdThemeConfig;
|
||||
private antdConfig!: AntdThemeConfig;
|
||||
|
||||
private constructor({ config }: { config?: AnyThemeConfig }) {
|
||||
this.SupersetThemeProvider = this.SupersetThemeProvider.bind(this);
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { ThemeProvider } from '@emotion/react';
|
||||
import { theme as antdTheme } from 'antd';
|
||||
import {
|
||||
|
||||
@@ -20,3 +20,10 @@
|
||||
* Stub for the untyped jed module.
|
||||
*/
|
||||
declare module 'jed';
|
||||
|
||||
/**
|
||||
* CSS side-effect imports from @fontsource packages. These are bundler-only
|
||||
* artifacts and carry no type information at runtime; declaring them here
|
||||
* silences TS2882 under TypeScript 6.0's stricter module-resolution rules.
|
||||
*/
|
||||
declare module '@fontsource/*';
|
||||
|
||||
@@ -33,17 +33,16 @@
|
||||
"@ant-design/icons": "^5.6.1",
|
||||
"@emotion/react": "^11.4.1",
|
||||
"@superset-ui/core": "*",
|
||||
"@testing-library/dom": "^8.20.1",
|
||||
"@testing-library/dom": "^9.3.4",
|
||||
"@testing-library/jest-dom": "*",
|
||||
"@testing-library/react": "^12.1.5",
|
||||
"@testing-library/react-hooks": "*",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/user-event": "*",
|
||||
"ace-builds": "^1.4.14",
|
||||
"brace": "^0.11.1",
|
||||
"memoize-one": "^5.1.1",
|
||||
"react": "^17.0.2",
|
||||
"react": "^18.2.0",
|
||||
"react-ace": "^10.1.0",
|
||||
"react-dom": "^17.0.2"
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
|
||||
import { isMatrixifyVisible } from './matrixifyControls';
|
||||
import type { ControlStateMapping } from '../types';
|
||||
|
||||
/**
|
||||
* Helper to build a controls object matching the shape used by
|
||||
@@ -25,7 +26,7 @@ import { isMatrixifyVisible } from './matrixifyControls';
|
||||
*/
|
||||
function makeControls(
|
||||
overrides: Record<string, unknown> = {},
|
||||
): Record<string, { value: unknown }> {
|
||||
): ControlStateMapping {
|
||||
const defaults: Record<string, unknown> = {
|
||||
matrixify_enable: false,
|
||||
matrixify_mode_rows: 'disabled',
|
||||
@@ -36,7 +37,7 @@ function makeControls(
|
||||
const merged = { ...defaults, ...overrides };
|
||||
return Object.fromEntries(
|
||||
Object.entries(merged).map(([k, v]) => [k, { value: v }]),
|
||||
);
|
||||
) as ControlStateMapping;
|
||||
}
|
||||
|
||||
// ── matrixify_enable guard ──────────────────────────────────────────
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
import { t } from '@apache-superset/core/translation';
|
||||
import { validateNonEmpty } from '@superset-ui/core';
|
||||
import { SharedControlConfig } from '../types';
|
||||
import { ControlStateMapping, SharedControlConfig } from '../types';
|
||||
import { dndAdhocMetricControl } from './dndControls';
|
||||
import { defineSavedMetrics } from '../utils';
|
||||
|
||||
@@ -29,9 +29,12 @@ import { defineSavedMetrics } from '../utils';
|
||||
* Controls for transforming charts into matrix/grid layouts
|
||||
*/
|
||||
|
||||
// Utility function to check if matrixify controls should be visible
|
||||
// Utility function to check if matrixify controls should be visible.
|
||||
// Controls both visibility callbacks and validator injection via mapStateToProps.
|
||||
// The matrixify_enable guard prevents hidden validators from firing on
|
||||
// pre-revamp charts with stale matrixify_mode defaults (fix for #38519).
|
||||
const isMatrixifyVisible = (
|
||||
controls: any,
|
||||
controls: ControlStateMapping | undefined,
|
||||
axis: 'rows' | 'columns',
|
||||
mode?: 'metrics' | 'dimensions',
|
||||
selectionMode?: 'members' | 'topn' | 'all',
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Tests for the matrixify_enable guard in isMatrixifyVisible() and
|
||||
* validator injection via mapStateToProps on real matrixify control definitions.
|
||||
*
|
||||
* These are TDD tests for the fix to apache/superset#38519 regression:
|
||||
* isMatrixifyVisible() must check matrixify_enable before evaluating mode,
|
||||
* otherwise pre-revamp charts with stale matrixify_mode defaults trigger
|
||||
* hidden validators that block save.
|
||||
*/
|
||||
|
||||
import {
|
||||
matrixifyControls,
|
||||
isMatrixifyVisible,
|
||||
} from '../../src/shared-controls/matrixifyControls';
|
||||
import type { ControlPanelState, ControlStateMapping } from '../../src/types';
|
||||
|
||||
// Helper: build a minimal controls object for ControlPanelState
|
||||
const buildControls = (
|
||||
overrides: Record<string, any> = {},
|
||||
): ControlStateMapping => {
|
||||
const controls: Record<string, { value: any }> = {};
|
||||
Object.entries(overrides).forEach(([key, value]) => {
|
||||
controls[key] = { value };
|
||||
});
|
||||
return controls as ControlStateMapping;
|
||||
};
|
||||
|
||||
// Helper: build a minimal ControlPanelState for mapStateToProps.
|
||||
// Only provides fields that isMatrixifyVisible and mapStateToProps actually read.
|
||||
const buildState = (
|
||||
controlValues: Record<string, any> = {},
|
||||
formData: Record<string, any> = {},
|
||||
) =>
|
||||
({
|
||||
controls: buildControls(controlValues),
|
||||
datasource: { columns: [], type: 'table' },
|
||||
form_data: formData,
|
||||
common: {},
|
||||
metadata: {},
|
||||
slice: { slice_id: 0 },
|
||||
}) as unknown as ControlPanelState;
|
||||
|
||||
// ============================================================
|
||||
// Validator injection tests via real mapStateToProps (rows)
|
||||
// ============================================================
|
||||
|
||||
// --- matrixify_dimension_rows ---
|
||||
|
||||
test('matrixify_dimension_rows: validators empty when matrixify_enable is falsy', () => {
|
||||
const control = matrixifyControls.matrixify_dimension_rows;
|
||||
const state = buildState(
|
||||
{
|
||||
matrixify_enable: undefined,
|
||||
matrixify_mode_rows: 'dimensions',
|
||||
matrixify_dimension_selection_mode_rows: 'members',
|
||||
},
|
||||
{ matrixify_mode_rows: 'dimensions' },
|
||||
);
|
||||
|
||||
const result = control.mapStateToProps!(state, {} as any);
|
||||
expect(result.validators).toEqual([]);
|
||||
});
|
||||
|
||||
test('matrixify_dimension_rows: validators present when matrixify_enable is true', () => {
|
||||
const control = matrixifyControls.matrixify_dimension_rows;
|
||||
const state = buildState(
|
||||
{
|
||||
matrixify_enable: true,
|
||||
matrixify_mode_rows: 'dimensions',
|
||||
matrixify_dimension_selection_mode_rows: 'members',
|
||||
},
|
||||
{ matrixify_mode_rows: 'dimensions' },
|
||||
);
|
||||
|
||||
const result = control.mapStateToProps!(state, {} as any);
|
||||
expect(result.validators.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
// --- matrixify_topn_value_rows ---
|
||||
|
||||
test('matrixify_topn_value_rows: validators empty when matrixify_enable is falsy', () => {
|
||||
const control = matrixifyControls.matrixify_topn_value_rows;
|
||||
const state = buildState(
|
||||
{
|
||||
matrixify_enable: undefined,
|
||||
matrixify_mode_rows: 'dimensions',
|
||||
matrixify_dimension_selection_mode_rows: 'topn',
|
||||
},
|
||||
{ matrixify_mode_rows: 'dimensions' },
|
||||
);
|
||||
|
||||
const result = control.mapStateToProps!(state, {} as any);
|
||||
expect(result.validators).toEqual([]);
|
||||
});
|
||||
|
||||
test('matrixify_topn_value_rows: validators present when matrixify_enable is true', () => {
|
||||
const control = matrixifyControls.matrixify_topn_value_rows;
|
||||
const state = buildState(
|
||||
{
|
||||
matrixify_enable: true,
|
||||
matrixify_mode_rows: 'dimensions',
|
||||
matrixify_dimension_selection_mode_rows: 'topn',
|
||||
},
|
||||
{ matrixify_mode_rows: 'dimensions' },
|
||||
);
|
||||
|
||||
const result = control.mapStateToProps!(state, {} as any);
|
||||
expect(result.validators.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
// --- matrixify_topn_metric_rows ---
|
||||
|
||||
test('matrixify_topn_metric_rows: validators empty when matrixify_enable is falsy', () => {
|
||||
const control = matrixifyControls.matrixify_topn_metric_rows;
|
||||
const state = buildState(
|
||||
{
|
||||
matrixify_enable: undefined,
|
||||
matrixify_mode_rows: 'dimensions',
|
||||
matrixify_dimension_selection_mode_rows: 'topn',
|
||||
},
|
||||
{ matrixify_mode_rows: 'dimensions' },
|
||||
);
|
||||
|
||||
const result = control.mapStateToProps!(state, {} as any);
|
||||
expect(result.validators).toEqual([]);
|
||||
});
|
||||
|
||||
test('matrixify_topn_metric_rows: validators present when matrixify_enable is true', () => {
|
||||
const control = matrixifyControls.matrixify_topn_metric_rows;
|
||||
const state = buildState(
|
||||
{
|
||||
matrixify_enable: true,
|
||||
matrixify_mode_rows: 'dimensions',
|
||||
matrixify_dimension_selection_mode_rows: 'topn',
|
||||
},
|
||||
{ matrixify_mode_rows: 'dimensions' },
|
||||
);
|
||||
|
||||
const result = control.mapStateToProps!(state, {} as any);
|
||||
expect(result.validators.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// Validator injection tests via real mapStateToProps (columns)
|
||||
// ============================================================
|
||||
|
||||
test('matrixify_dimension_columns: validators empty when matrixify_enable is falsy', () => {
|
||||
const control = matrixifyControls.matrixify_dimension_columns;
|
||||
const state = buildState(
|
||||
{
|
||||
matrixify_enable: undefined,
|
||||
matrixify_mode_columns: 'dimensions',
|
||||
matrixify_dimension_selection_mode_columns: 'members',
|
||||
},
|
||||
{ matrixify_mode_columns: 'dimensions' },
|
||||
);
|
||||
|
||||
const result = control.mapStateToProps!(state, {} as any);
|
||||
expect(result.validators).toEqual([]);
|
||||
});
|
||||
|
||||
test('matrixify_dimension_columns: validators present when matrixify_enable is true', () => {
|
||||
const control = matrixifyControls.matrixify_dimension_columns;
|
||||
const state = buildState(
|
||||
{
|
||||
matrixify_enable: true,
|
||||
matrixify_mode_columns: 'dimensions',
|
||||
matrixify_dimension_selection_mode_columns: 'members',
|
||||
},
|
||||
{ matrixify_mode_columns: 'dimensions' },
|
||||
);
|
||||
|
||||
const result = control.mapStateToProps!(state, {} as any);
|
||||
expect(result.validators.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// Direct isMatrixifyVisible guard tests
|
||||
// ============================================================
|
||||
|
||||
test.each([
|
||||
['undefined', undefined],
|
||||
['null', null],
|
||||
['false', false],
|
||||
['0', 0],
|
||||
])(
|
||||
'isMatrixifyVisible returns false when matrixify_enable is %s',
|
||||
(_, value) => {
|
||||
const controls = buildControls({
|
||||
matrixify_enable: value,
|
||||
matrixify_mode_rows: 'dimensions',
|
||||
});
|
||||
expect(isMatrixifyVisible(controls, 'rows')).toBe(false);
|
||||
},
|
||||
);
|
||||
|
||||
test('isMatrixifyVisible returns true when matrixify_enable is true and mode matches', () => {
|
||||
const controls = buildControls({
|
||||
matrixify_enable: true,
|
||||
matrixify_mode_rows: 'dimensions',
|
||||
});
|
||||
expect(isMatrixifyVisible(controls, 'rows', 'dimensions')).toBe(true);
|
||||
});
|
||||
|
||||
test('isMatrixifyVisible returns false when matrixify_enable is true but mode is disabled', () => {
|
||||
const controls = buildControls({
|
||||
matrixify_enable: true,
|
||||
matrixify_mode_rows: 'disabled',
|
||||
});
|
||||
expect(isMatrixifyVisible(controls, 'rows')).toBe(false);
|
||||
});
|
||||
|
||||
test('isMatrixifyVisible returns true when matrixify_enable is true and any non-disabled mode (no mode filter)', () => {
|
||||
const controls = buildControls({
|
||||
matrixify_enable: true,
|
||||
matrixify_mode_columns: 'metrics',
|
||||
});
|
||||
expect(isMatrixifyVisible(controls, 'columns')).toBe(true);
|
||||
});
|
||||
@@ -29,7 +29,7 @@
|
||||
"@babel/runtime": "^7.29.2",
|
||||
"@types/json-bigint": "^1.0.4",
|
||||
"@visx/responsive": "^3.12.0",
|
||||
"ace-builds": "^1.43.6",
|
||||
"ace-builds": "^1.44.0",
|
||||
"ag-grid-community": "35.2.1",
|
||||
"ag-grid-react": "35.2.1",
|
||||
"brace": "^0.11.1",
|
||||
@@ -56,7 +56,7 @@
|
||||
"react-js-cron": "^5.2.0",
|
||||
"react-markdown": "^8.0.7",
|
||||
"react-resize-detector": "^7.1.2",
|
||||
"react-syntax-highlighter": "^16.1.0",
|
||||
"react-syntax-highlighter": "^16.1.1",
|
||||
"react-ultimate-pagination": "^1.3.2",
|
||||
"regenerator-runtime": "^0.14.1",
|
||||
"rehype-raw": "^7.0.0",
|
||||
@@ -76,14 +76,13 @@
|
||||
"@types/d3-time-format": "^4.0.3",
|
||||
"@types/jquery": "^4.0.0",
|
||||
"@types/lodash": "^4.17.24",
|
||||
"@types/node": "^25.6.0",
|
||||
"@types/node": "^25.7.0",
|
||||
"@types/prop-types": "^15.7.15",
|
||||
"@types/react-syntax-highlighter": "^15.5.13",
|
||||
"@types/react-table": "^7.7.20",
|
||||
"@types/rison": "0.1.0",
|
||||
"@types/seedrandom": "^3.0.8",
|
||||
"fetch-mock": "^12.6.0",
|
||||
"jest-mock-console": "^2.0.0",
|
||||
"resize-observer-polyfill": "1.5.1",
|
||||
"timezone-mock": "^1.4.2"
|
||||
},
|
||||
@@ -91,10 +90,9 @@
|
||||
"@emotion/cache": "^11.4.0",
|
||||
"@emotion/react": "^11.4.1",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
"@testing-library/dom": "^8.20.1",
|
||||
"@testing-library/dom": "^9.3.4",
|
||||
"@testing-library/jest-dom": "*",
|
||||
"@testing-library/react": "^12.1.5",
|
||||
"@testing-library/react-hooks": "*",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/user-event": "*",
|
||||
"@types/react": "*",
|
||||
"@types/react-loadable": "*",
|
||||
@@ -102,8 +100,8 @@
|
||||
"@types/tinycolor2": "*",
|
||||
"antd": "^5.26.0",
|
||||
"nanoid": "^5.0.9",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-loadable": "^5.5.0",
|
||||
"tinycolor2": "*"
|
||||
},
|
||||
|
||||
@@ -17,8 +17,7 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/* eslint react/sort-comp: 'off' */
|
||||
import { PureComponent, ReactNode } from 'react';
|
||||
import { ReactNode, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
SupersetClientInterface,
|
||||
RequestConfig,
|
||||
@@ -67,103 +66,112 @@ export type ChartDataProviderState = {
|
||||
error?: ProvidedProps['error'];
|
||||
};
|
||||
|
||||
class ChartDataProvider extends PureComponent<
|
||||
ChartDataProviderProps,
|
||||
ChartDataProviderState
|
||||
> {
|
||||
readonly chartClient: ChartClient;
|
||||
function ChartDataProvider({
|
||||
children,
|
||||
client,
|
||||
formData,
|
||||
sliceId,
|
||||
loadDatasource,
|
||||
onError,
|
||||
onLoaded,
|
||||
formDataRequestOptions,
|
||||
datasourceRequestOptions,
|
||||
queryRequestOptions,
|
||||
}: ChartDataProviderProps): JSX.Element | null {
|
||||
const [state, setState] = useState<ChartDataProviderState>({
|
||||
status: 'uninitialized',
|
||||
});
|
||||
|
||||
constructor(props: ChartDataProviderProps) {
|
||||
super(props);
|
||||
this.state = { status: 'uninitialized' };
|
||||
this.chartClient = new ChartClient({ client: props.client });
|
||||
}
|
||||
const chartClient = useMemo(() => new ChartClient({ client }), [client]);
|
||||
|
||||
componentDidMount() {
|
||||
this.handleFetchData();
|
||||
}
|
||||
const extractSliceIdAndFormData = useCallback(
|
||||
(): SliceIdAndOrFormData =>
|
||||
formData ? { formData } : { sliceId: sliceId as number },
|
||||
[formData, sliceId],
|
||||
);
|
||||
|
||||
componentDidUpdate(prevProps: ChartDataProviderProps) {
|
||||
const { formData, sliceId } = this.props;
|
||||
if (formData !== prevProps.formData || sliceId !== prevProps.sliceId) {
|
||||
this.handleFetchData();
|
||||
const handleReceiveData = useCallback(
|
||||
(payload?: Payload) => {
|
||||
if (onLoaded) onLoaded(payload);
|
||||
setState({ payload, status: 'loaded' });
|
||||
},
|
||||
[onLoaded],
|
||||
);
|
||||
|
||||
const handleError = useCallback(
|
||||
(error: ProvidedProps['error']) => {
|
||||
if (onError) onError(error);
|
||||
setState({ error, status: 'error' });
|
||||
},
|
||||
[onError],
|
||||
);
|
||||
|
||||
const handleFetchData = useCallback(() => {
|
||||
setState({ status: 'loading' });
|
||||
try {
|
||||
chartClient
|
||||
.loadFormData(extractSliceIdAndFormData(), formDataRequestOptions)
|
||||
.then(loadedFormData =>
|
||||
Promise.all([
|
||||
loadDatasource
|
||||
? chartClient.loadDatasource(
|
||||
loadedFormData.datasource,
|
||||
datasourceRequestOptions,
|
||||
)
|
||||
: Promise.resolve(undefined),
|
||||
chartClient.loadQueryData(loadedFormData, queryRequestOptions),
|
||||
]).then(
|
||||
([datasource, queriesData]) =>
|
||||
({
|
||||
datasource,
|
||||
formData: loadedFormData,
|
||||
queriesData,
|
||||
}) as Payload,
|
||||
),
|
||||
)
|
||||
.then(handleReceiveData)
|
||||
.catch(handleError);
|
||||
} catch (error) {
|
||||
handleError(error as Error);
|
||||
}
|
||||
}
|
||||
}, [
|
||||
chartClient,
|
||||
extractSliceIdAndFormData,
|
||||
formDataRequestOptions,
|
||||
loadDatasource,
|
||||
datasourceRequestOptions,
|
||||
queryRequestOptions,
|
||||
handleReceiveData,
|
||||
handleError,
|
||||
]);
|
||||
|
||||
private extractSliceIdAndFormData() {
|
||||
const { formData, sliceId } = this.props;
|
||||
return formData ? { formData } : { sliceId: sliceId as number };
|
||||
}
|
||||
// Fetch on mount and only refetch when formData or sliceId changes.
|
||||
// This preserves the original class component's componentDidUpdate
|
||||
// semantics (which compared only formData and sliceId). Other
|
||||
// fetch-related inputs referenced by handleFetchData (callbacks and
|
||||
// request option props) are intentionally excluded from the dependency
|
||||
// array, so the exhaustive-deps rule is suppressed here.
|
||||
useEffect(() => {
|
||||
handleFetchData();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [formData, sliceId]);
|
||||
|
||||
private handleFetchData = () => {
|
||||
const {
|
||||
loadDatasource,
|
||||
formDataRequestOptions,
|
||||
datasourceRequestOptions,
|
||||
queryRequestOptions,
|
||||
} = this.props;
|
||||
const { status, payload, error } = state;
|
||||
|
||||
this.setState({ status: 'loading' }, () => {
|
||||
try {
|
||||
this.chartClient
|
||||
.loadFormData(
|
||||
this.extractSliceIdAndFormData(),
|
||||
formDataRequestOptions,
|
||||
)
|
||||
.then(formData =>
|
||||
Promise.all([
|
||||
loadDatasource
|
||||
? this.chartClient.loadDatasource(
|
||||
formData.datasource,
|
||||
datasourceRequestOptions,
|
||||
)
|
||||
: Promise.resolve(undefined),
|
||||
this.chartClient.loadQueryData(formData, queryRequestOptions),
|
||||
]).then(
|
||||
([datasource, queriesData]) =>
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
({
|
||||
datasource,
|
||||
formData,
|
||||
queriesData,
|
||||
}) as Payload,
|
||||
),
|
||||
)
|
||||
.then(this.handleReceiveData)
|
||||
.catch(this.handleError);
|
||||
} catch (error) {
|
||||
this.handleError(error as Error);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
private handleReceiveData = (payload?: Payload) => {
|
||||
const { onLoaded } = this.props;
|
||||
if (onLoaded) onLoaded(payload);
|
||||
this.setState({ payload, status: 'loaded' });
|
||||
};
|
||||
|
||||
private handleError = (error: ProvidedProps['error']) => {
|
||||
const { onError } = this.props;
|
||||
if (onError) onError(error);
|
||||
this.setState({ error, status: 'error' });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { children } = this.props;
|
||||
const { status, payload, error } = this.state;
|
||||
|
||||
switch (status) {
|
||||
case 'loading':
|
||||
return children({ loading: true });
|
||||
case 'loaded':
|
||||
return children({ payload });
|
||||
case 'error':
|
||||
return children({ error });
|
||||
case 'uninitialized':
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
// Wrap the children result in a Fragment so the component's return type
|
||||
// stays `JSX.Element | null` (which TypeScript requires for JSX components)
|
||||
// while still letting consumers return any ReactNode (strings, fragments,
|
||||
// arrays, null, etc.) from the render prop.
|
||||
switch (status) {
|
||||
case 'loading':
|
||||
return <>{children({ loading: true })}</>;
|
||||
case 'loaded':
|
||||
return <>{children({ payload })}</>;
|
||||
case 'error':
|
||||
return <>{children({ error })}</>;
|
||||
case 'uninitialized':
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,8 +21,11 @@ import {
|
||||
ReactNode,
|
||||
RefObject,
|
||||
ComponentType,
|
||||
PureComponent,
|
||||
Fragment,
|
||||
memo,
|
||||
useCallback,
|
||||
useMemo,
|
||||
useRef,
|
||||
} from 'react';
|
||||
|
||||
import {
|
||||
@@ -32,23 +35,19 @@ import {
|
||||
} from 'react-error-boundary';
|
||||
import { ParentSize } from '@visx/responsive';
|
||||
import { createSelector } from 'reselect';
|
||||
import { withTheme } from '@emotion/react';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { parseLength, Dimension } from '../../dimension';
|
||||
import getChartMetadataRegistry from '../registries/ChartMetadataRegistrySingleton';
|
||||
import SuperChartCore, { Props as SuperChartCoreProps } from './SuperChartCore';
|
||||
import SuperChartCore, {
|
||||
Props as SuperChartCoreProps,
|
||||
SuperChartCoreRef,
|
||||
} from './SuperChartCore';
|
||||
import DefaultFallbackComponent from './FallbackComponent';
|
||||
import ChartProps, { ChartPropsConfig } from '../models/ChartProps';
|
||||
import NoResultsComponent from './NoResultsComponent';
|
||||
import { isMatrixifyEnabled } from '../types/matrixify';
|
||||
import MatrixifyGridRenderer from './Matrixify/MatrixifyGridRenderer';
|
||||
|
||||
const defaultProps = {
|
||||
FallbackComponent: DefaultFallbackComponent,
|
||||
height: 400 as string | number,
|
||||
width: '100%' as string | number,
|
||||
enableNoResults: true,
|
||||
};
|
||||
|
||||
import { supersetTheme, SupersetTheme } from '@apache-superset/core/theme';
|
||||
export type FallbackPropsWithDimension = FallbackProps & Partial<Dimension>;
|
||||
|
||||
export type WrapperProps = Dimension & {
|
||||
@@ -56,7 +55,9 @@ export type WrapperProps = Dimension & {
|
||||
};
|
||||
|
||||
export type Props = Omit<SuperChartCoreProps, 'chartProps'> &
|
||||
Omit<ChartPropsConfig, 'width' | 'height'> & {
|
||||
Omit<ChartPropsConfig, 'width' | 'height' | 'theme'> & {
|
||||
/** Theme object (optional, falls back to ThemeProvider context) */
|
||||
theme?: SupersetTheme;
|
||||
/**
|
||||
* Set this to true to disable error boundary built-in in SuperChart
|
||||
* and let the error propagate to upper level
|
||||
@@ -102,215 +103,269 @@ export type Props = Omit<SuperChartCoreProps, 'chartProps'> &
|
||||
inContextMenu?: boolean;
|
||||
};
|
||||
|
||||
type PropsWithDefault = Props & Readonly<typeof defaultProps>;
|
||||
|
||||
class SuperChart extends PureComponent<Props, {}> {
|
||||
function SuperChart({
|
||||
id,
|
||||
className,
|
||||
chartType,
|
||||
preTransformProps,
|
||||
overrideTransformProps,
|
||||
postTransformProps,
|
||||
onRenderSuccess,
|
||||
onRenderFailure,
|
||||
disableErrorBoundary,
|
||||
FallbackComponent = DefaultFallbackComponent,
|
||||
onErrorBoundary,
|
||||
Wrapper,
|
||||
queriesData,
|
||||
enableNoResults = true,
|
||||
noResults,
|
||||
theme: themeProp,
|
||||
debounceTime,
|
||||
height = 400,
|
||||
width = '100%',
|
||||
...rest
|
||||
}: Props): JSX.Element {
|
||||
/**
|
||||
* SuperChart's core
|
||||
* SuperChart's core ref
|
||||
*/
|
||||
core?: SuperChartCore | null;
|
||||
const coreRef = useRef<SuperChartCoreRef | null>(null);
|
||||
|
||||
private createChartProps = ChartProps.createSelector();
|
||||
// Use theme from prop if provided, otherwise from context.
|
||||
// When no ThemeProvider is present, useTheme() returns an empty object,
|
||||
// so we fall back to the default supersetTheme to avoid passing an invalid theme downstream.
|
||||
const themeFromContext = useTheme() as Partial<SupersetTheme>;
|
||||
const theme =
|
||||
themeProp ??
|
||||
(Object.keys(themeFromContext).length > 0
|
||||
? (themeFromContext as SupersetTheme)
|
||||
: supersetTheme);
|
||||
|
||||
private parseDimension = createSelector(
|
||||
[
|
||||
({ width }: { width: string | number; height: string | number }) => width,
|
||||
({ height }) => height,
|
||||
],
|
||||
(width, height) => {
|
||||
// Parse them in case they are % or 'auto'
|
||||
const widthInfo = parseLength(width);
|
||||
const heightInfo = parseLength(height);
|
||||
const boxHeight = heightInfo.isDynamic
|
||||
? `${heightInfo.multiplier * 100}%`
|
||||
: heightInfo.value;
|
||||
const boxWidth = widthInfo.isDynamic
|
||||
? `${widthInfo.multiplier * 100}%`
|
||||
: widthInfo.value;
|
||||
const style = {
|
||||
height: boxHeight,
|
||||
width: boxWidth,
|
||||
};
|
||||
const createChartProps = useMemo(() => ChartProps.createSelector(), []);
|
||||
|
||||
// bounding box will ensure that when one dimension is not dynamic
|
||||
// e.g. height = 300
|
||||
// the auto size will be bound to that value instead of being 100% by default
|
||||
// e.g. height: 300 instead of height: '100%'
|
||||
const BoundingBox =
|
||||
widthInfo.isDynamic &&
|
||||
heightInfo.isDynamic &&
|
||||
widthInfo.multiplier === 1 &&
|
||||
heightInfo.multiplier === 1
|
||||
? Fragment
|
||||
: ({ children }: { children: ReactNode }) => (
|
||||
<div style={style}>{children}</div>
|
||||
);
|
||||
const parseDimension = useMemo(
|
||||
() =>
|
||||
createSelector(
|
||||
[
|
||||
({ width: w }: { width: string | number; height: string | number }) =>
|
||||
w,
|
||||
({
|
||||
height: h,
|
||||
}: {
|
||||
width: string | number;
|
||||
height: string | number;
|
||||
}) => h,
|
||||
],
|
||||
(w, h) => {
|
||||
// Parse them in case they are % or 'auto'
|
||||
const widthInfo = parseLength(w);
|
||||
const heightInfo = parseLength(h);
|
||||
const boxHeight = heightInfo.isDynamic
|
||||
? `${heightInfo.multiplier * 100}%`
|
||||
: heightInfo.value;
|
||||
const boxWidth = widthInfo.isDynamic
|
||||
? `${widthInfo.multiplier * 100}%`
|
||||
: widthInfo.value;
|
||||
const style = {
|
||||
height: boxHeight,
|
||||
width: boxWidth,
|
||||
};
|
||||
|
||||
return { BoundingBox, heightInfo, widthInfo };
|
||||
},
|
||||
// bounding box will ensure that when one dimension is not dynamic
|
||||
// e.g. height = 300
|
||||
// the auto size will be bound to that value instead of being 100% by default
|
||||
// e.g. height: 300 instead of height: '100%'
|
||||
const BoundingBox =
|
||||
widthInfo.isDynamic &&
|
||||
heightInfo.isDynamic &&
|
||||
widthInfo.multiplier === 1 &&
|
||||
heightInfo.multiplier === 1
|
||||
? Fragment
|
||||
: ({ children }: { children: ReactNode }) => (
|
||||
<div style={style}>{children}</div>
|
||||
);
|
||||
|
||||
return { BoundingBox, heightInfo, widthInfo };
|
||||
},
|
||||
),
|
||||
[],
|
||||
);
|
||||
|
||||
static defaultProps = defaultProps;
|
||||
const setRef = useCallback((core: SuperChartCoreRef | null) => {
|
||||
coreRef.current = core;
|
||||
}, []);
|
||||
|
||||
private setRef = (core: SuperChartCore | null) => {
|
||||
this.core = core;
|
||||
};
|
||||
const getQueryCount = useCallback(
|
||||
() => getChartMetadataRegistry().get(chartType)?.queryObjectCount ?? 1,
|
||||
[chartType],
|
||||
);
|
||||
|
||||
private getQueryCount = () =>
|
||||
getChartMetadataRegistry().get(this.props.chartType)?.queryObjectCount ?? 1;
|
||||
const renderChart = useCallback(
|
||||
(chartWidth: number, chartHeight: number) => {
|
||||
const chartProps = createChartProps({
|
||||
...rest,
|
||||
queriesData,
|
||||
height: chartHeight,
|
||||
width: chartWidth,
|
||||
theme,
|
||||
});
|
||||
|
||||
renderChart(width: number, height: number) {
|
||||
const {
|
||||
// Check if Matrixify is enabled - use rawFormData (snake_case)
|
||||
const matrixifyEnabled = isMatrixifyEnabled(chartProps.rawFormData);
|
||||
|
||||
if (matrixifyEnabled) {
|
||||
// When matrixify is enabled, queriesData is expected to be empty
|
||||
// since each cell fetches its own data via StatefulChart
|
||||
const matrixifyChart = (
|
||||
<MatrixifyGridRenderer
|
||||
formData={chartProps.rawFormData}
|
||||
datasource={chartProps.datasource}
|
||||
width={chartWidth}
|
||||
height={chartHeight}
|
||||
hooks={chartProps.hooks}
|
||||
/>
|
||||
);
|
||||
|
||||
// Apply wrapper if provided
|
||||
const wrappedChart = Wrapper ? (
|
||||
<Wrapper width={chartWidth} height={chartHeight}>
|
||||
{matrixifyChart}
|
||||
</Wrapper>
|
||||
) : (
|
||||
matrixifyChart
|
||||
);
|
||||
|
||||
// Include error boundary unless disabled
|
||||
return disableErrorBoundary === true ? (
|
||||
wrappedChart
|
||||
) : (
|
||||
<ErrorBoundary
|
||||
FallbackComponent={props => (
|
||||
<FallbackComponent
|
||||
width={chartWidth}
|
||||
height={chartHeight}
|
||||
{...props}
|
||||
/>
|
||||
)}
|
||||
onError={onErrorBoundary}
|
||||
>
|
||||
{wrappedChart}
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
// Check for no results only for non-matrixified charts
|
||||
const noResultQueries =
|
||||
enableNoResults &&
|
||||
(!queriesData ||
|
||||
queriesData
|
||||
.slice(0, getQueryCount())
|
||||
.every(
|
||||
({ data }) => !data || (Array.isArray(data) && data.length === 0),
|
||||
));
|
||||
|
||||
let chart: JSX.Element;
|
||||
if (noResultQueries) {
|
||||
chart = noResults ? (
|
||||
<>{noResults}</>
|
||||
) : (
|
||||
<NoResultsComponent
|
||||
id={id}
|
||||
className={className}
|
||||
height={chartHeight}
|
||||
width={chartWidth}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
const chartWithoutWrapper = (
|
||||
<SuperChartCore
|
||||
ref={setRef}
|
||||
id={id}
|
||||
className={className}
|
||||
chartType={chartType}
|
||||
chartProps={chartProps}
|
||||
preTransformProps={preTransformProps}
|
||||
overrideTransformProps={overrideTransformProps}
|
||||
postTransformProps={postTransformProps}
|
||||
onRenderSuccess={onRenderSuccess}
|
||||
onRenderFailure={onRenderFailure}
|
||||
/>
|
||||
);
|
||||
chart = Wrapper ? (
|
||||
<Wrapper width={chartWidth} height={chartHeight}>
|
||||
{chartWithoutWrapper}
|
||||
</Wrapper>
|
||||
) : (
|
||||
chartWithoutWrapper
|
||||
);
|
||||
}
|
||||
// Include the error boundary by default unless it is specifically disabled.
|
||||
return disableErrorBoundary === true ? (
|
||||
chart
|
||||
) : (
|
||||
<ErrorBoundary
|
||||
FallbackComponent={props => (
|
||||
<FallbackComponent
|
||||
width={chartWidth}
|
||||
height={chartHeight}
|
||||
{...props}
|
||||
/>
|
||||
)}
|
||||
onError={onErrorBoundary}
|
||||
>
|
||||
{chart}
|
||||
</ErrorBoundary>
|
||||
);
|
||||
},
|
||||
[
|
||||
createChartProps,
|
||||
rest,
|
||||
queriesData,
|
||||
theme,
|
||||
Wrapper,
|
||||
disableErrorBoundary,
|
||||
FallbackComponent,
|
||||
onErrorBoundary,
|
||||
enableNoResults,
|
||||
getQueryCount,
|
||||
noResults,
|
||||
id,
|
||||
className,
|
||||
setRef,
|
||||
chartType,
|
||||
preTransformProps,
|
||||
overrideTransformProps,
|
||||
postTransformProps,
|
||||
onRenderSuccess,
|
||||
onRenderFailure,
|
||||
disableErrorBoundary,
|
||||
FallbackComponent,
|
||||
onErrorBoundary,
|
||||
Wrapper,
|
||||
queriesData,
|
||||
enableNoResults,
|
||||
noResults,
|
||||
theme,
|
||||
...rest
|
||||
} = this.props as PropsWithDefault;
|
||||
],
|
||||
);
|
||||
|
||||
const chartProps = this.createChartProps({
|
||||
...rest,
|
||||
queriesData,
|
||||
height,
|
||||
width,
|
||||
theme,
|
||||
});
|
||||
const { heightInfo, widthInfo, BoundingBox } = parseDimension({
|
||||
width,
|
||||
height,
|
||||
});
|
||||
|
||||
// Check if Matrixify is enabled - use rawFormData (snake_case)
|
||||
const matrixifyEnabled = isMatrixifyEnabled(chartProps.rawFormData);
|
||||
|
||||
if (matrixifyEnabled) {
|
||||
// When matrixify is enabled, queriesData is expected to be empty
|
||||
// since each cell fetches its own data via StatefulChart
|
||||
const matrixifyChart = (
|
||||
<MatrixifyGridRenderer
|
||||
formData={chartProps.rawFormData}
|
||||
datasource={chartProps.datasource}
|
||||
width={width}
|
||||
height={height}
|
||||
hooks={chartProps.hooks}
|
||||
/>
|
||||
);
|
||||
|
||||
// Apply wrapper if provided
|
||||
const wrappedChart = Wrapper ? (
|
||||
<Wrapper width={width} height={height}>
|
||||
{matrixifyChart}
|
||||
</Wrapper>
|
||||
) : (
|
||||
matrixifyChart
|
||||
);
|
||||
|
||||
// Include error boundary unless disabled
|
||||
return disableErrorBoundary === true ? (
|
||||
wrappedChart
|
||||
) : (
|
||||
<ErrorBoundary
|
||||
FallbackComponent={props => (
|
||||
<FallbackComponent width={width} height={height} {...props} />
|
||||
)}
|
||||
onError={onErrorBoundary}
|
||||
>
|
||||
{wrappedChart}
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
// Check for no results only for non-matrixified charts
|
||||
const noResultQueries =
|
||||
enableNoResults &&
|
||||
(!queriesData ||
|
||||
queriesData
|
||||
.slice(0, this.getQueryCount())
|
||||
.every(
|
||||
({ data }) => !data || (Array.isArray(data) && data.length === 0),
|
||||
));
|
||||
|
||||
let chart;
|
||||
if (noResultQueries) {
|
||||
chart = noResults || (
|
||||
<NoResultsComponent
|
||||
id={id}
|
||||
className={className}
|
||||
height={height}
|
||||
width={width}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
const chartWithoutWrapper = (
|
||||
<SuperChartCore
|
||||
ref={this.setRef}
|
||||
id={id}
|
||||
className={className}
|
||||
chartType={chartType}
|
||||
chartProps={chartProps}
|
||||
preTransformProps={preTransformProps}
|
||||
overrideTransformProps={overrideTransformProps}
|
||||
postTransformProps={postTransformProps}
|
||||
onRenderSuccess={onRenderSuccess}
|
||||
onRenderFailure={onRenderFailure}
|
||||
/>
|
||||
);
|
||||
chart = Wrapper ? (
|
||||
<Wrapper width={width} height={height}>
|
||||
{chartWithoutWrapper}
|
||||
</Wrapper>
|
||||
) : (
|
||||
chartWithoutWrapper
|
||||
);
|
||||
}
|
||||
// Include the error boundary by default unless it is specifically disabled.
|
||||
return disableErrorBoundary === true ? (
|
||||
chart
|
||||
) : (
|
||||
<ErrorBoundary
|
||||
FallbackComponent={props => (
|
||||
<FallbackComponent width={width} height={height} {...props} />
|
||||
)}
|
||||
onError={onErrorBoundary}
|
||||
>
|
||||
{chart}
|
||||
</ErrorBoundary>
|
||||
// If any of the dimension is dynamic, get parent's dimension
|
||||
if (widthInfo.isDynamic || heightInfo.isDynamic) {
|
||||
return (
|
||||
<BoundingBox>
|
||||
<ParentSize debounceTime={debounceTime}>
|
||||
{({ width: parentWidth, height: parentHeight }) =>
|
||||
renderChart(
|
||||
widthInfo.isDynamic ? Math.floor(parentWidth) : widthInfo.value,
|
||||
heightInfo.isDynamic
|
||||
? Math.floor(parentHeight)
|
||||
: heightInfo.value,
|
||||
)
|
||||
}
|
||||
</ParentSize>
|
||||
</BoundingBox>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { heightInfo, widthInfo, BoundingBox } = this.parseDimension(
|
||||
this.props as PropsWithDefault,
|
||||
);
|
||||
|
||||
// If any of the dimension is dynamic, get parent's dimension
|
||||
if (widthInfo.isDynamic || heightInfo.isDynamic) {
|
||||
const { debounceTime } = this.props;
|
||||
|
||||
return (
|
||||
<BoundingBox>
|
||||
<ParentSize debounceTime={debounceTime}>
|
||||
{({ width, height }) =>
|
||||
this.renderChart(
|
||||
widthInfo.isDynamic ? Math.floor(width) : widthInfo.value,
|
||||
heightInfo.isDynamic ? Math.floor(height) : heightInfo.value,
|
||||
)
|
||||
}
|
||||
</ParentSize>
|
||||
</BoundingBox>
|
||||
);
|
||||
}
|
||||
|
||||
return this.renderChart(widthInfo.value, heightInfo.value);
|
||||
}
|
||||
return renderChart(widthInfo.value, heightInfo.value);
|
||||
}
|
||||
|
||||
export default withTheme(SuperChart);
|
||||
// Wrap in memo to preserve the shallow-prop-comparison behavior
|
||||
// of the original PureComponent implementation.
|
||||
export default memo(SuperChart);
|
||||
|
||||
@@ -17,8 +17,13 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/* eslint-disable react/jsx-sort-default-props */
|
||||
import { PureComponent } from 'react';
|
||||
import {
|
||||
forwardRef,
|
||||
useCallback,
|
||||
useImperativeHandle,
|
||||
useMemo,
|
||||
useRef,
|
||||
} from 'react';
|
||||
import { t } from '@apache-superset/core/translation';
|
||||
import { createSelector } from 'reselect';
|
||||
import getChartComponentRegistry from '../registries/ChartComponentRegistrySingleton';
|
||||
@@ -39,16 +44,6 @@ function IDENTITY<T>(x: T) {
|
||||
|
||||
const EMPTY = () => null;
|
||||
|
||||
const defaultProps = {
|
||||
id: '',
|
||||
className: '',
|
||||
preTransformProps: IDENTITY,
|
||||
overrideTransformProps: undefined,
|
||||
postTransformProps: IDENTITY,
|
||||
onRenderSuccess() {},
|
||||
onRenderFailure() {},
|
||||
};
|
||||
|
||||
interface LoadingProps {
|
||||
error: { toString(): string };
|
||||
}
|
||||
@@ -78,174 +73,231 @@ export type Props = {
|
||||
onRenderFailure?: HandlerFunction;
|
||||
};
|
||||
|
||||
export default class SuperChartCore extends PureComponent<Props, {}> {
|
||||
/**
|
||||
* The HTML element that wraps all chart content
|
||||
*/
|
||||
container?: HTMLElement | null;
|
||||
export interface SuperChartCoreRef {
|
||||
container: HTMLElement | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* memoized function so it will not recompute and return previous value
|
||||
* unless one of
|
||||
* - preTransformProps
|
||||
* - chartProps
|
||||
* is changed.
|
||||
*/
|
||||
preSelector = createSelector(
|
||||
[
|
||||
(input: {
|
||||
const SuperChartCore = forwardRef<SuperChartCoreRef, Props>(
|
||||
function SuperChartCore(
|
||||
{
|
||||
id = '',
|
||||
className = '',
|
||||
chartProps = BLANK_CHART_PROPS,
|
||||
chartType,
|
||||
preTransformProps = IDENTITY,
|
||||
overrideTransformProps,
|
||||
postTransformProps = IDENTITY,
|
||||
onRenderSuccess = () => {},
|
||||
onRenderFailure = () => {},
|
||||
},
|
||||
ref,
|
||||
) {
|
||||
const containerRef = useRef<HTMLElement | null>(null);
|
||||
|
||||
// Expose container via ref
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => ({
|
||||
get container() {
|
||||
return containerRef.current;
|
||||
},
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
/**
|
||||
* memoized function so it will not recompute and return previous value
|
||||
* unless one of
|
||||
* - preTransformProps
|
||||
* - chartProps
|
||||
* is changed.
|
||||
*/
|
||||
const preSelector = useMemo(
|
||||
() =>
|
||||
createSelector(
|
||||
[
|
||||
(input: {
|
||||
chartProps: ChartProps;
|
||||
preTransformProps?: PreTransformProps;
|
||||
}) => input.chartProps,
|
||||
input => input.preTransformProps,
|
||||
],
|
||||
(inputChartProps, pre = IDENTITY) => pre(inputChartProps),
|
||||
),
|
||||
[],
|
||||
);
|
||||
|
||||
/**
|
||||
* memoized function so it will not recompute and return previous value
|
||||
* unless one of the input arguments have changed.
|
||||
*/
|
||||
const transformSelector = useMemo(
|
||||
() =>
|
||||
createSelector(
|
||||
[
|
||||
(input: {
|
||||
chartProps: ChartProps;
|
||||
transformProps?: TransformProps;
|
||||
}) => input.chartProps,
|
||||
input => input.transformProps,
|
||||
],
|
||||
(preprocessedChartProps, transform = IDENTITY) =>
|
||||
transform(preprocessedChartProps),
|
||||
),
|
||||
[],
|
||||
);
|
||||
|
||||
/**
|
||||
* memoized function so it will not recompute and return previous value
|
||||
* unless one of the input arguments have changed.
|
||||
*/
|
||||
const postSelector = useMemo(
|
||||
() =>
|
||||
createSelector(
|
||||
[
|
||||
(input: {
|
||||
chartProps: ChartProps;
|
||||
postTransformProps?: PostTransformProps;
|
||||
}) => input.chartProps,
|
||||
input => input.postTransformProps,
|
||||
],
|
||||
(transformedChartProps, post = IDENTITY) =>
|
||||
post(transformedChartProps),
|
||||
),
|
||||
[],
|
||||
);
|
||||
|
||||
/**
|
||||
* Using each memoized function to retrieve the computed chartProps
|
||||
*/
|
||||
const processChartProps = useCallback(
|
||||
({
|
||||
chartProps: inputChartProps,
|
||||
preTransformProps: pre,
|
||||
transformProps,
|
||||
postTransformProps: post,
|
||||
}: {
|
||||
chartProps: ChartProps;
|
||||
preTransformProps?: PreTransformProps;
|
||||
}) => input.chartProps,
|
||||
input => input.preTransformProps,
|
||||
],
|
||||
(chartProps, pre = IDENTITY) => pre(chartProps),
|
||||
);
|
||||
|
||||
/**
|
||||
* memoized function so it will not recompute and return previous value
|
||||
* unless one of the input arguments have changed.
|
||||
*/
|
||||
transformSelector = createSelector(
|
||||
[
|
||||
(input: { chartProps: ChartProps; transformProps?: TransformProps }) =>
|
||||
input.chartProps,
|
||||
input => input.transformProps,
|
||||
],
|
||||
(preprocessedChartProps, transform = IDENTITY) =>
|
||||
transform(preprocessedChartProps),
|
||||
);
|
||||
|
||||
/**
|
||||
* memoized function so it will not recompute and return previous value
|
||||
* unless one of the input arguments have changed.
|
||||
*/
|
||||
postSelector = createSelector(
|
||||
[
|
||||
(input: {
|
||||
chartProps: ChartProps;
|
||||
transformProps?: TransformProps;
|
||||
postTransformProps?: PostTransformProps;
|
||||
}) => input.chartProps,
|
||||
input => input.postTransformProps,
|
||||
],
|
||||
(transformedChartProps, post = IDENTITY) => post(transformedChartProps),
|
||||
);
|
||||
|
||||
/**
|
||||
* Using each memoized function to retrieve the computed chartProps
|
||||
*/
|
||||
processChartProps = ({
|
||||
chartProps,
|
||||
preTransformProps,
|
||||
transformProps,
|
||||
postTransformProps,
|
||||
}: {
|
||||
chartProps: ChartProps;
|
||||
preTransformProps?: PreTransformProps;
|
||||
transformProps?: TransformProps;
|
||||
postTransformProps?: PostTransformProps;
|
||||
}) =>
|
||||
this.postSelector({
|
||||
chartProps: this.transformSelector({
|
||||
chartProps: this.preSelector({ chartProps, preTransformProps }),
|
||||
transformProps,
|
||||
}),
|
||||
postTransformProps,
|
||||
});
|
||||
|
||||
/**
|
||||
* memoized function so it will not recompute
|
||||
* and return previous value
|
||||
* unless one of
|
||||
* - chartType
|
||||
* - overrideTransformProps
|
||||
* is changed.
|
||||
*/
|
||||
private createLoadableRenderer = createSelector(
|
||||
[
|
||||
(input: { chartType: string; overrideTransformProps?: TransformProps }) =>
|
||||
input.chartType,
|
||||
input => input.overrideTransformProps,
|
||||
],
|
||||
(chartType, overrideTransformProps) => {
|
||||
if (chartType) {
|
||||
const Renderer = createLoadableRenderer({
|
||||
loader: {
|
||||
Chart: () => getChartComponentRegistry().getAsPromise(chartType),
|
||||
transformProps: overrideTransformProps
|
||||
? () => Promise.resolve(overrideTransformProps)
|
||||
: () => getChartTransformPropsRegistry().getAsPromise(chartType),
|
||||
},
|
||||
loading: (loadingProps: LoadingProps) =>
|
||||
this.renderLoading(loadingProps, chartType),
|
||||
render: this.renderChart,
|
||||
});
|
||||
|
||||
// Trigger preloading.
|
||||
Renderer.preload();
|
||||
|
||||
return Renderer;
|
||||
}
|
||||
|
||||
return EMPTY;
|
||||
},
|
||||
);
|
||||
|
||||
static defaultProps = defaultProps;
|
||||
|
||||
private renderChart = (loaded: LoadedModules, props: RenderProps) => {
|
||||
const { Chart, transformProps } = loaded;
|
||||
const { chartProps, preTransformProps, postTransformProps } = props;
|
||||
|
||||
return (
|
||||
<Chart
|
||||
{...this.processChartProps({
|
||||
chartProps,
|
||||
preTransformProps,
|
||||
transformProps,
|
||||
postTransformProps,
|
||||
})}
|
||||
/>
|
||||
}) =>
|
||||
postSelector({
|
||||
chartProps: transformSelector({
|
||||
chartProps: preSelector({
|
||||
chartProps: inputChartProps,
|
||||
preTransformProps: pre,
|
||||
}),
|
||||
transformProps,
|
||||
}),
|
||||
postTransformProps: post,
|
||||
}),
|
||||
[preSelector, transformSelector, postSelector],
|
||||
);
|
||||
};
|
||||
|
||||
private renderLoading = (loadingProps: LoadingProps, chartType: string) => {
|
||||
const { error } = loadingProps;
|
||||
const renderLoading = useCallback(
|
||||
(loadingProps: LoadingProps, loadingChartType: string) => {
|
||||
const { error } = loadingProps;
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="alert alert-warning" role="alert">
|
||||
<strong>{t('ERROR')}</strong>
|
||||
<code>chartType="{chartType}"</code> —
|
||||
{error.toString()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (error) {
|
||||
return (
|
||||
<div className="alert alert-warning" role="alert">
|
||||
<strong>{t('ERROR')}</strong>
|
||||
<code>chartType="{loadingChartType}"</code> —
|
||||
{error.toString()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
return null;
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
private setRef = (container: HTMLElement | null) => {
|
||||
this.container = container;
|
||||
};
|
||||
const renderChart = useCallback(
|
||||
(loaded: LoadedModules, props: RenderProps) => {
|
||||
const { Chart, transformProps } = loaded;
|
||||
const {
|
||||
chartProps: renderChartProps,
|
||||
preTransformProps: pre,
|
||||
postTransformProps: post,
|
||||
} = props;
|
||||
|
||||
render() {
|
||||
const {
|
||||
id,
|
||||
className,
|
||||
preTransformProps,
|
||||
postTransformProps,
|
||||
chartProps = BLANK_CHART_PROPS,
|
||||
onRenderSuccess,
|
||||
onRenderFailure,
|
||||
} = this.props;
|
||||
return (
|
||||
<Chart
|
||||
{...processChartProps({
|
||||
chartProps: renderChartProps,
|
||||
preTransformProps: pre,
|
||||
transformProps,
|
||||
postTransformProps: post,
|
||||
})}
|
||||
/>
|
||||
);
|
||||
},
|
||||
[processChartProps],
|
||||
);
|
||||
|
||||
/**
|
||||
* memoized function so it will not recompute
|
||||
* and return previous value
|
||||
* unless one of
|
||||
* - chartType
|
||||
* - overrideTransformProps
|
||||
* is changed.
|
||||
*/
|
||||
const createLoadableRendererSelector = useMemo(
|
||||
() =>
|
||||
createSelector(
|
||||
[
|
||||
(input: {
|
||||
chartType: string;
|
||||
overrideTransformProps?: TransformProps;
|
||||
}) => input.chartType,
|
||||
input => input.overrideTransformProps,
|
||||
],
|
||||
(selectorChartType, selectorOverrideTransformProps) => {
|
||||
if (selectorChartType) {
|
||||
const Renderer = createLoadableRenderer({
|
||||
loader: {
|
||||
Chart: () =>
|
||||
getChartComponentRegistry().getAsPromise(selectorChartType),
|
||||
transformProps: selectorOverrideTransformProps
|
||||
? () => Promise.resolve(selectorOverrideTransformProps)
|
||||
: () =>
|
||||
getChartTransformPropsRegistry().getAsPromise(
|
||||
selectorChartType,
|
||||
),
|
||||
},
|
||||
loading: (loadingProps: LoadingProps) =>
|
||||
renderLoading(loadingProps, selectorChartType),
|
||||
render: renderChart,
|
||||
});
|
||||
|
||||
// Trigger preloading.
|
||||
Renderer.preload();
|
||||
|
||||
return Renderer;
|
||||
}
|
||||
|
||||
return EMPTY;
|
||||
},
|
||||
),
|
||||
[renderLoading, renderChart],
|
||||
);
|
||||
|
||||
const setRef = useCallback((container: HTMLElement | null) => {
|
||||
containerRef.current = container;
|
||||
}, []);
|
||||
|
||||
// Create LoadableRenderer and start preloading
|
||||
// the lazy-loaded Chart components
|
||||
const Renderer = this.createLoadableRenderer(this.props);
|
||||
const Renderer = createLoadableRendererSelector({
|
||||
chartType,
|
||||
overrideTransformProps,
|
||||
});
|
||||
|
||||
// Do not render if chartProps is set to null.
|
||||
// but the pre-loading has been started in this.createLoadableRenderer
|
||||
// but the pre-loading has been started in createLoadableRendererSelector
|
||||
// to prepare for rendering once chartProps becomes available.
|
||||
if (chartProps === null) {
|
||||
return null;
|
||||
@@ -263,7 +315,7 @@ export default class SuperChartCore extends PureComponent<Props, {}> {
|
||||
}
|
||||
|
||||
return (
|
||||
<div {...containerProps} ref={this.setRef}>
|
||||
<div {...containerProps} ref={setRef}>
|
||||
<Renderer
|
||||
preTransformProps={preTransformProps}
|
||||
postTransformProps={postTransformProps}
|
||||
@@ -273,5 +325,7 @@ export default class SuperChartCore extends PureComponent<Props, {}> {
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export default SuperChartCore;
|
||||
|
||||
@@ -17,59 +17,109 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import Loadable from 'react-loadable';
|
||||
import { ComponentClass } from 'react';
|
||||
import { ReactElement, useEffect, useRef, useState } from 'react';
|
||||
|
||||
export type LoadableRendererProps = {
|
||||
onRenderFailure?: Function;
|
||||
onRenderSuccess?: Function;
|
||||
onRenderFailure?: (error: Error) => void;
|
||||
onRenderSuccess?: () => void;
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
onRenderFailure() {},
|
||||
onRenderSuccess() {},
|
||||
type LoaderMap<Exports> = {
|
||||
[K in keyof Exports]: () => Promise<Exports[K]> | Exports[K];
|
||||
};
|
||||
|
||||
export interface LoadableRenderer<Props>
|
||||
extends
|
||||
ComponentClass<Props & LoadableRendererProps>,
|
||||
Loadable.LoadableComponent {}
|
||||
|
||||
export default function createLoadableRenderer<
|
||||
Props,
|
||||
Exports extends { [key: string]: any },
|
||||
>(options: Loadable.OptionsWithMap<Props, Exports>): LoadableRenderer<Props> {
|
||||
const LoadableRenderer = Loadable.Map<Props, Exports>(
|
||||
options,
|
||||
) as LoadableRenderer<Props>;
|
||||
|
||||
// Extends the behavior of LoadableComponent to provide post-render listeners
|
||||
class CustomLoadableRenderer extends LoadableRenderer {
|
||||
static defaultProps: object;
|
||||
|
||||
componentDidMount() {
|
||||
this.afterRender();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.afterRender();
|
||||
}
|
||||
|
||||
afterRender() {
|
||||
const { loaded, loading, error } = this.state;
|
||||
const { onRenderFailure, onRenderSuccess } = this.props;
|
||||
if (!loading) {
|
||||
if (error) {
|
||||
(onRenderFailure as Function)(error);
|
||||
} else if (loaded && Object.keys(loaded).length > 0) {
|
||||
(onRenderSuccess as Function)();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CustomLoadableRenderer.defaultProps = defaultProps;
|
||||
CustomLoadableRenderer.preload = LoadableRenderer.preload;
|
||||
|
||||
return CustomLoadableRenderer;
|
||||
export interface LoadingProps {
|
||||
error?: { toString(): string };
|
||||
}
|
||||
|
||||
export interface LoadableOptions<Props, Exports> {
|
||||
loader: LoaderMap<Exports>;
|
||||
loading: (loadingProps: LoadingProps) => ReactElement | null;
|
||||
render: (loaded: Exports, props: Props) => ReactElement;
|
||||
}
|
||||
|
||||
export interface LoadableRenderer<Props> {
|
||||
(props: Props & LoadableRendererProps): ReactElement | null;
|
||||
preload: () => Promise<unknown>;
|
||||
displayName?: string;
|
||||
}
|
||||
|
||||
export default function createLoadableRenderer<Props, Exports>(
|
||||
options: LoadableOptions<Props, Exports>,
|
||||
): LoadableRenderer<Props> {
|
||||
let promise: Promise<Exports> | null = null;
|
||||
let cachedResult: Exports | null = null;
|
||||
let cachedError: Error | null = null;
|
||||
|
||||
const load = (): Promise<Exports> => {
|
||||
if (promise) return promise;
|
||||
const keys = Object.keys(options.loader) as (keyof Exports)[];
|
||||
promise = Promise.all(
|
||||
keys.map(key => Promise.resolve(options.loader[key]())),
|
||||
).then(
|
||||
values => {
|
||||
const loaded = {} as Exports;
|
||||
keys.forEach((key, i) => {
|
||||
loaded[key] = values[i] as Exports[typeof key];
|
||||
});
|
||||
cachedResult = loaded;
|
||||
return loaded;
|
||||
},
|
||||
err => {
|
||||
cachedError = err instanceof Error ? err : new Error(String(err));
|
||||
throw cachedError;
|
||||
},
|
||||
);
|
||||
return promise;
|
||||
};
|
||||
|
||||
const Renderer: LoadableRenderer<Props> = props => {
|
||||
const [state, setState] = useState<{
|
||||
loaded: Exports | null;
|
||||
error: Error | null;
|
||||
}>(() => ({ loaded: cachedResult, error: cachedError }));
|
||||
|
||||
useEffect(() => {
|
||||
if (state.loaded || state.error) return undefined;
|
||||
let cancelled = false;
|
||||
load().then(
|
||||
loaded => {
|
||||
if (!cancelled) setState({ loaded, error: null });
|
||||
},
|
||||
err => {
|
||||
if (!cancelled) setState({ loaded: null, error: err });
|
||||
},
|
||||
);
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [state.loaded, state.error]);
|
||||
|
||||
// Keep callback refs current without retriggering the post-load effect on
|
||||
// every prop update.
|
||||
const onRenderSuccessRef = useRef(props.onRenderSuccess);
|
||||
const onRenderFailureRef = useRef(props.onRenderFailure);
|
||||
onRenderSuccessRef.current = props.onRenderSuccess;
|
||||
onRenderFailureRef.current = props.onRenderFailure;
|
||||
|
||||
useEffect(() => {
|
||||
if (state.error) {
|
||||
onRenderFailureRef.current?.(state.error);
|
||||
} else if (state.loaded && Object.keys(state.loaded).length > 0) {
|
||||
onRenderSuccessRef.current?.();
|
||||
}
|
||||
}, [state.loaded, state.error]);
|
||||
|
||||
if (state.error) {
|
||||
return options.loading({ error: state.error });
|
||||
}
|
||||
if (!state.loaded) {
|
||||
return options.loading({});
|
||||
}
|
||||
return options.render(state.loaded, props as Props);
|
||||
};
|
||||
|
||||
Renderer.preload = load;
|
||||
|
||||
return Renderer;
|
||||
}
|
||||
|
||||
@@ -94,11 +94,20 @@ class CategoricalColorScale extends ExtensibleFunction {
|
||||
|
||||
/**
|
||||
* Increment the color range with analogous colors
|
||||
*
|
||||
* @param forceMinimumExpansion When true, expand at least once even if the
|
||||
* ordinal domain is still shorter than the palette. Shared dashboard labels
|
||||
* can resolve from the global map without entering the scale domain, so
|
||||
* domain-based sizing alone would skip expansion while collision resolution
|
||||
* still needs analogous colors.
|
||||
*/
|
||||
incrementColorRange() {
|
||||
const multiple = Math.floor(
|
||||
incrementColorRange(forceMinimumExpansion = false) {
|
||||
const domainBasedMultiple = Math.floor(
|
||||
this.domain().length / this.originColors.length,
|
||||
);
|
||||
const multiple = forceMinimumExpansion
|
||||
? Math.max(domainBasedMultiple, 1)
|
||||
: domainBasedMultiple;
|
||||
// the domain has grown larger than the original range
|
||||
// increments the range with analogous colors
|
||||
if (multiple > this.multiple) {
|
||||
@@ -144,6 +153,7 @@ class CategoricalColorScale extends ExtensibleFunction {
|
||||
if (isFeatureEnabled(FeatureFlag.UseAnalogousColors)) {
|
||||
this.incrementColorRange();
|
||||
}
|
||||
|
||||
if (
|
||||
// feature flag to be deprecated (will become standard behaviour)
|
||||
isFeatureEnabled(FeatureFlag.AvoidColorsCollision) &&
|
||||
@@ -154,6 +164,39 @@ class CategoricalColorScale extends ExtensibleFunction {
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
isFeatureEnabled(FeatureFlag.AvoidColorsCollision) &&
|
||||
source === LabelsColorMapSource.Dashboard &&
|
||||
(forcedColor || isExistingLabel)
|
||||
) {
|
||||
const colliding = [...this.chartLabelsColorMap.entries()].filter(
|
||||
([labelKey, c]) => c === color && labelKey !== cleanedValue,
|
||||
);
|
||||
if (
|
||||
colliding.length > 0 &&
|
||||
isFeatureEnabled(FeatureFlag.UseAnalogousColors)
|
||||
) {
|
||||
this.incrementColorRange(true);
|
||||
}
|
||||
for (const [otherLabel] of colliding) {
|
||||
if (
|
||||
Object.prototype.hasOwnProperty.call(this.forcedColors, otherLabel)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
const newColor = this.getNextAvailableColor(otherLabel, color);
|
||||
this.chartLabelsColorMap.set(otherLabel, newColor);
|
||||
if (sliceId) {
|
||||
this.labelsColorMapInstance.addSlice(
|
||||
otherLabel,
|
||||
newColor,
|
||||
sliceId,
|
||||
appliedColorScheme,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// keep track of values in this slice
|
||||
this.chartLabelsColorMap.set(cleanedValue, color);
|
||||
|
||||
|
||||
@@ -17,19 +17,15 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import type { ReactElement } from 'react';
|
||||
import {
|
||||
Tooltip,
|
||||
type TooltipPlacement,
|
||||
type IconType,
|
||||
} from '@superset-ui/core/components';
|
||||
import type { ReactElement, ReactNode } from 'react';
|
||||
import { Tooltip, type TooltipPlacement } from '@superset-ui/core/components';
|
||||
import { css, useTheme } from '@apache-superset/core/theme';
|
||||
|
||||
export interface ActionProps {
|
||||
label: string;
|
||||
tooltip?: string | ReactElement;
|
||||
placement?: TooltipPlacement;
|
||||
icon: IconType;
|
||||
icon: ReactNode;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { useJsonValidation } from './useJsonValidation';
|
||||
|
||||
describe('useJsonValidation', () => {
|
||||
|
||||
@@ -16,16 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import {
|
||||
useEffect,
|
||||
useState,
|
||||
RefObject,
|
||||
forwardRef,
|
||||
ComponentType,
|
||||
ForwardRefExoticComponent,
|
||||
PropsWithoutRef,
|
||||
RefAttributes,
|
||||
} from 'react';
|
||||
import React, { useEffect, useState, forwardRef, ComponentType } from 'react';
|
||||
|
||||
import { Loading } from '../Loading';
|
||||
import type { PlaceholderProps } from './types';
|
||||
@@ -93,15 +84,16 @@ export function AsyncEsmComponent<
|
||||
return promise;
|
||||
}
|
||||
|
||||
type AsyncComponent = ForwardRefExoticComponent<
|
||||
PropsWithoutRef<FullProps> & RefAttributes<ComponentType<FullProps>>
|
||||
type AsyncComponent = React.ForwardRefExoticComponent<
|
||||
React.PropsWithoutRef<FullProps> & React.RefAttributes<unknown>
|
||||
> & {
|
||||
preload?: typeof waitForPromise;
|
||||
};
|
||||
|
||||
// @ts-expect-error -- generic forwardRef has PropsWithoutRef incompatibility with FullProps
|
||||
const AsyncComponent: AsyncComponent = forwardRef(function AsyncComponent(
|
||||
props: FullProps,
|
||||
ref: RefObject<ComponentType<FullProps>>,
|
||||
ref,
|
||||
) {
|
||||
const [loaded, setLoaded] = useState(component !== undefined);
|
||||
useEffect(() => {
|
||||
|
||||
@@ -24,7 +24,6 @@ import type {
|
||||
ButtonVariantType,
|
||||
ButtonColorType,
|
||||
} from 'antd/es/button';
|
||||
import { IconType } from '@superset-ui/core/components/Icons/types';
|
||||
import type { TooltipPlacement } from '../Tooltip/types';
|
||||
|
||||
export type { AntdButtonProps, ButtonType, ButtonVariantType, ButtonColorType };
|
||||
@@ -49,5 +48,5 @@ export type ButtonProps = Omit<AntdButtonProps, 'css'> & {
|
||||
buttonStyle?: ButtonStyle;
|
||||
cta?: boolean;
|
||||
showMarginRight?: boolean;
|
||||
icon?: IconType;
|
||||
icon?: ReactNode;
|
||||
};
|
||||
|
||||
@@ -73,7 +73,7 @@ export const Component = (props: DropdownContainerProps) => {
|
||||
const [overflowingState, setOverflowingState] = useState<OverflowingState>();
|
||||
const containerRef = useRef<DropdownRef>(null);
|
||||
const onOverflowingStateChange = useCallback(
|
||||
value => {
|
||||
(value: OverflowingState) => {
|
||||
if (!isEqual(overflowingState, value)) {
|
||||
setItems(generateItems(value));
|
||||
setOverflowingState(value);
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
* under the License.
|
||||
*/
|
||||
import type { CSSProperties, ReactElement, RefObject, ReactNode } from 'react';
|
||||
import { IconType } from '../Icons';
|
||||
|
||||
/**
|
||||
* Container item.
|
||||
@@ -70,7 +69,7 @@ export interface DropdownContainerProps {
|
||||
/**
|
||||
* Icon of the dropdown trigger.
|
||||
*/
|
||||
dropdownTriggerIcon?: IconType;
|
||||
dropdownTriggerIcon?: ReactNode;
|
||||
/**
|
||||
* Text of the dropdown trigger.
|
||||
*/
|
||||
|
||||