mirror of
https://github.com/apache/superset.git
synced 2026-09-11 01:34:35 +00:00
Compare commits
351
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
235d4ea516 | ||
|
|
860f8cbe0f | ||
|
|
2fad87569c | ||
|
|
c6f54471dc | ||
|
|
7539138702 | ||
|
|
e0b1b557d7 | ||
|
|
bc5a5c2ac5 | ||
|
|
3a562dbe29 | ||
|
|
73b780a28c | ||
|
|
caeb6a6b7c | ||
|
|
19072074c5 | ||
|
|
f2037fa332 | ||
|
|
6c71800436 | ||
|
|
d63308ca37 | ||
|
|
63cceb6a79 | ||
|
|
b8b2bdedf9 | ||
|
|
d5017e60c3 | ||
|
|
2e80f2a473 | ||
|
|
4c2dd63464 | ||
|
|
62302ad8c3 | ||
|
|
ed659958f3 | ||
|
|
36de05fe36 | ||
|
|
a64609f4f3 | ||
|
|
140f0001f2 | ||
|
|
587fe4af63 | ||
|
|
3a3a6536b7 | ||
|
|
4f695e1b4d | ||
|
|
6ba9096870 | ||
|
|
5106afb07f | ||
|
|
2bd4131636 | ||
|
|
7e452df1cc | ||
|
|
a626d06415 | ||
|
|
d159edc9a6 | ||
|
|
96fa2cbd2b | ||
|
|
9750881193 | ||
|
|
3db92021c7 | ||
|
|
5ccfc530b2 | ||
|
|
5f9fc31ae2 | ||
|
|
8e811de564 | ||
|
|
027de6339b | ||
|
|
bf9aff19b5 | ||
|
|
b05764d070 | ||
|
|
7be2acb2f3 | ||
|
|
83ad1eca26 | ||
|
|
92747246fc | ||
|
|
7380a59ab8 | ||
|
|
e56f8cc4fb | ||
|
|
7c79b9ab61 | ||
|
|
a62be684a0 | ||
|
|
a3dfbd7bff | ||
|
|
12eb40db01 | ||
|
|
d796543f5a | ||
|
|
e5ae626433 | ||
|
|
8195574345 | ||
|
|
6b029997d9 | ||
|
|
7a64483e6b | ||
|
|
e424b55036 | ||
|
|
613e6d6cde | ||
|
|
b3a402d936 | ||
|
|
c7d175b842 | ||
|
|
851bbeea48 | ||
|
|
c5bce756f0 | ||
|
|
3239f058c8 | ||
|
|
7e0c634c3a | ||
|
|
a9ced5c881 | ||
|
|
ace5f9d8c2 | ||
|
|
0452d1515a | ||
|
|
0330fdeb00 | ||
|
|
f2ff24d811 | ||
|
|
c51132f824 | ||
|
|
b4cb815ebf | ||
|
|
08d1ddd9fb | ||
|
|
23ac4cb3a4 | ||
|
|
5662ecab15 | ||
|
|
9e27d682f6 | ||
|
|
f0fcdcc76a | ||
|
|
135e0f8099 | ||
|
|
25eea295f6 | ||
|
|
c372f5980c | ||
|
|
3802acb1e0 | ||
|
|
bdb0030cf8 | ||
|
|
87f0540acd | ||
|
|
985d7b6a79 | ||
|
|
59f92f979a | ||
|
|
5cc286e383 | ||
|
|
26f4a5acad | ||
|
|
fdd08d3b70 | ||
|
|
1aac6c9474 | ||
|
|
7acb0c6d05 | ||
|
|
00eb86d03f | ||
|
|
1d0e836a29 | ||
|
|
ec6640b188 | ||
|
|
ff3b8d8398 | ||
|
|
022342839a | ||
|
|
38f0dc74f7 | ||
|
|
0bae05d4a9 | ||
|
|
1bb41a6e60 | ||
|
|
4423134739 | ||
|
|
190f1a59c5 | ||
|
|
5f99d613a0 | ||
|
|
6adc816805 | ||
|
|
aa97679327 | ||
|
|
94d8735d4b | ||
|
|
64c8d652e1 | ||
|
|
d30c5b4eee | ||
|
|
8ed75787cb | ||
|
|
4ee391e0d7 | ||
|
|
a67ca052d6 | ||
|
|
6b6e3803d1 | ||
|
|
51ec61c675 | ||
|
|
424f99efdf | ||
|
|
070be3de8b | ||
|
|
bd98269628 | ||
|
|
7ce371080c | ||
|
|
872632aca0 | ||
|
|
64bd03bd70 | ||
|
|
1e2d0faa55 | ||
|
|
8559786cc2 | ||
|
|
d4d22909cb | ||
|
|
4fae5758d5 | ||
|
|
f85efe6139 | ||
|
|
6ea9f2ade9 | ||
|
|
4036b784ed | ||
|
|
08a4ad662a | ||
|
|
e4021fb6e7 | ||
|
|
53b1d1097c | ||
|
|
55aa36fef8 | ||
|
|
3abcfb797a | ||
|
|
a741ddc03c | ||
|
|
7d26e33346 | ||
|
|
f6cd8066ab | ||
|
|
daefedebcd | ||
|
|
c37a3ec292 | ||
|
|
4245720851 | ||
|
|
f0b20dc445 | ||
|
|
e6f1209318 | ||
|
|
944944c49e | ||
|
|
ecbf396d4a | ||
|
|
b1474aaa60 | ||
|
|
b49e899974 | ||
|
|
11f2140c37 | ||
|
|
f1cd1ae710 | ||
|
|
e0a0a22542 | ||
|
|
2c9cf0bd55 | ||
|
|
38fdfb4ca2 | ||
|
|
15bab227bb | ||
|
|
d331a043a3 | ||
|
|
41d401a879 | ||
|
|
89f7e5e7ba | ||
|
|
aa1a69555b | ||
|
|
d1903afc69 | ||
|
|
dbc25dc555 | ||
|
|
a5d2324e21 | ||
|
|
38e82e4084 | ||
|
|
6bcc8bf2b2 | ||
|
|
f832f9b0d5 | ||
|
|
fc705d94e3 | ||
|
|
65eae027fa | ||
|
|
ac96f46c76 | ||
|
|
5c782397bb | ||
|
|
40387d5daa | ||
|
|
7f3351011d | ||
|
|
d6a6b6db14 | ||
|
|
388a1fd0be | ||
|
|
c2c929bf94 | ||
|
|
41473a520e | ||
|
|
50a5bb0671 | ||
|
|
20d0cfd156 | ||
|
|
5ad91fbb09 | ||
|
|
6229c99050 | ||
|
|
7e69d5d839 | ||
|
|
8700ec4e6d | ||
|
|
8cbf5fb8df | ||
|
|
9c288d66b5 | ||
|
|
8983edea66 | ||
|
|
95820fb9e6 | ||
|
|
6dc3d7ad9f | ||
|
|
cfa1aba1e0 | ||
|
|
43816d7528 | ||
|
|
6dd82afb0b | ||
|
|
e045f49787 | ||
|
|
38d3a39c06 | ||
|
|
23a5e95884 | ||
|
|
16f5a2a41a | ||
|
|
04e07acf98 | ||
|
|
3506773f51 | ||
|
|
d32e975eb9 | ||
|
|
21fb5a27e9 | ||
|
|
403f4ad78c | ||
|
|
ba5820b088 | ||
|
|
a93e319716 | ||
|
|
12aca72074 | ||
|
|
3fb903fdc6 | ||
|
|
4b26f8c712 | ||
|
|
37c4a36fdb | ||
|
|
811dcb3715 | ||
|
|
ccaac306e5 | ||
|
|
c596df9294 | ||
|
|
6852349d24 | ||
|
|
7c9d75b69e | ||
|
|
42201a98a1 | ||
|
|
09594b32f9 | ||
|
|
e2bb20121e | ||
|
|
56ebfb7848 | ||
|
|
5d9f53ff0c | ||
|
|
89d1b80ce7 | ||
|
|
962abf6904 | ||
|
|
ed3c5280a9 | ||
|
|
7222327992 | ||
|
|
e0b524fff2 | ||
|
|
e67bc5bee5 | ||
|
|
86a260e39b | ||
|
|
fdcb942f3c | ||
|
|
7a5c07b99c | ||
|
|
6d93eeb533 | ||
|
|
44179199ba | ||
|
|
100ad7d9ee | ||
|
|
c96c817ef5 | ||
|
|
519a64da82 | ||
|
|
24be9cd515 | ||
|
|
1987e816a5 | ||
|
|
0f4aa1ceea | ||
|
|
601fb45142 | ||
|
|
c9ebb13fa1 | ||
|
|
618113079f | ||
|
|
cc34d19d24 | ||
|
|
02ffb52f4a | ||
|
|
361afff798 | ||
|
|
2a6b0215f0 | ||
|
|
c1c296233f | ||
|
|
e05fdd8acd | ||
|
|
83823911b5 | ||
|
|
7004369c68 | ||
|
|
f5d7ce0f86 | ||
|
|
32eb8c8263 | ||
|
|
44c2c765ae | ||
|
|
0d5721910e | ||
|
|
28d67d59cd | ||
|
|
1d72480c17 | ||
|
|
1af5da6aad | ||
|
|
ea1c6ee30f | ||
|
|
97ea479cdc | ||
|
|
e088979fbe | ||
|
|
5e5c05362c | ||
|
|
c2a21915ff | ||
|
|
cbb2b2f3c2 | ||
|
|
82a74c88aa | ||
|
|
6b9dd23e3a | ||
|
|
b754f2d173 | ||
|
|
ee233d16d6 | ||
|
|
65f13f773e | ||
|
|
d4646d43a7 | ||
|
|
6465450b64 | ||
|
|
01aa4d3281 | ||
|
|
211f29b723 | ||
|
|
d6bfc98a61 | ||
|
|
5457c2da67 | ||
|
|
14b1b456e1 | ||
|
|
972e15e601 | ||
|
|
03de7e1ec6 | ||
|
|
3edf75123a | ||
|
|
fd1c423826 | ||
|
|
a314e5b35e | ||
|
|
e02ca8871d | ||
|
|
834d2abe70 | ||
|
|
91986fff02 | ||
|
|
05b9970aa6 | ||
|
|
6f301707f9 | ||
|
|
5865176f36 | ||
|
|
461037f645 | ||
|
|
c980f39aab | ||
|
|
a854fa60a2 | ||
|
|
1c8224f4c6 | ||
|
|
ca403dc45d | ||
|
|
96705c156a | ||
|
|
7909095ff3 | ||
|
|
aa5adb0fce | ||
|
|
dcb414aa06 | ||
|
|
afe093f1ca | ||
|
|
cc066b3576 | ||
|
|
39cd1cdd43 | ||
|
|
176bf00c16 | ||
|
|
68e38c8893 | ||
|
|
48220fb33f | ||
|
|
ed622e254a | ||
|
|
6e7d6a85b4 | ||
|
|
e8061a9c2b | ||
|
|
97a66f7a64 | ||
|
|
b6c3b3ef46 | ||
|
|
f4a57a13bc | ||
|
|
242636b36b | ||
|
|
ba7d7dcec0 | ||
|
|
ba7271b4d8 | ||
|
|
ca2d26a1e2 | ||
|
|
f6106cd26f | ||
|
|
1867336907 | ||
|
|
f5383263bc | ||
|
|
d5cf77cd60 | ||
|
|
f458e2d484 | ||
|
|
af5e05db2e | ||
|
|
32a64d02c7 | ||
|
|
9516d1a306 | ||
|
|
d91b96814e | ||
|
|
56d6bb1913 | ||
|
|
fc156d0014 | ||
|
|
0b8df8d3f2 | ||
|
|
83955e87ac | ||
|
|
4a9db243a1 | ||
|
|
d4f1f8db00 | ||
|
|
95f61bd223 | ||
|
|
7f476a79b3 | ||
|
|
65e21cf13c | ||
|
|
7943af359c | ||
|
|
09e9c6a522 | ||
|
|
a9def2fc15 | ||
|
|
27197faba9 | ||
|
|
ffe60bd960 | ||
|
|
d752be5f74 | ||
|
|
3056c41507 | ||
|
|
d42e9c4d1b | ||
|
|
5912941942 | ||
|
|
9b8106b382 | ||
|
|
9215eb5e45 | ||
|
|
fe7f220c21 | ||
|
|
3bb9704cd5 | ||
|
|
eb77452857 | ||
|
|
6d7cfac8b2 | ||
|
|
31754a39c9 | ||
|
|
bde48e563e | ||
|
|
0cfd760a36 | ||
|
|
13fe88000a | ||
|
|
cc8ad23d6f | ||
|
|
5c2cbb58bc | ||
|
|
6342c4f338 | ||
|
|
5fa70bdbd8 | ||
|
|
2a876e8b86 | ||
|
|
0533ca9941 | ||
|
|
5f20d2e15a | ||
|
|
6d1d5d64d1 | ||
|
|
06d6b513cd | ||
|
|
afa51125de | ||
|
|
26c07b1ffb | ||
|
|
9ecca47e69 | ||
|
|
6c1df93215 | ||
|
|
06fd0658ae | ||
|
|
a17f38a4e2 | ||
|
|
6ef4794778 | ||
|
|
4cd3ce164d | ||
|
|
8e3e57c1c8 | ||
|
|
61fbfda501 | ||
|
|
9017b9a74f |
@@ -24,7 +24,9 @@ notifications:
|
||||
discussions: notifications@superset.apache.org
|
||||
|
||||
github:
|
||||
del_branch_on_merge: true
|
||||
pull_requests:
|
||||
del_branch_on_merge: true
|
||||
allow_update_branch: true
|
||||
description: "Apache Superset is a Data Visualization and Data Exploration Platform"
|
||||
homepage: https://superset.apache.org/
|
||||
labels:
|
||||
|
||||
+33
-4
@@ -18,10 +18,32 @@ e-mail address [security@superset.apache.org](mailto:security@superset.apache.or
|
||||
More details can be found on the ASF website at
|
||||
[ASF vulnerability reporting process](https://apache.org/security/#reporting-a-vulnerability)
|
||||
|
||||
We kindly ask you to include the following information in your report:
|
||||
- Apache Superset version that you are using
|
||||
- A sanitized copy of your `superset_config.py` file or any config overrides
|
||||
- Detailed steps to reproduce the vulnerability
|
||||
**Submission Standards & AI Policy**
|
||||
|
||||
To ensure engineering focus remains on verified risks and to manage high reporting volumes, all reports must meet the following criteria:
|
||||
- Plain Text Format: In accordance with Apache guidelines, please provide all details in plain text within the email body. Avoid sending PDFs, Word documents, or password-protected archives.
|
||||
- Mandatory AI Disclosure: If you utilized Large Language Models (LLMs) or AI tools to identify a flaw or assist in writing a report, you must disclose this in your submission so our triage team can contextualize the findings.
|
||||
- Human-Verified PoC: All submissions must include a manual, step-by-step Proof of Concept (PoC) performed on a supported release. Raw AI outputs, hypothetical chat transcripts, or unverified scanner logs will be closed as Invalid.
|
||||
|
||||
We kindly ask you to include the following information in your report to assist our developers in triaging and remediating issues efficiently:
|
||||
- Version/Commit: The specific version of Apache Superset or the Git commit hash you are using.
|
||||
- Configuration: A sanitized copy of your `superset_config.py` file or any config overrides.
|
||||
- Environment: Your deployment method (e.g., Docker Compose, Helm, or source) and relevant OS/Browser details.
|
||||
- Impacted Component: Identification of the affected area (e.g., Python backend, React frontend, or a specific database connector).
|
||||
- Expected vs. Actual Behavior: A clear description of the intended system behavior versus the observed vulnerability.
|
||||
- Detailed Reproduction Steps: Clear, manual steps to reproduce the vulnerability.
|
||||
|
||||
**Out of Scope Vulnerabilities**
|
||||
|
||||
To prioritize engineering efforts on genuine architectural risks, the following scenarios are explicitly out of scope and will not be issued a CVE:
|
||||
- Attacks requiring Admin privileges: (e.g., CSS injection, template manipulation, dashboard ownership overrides, or modifying global system settings). Per the CVE vulnerability definition in CNA Operational Rules 4.1, a qualifying vulnerability must allow violation of a security policy. The Admin role is a fully trusted operational boundary defined by Apache Superset's security policy; actions within this boundary do not violate that policy and are therefore considered intended capabilities 'by design,' not vulnerabilities.
|
||||
- Brute Force and Rate Limiting: Reports targeting a lack of resource exhaustion protections, generic rate-limiting, or volumetric Denial of Service (DoS) attempts.
|
||||
- Theoretical attack vectors: Issues without a demonstrable, reproducible exploit path.
|
||||
- Non-Exploitable Findings: Missing security headers, generic banner disclosures, or descriptive error messages that do not lead to a direct, documented exploit.
|
||||
|
||||
**Outcome of Reports**
|
||||
|
||||
Reports that are deemed out-of-scope for a CVE but represent valid security best practices or hardening opportunities may be converted into public GitHub issues. This allows the community to contribute to the general hardening of the platform even when a specific vulnerability threshold is not met.
|
||||
|
||||
Note that Apache Superset is not responsible for any third-party dependencies that may
|
||||
have security issues. Any vulnerabilities found in third-party dependencies should be
|
||||
@@ -29,6 +51,13 @@ reported to the maintainers of those projects. Results from security scans of Ap
|
||||
Superset dependencies found on its official Docker image can be remediated at release time
|
||||
by extending the image itself.
|
||||
|
||||
**Vulnerability Aggregation & CVE Attribution**
|
||||
|
||||
In accordance with MITRE CNA Operational Rules (4.1.10, 4.1.11, and 4.2.13), Apache Superset issues CVEs based on the underlying architectural root cause rather than the number of affected endpoints or exploit payloads.
|
||||
- Aggregation: If multiple exploit vectors stem from the same programmatic failure or shared vulnerable code, they must be aggregated into a single, comprehensive report.
|
||||
- Independent Fixes: Separate CVEs will only be assigned if the vulnerabilities reside in decoupled architectural modules and can be fixed independently of one another.
|
||||
Reports that fail to aggregate related findings will be merged during triage to ensure an accurate and defensible CVE record.
|
||||
|
||||
**Your responsible disclosure and collaboration are invaluable.**
|
||||
|
||||
## Extra Information
|
||||
|
||||
@@ -26,16 +26,16 @@ runs:
|
||||
|
||||
- name: Set up QEMU
|
||||
if: ${{ inputs.build == 'true' }}
|
||||
uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0
|
||||
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
if: ${{ inputs.build == 'true' }}
|
||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
||||
|
||||
- name: Try to login to DockerHub
|
||||
if: ${{ inputs.login-to-dockerhub == 'true' }}
|
||||
continue-on-error: true
|
||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
with:
|
||||
username: ${{ inputs.dockerhub-user }}
|
||||
password: ${{ inputs.dockerhub-token }}
|
||||
|
||||
@@ -10,7 +10,7 @@ runs:
|
||||
steps:
|
||||
|
||||
- name: Setup Node Env
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
@@ -21,7 +21,7 @@ runs:
|
||||
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
if: ${{ inputs.from-npm == 'false' }}
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: apache-superset/supersetbot
|
||||
path: supersetbot
|
||||
|
||||
@@ -51,27 +51,31 @@ jobs:
|
||||
- name: supersetbot bump-python -p "${{ github.event.inputs.package }}"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
INPUT_PACKAGE: ${{ github.event.inputs.package }}
|
||||
INPUT_GROUP: ${{ github.event.inputs.group }}
|
||||
INPUT_EXTRA_FLAGS: ${{ github.event.inputs.extra-flags }}
|
||||
INPUT_LIMIT: ${{ github.event.inputs.limit }}
|
||||
run: |
|
||||
git config --global user.email "action@github.com"
|
||||
git config --global user.name "GitHub Action"
|
||||
|
||||
PACKAGE_OPT=""
|
||||
if [ -n "${{ github.event.inputs.package }}" ]; then
|
||||
PACKAGE_OPT="-p ${{ github.event.inputs.package }}"
|
||||
if [ -n "${INPUT_PACKAGE}" ]; then
|
||||
PACKAGE_OPT="-p ${INPUT_PACKAGE}"
|
||||
fi
|
||||
|
||||
GROUP_OPT=""
|
||||
if [ -n "${{ github.event.inputs.group }}" ]; then
|
||||
GROUP_OPT="-g ${{ github.event.inputs.group }}"
|
||||
if [ -n "${INPUT_GROUP}" ]; then
|
||||
GROUP_OPT="-g ${INPUT_GROUP}"
|
||||
fi
|
||||
|
||||
EXTRA_FLAGS="${{ github.event.inputs.extra-flags }}"
|
||||
EXTRA_FLAGS="${INPUT_EXTRA_FLAGS}"
|
||||
|
||||
supersetbot bump-python \
|
||||
--verbose \
|
||||
--use-current-repo \
|
||||
--include-subpackages \
|
||||
--limit ${{ github.event.inputs.limit }} \
|
||||
--limit ${INPUT_LIMIT} \
|
||||
$PACKAGE_OPT \
|
||||
$GROUP_OPT \
|
||||
$EXTRA_FLAGS
|
||||
|
||||
@@ -76,7 +76,7 @@ jobs:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Run Claude PR Action
|
||||
uses: anthropics/claude-code-action@28f83620103c48a57093dcc2837eec89e036bb9f # beta
|
||||
uses: anthropics/claude-code-action@6e2bd52842c65e914eba5c8badd17560bd26b5de # beta
|
||||
with:
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
timeout_minutes: "60"
|
||||
|
||||
@@ -14,6 +14,7 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
|
||||
setup_matrix:
|
||||
runs-on: ubuntu-24.04
|
||||
outputs:
|
||||
@@ -39,6 +40,7 @@ jobs:
|
||||
IMAGE_TAG: apache/superset:GHA-${{ matrix.build_preset }}-${{ github.run_id }}
|
||||
|
||||
steps:
|
||||
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
@@ -89,11 +91,7 @@ jobs:
|
||||
# in the context of push (using multi-platform build), we need to pull the image locally
|
||||
- name: Docker pull
|
||||
if: github.event_name == 'push' && (steps.check.outputs.python || steps.check.outputs.frontend || steps.check.outputs.docker)
|
||||
run: |
|
||||
for i in 1 2 3; do
|
||||
docker pull $IMAGE_TAG && break
|
||||
[ $i -lt 3 ] && sleep 30
|
||||
done
|
||||
run: docker pull $IMAGE_TAG
|
||||
|
||||
- name: Print docker stats
|
||||
if: steps.check.outputs.python || steps.check.outputs.frontend || steps.check.outputs.docker
|
||||
|
||||
@@ -16,10 +16,12 @@ jobs:
|
||||
id: check
|
||||
shell: bash
|
||||
run: |
|
||||
if [ -n "${{ (secrets.NPM_TOKEN != '') || '' }}" ]; then
|
||||
if [ -n "${NPM_TOKEN}" ]; then
|
||||
echo "has-secrets=1" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
env:
|
||||
NPM_TOKEN: ${{ (secrets.NPM_TOKEN != '') || '' }}
|
||||
build:
|
||||
needs: config
|
||||
if: needs.config.outputs.has-secrets
|
||||
|
||||
@@ -20,10 +20,12 @@ jobs:
|
||||
id: check
|
||||
shell: bash
|
||||
run: |
|
||||
if [ -n "${{ (secrets.AWS_ACCESS_KEY_ID != '' && secrets.AWS_SECRET_ACCESS_KEY != '') || '' }}" ]; then
|
||||
if [ -n "${AWS_ACCESS_KEY_ID}" ]; then
|
||||
echo "has-secrets=1" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ (secrets.AWS_ACCESS_KEY_ID != '' && secrets.AWS_SECRET_ACCESS_KEY != '') || '' }}
|
||||
ephemeral-env-cleanup:
|
||||
needs: config
|
||||
if: needs.config.outputs.has-secrets
|
||||
@@ -33,7 +35,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v6
|
||||
uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 # v6
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
@@ -56,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@v2
|
||||
uses: aws-actions/amazon-ecr-login@f2e9fc6c2b355c1890b65e6f6f0e2ac3e6e22f78 # v2
|
||||
|
||||
- name: Delete ECR image tag
|
||||
if: steps.describe-services.outputs.active == 'true'
|
||||
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
id: eval-label
|
||||
run: |
|
||||
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
||||
LABEL_NAME="${{ github.event.inputs.label_name }}"
|
||||
LABEL_NAME="${INPUT_LABEL_NAME}"
|
||||
else
|
||||
LABEL_NAME="${{ github.event.label.name }}"
|
||||
fi
|
||||
@@ -60,6 +60,8 @@ jobs:
|
||||
echo "result=noop" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
env:
|
||||
INPUT_LABEL_NAME: ${{ github.event.inputs.label_name }}
|
||||
- name: Get event SHA
|
||||
id: get-sha
|
||||
if: steps.eval-label.outputs.result == 'up'
|
||||
@@ -197,7 +199,7 @@ jobs:
|
||||
|
||||
- name: Login to Amazon ECR
|
||||
id: login-ecr
|
||||
uses: aws-actions/amazon-ecr-login@c962da2960ed15f492addc26fffa274485265950 # v2
|
||||
uses: aws-actions/amazon-ecr-login@f2e9fc6c2b355c1890b65e6f6f0e2ac3e6e22f78 # v2
|
||||
|
||||
- name: Load, tag and push image to ECR
|
||||
id: push-image
|
||||
@@ -233,7 +235,7 @@ jobs:
|
||||
|
||||
- name: Login to Amazon ECR
|
||||
id: login-ecr
|
||||
uses: aws-actions/amazon-ecr-login@c962da2960ed15f492addc26fffa274485265950 # v2
|
||||
uses: aws-actions/amazon-ecr-login@f2e9fc6c2b355c1890b65e6f6f0e2ac3e6e22f78 # v2
|
||||
|
||||
- name: Check target image exists in ECR
|
||||
id: check-image
|
||||
@@ -276,7 +278,9 @@ jobs:
|
||||
- name: Describe ECS service
|
||||
id: describe-services
|
||||
run: |
|
||||
echo "active=$(aws ecs describe-services --cluster superset-ci --services pr-${{ github.event.inputs.issue_number || github.event.pull_request.number }}-service | jq '.services[] | select(.status == "ACTIVE") | any')" >> $GITHUB_OUTPUT
|
||||
echo "active=$(aws ecs describe-services --cluster superset-ci --services pr-${INPUT_ISSUE_NUMBER}-service | jq '.services[] | select(.status == "ACTIVE") | any')" >> $GITHUB_OUTPUT
|
||||
env:
|
||||
INPUT_ISSUE_NUMBER: ${{ github.event.inputs.issue_number || github.event.pull_request.number }}
|
||||
- name: Create ECS service
|
||||
id: create-service
|
||||
if: steps.describe-services.outputs.active != 'true'
|
||||
@@ -296,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@cbf54ec46642b86ff78c2f5793da6746954cf8ff # v2
|
||||
uses: aws-actions/amazon-ecs-deploy-task-definition@fc8fc60f3a60ffd500fcb13b209c59d221ac8c8c # v2
|
||||
with:
|
||||
task-definition: ${{ steps.task-def.outputs.task-definition }}
|
||||
service: pr-${{ github.event.inputs.issue_number || github.event.pull_request.number }}-service
|
||||
@@ -307,7 +311,9 @@ jobs:
|
||||
- name: List tasks
|
||||
id: list-tasks
|
||||
run: |
|
||||
echo "task=$(aws ecs list-tasks --cluster superset-ci --service-name pr-${{ github.event.inputs.issue_number || github.event.pull_request.number }}-service | jq '.taskArns | first')" >> $GITHUB_OUTPUT
|
||||
echo "task=$(aws ecs list-tasks --cluster superset-ci --service-name pr-${INPUT_ISSUE_NUMBER}-service | jq '.taskArns | first')" >> $GITHUB_OUTPUT
|
||||
env:
|
||||
INPUT_ISSUE_NUMBER: ${{ github.event.inputs.issue_number || github.event.pull_request.number }}
|
||||
- name: Get network interface
|
||||
id: get-eni
|
||||
run: |
|
||||
|
||||
@@ -16,10 +16,12 @@ jobs:
|
||||
id: check
|
||||
shell: bash
|
||||
run: |
|
||||
if [ -n "${{ (secrets.FOSSA_API_KEY != '' ) || '' }}" ]; then
|
||||
if [ -n "${FOSSA_API_KEY}" ]; then
|
||||
echo "has-secrets=1" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
env:
|
||||
FOSSA_API_KEY: ${{ (secrets.FOSSA_API_KEY != '' ) || '' }}
|
||||
license_check:
|
||||
needs: config
|
||||
if: needs.config.outputs.has-secrets
|
||||
|
||||
@@ -28,18 +28,19 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup Python
|
||||
uses: ./.github/actions/setup-backend/
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
|
||||
|
||||
- name: Install helm-docs
|
||||
run: go install github.com/norwoodj/helm-docs/cmd/helm-docs@v1.14.2
|
||||
|
||||
- name: Enable brew and helm-docs
|
||||
# Add brew to the path - see https://github.com/actions/runner-images/issues/6283
|
||||
run: |
|
||||
echo "/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin" >> $GITHUB_PATH
|
||||
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
|
||||
echo "HOMEBREW_PREFIX=$HOMEBREW_PREFIX" >>"${GITHUB_ENV}"
|
||||
echo "HOMEBREW_CELLAR=$HOMEBREW_CELLAR" >>"${GITHUB_ENV}"
|
||||
echo "HOMEBREW_REPOSITORY=$HOMEBREW_REPOSITORY" >>"${GITHUB_ENV}"
|
||||
brew install norwoodj/tap/helm-docs
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
with:
|
||||
@@ -63,11 +64,17 @@ jobs:
|
||||
restore-keys: |
|
||||
pre-commit-v2-${{ runner.os }}-py${{ matrix.python-version }}-
|
||||
|
||||
- name: Get changed files
|
||||
id: changed_files
|
||||
uses: ./.github/actions/file-changes-action
|
||||
with:
|
||||
output: ' '
|
||||
|
||||
- name: pre-commit
|
||||
run: |
|
||||
set +e # Don't exit immediately on failure
|
||||
export SKIP=eslint-frontend,type-checking-frontend
|
||||
pre-commit run --all-files
|
||||
export SKIP=type-checking-frontend
|
||||
pre-commit run --files ${{ steps.changed_files.outputs.files }}
|
||||
PRE_COMMIT_EXIT_CODE=$?
|
||||
git diff --quiet --exit-code
|
||||
GIT_DIFF_EXIT_CODE=$?
|
||||
|
||||
@@ -16,10 +16,12 @@ jobs:
|
||||
id: check
|
||||
shell: bash
|
||||
run: |
|
||||
if [ -n "${{ (secrets.NPM_TOKEN != '' && secrets.GH_PERSONAL_ACCESS_TOKEN != '') || '' }}" ]; then
|
||||
if [ -n "${NPM_TOKEN}" ]; then
|
||||
echo "has-secrets=1" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
env:
|
||||
NPM_TOKEN: ${{ (secrets.NPM_TOKEN != '' && secrets.GH_PERSONAL_ACCESS_TOKEN != '') || '' }}
|
||||
build:
|
||||
needs: config
|
||||
if: needs.config.outputs.has-secrets
|
||||
|
||||
@@ -102,10 +102,12 @@ jobs:
|
||||
- name: Install Superset Showtime
|
||||
if: steps.auth.outputs.authorized == 'true'
|
||||
run: |
|
||||
echo "::notice::Maintainer ${{ github.actor }} triggered deploy for PR ${{ github.event.pull_request.number || github.event.inputs.pr_number }}"
|
||||
echo "::notice::Maintainer ${{ github.actor }} triggered deploy for PR ${PULL_REQUEST_NUMBER}"
|
||||
pip install --upgrade superset-showtime
|
||||
showtime version
|
||||
|
||||
env:
|
||||
PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number || github.event.inputs.pr_number }}
|
||||
- name: Check what actions are needed
|
||||
if: steps.auth.outputs.authorized == 'true'
|
||||
id: check
|
||||
@@ -113,12 +115,14 @@ jobs:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
INPUT_PR_NUMBER: ${{ github.event.inputs.pr_number }}
|
||||
INPUT_SHA: ${{ github.event.inputs.sha }}
|
||||
run: |
|
||||
# Bulletproof PR number extraction
|
||||
if [[ -n "${{ github.event.pull_request.number }}" ]]; then
|
||||
PR_NUM="${{ github.event.pull_request.number }}"
|
||||
elif [[ -n "${{ github.event.inputs.pr_number }}" ]]; then
|
||||
PR_NUM="${{ github.event.inputs.pr_number }}"
|
||||
elif [[ -n "${INPUT_PR_NUMBER}" ]]; then
|
||||
PR_NUM="${INPUT_PR_NUMBER}"
|
||||
else
|
||||
echo "❌ No PR number found in event or inputs"
|
||||
exit 1
|
||||
@@ -127,8 +131,8 @@ jobs:
|
||||
echo "Using PR number: $PR_NUM"
|
||||
|
||||
# Run sync check-only with optional SHA override
|
||||
if [[ -n "${{ github.event.inputs.sha }}" ]]; then
|
||||
OUTPUT=$(python -m showtime sync $PR_NUM --check-only --sha "${{ github.event.inputs.sha }}")
|
||||
if [[ -n "${INPUT_SHA}" ]]; then
|
||||
OUTPUT=$(python -m showtime sync $PR_NUM --check-only --sha "${INPUT_SHA}")
|
||||
else
|
||||
OUTPUT=$(python -m showtime sync $PR_NUM --check-only)
|
||||
fi
|
||||
|
||||
@@ -27,10 +27,12 @@ jobs:
|
||||
id: check
|
||||
shell: bash
|
||||
run: |
|
||||
if [ -n "${{ (secrets.SUPERSET_SITE_BUILD != '' && secrets.SUPERSET_SITE_BUILD != '') || '' }}" ]; then
|
||||
if [ -n "${SUPERSET_SITE_BUILD}" ]; then
|
||||
echo "has-secrets=1" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
env:
|
||||
SUPERSET_SITE_BUILD: ${{ (secrets.SUPERSET_SITE_BUILD != '' && secrets.SUPERSET_SITE_BUILD != '') || '' }}
|
||||
build-deploy:
|
||||
needs: config
|
||||
if: needs.config.outputs.has-secrets
|
||||
@@ -46,13 +48,13 @@ jobs:
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
with:
|
||||
node-version-file: "./docs/.nvmrc"
|
||||
node-version-file: './docs/.nvmrc'
|
||||
- name: Setup Python
|
||||
uses: ./.github/actions/setup-backend/
|
||||
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
|
||||
with:
|
||||
distribution: "zulu"
|
||||
java-version: "21"
|
||||
distribution: 'zulu'
|
||||
java-version: '21'
|
||||
- name: Install Graphviz
|
||||
run: sudo apt-get install -y graphviz
|
||||
- name: Compute Entity Relationship diagram (ERD)
|
||||
@@ -68,7 +70,7 @@ jobs:
|
||||
yarn install --check-cache
|
||||
- name: Download database diagnostics (if triggered by integration tests)
|
||||
if: github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success'
|
||||
uses: dawidd6/action-download-artifact@8a338493df3d275e4a7a63bcff3b8fe97e51a927 # v19
|
||||
uses: dawidd6/action-download-artifact@8305c0f1062bb0d184d09ef4493ecb9288447732 # v20
|
||||
continue-on-error: true
|
||||
with:
|
||||
workflow: superset-python-integrationtest.yml
|
||||
@@ -77,7 +79,7 @@ jobs:
|
||||
path: docs/src/data/
|
||||
- name: Try to download latest diagnostics (for push/dispatch triggers)
|
||||
if: github.event_name != 'workflow_run'
|
||||
uses: dawidd6/action-download-artifact@8a338493df3d275e4a7a63bcff3b8fe97e51a927 # v19
|
||||
uses: dawidd6/action-download-artifact@8305c0f1062bb0d184d09ef4493ecb9288447732 # v20
|
||||
continue-on-error: true
|
||||
with:
|
||||
workflow: superset-python-integrationtest.yml
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
# Do not bump this linkinator-action version without opening
|
||||
# an ASF Infra ticket to allow the new version first!
|
||||
- uses: JustinBeckwith/linkinator-action@af984b9f30f63e796ae2ea5be5e07cb587f1bbd9 # v2.3
|
||||
- uses: JustinBeckwith/linkinator-action@af984b9f30f63e796ae2ea5be5e07cb587f1bbd9 # v2.3
|
||||
continue-on-error: true # This will make the job advisory (non-blocking, no red X)
|
||||
with:
|
||||
paths: "**/*.md, **/*.mdx"
|
||||
@@ -74,7 +74,7 @@ jobs:
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
with:
|
||||
node-version-file: "./docs/.nvmrc"
|
||||
node-version-file: './docs/.nvmrc'
|
||||
- name: yarn install
|
||||
run: |
|
||||
yarn install --check-cache
|
||||
@@ -106,18 +106,18 @@ jobs:
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
with:
|
||||
node-version-file: "./docs/.nvmrc"
|
||||
node-version-file: './docs/.nvmrc'
|
||||
- name: yarn install
|
||||
run: |
|
||||
yarn install --check-cache
|
||||
- name: Download database diagnostics from integration tests
|
||||
uses: dawidd6/action-download-artifact@8a338493df3d275e4a7a63bcff3b8fe97e51a927 # v19
|
||||
uses: dawidd6/action-download-artifact@8305c0f1062bb0d184d09ef4493ecb9288447732 # v20
|
||||
with:
|
||||
workflow: superset-python-integrationtest.yml
|
||||
run_id: ${{ github.event.workflow_run.id }}
|
||||
name: database-diagnostics
|
||||
path: docs/src/data/
|
||||
if_no_artifact_found: "warning"
|
||||
if_no_artifact_found: 'warning'
|
||||
- name: Use fresh diagnostics
|
||||
run: |
|
||||
if [ -f "src/data/databases-diagnostics.json" ]; then
|
||||
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
|
||||
- name: Upload coverage reports to Codecov
|
||||
if: steps.check.outputs.superset-extensions-cli
|
||||
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5
|
||||
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v5
|
||||
with:
|
||||
file: ./coverage.xml
|
||||
flags: superset-extensions-cli
|
||||
|
||||
@@ -54,14 +54,14 @@ jobs:
|
||||
- name: Save Docker Image as Artifact
|
||||
if: steps.check.outputs.frontend
|
||||
run: |
|
||||
docker save $TAG | gzip > docker-image.tar.gz
|
||||
docker save $TAG | zstd -3 --threads=0 > docker-image.tar.zst
|
||||
|
||||
- name: Upload Docker Image Artifact
|
||||
if: steps.check.outputs.frontend
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
with:
|
||||
name: docker-image
|
||||
path: docker-image.tar.gz
|
||||
path: docker-image.tar.zst
|
||||
|
||||
sharded-jest-tests:
|
||||
needs: frontend-build
|
||||
@@ -78,7 +78,8 @@ jobs:
|
||||
name: docker-image
|
||||
|
||||
- name: Load Docker Image
|
||||
run: docker load < docker-image.tar.gz
|
||||
run: |
|
||||
zstd -d < docker-image.tar.zst | docker load
|
||||
|
||||
- name: npm run test with coverage
|
||||
run: |
|
||||
@@ -127,7 +128,7 @@ jobs:
|
||||
run: npx nyc merge coverage/ merged-output/coverage-summary.json
|
||||
|
||||
- name: Upload Code Coverage
|
||||
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5
|
||||
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v5
|
||||
with:
|
||||
flags: javascript
|
||||
use_oidc: true
|
||||
@@ -148,7 +149,7 @@ jobs:
|
||||
|
||||
- name: Load Docker Image
|
||||
run: |
|
||||
docker load < docker-image.tar.gz
|
||||
zstd -d < docker-image.tar.zst | docker load
|
||||
|
||||
- name: lint
|
||||
run: |
|
||||
@@ -171,7 +172,8 @@ jobs:
|
||||
name: docker-image
|
||||
|
||||
- name: Load Docker Image
|
||||
run: docker load < docker-image.tar.gz
|
||||
run: |
|
||||
zstd -d < docker-image.tar.zst | docker load
|
||||
|
||||
- name: Build Plugins Packages
|
||||
run: |
|
||||
@@ -189,7 +191,8 @@ jobs:
|
||||
name: docker-image
|
||||
|
||||
- name: Load Docker Image
|
||||
run: docker load < docker-image.tar.gz
|
||||
run: |
|
||||
zstd -d < docker-image.tar.zst | docker load
|
||||
|
||||
- name: Build Storybook and Run Tests
|
||||
run: |
|
||||
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Helm
|
||||
uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4
|
||||
uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5.0.0
|
||||
with:
|
||||
version: v3.16.4
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
|
||||
|
||||
- name: Install Helm
|
||||
uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4
|
||||
uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5.0.0
|
||||
with:
|
||||
version: v3.5.4
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@ concurrency:
|
||||
jobs:
|
||||
test-mysql:
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
id-token: write
|
||||
env:
|
||||
PYTHONPATH: ${{ github.workspace }}
|
||||
SUPERSET_CONFIG: tests.integration_tests.superset_test_config
|
||||
@@ -68,11 +70,12 @@ jobs:
|
||||
run: |
|
||||
./scripts/python_tests.sh
|
||||
- name: Upload code coverage
|
||||
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5
|
||||
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v5
|
||||
with:
|
||||
flags: python,mysql
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
verbose: true
|
||||
use_oidc: true
|
||||
slug: apache/superset
|
||||
- name: Generate database diagnostics for docs
|
||||
if: steps.check.outputs.python
|
||||
env:
|
||||
@@ -105,6 +108,8 @@ jobs:
|
||||
retention-days: 7
|
||||
test-postgres:
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
id-token: write
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["current", "previous", "next"]
|
||||
@@ -159,14 +164,17 @@ jobs:
|
||||
run: |
|
||||
./scripts/python_tests.sh
|
||||
- name: Upload code coverage
|
||||
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5
|
||||
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v5
|
||||
with:
|
||||
flags: python,postgres
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
verbose: true
|
||||
use_oidc: true
|
||||
slug: apache/superset
|
||||
|
||||
test-sqlite:
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
id-token: write
|
||||
env:
|
||||
PYTHONPATH: ${{ github.workspace }}
|
||||
SUPERSET_CONFIG: tests.integration_tests.superset_test_config
|
||||
@@ -211,8 +219,9 @@ jobs:
|
||||
run: |
|
||||
./scripts/python_tests.sh
|
||||
- name: Upload code coverage
|
||||
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5
|
||||
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v5
|
||||
with:
|
||||
flags: python,sqlite
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
verbose: true
|
||||
use_oidc: true
|
||||
slug: apache/superset
|
||||
|
||||
@@ -17,6 +17,8 @@ concurrency:
|
||||
jobs:
|
||||
test-postgres-presto:
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
id-token: write
|
||||
env:
|
||||
PYTHONPATH: ${{ github.workspace }}
|
||||
SUPERSET_CONFIG: tests.integration_tests.superset_test_config
|
||||
@@ -77,14 +79,17 @@ jobs:
|
||||
run: |
|
||||
./scripts/python_tests.sh -m 'chart_data_flow or sql_json_flow'
|
||||
- name: Upload code coverage
|
||||
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5
|
||||
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v5
|
||||
with:
|
||||
flags: python,presto
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
verbose: true
|
||||
use_oidc: true
|
||||
slug: apache/superset
|
||||
|
||||
test-postgres-hive:
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
id-token: write
|
||||
env:
|
||||
PYTHONPATH: ${{ github.workspace }}
|
||||
SUPERSET_CONFIG: tests.integration_tests.superset_test_config
|
||||
@@ -145,8 +150,9 @@ jobs:
|
||||
pip install -e .[hive]
|
||||
./scripts/python_tests.sh -m 'chart_data_flow or sql_json_flow'
|
||||
- name: Upload code coverage
|
||||
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5
|
||||
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v5
|
||||
with:
|
||||
flags: python,hive
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
verbose: true
|
||||
use_oidc: true
|
||||
slug: apache/superset
|
||||
|
||||
@@ -17,6 +17,8 @@ concurrency:
|
||||
jobs:
|
||||
unit-tests:
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
id-token: write
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["previous", "current", "next"]
|
||||
@@ -53,8 +55,9 @@ jobs:
|
||||
run: |
|
||||
pytest --durations-min=0.5 --cov=superset/sql/ ./tests/unit_tests/sql/ --cache-clear --cov-fail-under=100
|
||||
- name: Upload code coverage
|
||||
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5
|
||||
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v5
|
||||
with:
|
||||
flags: python,unit
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
verbose: true
|
||||
use_oidc: true
|
||||
slug: apache/superset
|
||||
|
||||
@@ -31,10 +31,12 @@ jobs:
|
||||
id: check
|
||||
shell: bash
|
||||
run: |
|
||||
if [ -n "${{ (secrets.DOCKERHUB_USER != '' && secrets.DOCKERHUB_TOKEN != '') || '' }}" ]; then
|
||||
if [ -n "${DOCKERHUB_USER}" ]; then
|
||||
echo "has-secrets=1" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
env:
|
||||
DOCKERHUB_USER: ${{ (secrets.DOCKERHUB_USER != '' && secrets.DOCKERHUB_TOKEN != '') || '' }}
|
||||
docker-release:
|
||||
needs: config
|
||||
if: needs.config.outputs.has-secrets
|
||||
@@ -72,17 +74,20 @@ jobs:
|
||||
DOCKERHUB_USER: ${{ secrets.DOCKERHUB_USER }}
|
||||
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
INPUT_RELEASE: ${{ github.event.inputs.release }}
|
||||
INPUT_FORCE_LATEST: ${{ github.event.inputs.force-latest }}
|
||||
INPUT_GIT_REF: ${{ github.event.inputs.git-ref }}
|
||||
run: |
|
||||
RELEASE="${{ github.event.release.tag_name }}"
|
||||
FORCE_LATEST=""
|
||||
EVENT="${{github.event_name}}"
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
# in the case of a manually-triggered run, read release from input
|
||||
RELEASE="${{ github.event.inputs.release }}"
|
||||
if [ "${{ github.event.inputs.force-latest }}" = "true" ]; then
|
||||
RELEASE="${INPUT_RELEASE}"
|
||||
if [ "${INPUT_FORCE_LATEST}" = "true" ]; then
|
||||
FORCE_LATEST="--force-latest"
|
||||
fi
|
||||
git checkout "${{ github.event.inputs.git-ref }}"
|
||||
git checkout "${INPUT_GIT_REF}"
|
||||
EVENT="release"
|
||||
fi
|
||||
|
||||
@@ -122,6 +127,7 @@ jobs:
|
||||
- name: Label the PRs with the right release-related labels
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
INPUT_RELEASE: ${{ github.event.inputs.release }}
|
||||
run: |
|
||||
export GITHUB_ACTOR=""
|
||||
git fetch --all --tags
|
||||
@@ -129,6 +135,6 @@ jobs:
|
||||
RELEASE="${{ github.event.release.tag_name }}"
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
# in the case of a manually-triggered run, read release from input
|
||||
RELEASE="${{ github.event.inputs.release }}"
|
||||
RELEASE="${INPUT_RELEASE}"
|
||||
fi
|
||||
supersetbot release-label $RELEASE
|
||||
|
||||
@@ -19,10 +19,12 @@ jobs:
|
||||
id: check
|
||||
shell: bash
|
||||
run: |
|
||||
if [ -n "${{ (secrets.GSHEET_KEY != '' ) || '' }}" ]; then
|
||||
if [ -n "${GSHEET_KEY}" ]; then
|
||||
echo "has-secrets=1" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
env:
|
||||
GSHEET_KEY: ${{ (secrets.GSHEET_KEY != '' ) || '' }}
|
||||
process-and-upload:
|
||||
needs: config
|
||||
if: needs.config.outputs.has-secrets
|
||||
|
||||
@@ -53,7 +53,7 @@ extension-pkg-whitelist=pyarrow
|
||||
|
||||
[MESSAGES CONTROL]
|
||||
disable=all
|
||||
enable=disallowed-sql-import,consider-using-transaction
|
||||
enable=json-import,disallowed-sql-import,consider-using-transaction
|
||||
|
||||
|
||||
[REPORTS]
|
||||
|
||||
@@ -50,4 +50,3 @@ under the License.
|
||||
- [4.1.4](./CHANGELOG/4.1.4.md)
|
||||
- [5.0.0](./CHANGELOG/5.0.0.md)
|
||||
- [6.0.0](./CHANGELOG/6.0.0.md)
|
||||
- [6.1.0](./CHANGELOG/6.1.0.md)
|
||||
|
||||
-1564
File diff suppressed because it is too large
Load Diff
+70
-96
@@ -24,42 +24,54 @@ assists people when migrating to a new version.
|
||||
|
||||
## Next
|
||||
|
||||
### Row-level security now filters table reads a same-named CTE used to hide
|
||||
### Deck.gl MapBox viewport and opacity controls are functional
|
||||
|
||||
`extract_tables_from_statement()` decided whether a reference was a CTE by matching its
|
||||
bare name against the enclosing scope's CTE names; it now resolves the name through
|
||||
`Scope.cte_sources`. Three kinds of real table read whose bare name collided with a CTE's
|
||||
were mistaken for the CTE and dropped from a statement's tables, so they were neither
|
||||
RLS-filtered nor access-checked: a schema- or catalog-qualified reference, a non-recursive
|
||||
CTE's own name inside its body, and a forward reference to a later `WITH` item.
|
||||
The Deck.gl MapBox chart's **Opacity**, **Default longitude**, **Default latitude**, and **Zoom** controls were previously non-functional — changing them had no effect on the rendered map. These controls are now wired up correctly.
|
||||
|
||||
```sql
|
||||
WITH orders AS (SELECT 1 AS d) SELECT * FROM (SELECT * FROM public.orders) AS z
|
||||
WITH orders AS (SELECT * FROM orders) SELECT * FROM orders
|
||||
WITH q1 AS (SELECT key FROM q2), q2 AS (SELECT 1 AS key) SELECT * FROM q1
|
||||
```
|
||||
**Behavior change for existing charts:** Previously, the viewport controls had hard-coded default values (`-122.405293`, `37.772123`, zoom `11` — San Francisco) that were stored in each chart's `form_data` but never applied. The map always used `fitBounds` to center on the data. With this fix, those stored values are now respected, which means existing MapBox charts may open centered on the old default coordinates instead of fitting to data bounds.
|
||||
|
||||
Each read is now reported, so it is filtered when `RLS_IN_SQLLAB` is enabled, matched
|
||||
against `DISALLOWED_SQL_TABLES`, and requires dataset access under
|
||||
`raise_for_access(force_dataset_match=True)`. A query that previously ran, reading those
|
||||
rows unfiltered, may now be filtered or rejected. There is no opt-out — the previous
|
||||
behavior was a row-level-security bypass.
|
||||
|
||||
### Table aliases keep their quoting through the row-level security rewrite
|
||||
|
||||
Both RLS transformers took the table alias as a string with its quoting stripped and
|
||||
emitted it verbatim; they now carry the parsed identifier. Emitted SQL is unchanged for an
|
||||
unquoted identifier; a quoted one keeps its quoting, and a column-alias list
|
||||
(`FROM t AS x (c1, c2)`) survives the rewrite instead of being dropped. This repairs
|
||||
row-level security for any aliased table on Snowflake, and for at least one statement shape
|
||||
on MSSQL where the rewrite previously raised `AttributeError`.
|
||||
|
||||
## 6.1.0
|
||||
**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.
|
||||
|
||||
### 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.
|
||||
|
||||
### MCP Tool Observability
|
||||
|
||||
MCP (Model Context Protocol) tools now include enhanced observability instrumentation for monitoring and debugging:
|
||||
|
||||
**Two-layer instrumentation:**
|
||||
1. **Middleware layer** (`LoggingMiddleware`): Automatically logs all MCP tool calls with `duration_ms` and `success` status in the audit log (Action Log UI, logs table)
|
||||
2. **Sub-operation tracking**: All 19 MCP tools include granular `event_logger.log_context()` blocks for tracking individual operations like validation, database writes, and query execution
|
||||
|
||||
**Action naming convention:**
|
||||
- Tool-level logs: `mcp_tool_call` (via middleware)
|
||||
- Sub-operation logs: `mcp.{tool_name}.{operation}` (e.g., `mcp.generate_chart.validation`, `mcp.execute_sql.query_execution`)
|
||||
|
||||
**Querying MCP logs:**
|
||||
```sql
|
||||
-- Top slowest MCP operations
|
||||
SELECT action, COUNT(*) as calls, AVG(duration_ms) as avg_ms
|
||||
FROM logs
|
||||
WHERE action LIKE 'mcp.%'
|
||||
GROUP BY action
|
||||
ORDER BY avg_ms DESC
|
||||
LIMIT 20;
|
||||
|
||||
-- MCP tool success rate
|
||||
SELECT
|
||||
json_extract(curated_payload, '$.tool') as tool,
|
||||
COUNT(*) as total_calls,
|
||||
SUM(CASE WHEN json_extract(curated_payload, '$.success') = 'true' THEN 1 ELSE 0 END) as successful,
|
||||
ROUND(100.0 * SUM(CASE WHEN json_extract(curated_payload, '$.success') = 'true' THEN 1 ELSE 0 END) / COUNT(*), 2) as success_rate
|
||||
FROM logs
|
||||
WHERE action = 'mcp_tool_call'
|
||||
GROUP BY tool
|
||||
ORDER BY total_calls DESC;
|
||||
```
|
||||
|
||||
**Security note:** Sensitive parameters (passwords, API keys, tokens) are automatically redacted in logs as `[REDACTED]`.
|
||||
|
||||
### Distributed Coordination Backend
|
||||
|
||||
A new `DISTRIBUTED_COORDINATION_CONFIG` configuration provides a unified Redis-based backend for real-time coordination features in Superset. This backend enables:
|
||||
@@ -71,7 +83,6 @@ A new `DISTRIBUTED_COORDINATION_CONFIG` configuration provides a unified Redis-b
|
||||
The distributed coordination is used by the Global Task Framework (GTF) for abort notifications and task completion signaling, and will eventually replace `GLOBAL_ASYNC_QUERIES_CACHE_BACKEND` as the standard signaling backend. Configuring this is recommended for Redis enabled production deployments.
|
||||
|
||||
Example configuration in `superset_config.py`:
|
||||
|
||||
```python
|
||||
DISTRIBUTED_COORDINATION_CONFIG = {
|
||||
"CACHE_TYPE": "RedisCache",
|
||||
@@ -86,11 +97,9 @@ See `superset/config.py` for complete configuration options.
|
||||
### WebSocket config for GAQ with Docker
|
||||
|
||||
[35896](https://github.com/apache/superset/pull/35896) and [37624](https://github.com/apache/superset/pull/37624) updated documentation on how to run and configure Superset with Docker. Specifically for the WebSocket configuration, a new `docker/superset-websocket/config.example.json` was added to the repo, so that users could copy it to create a `docker/superset-websocket/config.json` file. The existing `docker/superset-websocket/config.json` was removed and git-ignored, so if you're using GAQ / WebSocket make sure to:
|
||||
|
||||
- Stash/backup your existing `config.json` file, to re-apply it after (will get git-ignored going forward)
|
||||
- Update the `volumes` configuration for the `superset-websocket` service in your `docker-compose.override.yml` file, to include the `docker/superset-websocket/config.json` file. For example:
|
||||
|
||||
```yaml
|
||||
``` yaml
|
||||
services:
|
||||
superset-websocket:
|
||||
volumes:
|
||||
@@ -103,9 +112,7 @@ services:
|
||||
### Example Data Loading Improvements
|
||||
|
||||
#### New Directory Structure
|
||||
|
||||
Examples are now organized by name with data and configs co-located:
|
||||
|
||||
```
|
||||
superset/examples/
|
||||
├── _shared/ # Shared database & metadata configs
|
||||
@@ -117,12 +124,31 @@ superset/examples/
|
||||
└── ...
|
||||
```
|
||||
|
||||
#### Simplified Parquet-based Loading
|
||||
- Auto-discovery: create `superset/examples/my_dataset/data.parquet` to add a new example
|
||||
- Parquet is an Apache project format: compressed (~27% smaller), self-describing schema
|
||||
- YAML configs define datasets, charts, and dashboards declaratively
|
||||
- Removed Python-based data generation from individual example files
|
||||
|
||||
#### Test Data Reorganization
|
||||
- Moved `big_data.py` to `superset/cli/test_loaders.py` - better reflects its purpose as a test utility
|
||||
- Fixed inverted logic for `--load-test-data` flag (now correctly includes .test.yaml files when flag is set)
|
||||
- Clarified CLI flags:
|
||||
- `--force` / `-f`: Force reload even if tables exist
|
||||
- `--only-metadata` / `-m`: Create table metadata without loading data
|
||||
- `--load-test-data` / `-t`: Include test dashboards and .test.yaml configs
|
||||
- `--load-big-data` / `-b`: Generate synthetic stress-test data
|
||||
|
||||
#### Bug Fixes
|
||||
- Fixed numpy array serialization for PostgreSQL (converts complex types to JSON strings)
|
||||
- Fixed KeyError for `allow_csv_upload` field in database configs (now optional with default)
|
||||
- Fixed test data loading logic that was incorrectly filtering files
|
||||
|
||||
### MCP Service
|
||||
|
||||
The MCP (Model Context Protocol) service enables AI assistants and automation tools to interact programmatically with Superset.
|
||||
|
||||
#### New Features
|
||||
|
||||
- MCP service infrastructure with FastMCP framework
|
||||
- Tools for dashboards, charts, datasets, SQL Lab, and instance metadata
|
||||
- Optional dependency: install with `pip install apache-superset[fastmcp]`
|
||||
@@ -132,7 +158,6 @@ The MCP (Model Context Protocol) service enables AI assistants and automation to
|
||||
#### New Configuration Options
|
||||
|
||||
**Development** (single-user, local testing):
|
||||
|
||||
```python
|
||||
# superset_config.py
|
||||
MCP_DEV_USERNAME = "admin" # User for MCP authentication
|
||||
@@ -141,7 +166,6 @@ MCP_SERVICE_PORT = 5008
|
||||
```
|
||||
|
||||
**Production** (JWT-based, multi-user):
|
||||
|
||||
```python
|
||||
# superset_config.py
|
||||
MCP_AUTH_ENABLED = True
|
||||
@@ -187,14 +211,12 @@ superset mcp run --port 5008 --use-factory-config
|
||||
The MCP service runs as a **separate process** from the Superset web server.
|
||||
|
||||
**Important**:
|
||||
|
||||
- Requires same Python environment and configuration as Superset
|
||||
- Shares database connections with main Superset app
|
||||
- Can be scaled independently from web server
|
||||
- Requires `fastmcp` package (optional dependency)
|
||||
|
||||
**Installation**:
|
||||
|
||||
```bash
|
||||
# Install with MCP support
|
||||
pip install apache-superset[fastmcp]
|
||||
@@ -208,7 +230,6 @@ Use systemd, supervisord, or Kubernetes to manage the MCP service process.
|
||||
See `superset/mcp_service/PRODUCTION.md` for deployment guides.
|
||||
|
||||
**Security**:
|
||||
|
||||
- Development: Uses `MCP_DEV_USERNAME` for single-user access
|
||||
- Production: **MUST** configure JWT authentication
|
||||
- See `superset/mcp_service/SECURITY.md` for details
|
||||
@@ -221,50 +242,14 @@ See `superset/mcp_service/PRODUCTION.md` for deployment guides.
|
||||
- Developer Guide: `superset/mcp_service/CLAUDE.md`
|
||||
- Quick Start: `superset/mcp_service/README.md`
|
||||
|
||||
### MCP Tool Observability
|
||||
---
|
||||
|
||||
MCP (Model Context Protocol) tools now include enhanced observability instrumentation for monitoring and debugging:
|
||||
|
||||
**Two-layer instrumentation:**
|
||||
|
||||
1. **Middleware layer** (`LoggingMiddleware`): Automatically logs all MCP tool calls with `duration_ms` and `success` status in the audit log (Action Log UI, logs table)
|
||||
2. **Sub-operation tracking**: All 19 MCP tools include granular `event_logger.log_context()` blocks for tracking individual operations like validation, database writes, and query execution
|
||||
|
||||
**Action naming convention:**
|
||||
|
||||
- Tool-level logs: `mcp_tool_call` (via middleware)
|
||||
- Sub-operation logs: `mcp.{tool_name}.{operation}` (e.g., `mcp.generate_chart.validation`, `mcp.execute_sql.query_execution`)
|
||||
|
||||
**Querying MCP logs:**
|
||||
|
||||
```sql
|
||||
-- Top slowest MCP operations
|
||||
SELECT action, COUNT(*) as calls, AVG(duration_ms) as avg_ms
|
||||
FROM logs
|
||||
WHERE action LIKE 'mcp.%'
|
||||
GROUP BY action
|
||||
ORDER BY avg_ms DESC
|
||||
LIMIT 20;
|
||||
|
||||
-- MCP tool success rate
|
||||
SELECT
|
||||
json_extract(curated_payload, '$.tool') as tool,
|
||||
COUNT(*) as total_calls,
|
||||
SUM(CASE WHEN json_extract(curated_payload, '$.success') = 'true' THEN 1 ELSE 0 END) as successful,
|
||||
ROUND(100.0 * SUM(CASE WHEN json_extract(curated_payload, '$.success') = 'true' THEN 1 ELSE 0 END) / COUNT(*), 2) as success_rate
|
||||
FROM logs
|
||||
WHERE action = 'mcp_tool_call'
|
||||
GROUP BY tool
|
||||
ORDER BY total_calls DESC;
|
||||
```
|
||||
|
||||
**Security note:** Sensitive parameters (passwords, API keys, tokens) are automatically redacted in logs as `[REDACTED]`.
|
||||
|
||||
### APP_NAME configuration
|
||||
- [35621](https://github.com/apache/superset/pull/35621): The default hash algorithm has changed from MD5 to SHA-256 for improved security and FedRAMP compliance. This affects cache keys for thumbnails, dashboard digests, chart digests, and filter option names. Existing cached data will be invalidated upon upgrade. To opt out of this change and maintain backward compatibility, set `HASH_ALGORITHM = "md5"` in your `superset_config.py`.
|
||||
- [35062](https://github.com/apache/superset/pull/35062): Changed the function signature of `setupExtensions` to `setupCodeOverrides` with options as arguments.
|
||||
|
||||
### Breaking Changes
|
||||
- [37370](https://github.com/apache/superset/pull/37370): The `APP_NAME` configuration variable no longer controls the browser window/tab title or other frontend branding. Application names should now be configured using the theme system with the `brandAppName` token. The `APP_NAME` config is still used for backend contexts (MCP service, logs, etc.) and serves as a fallback if `brandAppName` is not set.
|
||||
- **Migration:**
|
||||
|
||||
```python
|
||||
# Before (Superset 5.x)
|
||||
APP_NAME = "My Custom App"
|
||||
@@ -283,22 +268,16 @@ ORDER BY total_calls DESC;
|
||||
APP_NAME = "My Custom App"
|
||||
# But you should migrate to THEME_DEFAULT.token.brandAppName
|
||||
```
|
||||
|
||||
- **Note:** For dark mode, set the same tokens in `THEME_DARK` configuration.
|
||||
|
||||
### CUSTOM_FONT_URLS configuration
|
||||
|
||||
- [36317](https://github.com/apache/superset/pull/36317): The `CUSTOM_FONT_URLS` configuration option has been removed. Use the new per-theme `fontUrls` token in `THEME_DEFAULT` or database-managed themes instead.
|
||||
- **Before:**
|
||||
|
||||
```python
|
||||
CUSTOM_FONT_URLS = [
|
||||
"https://fonts.example.com/myfont.css",
|
||||
]
|
||||
```
|
||||
|
||||
- **After:**
|
||||
|
||||
```python
|
||||
THEME_DEFAULT = {
|
||||
"token": {
|
||||
@@ -310,13 +289,7 @@ ORDER BY total_calls DESC;
|
||||
}
|
||||
```
|
||||
|
||||
### Other
|
||||
|
||||
- [35621](https://github.com/apache/superset/pull/35621): The default hash algorithm has changed from MD5 to SHA-256 for improved security and FedRAMP compliance. This affects cache keys for thumbnails, dashboard digests, chart digests, and filter option names. Existing cached data will be invalidated upon upgrade. To opt out of this change and maintain backward compatibility, set `HASH_ALGORITHM = "md5"` in your `superset_config.py`.
|
||||
- [35062](https://github.com/apache/superset/pull/35062): Changed the function signature of `setupExtensions` to `setupCodeOverrides` with options as arguments.
|
||||
|
||||
## 6.0.0
|
||||
|
||||
- [33055](https://github.com/apache/superset/pull/33055): Upgrades Flask-AppBuilder to 5.0.0. The AUTH_OID authentication type has been deprecated and is no longer available as an option in Flask-AppBuilder. OpenID (OID) is considered a deprecated authentication protocol - if you are using AUTH_OID, you will need to migrate to an alternative authentication method such as OAuth, LDAP, or database authentication before upgrading.
|
||||
- [34871](https://github.com/apache/superset/pull/34871): Fixed Jest test hanging issue from Ant Design v5 upgrade. MessageChannel is now mocked in test environment to prevent rc-overflow from causing Jest to hang. Test environment only - no production impact.
|
||||
- [34782](https://github.com/apache/superset/pull/34782): Dataset exports now include the dataset ID in their file name (similar to charts and dashboards). If managing assets as code, make sure to rename existing dataset YAMLs to include the ID (and avoid duplicated files).
|
||||
@@ -325,8 +298,8 @@ ORDER BY total_calls DESC;
|
||||
- Change any hex color values to one of: `"success"`, `"processing"`, `"error"`, `"warning"`, `"default"`
|
||||
- Custom colors are no longer supported to maintain consistency with Ant Design components
|
||||
- [34561](https://github.com/apache/superset/pull/34561) Added tiled screenshot functionality for Playwright-based reports to handle large dashboards more efficiently. When enabled (default: `SCREENSHOT_TILED_ENABLED = True`), dashboards with 20+ charts or height exceeding 5000px will be captured using multiple viewport-sized tiles and combined into a single image. This improves report generation performance and reliability for large dashboards.
|
||||
Note: Pillow is now a required dependency (previously optional) to support image processing for tiled screenshots.
|
||||
`thumbnails` optional dependency is now deprecated and will be removed in the next major release (7.0).
|
||||
Note: Pillow is now a required dependency (previously optional) to support image processing for tiled screenshots.
|
||||
`thumbnails` optional dependency is now deprecated and will be removed in the next major release (7.0).
|
||||
- [33084](https://github.com/apache/superset/pull/33084) The DISALLOWED_SQL_FUNCTIONS configuration now includes additional potentially sensitive database functions across PostgreSQL, MySQL, SQLite, MS SQL Server, and ClickHouse. Existing queries using these functions may now be blocked. Review your SQL Lab queries and dashboards if you encounter "disallowed function" errors after upgrading
|
||||
- [34235](https://github.com/apache/superset/pull/34235) CSV exports now use `utf-8-sig` encoding by default to include a UTF-8 BOM, improving compatibility with Excel.
|
||||
- [34258](https://github.com/apache/superset/pull/34258) changing the default in Dockerfile to INCLUDE_CHROMIUM="false" (from "true") in the past. This ensures the `lean` layer is lean by default, and people can opt-in to the `chromium` layer by setting the build arg `INCLUDE_CHROMIUM=true`. This is a breaking change for anyone using the `lean` layer, as it will no longer include Chromium by default.
|
||||
@@ -335,13 +308,13 @@ ORDER BY total_calls DESC;
|
||||
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.
|
||||
- [32432](https://github.com/apache/superset/pull/31260) 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.
|
||||
- [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.
|
||||
|
||||
## 5.0.0
|
||||
|
||||
- [31976](https://github.com/apache/superset/pull/31976) Removed the `DISABLE_LEGACY_DATASOURCE_EDITOR` feature flag. The previous value of the feature flag was `True` and now the feature is permanently removed.
|
||||
- [31959](https://github.com/apache/superset/pull/32000) Removes CSV_UPLOAD_MAX_SIZE config, use your web server to control file upload size.
|
||||
- [32000](https://github.com/apache/superset/pull/32000) Removes CSV_UPLOAD_MAX_SIZE config, use your web server to control file upload size.
|
||||
- [31959](https://github.com/apache/superset/pull/31959) Removes the following endpoints from data uploads: `/api/v1/database/<id>/<file type>_upload` and `/api/v1/database/<file type>_metadata`, in favour of new one (Details on the PR). And simplifies permissions.
|
||||
- [31844](https://github.com/apache/superset/pull/31844) The `ALERT_REPORTS_EXECUTE_AS` and `THUMBNAILS_EXECUTE_AS` config parameters have been renamed to `ALERT_REPORTS_EXECUTORS` and `THUMBNAILS_EXECUTORS` respectively. A new config flag `CACHE_WARMUP_EXECUTORS` has also been introduced to be able to control which user is used to execute cache warmup tasks. Finally, the config flag `THUMBNAILS_SELENIUM_USER` has been removed. To use a fixed executor for async tasks, use the new `FixedExecutor` class. See the config and docs for more info on setting up different executor profiles.
|
||||
- [31894](https://github.com/apache/superset/pull/31894) Domain sharding is deprecated in favor of HTTP2. The `SUPERSET_WEBSERVER_DOMAINS` configuration will be removed in the next major version (6.0)
|
||||
@@ -716,6 +689,7 @@ ORDER BY total_calls DESC;
|
||||
|
||||
- [11509](https://github.com/apache/superset/pull/12491): Dataset metadata updates check user ownership, only owners or an Admin are allowed.
|
||||
- Security simplification (SIP-19), the following permission domains were simplified:
|
||||
|
||||
- [12072](https://github.com/apache/superset/pull/12072): `Query` with `can_read`, `can_write`
|
||||
- [12036](https://github.com/apache/superset/pull/12036): `Database` with `can_read`, `can_write`.
|
||||
- [12012](https://github.com/apache/superset/pull/12036): `Dashboard` with `can_read`, `can_write`.
|
||||
|
||||
@@ -115,6 +115,10 @@ services:
|
||||
DATABASE_HOST: db-light
|
||||
DATABASE_DB: superset_light
|
||||
POSTGRES_DB: superset_light
|
||||
EXAMPLES_HOST: db-light
|
||||
EXAMPLES_DB: superset_light
|
||||
EXAMPLES_USER: superset
|
||||
EXAMPLES_PASSWORD: superset
|
||||
SUPERSET_CONFIG_PATH: /app/docker/pythonpath_dev/superset_config_docker_light.py
|
||||
GITHUB_HEAD_REF: ${GITHUB_HEAD_REF:-}
|
||||
GITHUB_SHA: ${GITHUB_SHA:-}
|
||||
@@ -137,6 +141,10 @@ services:
|
||||
DATABASE_HOST: db-light
|
||||
DATABASE_DB: superset_light
|
||||
POSTGRES_DB: superset_light
|
||||
EXAMPLES_HOST: db-light
|
||||
EXAMPLES_DB: superset_light
|
||||
EXAMPLES_USER: superset
|
||||
EXAMPLES_PASSWORD: superset
|
||||
SUPERSET_CONFIG_PATH: /app/docker/pythonpath_dev/superset_config_docker_light.py
|
||||
healthcheck:
|
||||
disable: true
|
||||
@@ -157,6 +165,7 @@ services:
|
||||
BUILD_SUPERSET_FRONTEND_IN_DOCKER: true
|
||||
NPM_RUN_PRUNE: false
|
||||
SCARF_ANALYTICS: "${SCARF_ANALYTICS:-}"
|
||||
DISABLE_TS_CHECKER: "${DISABLE_TS_CHECKER:-true}"
|
||||
# configuring the dev-server to use the host.docker.internal to connect to the backend
|
||||
superset: "http://superset-light:8088"
|
||||
# Webpack dev server must bind to 0.0.0.0 to be accessible from outside the container
|
||||
|
||||
@@ -80,7 +80,7 @@ case "${1}" in
|
||||
;;
|
||||
app)
|
||||
echo "Starting web app (using development server)..."
|
||||
flask run -p $PORT --reload --debugger --without-threads --host=0.0.0.0 --exclude-patterns "*/node_modules/*:*/.venv/*:*/build/*:*/__pycache__/*"
|
||||
flask run -p $PORT --reload --debugger --host=0.0.0.0 --exclude-patterns "*/node_modules/*:*/.venv/*:*/build/*:*/__pycache__/*:*/superset-frontend/*"
|
||||
;;
|
||||
app-gunicorn)
|
||||
echo "Starting web app..."
|
||||
|
||||
@@ -1,162 +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.
|
||||
*/}
|
||||
|
||||
---
|
||||
title: AWS IAM Authentication
|
||||
sidebar_label: AWS IAM Authentication
|
||||
sidebar_position: 15
|
||||
---
|
||||
|
||||
# AWS IAM Authentication for AWS Databases
|
||||
|
||||
Superset supports IAM-based authentication for **Amazon Aurora** (PostgreSQL and MySQL) and **Amazon Redshift**. IAM auth eliminates the need for database passwords — Superset generates a short-lived auth token using temporary AWS credentials instead.
|
||||
|
||||
Cross-account IAM role assumption via STS `AssumeRole` is supported, allowing a Superset deployment in one AWS account to connect to databases in a different account.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Enable the `AWS_DATABASE_IAM_AUTH` feature flag in `superset_config.py`. IAM authentication is gated behind this flag; if it is disabled, connections using `aws_iam` fail with *"AWS IAM database authentication is not enabled."*
|
||||
```python
|
||||
FEATURE_FLAGS = {
|
||||
"AWS_DATABASE_IAM_AUTH": True,
|
||||
}
|
||||
```
|
||||
- `boto3` must be installed in your Superset environment:
|
||||
```bash
|
||||
pip install boto3
|
||||
```
|
||||
- The Superset server's IAM role (or static credentials) must have permission to call `sts:AssumeRole` (for cross-account) or the same-account permissions for the target service:
|
||||
- **Aurora (RDS)**: `rds-db:connect`
|
||||
- **Redshift provisioned**: `redshift:GetClusterCredentials`
|
||||
- **Redshift Serverless**: `redshift-serverless:GetCredentials` and `redshift-serverless:GetWorkgroup`
|
||||
- SSL must be enabled on the Aurora / Redshift endpoint (required for IAM token auth).
|
||||
|
||||
## Configuration
|
||||
|
||||
IAM authentication is configured via the **encrypted_extra** field of the database connection. Access this field in the **Advanced** → **Security** section of the database connection form, under **Secure Extra**.
|
||||
|
||||
### Aurora PostgreSQL or Aurora MySQL
|
||||
|
||||
```json
|
||||
{
|
||||
"aws_iam": {
|
||||
"enabled": true,
|
||||
"role_arn": "arn:aws:iam::222222222222:role/SupersetDatabaseAccess",
|
||||
"external_id": "superset-prod-12345",
|
||||
"region": "us-east-1",
|
||||
"db_username": "superset_iam_user",
|
||||
"session_duration": 3600
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Required | Description |
|
||||
|-------|----------|-------------|
|
||||
| `enabled` | Yes | Set to `true` to activate IAM auth |
|
||||
| `role_arn` | No | ARN of the cross-account IAM role to assume via STS. Omit for same-account auth |
|
||||
| `external_id` | No | External ID for the STS `AssumeRole` call, if required by the target role's trust policy |
|
||||
| `region` | Yes | AWS region of the database cluster |
|
||||
| `db_username` | Yes | The database username associated with the IAM identity |
|
||||
| `session_duration` | No | STS session duration in seconds (default: `3600`) |
|
||||
|
||||
### Redshift (Serverless)
|
||||
|
||||
```json
|
||||
{
|
||||
"aws_iam": {
|
||||
"enabled": true,
|
||||
"role_arn": "arn:aws:iam::222222222222:role/SupersetRedshiftAccess",
|
||||
"region": "us-east-1",
|
||||
"workgroup_name": "my-workgroup",
|
||||
"db_name": "dev"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Redshift (Provisioned Cluster)
|
||||
|
||||
```json
|
||||
{
|
||||
"aws_iam": {
|
||||
"enabled": true,
|
||||
"role_arn": "arn:aws:iam::222222222222:role/SupersetRedshiftAccess",
|
||||
"region": "us-east-1",
|
||||
"cluster_identifier": "my-cluster",
|
||||
"db_username": "superset_iam_user",
|
||||
"db_name": "dev"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Cross-Account IAM Setup
|
||||
|
||||
To connect to a database in Account B from a Superset deployment in Account A:
|
||||
|
||||
**1. In Account B — create a database-access role:**
|
||||
|
||||
```json
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": ["rds-db:connect"],
|
||||
"Resource": "arn:aws:rds-db:us-east-1:222222222222:dbuser/db-XXXXXXXXXXXX/superset_iam_user"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Trust policy** (allows Account A's Superset role to assume it):
|
||||
|
||||
```json
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Principal": {
|
||||
"AWS": "arn:aws:iam::111111111111:role/SupersetInstanceRole"
|
||||
},
|
||||
"Action": "sts:AssumeRole",
|
||||
"Condition": {
|
||||
"StringEquals": {
|
||||
"sts:ExternalId": "superset-prod-12345"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**2. In Account A — grant Superset's role permission to assume the Account B role:**
|
||||
|
||||
```json
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": "sts:AssumeRole",
|
||||
"Resource": "arn:aws:iam::222222222222:role/SupersetDatabaseAccess"
|
||||
}
|
||||
```
|
||||
|
||||
**3. Configure the database connection in Superset** using the `role_arn` and `external_id` from the trust policy (as shown in the configuration example above).
|
||||
|
||||
## Credential Caching
|
||||
|
||||
STS credentials are cached in memory keyed by `(role_arn, region, external_id)` with a 10-minute TTL. This reduces the number of STS API calls when multiple queries are executed with the same connection. Tokens are refreshed automatically before expiry.
|
||||
@@ -76,21 +76,6 @@ value defined in `DATA_CACHE_CONFIG`.
|
||||
Note, that by setting the cache timeout to `-1`, caching for charting data can be disabled, either
|
||||
per chart, dataset or database, or by default if set in `DATA_CACHE_CONFIG`.
|
||||
|
||||
## Limiting Cached Result Size
|
||||
|
||||
Very large chart or SQL query results can flood the cache backend (Redis/Memcached), evicting many
|
||||
smaller useful entries or exhausting memory. To cap the size of any single value written to the data
|
||||
cache, set `DATA_CACHE_MAX_VALUE_SIZE` (in bytes) in `superset_config.py`:
|
||||
|
||||
```python
|
||||
DATA_CACHE_MAX_VALUE_SIZE = 10 * 1024 * 1024 # 10 MB
|
||||
```
|
||||
|
||||
When a result's serialized size exceeds this threshold it is not written to the data cache — the
|
||||
chart still renders, but the next load re-queries the datasource instead of getting a cache hit. The
|
||||
`skip_cache_value_too_large` statsd metric is incremented each time this happens. Set to `None` (the
|
||||
default) to disable the check.
|
||||
|
||||
## SQL Lab Query Results
|
||||
|
||||
Caching for SQL Lab query results is used when async queries are enabled and is configured using
|
||||
@@ -153,33 +138,14 @@ THUMBNAIL_CACHE_CONFIG = init_thumbnail_cache
|
||||
```
|
||||
|
||||
Using the above example cache keys for dashboards will be `superset_thumb__dashboard__{ID}`. You can
|
||||
override the base URL for Selenium using:
|
||||
override the base URL for selenium using:
|
||||
|
||||
```
|
||||
WEBDRIVER_BASEURL = "https://superset.company.com"
|
||||
```
|
||||
|
||||
To control which user account is used for rendering thumbnails and warming up caches, configure
|
||||
`THUMBNAIL_EXECUTORS` and `CACHE_WARMUP_EXECUTORS`. Each accepts a list of executor types (which
|
||||
resolve to an owner, creator, modifier, or the currently-logged-in user) and/or a `FixedExecutor`
|
||||
pinned to a specific username. By default, thumbnails render as the current user
|
||||
(`ExecutorType.CURRENT_USER`) and cache warmup runs as the chart/dashboard owner
|
||||
(`ExecutorType.OWNER`).
|
||||
|
||||
To force both to run as a dedicated service account (`admin` in this example):
|
||||
|
||||
```python
|
||||
from superset.tasks.types import ExecutorType, FixedExecutor
|
||||
|
||||
THUMBNAIL_EXECUTORS = [FixedExecutor("admin")]
|
||||
CACHE_WARMUP_EXECUTORS = [FixedExecutor("admin")]
|
||||
```
|
||||
|
||||
Use a dedicated read-only service account here rather than a personal admin account, so that
|
||||
thumbnail rendering and cache warmup tasks don't fail if a specific user's credentials change.
|
||||
|
||||
Additional Selenium WebDriver configuration can be set using `WEBDRIVER_CONFIGURATION`. You can
|
||||
implement a custom function to authenticate Selenium. The default function uses the `flask-login`
|
||||
Additional selenium web drive configuration can be set using `WEBDRIVER_CONFIGURATION`. You can
|
||||
implement a custom function to authenticate selenium. The default function uses the `flask-login`
|
||||
session cookie. Here's an example of a custom function signature:
|
||||
|
||||
```python
|
||||
@@ -193,20 +159,6 @@ Then on configuration:
|
||||
WEBDRIVER_AUTH_FUNC = auth_driver
|
||||
```
|
||||
|
||||
## ETag Support for Thumbnails
|
||||
|
||||
Thumbnail and screenshot endpoints return `ETag` response headers based on the cached content digest. Clients can use conditional requests to avoid downloading unchanged images:
|
||||
|
||||
```
|
||||
GET /api/v1/chart/42/thumbnail/
|
||||
If-None-Match: "abc123..."
|
||||
|
||||
→ 304 Not Modified (if unchanged)
|
||||
→ 200 OK (with new image if changed)
|
||||
```
|
||||
|
||||
This is particularly useful for embedded dashboards and external integrations that periodically poll for updated screenshots — unchanged thumbnails return immediately with no payload.
|
||||
|
||||
## Distributed Coordination Backend
|
||||
|
||||
Superset supports an optional distributed coordination (`DISTRIBUTED_COORDINATION_CONFIG`) for
|
||||
|
||||
@@ -364,26 +364,6 @@ CUSTOM_SECURITY_MANAGER = CustomSsoSecurityManager
|
||||
]
|
||||
```
|
||||
|
||||
### PKCE Support
|
||||
|
||||
For public OAuth2 clients that cannot securely store a client secret, enable Proof Key for Code Exchange (PKCE) by adding `code_challenge_method` to the `remote_app` configuration:
|
||||
|
||||
```python
|
||||
OAUTH_PROVIDERS = [
|
||||
{
|
||||
'name': 'myProvider',
|
||||
'remote_app': {
|
||||
'client_id': 'myClientId',
|
||||
'client_secret': 'mySecret', # may be empty for pure public clients
|
||||
'code_challenge_method': 'S256', # enables PKCE
|
||||
'server_metadata_url': 'https://myAuthorizationServer/.well-known/openid-configuration'
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
PKCE (`S256`) is recommended for all OAuth2 flows, even when a client secret is present, as it protects against authorization code interception attacks.
|
||||
|
||||
## LDAP Authentication
|
||||
|
||||
FAB supports authenticating user credentials against an LDAP server.
|
||||
|
||||
@@ -10,10 +10,6 @@ version: 1
|
||||
The superset cli allows you to import and export datasources from and to YAML. Datasources include
|
||||
databases. The data is expected to be organized in the following hierarchy:
|
||||
|
||||
:::info
|
||||
Superset's ZIP-based import/export also covers **dashboards**, **charts**, and **saved queries**, exercised through the UI and REST API. The [Dashboard Import Overwrite Behavior](#dashboard-import-overwrite-behavior) and [UUIDs in API Responses](#uuids-in-api-responses) sections below document the behavior shared across all asset types.
|
||||
:::
|
||||
|
||||
```text
|
||||
├──databases
|
||||
| ├──database_1
|
||||
@@ -30,10 +26,6 @@ Superset's ZIP-based import/export also covers **dashboards**, **charts**, and *
|
||||
| └── ... (more databases)
|
||||
```
|
||||
|
||||
:::note
|
||||
When you export a database connection, the `masked_encrypted_extra` field (used for sensitive connection parameters such as service account JSON, OAuth tokens, and other encrypted credentials) is included in the export. When importing on another instance, these values are decrypted and re-encrypted using the destination instance's `SECRET_KEY`. Ensure the receiving instance has a valid `SECRET_KEY` configured before importing.
|
||||
:::
|
||||
|
||||
## Exporting Datasources to YAML
|
||||
|
||||
You can print your current datasources to stdout by running:
|
||||
@@ -83,29 +75,6 @@ The optional username flag **-u** sets the user used for the datasource import.
|
||||
superset import_datasources -p <path / filename> -u 'admin'
|
||||
```
|
||||
|
||||
## Dashboard Import Overwrite Behavior
|
||||
|
||||
When importing a dashboard ZIP with the **overwrite** option enabled, any existing charts that are part of the dashboard are **replaced** rather than duplicated. This applies to:
|
||||
|
||||
- Charts whose UUID matches a chart already present in the target instance
|
||||
- The full chart configuration (query, visualization type, columns, metrics) is replaced by the imported version
|
||||
|
||||
If you import without the overwrite flag, existing charts with conflicting UUIDs are left unchanged and the import skips those objects. Use overwrite when you want to push a fully updated dashboard (including chart definitions) from a development or staging environment to production.
|
||||
|
||||
## UUIDs in API Responses
|
||||
|
||||
The REST API POST endpoints for **datasets**, **charts**, and **dashboards** include the auto-generated `uuid` field in the response body:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 42,
|
||||
"uuid": "b8a8d5c3-1234-4abc-8def-0123456789ab",
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
UUIDs remain stable across import/export cycles and can be used for cross-environment workflows — for example, recording a UUID when creating a chart in development and using it to identify the matching chart after importing into production.
|
||||
|
||||
## Legacy Importing Datasources
|
||||
|
||||
### From older versions of Superset to current version
|
||||
|
||||
@@ -501,8 +501,6 @@ All MCP settings go in `superset_config.py`. Defaults are defined in `superset/m
|
||||
| `MCP_SERVICE_URL` | `None` | Public base URL for MCP-generated links (set this when behind a reverse proxy) |
|
||||
| `MCP_DEBUG` | `False` | Enable debug logging |
|
||||
| `MCP_DEV_USERNAME` | -- | Superset username for development mode (no auth) |
|
||||
| `MCP_RBAC_ENABLED` | `True` | Enforce Superset's role-based access control on MCP tool calls. When `True`, each tool checks that the authenticated user has the required FAB permission before executing. Disable only for testing or trusted-network deployments. |
|
||||
| `MCP_DISABLED_TOOLS` | `set()` | Set of tool names to remove from the MCP server at startup. Disabled tools are never advertised to AI clients during tool discovery. Useful when a custom extension tool should replace a built-in Superset tool. See [Disabling built-in tools](#disabling-built-in-tools). |
|
||||
|
||||
### Authentication
|
||||
|
||||
@@ -518,7 +516,6 @@ All MCP settings go in `superset_config.py`. Defaults are defined in `superset/m
|
||||
| `MCP_REQUIRED_SCOPES` | `[]` | Required JWT scopes |
|
||||
| `MCP_JWT_DEBUG_ERRORS` | `False` | Log detailed JWT errors server-side (never exposed in HTTP responses per RFC 6750) |
|
||||
| `MCP_AUTH_FACTORY` | `None` | Custom auth provider factory `(flask_app) -> auth_provider`. Takes precedence over built-in JWT |
|
||||
| `MCP_USER_RESOLVER` | `None` | Custom function `(app, access_token) -> username` to extract a Superset username from a validated JWT token. When `None`, the default resolver checks `preferred_username`, `username`, `email`, and `sub` claims in that order. |
|
||||
|
||||
### Response Size Guard
|
||||
|
||||
@@ -529,7 +526,6 @@ MCP_RESPONSE_SIZE_CONFIG = {
|
||||
"enabled": True,
|
||||
"token_limit": 25000,
|
||||
"warn_threshold_pct": 80,
|
||||
"max_list_items": 100,
|
||||
"excluded_tools": [
|
||||
"health_check",
|
||||
"get_chart_preview",
|
||||
@@ -544,7 +540,6 @@ MCP_RESPONSE_SIZE_CONFIG = {
|
||||
| `enabled` | `True` | Enable response size checking |
|
||||
| `token_limit` | `25000` | Maximum estimated token count per response |
|
||||
| `warn_threshold_pct` | `80` | Warn when response exceeds this percentage of the limit |
|
||||
| `max_list_items` | `100` | Cap on list-field length (e.g. `charts`, `native_filters`) applied to the `get_*_info` tools before falling back to more aggressive truncation. Raised from a hardcoded 30 in earlier versions; the higher default only keeps more data before the same token-budget fallback kicks in, so it's not a breaking change, but tenants that tuned workflows around the old 30-item cap should lower this value explicitly. |
|
||||
| `excluded_tools` | See above | Tools exempt from size checking (e.g., tools that return URLs, not data) |
|
||||
|
||||
### Caching
|
||||
@@ -604,43 +599,6 @@ MCP_STORE_CONFIG = {
|
||||
| `event_store_max_events` | `100` | Maximum events retained per session |
|
||||
| `event_store_ttl` | `3600` | Event TTL in seconds |
|
||||
|
||||
### Tool Search
|
||||
|
||||
By default the MCP server exposes a lightweight tool-search interface instead of advertising every tool at once. This reduces the initial context sent to the LLM by ~70%, which lowers cost and latency. The AI client discovers tools on demand by calling `search_tools` and then invokes them via `call_tool`.
|
||||
|
||||
```python
|
||||
MCP_TOOL_SEARCH_CONFIG = {
|
||||
"enabled": True,
|
||||
"strategy": "bm25", # "bm25" (natural language) or "regex"
|
||||
"max_results": 5,
|
||||
"always_visible": [ # Tools always listed (pinned)
|
||||
"health_check",
|
||||
"get_instance_info",
|
||||
],
|
||||
"search_tool_name": "search_tools",
|
||||
"call_tool_name": "call_tool",
|
||||
"include_schemas": False, # False=summary mode (name + parameters_hint)
|
||||
"compact_schemas": True, # Strip $defs (only applies when include_schemas=True)
|
||||
"max_description_length": 300,
|
||||
}
|
||||
```
|
||||
|
||||
| Key | Default | Description |
|
||||
|-----|---------|-------------|
|
||||
| `enabled` | `True` | Enable tool search. When `False`, all tools are listed upfront |
|
||||
| `strategy` | `"bm25"` | Search ranking algorithm. `"bm25"` supports natural language; `"regex"` supports pattern matching |
|
||||
| `max_results` | `5` | Maximum tools returned per search query |
|
||||
| `always_visible` | See above | Tools that always appear in `list_tools`, regardless of search |
|
||||
| `include_schemas` | `False` | When `False` (default, "summary mode"), search results omit `inputSchema` entirely and include a lightweight `parameters_hint` listing top-level parameter names. Set to `True` to include the full `inputSchema` in search results. Full schemas are always used when a tool is actually invoked via `call_tool`. |
|
||||
| `compact_schemas` | `True` | Strip `$defs` / `$ref` and replace with `{"type": "object"}` in search results to reduce token cost. Only takes effect when `include_schemas=True` — ignored in summary mode. |
|
||||
| `max_description_length` | `300` | Truncate tool descriptions in search results (0 = no truncation). Applies in both summary and full-schema modes. |
|
||||
|
||||
:::tip
|
||||
Set `enabled: False` to revert to the traditional "show all tools at once" behavior, which some clients or workflows may prefer.
|
||||
:::
|
||||
|
||||
Tool search reduces the initial token cost from ~15–20K tokens (full catalog) down to ~4–5K tokens (pinned tools + search interface) — roughly 85% savings at the start of each conversation.
|
||||
|
||||
### Session & CSRF
|
||||
|
||||
These values are flat-merged into the Flask app config used by the MCP server process:
|
||||
@@ -662,102 +620,6 @@ MCP_CSRF_CONFIG = {
|
||||
|
||||
---
|
||||
|
||||
## Access Control
|
||||
|
||||
### RBAC Enforcement
|
||||
|
||||
The MCP server respects Superset's full role-based access control (RBAC). Every authenticated user can only access the data and operations their Superset roles permit — the same rules that apply in the Superset UI apply through MCP.
|
||||
|
||||
Each tool declares one or more required FAB permissions. The table below maps tool groups to their permission requirements:
|
||||
|
||||
| Tool group | Required FAB permission |
|
||||
|------------|------------------------|
|
||||
| `list_charts`, `get_chart_info`, `get_chart_data`, `get_chart_preview`, `generate_chart`, `update_chart` | `can_read` on `Chart` (read), `can_write` on `Chart` (mutate) |
|
||||
| `list_dashboards`, `get_dashboard_info`, `generate_dashboard`, `add_chart_to_existing_dashboard` | `can_read` on `Dashboard` (read), `can_write` on `Dashboard` (mutate) |
|
||||
| `list_datasets`, `get_dataset_info`, `create_virtual_dataset` | `can_read` on `Dataset` (read), `can_write` on `Dataset` (mutate) |
|
||||
| `list_databases`, `get_database_info` | `can_read` on `Database` |
|
||||
| `execute_sql` | `can_execute_sql_query` on `SQLLab` |
|
||||
| `open_sql_lab_with_context` | `can_read` on `SQLLab` |
|
||||
| `save_sql_query` | `can_write` on `SavedQuery` |
|
||||
| `health_check` | None (public) |
|
||||
|
||||
To disable RBAC checking globally (for trusted-network deployments or testing), set:
|
||||
|
||||
```python
|
||||
# superset_config.py
|
||||
MCP_RBAC_ENABLED = False
|
||||
```
|
||||
|
||||
:::warning
|
||||
Disabling RBAC removes all permission checks from MCP tool calls. Only do this on isolated, internal deployments where all MCP users are trusted admins.
|
||||
:::
|
||||
|
||||
### Audit Log
|
||||
|
||||
All MCP tool calls are recorded in Superset's action log. You can view them at **Settings → Action Log** (admin only). Each log entry records:
|
||||
|
||||
- The tool name (e.g., `mcp.generate_chart.db_write`)
|
||||
- The authenticated user
|
||||
- A timestamp
|
||||
|
||||
This makes MCP activity fully auditable alongside regular Superset activity. The action log uses the same event logger as the rest of Superset, so existing log ingestion pipelines (e.g., sending logs to Elasticsearch or a SIEM) capture MCP events automatically.
|
||||
|
||||
### Middleware Pipeline
|
||||
|
||||
Every MCP request passes through a middleware stack before reaching the tool function. The default stack (assembled in `build_middleware_list()` in `server.py`) is:
|
||||
|
||||
| Middleware | Purpose | Default |
|
||||
|------------|---------|---------|
|
||||
| `StructuredContentStripperMiddleware` | Strips `structuredContent` from responses for Claude.ai bridge compatibility | Enabled |
|
||||
| `LoggingMiddleware` | Logs each tool call with user, parameters, and duration | Enabled |
|
||||
| `GlobalErrorHandlerMiddleware` | Catches unhandled exceptions and sanitizes sensitive data before it reaches the client | Enabled |
|
||||
| `ResponseSizeGuardMiddleware` | Estimates token count, warns at 80% of limit, blocks at limit | Enabled (configurable via `MCP_RESPONSE_SIZE_CONFIG`) |
|
||||
| `ResponseCachingMiddleware` | Caches read-heavy tool responses (in-memory or Redis) | Disabled (enable via `MCP_CACHE_CONFIG`) |
|
||||
|
||||
Additional middleware classes (`RateLimitMiddleware`, `FieldPermissionsMiddleware`, `PrivateToolMiddleware`) are implemented in `superset/mcp_service/middleware.py` but are not added to the default pipeline. They are available for operators who want to layer them in via a custom startup path.
|
||||
|
||||
### Error Sanitization
|
||||
|
||||
The `GlobalErrorHandlerMiddleware` automatically redacts sensitive information from all error messages before they reach the LLM client. The following are replaced with generic messages:
|
||||
|
||||
- **Database connection strings** — replaced with a generic connection error message
|
||||
- **API keys and tokens** — redacted from error traces
|
||||
- **File system paths** — stripped to prevent information disclosure
|
||||
- **IP addresses** — removed from error context
|
||||
|
||||
This ensures that a misconfigured database connection or an unexpected exception never leaks credentials or internal topology to the LLM or its users. All regex patterns used for redaction are bounded to prevent ReDoS attacks.
|
||||
|
||||
---
|
||||
|
||||
## Performance
|
||||
|
||||
### Connection Pooling
|
||||
|
||||
Each MCP server process maintains its own SQLAlchemy connection pool to the database. For multi-worker deployments, total open connections = **workers × pool size**.
|
||||
|
||||
```python
|
||||
# superset_config.py
|
||||
SQLALCHEMY_POOL_SIZE = 5
|
||||
SQLALCHEMY_MAX_OVERFLOW = 10
|
||||
SQLALCHEMY_POOL_TIMEOUT = 30
|
||||
SQLALCHEMY_POOL_RECYCLE = 3600 # Recycle connections after 1 hour
|
||||
```
|
||||
|
||||
For a 3-pod Kubernetes deployment with the defaults above, expect up to 3 × (5 + 10) = 45 connections. Size your database's `max_connections` accordingly.
|
||||
|
||||
### Response Caching
|
||||
|
||||
Enable response caching for read-heavy workloads (dashboards/datasets that don't change frequently). With the in-memory backend (default when `MCP_STORE_CONFIG` is disabled), caching is per-process. Use Redis-backed caching for consistent cache hits across multiple pods:
|
||||
|
||||
```python
|
||||
MCP_CACHE_CONFIG = {"enabled": True, "call_tool_ttl": 3600}
|
||||
MCP_STORE_CONFIG = {"enabled": True, "CACHE_REDIS_URL": "redis://redis:6379/0"}
|
||||
```
|
||||
|
||||
Mutating tools (`generate_chart`, `update_chart`, `execute_sql`, `generate_dashboard`) are always excluded from caching regardless of this setting.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Server won't start
|
||||
@@ -802,58 +664,6 @@ Mutating tools (`generate_chart`, `update_chart`, `execute_sql`, `generate_dashb
|
||||
|
||||
---
|
||||
|
||||
## Audit Events
|
||||
|
||||
All MCP tool calls are logged to Superset's event logger, the same system used by the web UI (viewable at **Settings → Action Log**). Each event captures:
|
||||
|
||||
- **Action**: `mcp.<tool_name>.<phase>` (e.g., `mcp.list_databases.query`)
|
||||
- **User**: the resolved Superset username from the JWT or dev config
|
||||
- **Timestamp**: when the operation ran
|
||||
|
||||
This means MCP activity is auditable alongside normal user activity. No additional configuration is required — logging is on by default whenever the event logger is enabled in your Superset deployment.
|
||||
|
||||
## Tool Pagination
|
||||
|
||||
MCP list tools (`list_datasets`, `list_charts`, `list_dashboards`, `list_databases`) use **offset pagination** via `page` (1-based) and `page_size` parameters. Responses include `page`, `page_size`, `total_count`, `total_pages`, `has_previous`, and `has_next`. To iterate through all results:
|
||||
|
||||
```python
|
||||
# Example: fetch all charts across pages
|
||||
all_charts = []
|
||||
page = 1
|
||||
while True:
|
||||
result = mcp.list_charts(page=page, page_size=50)
|
||||
all_charts.extend(result["charts"])
|
||||
if not result.get("has_next"):
|
||||
break
|
||||
page += 1
|
||||
```
|
||||
|
||||
## Disabling built-in tools
|
||||
|
||||
If you have deployed a custom tool via a Superset extension that supersedes one of the built-in Superset tools, you can suppress the built-in version so AI clients only discover your replacement. Disabled tools are removed from the server at startup and are never advertised during tool discovery.
|
||||
|
||||
Set `MCP_DISABLED_TOOLS` in your `superset_config.py` to a set of tool names:
|
||||
|
||||
```python
|
||||
# superset_config.py
|
||||
|
||||
# Disable one tool
|
||||
MCP_DISABLED_TOOLS = {"execute_sql"}
|
||||
|
||||
# Disable multiple tools
|
||||
MCP_DISABLED_TOOLS = {"execute_sql", "health_check"}
|
||||
```
|
||||
|
||||
Tool names match the function name used in the `@tool` decorator (e.g., `execute_sql`, `list_charts`, `health_check`). Extension-prefixed tools can also be disabled using their full prefixed name:
|
||||
|
||||
```python
|
||||
MCP_DISABLED_TOOLS = {"extensions.myorg.myextension.some_tool"}
|
||||
```
|
||||
|
||||
:::note
|
||||
Specifying a tool name that does not exist logs a warning at startup and is otherwise ignored — it will not prevent the server from starting.
|
||||
:::
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
- **Use TLS** for all production MCP endpoints -- place the server behind a reverse proxy with HTTPS
|
||||
|
||||
@@ -22,6 +22,15 @@ While powerful, this feature executes template code on the server. Within the Su
|
||||
|
||||
If you grant these permissions to untrusted users, this feature can be exploited as a **Server-Side Template Injection (SSTI)** vulnerability. Do not enable `ENABLE_TEMPLATE_PROCESSING` unless you fully understand and accept the associated security risks.
|
||||
|
||||
Additionally:
|
||||
|
||||
- The `url_param()` macro allows URL parameters to influence the rendered SQL. Always validate or restrict `url_param()` values in your templates rather than interpolating them directly.
|
||||
- `filter.get('val')` returns raw filter values without escaping. Use the safe helpers described below (`|where_in`, `| replace("'", "''")`) rather than concatenating values directly into SQL strings.
|
||||
|
||||
:::
|
||||
|
||||
:::tip
|
||||
`ENABLE_TEMPLATE_PROCESSING` defaults to `False`. Only enable it if your deployment requires Jinja templates and all users with dataset/chart edit access are administrators or fully trusted internal users.
|
||||
:::
|
||||
|
||||
When templating is enabled, python code can be embedded in virtual datasets and
|
||||
@@ -324,6 +333,16 @@ cache hit in the future and Superset can retrieve cached data.
|
||||
The `{{ url_param('custom_variable') }}` macro lets you define arbitrary URL
|
||||
parameters and reference them in your SQL code.
|
||||
|
||||
:::warning
|
||||
Always treat `url_param()` values as untrusted input. Escaping behaviour varies by context and configuration, so do not rely on it. Restrict values to an explicit allowlist before using them in SQL:
|
||||
|
||||
```sql
|
||||
{% set cc = url_param('countrycode') %}
|
||||
{% if cc not in ('US', 'ES', 'FR') %}{% set cc = 'US' %}{% endif %}
|
||||
WHERE country_code = '{{ cc }}'
|
||||
```
|
||||
:::
|
||||
|
||||
Here's a concrete example:
|
||||
|
||||
- You write the following query in SQL Lab:
|
||||
@@ -398,6 +417,16 @@ This is useful if:
|
||||
- You want to handle generating custom SQL conditions for a filter
|
||||
- You want to have the ability to filter inside the main query for speed purposes
|
||||
|
||||
:::warning
|
||||
`filter.get('val')` returns the raw filter value without escaping. For multi-value filters, use the `|where_in` Jinja filter, which handles quoting safely. For single-value operators like `LIKE`, escape single quotes before interpolating:
|
||||
|
||||
```sql
|
||||
{%- if filter.get('op') == 'LIKE' -%}
|
||||
AND full_name LIKE '{{ filter.get('val') | replace("'", "''") }}'
|
||||
{%- endif -%}
|
||||
```
|
||||
:::
|
||||
|
||||
Here's a concrete example:
|
||||
|
||||
```sql
|
||||
@@ -424,7 +453,7 @@ Here's a concrete example:
|
||||
|
||||
{%- if filter.get('op') == 'LIKE' -%}
|
||||
AND
|
||||
full_name LIKE {{ "'" + filter.get('val') + "'" }}
|
||||
full_name LIKE '{{ filter.get('val') | replace("'", "''") }}'
|
||||
{%- endif -%}
|
||||
|
||||
{%- endfor -%}
|
||||
|
||||
@@ -84,35 +84,6 @@ THEME_DARK = {
|
||||
# - OS preference detection is automatically enabled
|
||||
```
|
||||
|
||||
### App Branding
|
||||
|
||||
The application name shown in the browser title bar and navigation can be
|
||||
set via the `brandAppName` theme token:
|
||||
|
||||
```python
|
||||
THEME_DEFAULT = {
|
||||
"token": {
|
||||
"brandAppName": "Acme Analytics",
|
||||
# ... other tokens
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Or in the theme CRUD UI JSON editor:
|
||||
|
||||
```json
|
||||
{
|
||||
"token": {
|
||||
"brandAppName": "Acme Analytics"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The existing `APP_NAME` Python config key continues to work for backward compatibility.
|
||||
`brandAppName` takes precedence when both are set, and allows different themes to carry different brand names.
|
||||
Email and alert/report notification subjects are driven by backend settings such as
|
||||
`EMAIL_REPORTS_SUBJECT_PREFIX` and `APP_NAME`, not by this theme token.
|
||||
|
||||
### Migration from Configuration to UI
|
||||
|
||||
When `ENABLE_UI_THEME_ADMINISTRATION = True`:
|
||||
@@ -341,25 +312,11 @@ Available chart types for `echartsOptionsOverridesByChartType`:
|
||||
- `echarts_heatmap` - Heatmaps
|
||||
- `echarts_mixed_timeseries` - Mixed time series
|
||||
|
||||
### Array Property Overrides
|
||||
|
||||
Array properties (such as color palettes) are fully supported in overrides. Arrays are **replaced entirely** rather than merged, so specify the complete array:
|
||||
|
||||
```python
|
||||
THEME_DEFAULT = {
|
||||
"token": { ... },
|
||||
"echartsOptionsOverrides": {
|
||||
# Replace the default color palette for all ECharts visualizations
|
||||
"color": ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Start with global overrides** for consistent styling across all charts
|
||||
2. **Use chart-specific overrides** for unique requirements per visualization type
|
||||
3. **Test thoroughly** as overrides use deep merge for objects, but arrays are completely replaced — always specify the full array value
|
||||
3. **Test thoroughly** as overrides use deep merge - nested objects are combined, but arrays are completely replaced
|
||||
4. **Document your overrides** to help team members understand custom styling
|
||||
5. **Consider performance** - complex overrides may impact chart rendering speed
|
||||
|
||||
|
||||
@@ -24,6 +24,14 @@ A table with the permissions for these roles can be found at [/RESOURCES/STANDAR
|
||||
Admins have all possible rights, including granting or revoking rights from other
|
||||
users and altering other people’s slices and dashboards.
|
||||
|
||||
>#### Threat Model and Privilege Boundaries: The Admin Role
|
||||
>
|
||||
>Apache Superset is built with a granular permission model where users assigned the Admin role are considered fully trusted. Admins possess complete control over the application's configuration, UI rendering, and access controls.
|
||||
>
|
||||
>Consequently, actions performed by an Admin that alter the application's behavior or presentation—such as injecting custom CSS, modifying Jinja templates, or altering security flags—are intended administrative capabilities by design.
|
||||
>
|
||||
>In accordance with MITRE CNA Rule 4.1, a vulnerability must represent a violation of an explicit security policy. Because the Admin role is defined as a trusted operational boundary, actions executed with Admin privileges do not cross a security perimeter. Therefore, exploit vectors that strictly require Admin access are not classified as security vulnerabilities and are ineligible for CVE assignment.
|
||||
|
||||
### Alpha
|
||||
|
||||
Alpha users have access to all data sources, but they cannot grant or revoke access
|
||||
@@ -44,15 +52,6 @@ only see the objects that they have access to.
|
||||
The **sql_lab** role grants access to SQL Lab. Note that while **Admin** users have access
|
||||
to all databases by default, both **Alpha** and **Gamma** users need to be given access on a per database basis.
|
||||
|
||||
Beyond the base `sql_lab` role, two additional SQL Lab permissions must be explicitly granted for users who need these capabilities:
|
||||
|
||||
| Permission | Feature |
|
||||
|------------|---------|
|
||||
| `can_estimate_query_cost` on `SQLLab` | Estimate query cost before running |
|
||||
| `can_format_sql` on `SQLLab` | Format SQL using the database's dialect |
|
||||
|
||||
Grant these in **Security → List Roles** by adding the permissions to the relevant role.
|
||||
|
||||
### Public
|
||||
|
||||
The **Public** role is the most restrictive built-in role, designed specifically for anonymous/unauthenticated
|
||||
@@ -183,8 +182,6 @@ However, it is crucial to understand the following:
|
||||
|
||||
By combining Superset's configurable safeguards with strong database-level security practices, you can achieve a more robust and layered security posture.
|
||||
|
||||
**Dataset Sample Access**: The `get_samples()` endpoint now enforces datasource-level access control. Users can only fetch sample rows from datasets they have been explicitly granted access to — the same permission check applied when running chart queries. This closes a prior gap where unauthenticated or under-privileged access could retrieve sample data.
|
||||
|
||||
### REST API for user & role management
|
||||
|
||||
Flask-AppBuilder supports a REST API for user CRUD,
|
||||
|
||||
@@ -485,7 +485,7 @@ Frontend assets (TypeScript, JavaScript, CSS, and images) must be compiled in or
|
||||
|
||||
First, be sure you are using the following versions of Node.js and npm:
|
||||
|
||||
- `Node.js`: Version 22 (LTS)
|
||||
- `Node.js`: Version 20
|
||||
- `npm`: Version 10
|
||||
|
||||
We recommend using [nvm](https://github.com/nvm-sh/nvm) to manage your node environment:
|
||||
|
||||
@@ -34,14 +34,15 @@ Frontend contribution types allow extensions to extend Superset's user interface
|
||||
|
||||
Extensions can add new views or panels to the host application, such as custom SQL Lab panels, dashboards, or other UI components. Contribution areas are uniquely identified (e.g., `sqllab.panels` for SQL Lab panels), enabling seamless integration into specific parts of the application.
|
||||
|
||||
```typescript
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { views } from '@apache-superset/core';
|
||||
import MyPanel from './MyPanel';
|
||||
|
||||
views.registerView(
|
||||
{ id: 'my-extension.main', name: 'My Panel Name' },
|
||||
'sqllab.panels',
|
||||
MyPanel,
|
||||
() => <MyPanel />,
|
||||
);
|
||||
```
|
||||
|
||||
@@ -111,24 +112,6 @@ editors.registerEditor(
|
||||
|
||||
See [Editors Extension Point](./extension-points/editors) for implementation details.
|
||||
|
||||
### Chat
|
||||
|
||||
Extensions can add a chat interface to Superset by registering a trigger component and a panel component. The host owns the layout, open/close state, and display mode — the extension only provides the UI. The panel can be displayed as a floating overlay or docked as a resizable sidebar beside the page content, and the user's preference is persisted across reloads.
|
||||
|
||||
```tsx
|
||||
import { chat } from '@apache-superset/core';
|
||||
import ChatTrigger from './ChatTrigger';
|
||||
import ChatPanel from './ChatPanel';
|
||||
|
||||
chat.registerChat(
|
||||
{ id: 'my-org.my-chat', name: 'My Chat' },
|
||||
ChatTrigger,
|
||||
ChatPanel,
|
||||
);
|
||||
```
|
||||
|
||||
See [Chat](./extension-points/chat.md) for implementation details.
|
||||
|
||||
## Backend
|
||||
|
||||
Backend contribution types allow extensions to extend Superset's server-side capabilities. Backend contributions are registered at startup via classes and functions imported from the auto-discovered `entrypoint.py` file.
|
||||
|
||||
@@ -39,7 +39,11 @@ superset-extensions bundle: Packages the extension into a .supx file.
|
||||
|
||||
superset-extensions dev: Automatically rebuilds the extension as files change.
|
||||
|
||||
superset-extensions validate: Validates the extension structure and metadata.
|
||||
superset-extensions validate: Validates the extension structure and metadata consistency.
|
||||
|
||||
superset-extensions update: Updates derived and generated files in the extension project.
|
||||
Use --version [<version>] to update the version (prompts if no value given).
|
||||
Use --license [<license>] to update the license (prompts if no value given).
|
||||
```
|
||||
|
||||
When creating a new extension with `superset-extensions init`, the CLI generates a standardized folder structure:
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
---
|
||||
title: Chat
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Chat Contributions
|
||||
|
||||
Extensions can add a chat interface to Superset by registering a trigger and a panel. The host owns the layout, open/close state, and display mode — the extension only needs to provide the UI components.
|
||||
|
||||
## Overview
|
||||
|
||||
A chat registration consists of two React components:
|
||||
|
||||
| Component | Role |
|
||||
|-----------|------|
|
||||
| **Trigger** | Always-visible entry point (e.g., a floating button). Rendered in the bottom-right corner in floating mode, or as a fixed overlay in panel mode. |
|
||||
| **Panel** | The chat UI itself (message list, input, etc.). Mounted by the host in the active display mode. |
|
||||
|
||||
## Display Modes
|
||||
|
||||
The host supports two display modes, switchable by the user or the extension at runtime:
|
||||
|
||||
| Mode | Behavior |
|
||||
|------|----------|
|
||||
| `floating` | Panel floats above page content, anchored to the bottom-right corner. |
|
||||
| `panel` | Panel is docked to the right side of the application as a resizable sidebar, sitting beside the page content. |
|
||||
|
||||
The user's last selected mode and open/closed state are persisted across page reloads.
|
||||
|
||||
## Registering a Chat
|
||||
|
||||
Call `chat.registerChat` from your extension's entry point with a descriptor, a trigger factory, and a panel factory:
|
||||
|
||||
```tsx
|
||||
import { chat } from '@apache-superset/core';
|
||||
import ChatTrigger from './ChatTrigger';
|
||||
import ChatPanel from './ChatPanel';
|
||||
|
||||
chat.registerChat(
|
||||
{ id: 'my-org.my-chat', name: 'My Chat' },
|
||||
ChatTrigger,
|
||||
ChatPanel,
|
||||
);
|
||||
```
|
||||
|
||||
Only one chat registration is active at a time. If a second extension calls `registerChat`, it replaces the first and a warning is logged.
|
||||
|
||||
## Opening and Closing the Chat
|
||||
|
||||
The trigger component is responsible for toggling the panel. Use `chat.isOpen()`, `chat.open()`, and `chat.close()` to control visibility:
|
||||
|
||||
```tsx
|
||||
import { chat } from '@apache-superset/core';
|
||||
|
||||
export default function ChatTrigger() {
|
||||
return (
|
||||
<button onClick={() => (chat.isOpen() ? chat.close() : chat.open())}>
|
||||
💬
|
||||
</button>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
You can also subscribe to open/close events from any component:
|
||||
|
||||
```tsx
|
||||
useEffect(() => {
|
||||
const { dispose } = chat.onDidOpen(() => console.log('chat opened'));
|
||||
return dispose;
|
||||
}, []);
|
||||
```
|
||||
|
||||
## Changing the Display Mode
|
||||
|
||||
Call `chat.setDisplayMode` to switch between `'floating'` and `'panel'` modes. In your panel component, subscribe to `onDidChangeDisplayMode` to react to changes (including those triggered by the user):
|
||||
|
||||
```tsx
|
||||
import { useState, useEffect } from 'react';
|
||||
import { chat } from '@apache-superset/core';
|
||||
|
||||
export default function ChatPanel() {
|
||||
const [mode, setMode] = useState(chat.getDisplayMode());
|
||||
|
||||
useEffect(() => {
|
||||
const { dispose } = chat.onDidChangeDisplayMode(m => setMode(m));
|
||||
return dispose;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div style={{ height: mode === 'panel' ? '100%' : '80vh' }}>
|
||||
<button onClick={() => chat.setDisplayMode(mode === 'panel' ? 'floating' : 'panel')}>
|
||||
{mode === 'panel' ? 'Float' : 'Dock'}
|
||||
</button>
|
||||
{/* message list and input */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Chat API Reference
|
||||
|
||||
All methods are available on the `chat` namespace from `@apache-superset/core`:
|
||||
|
||||
| Method / Event | Description |
|
||||
|----------------|-------------|
|
||||
| `registerChat(descriptor, trigger, panel)` | Register a chat extension. Returns a `Disposable` to unregister. |
|
||||
| `open()` | Open the chat panel. No-op if already open or no registration. |
|
||||
| `close()` | Close the chat panel. |
|
||||
| `isOpen()` | Returns `true` if the panel is currently open. |
|
||||
| `getDisplayMode()` | Returns the current display mode (`'floating'` or `'panel'`). |
|
||||
| `setDisplayMode(mode)` | Switch between `'floating'` and `'panel'` mode. |
|
||||
| `onDidOpen(listener)` | Subscribe to panel open events. Returns a `Disposable`. |
|
||||
| `onDidClose(listener)` | Subscribe to panel close events. Returns a `Disposable`. |
|
||||
| `onDidChangeDisplayMode(listener)` | Subscribe to display mode changes. Returns a `Disposable`. |
|
||||
| `onDidRegisterChat(listener)` | Subscribe to registration events. |
|
||||
| `onDidUnregisterChat(listener)` | Subscribe to unregistration events. |
|
||||
| `onDidResizePanel(listener)` | Subscribe to panel resize events (panel mode only). Not all hosts provide a resizer — do not rely on this firing. Returns a `Disposable`. |
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[Contribution Types](../contribution-types.md)** — Explore other contribution types
|
||||
- **[Development](../development.md)** — Set up your development environment
|
||||
@@ -47,8 +47,6 @@ module.exports = {
|
||||
collapsed: true,
|
||||
items: [
|
||||
'extensions/extension-points/sqllab',
|
||||
'extensions/extension-points/editors',
|
||||
'extensions/extension-points/chat',
|
||||
],
|
||||
},
|
||||
'extensions/development',
|
||||
|
||||
@@ -63,6 +63,109 @@ pytest tests/unit_tests/
|
||||
pytest tests/integration_tests/
|
||||
```
|
||||
|
||||
## Testing Alerts & Reports with Celery and MailHog
|
||||
|
||||
The Alerts & Reports feature relies on Celery for task scheduling and execution. To test it locally, you need Redis (message broker), Celery Beat (scheduler), a Celery Worker (executor), and an SMTP server to receive email notifications.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Redis running on `localhost:6379`
|
||||
- [MailHog](https://github.com/mailhog/MailHog) installed (a local SMTP server with a web UI for viewing caught emails)
|
||||
|
||||
### superset_config.py
|
||||
|
||||
Your `CeleryConfig` **must** include `beat_schedule`. When you define a custom `CeleryConfig` class in `superset_config.py`, it replaces the default entirely. If you omit `beat_schedule`, Celery Beat will start but never schedule any report tasks.
|
||||
|
||||
```python
|
||||
from celery.schedules import crontab
|
||||
from superset.tasks.types import ExecutorType
|
||||
|
||||
REDIS_HOST = "localhost"
|
||||
REDIS_PORT = "6379"
|
||||
|
||||
class CeleryConfig:
|
||||
broker_url = f"redis://{REDIS_HOST}:{REDIS_PORT}/0"
|
||||
result_backend = f"redis://{REDIS_HOST}:{REDIS_PORT}/0"
|
||||
broker_connection_retry_on_startup = True
|
||||
imports = (
|
||||
"superset.sql_lab",
|
||||
"superset.tasks.scheduler",
|
||||
"superset.tasks.thumbnails",
|
||||
"superset.tasks.cache",
|
||||
)
|
||||
worker_prefetch_multiplier = 10
|
||||
task_acks_late = True
|
||||
beat_schedule = {
|
||||
"reports.scheduler": {
|
||||
"task": "reports.scheduler",
|
||||
"schedule": crontab(minute="*", hour="*"),
|
||||
},
|
||||
"reports.prune_log": {
|
||||
"task": "reports.prune_log",
|
||||
"schedule": crontab(minute=0, hour=0),
|
||||
},
|
||||
}
|
||||
|
||||
CELERY_CONFIG = CeleryConfig
|
||||
|
||||
# SMTP settings pointing to MailHog
|
||||
SMTP_HOST = "localhost"
|
||||
SMTP_PORT = 1025
|
||||
SMTP_STARTTLS = False
|
||||
SMTP_SSL = False
|
||||
SMTP_USER = ""
|
||||
SMTP_PASSWORD = ""
|
||||
SMTP_MAIL_FROM = "superset@localhost"
|
||||
|
||||
# Must match where your frontend is running
|
||||
WEBDRIVER_BASEURL = "http://localhost:9000/"
|
||||
|
||||
ALERT_REPORTS_EXECUTE_AS = [ExecutorType.OWNER]
|
||||
|
||||
FEATURE_FLAGS = {
|
||||
"ALERT_REPORTS": True,
|
||||
# Recommended for better screenshot support (WebGL/DeckGL charts)
|
||||
"PLAYWRIGHT_REPORTS_AND_THUMBNAILS": True,
|
||||
}
|
||||
```
|
||||
|
||||
:::note
|
||||
Do not include `"superset.tasks.async_queries"` in `CeleryConfig.imports` unless you need Global Async Queries. That module accesses `current_app.config` at import time and will crash the worker with a "Working outside of application context" error.
|
||||
:::
|
||||
|
||||
### Starting the Services
|
||||
|
||||
Start MailHog, then Celery Beat and Worker in separate terminals:
|
||||
|
||||
```bash
|
||||
# Terminal 1 - MailHog (SMTP on :1025, Web UI on :8025)
|
||||
MailHog
|
||||
|
||||
# Terminal 2 - Celery Beat (scheduler)
|
||||
celery --app=superset.tasks.celery_app:app beat --loglevel=info
|
||||
|
||||
# Terminal 3 - Celery Worker (executor)
|
||||
celery --app=superset.tasks.celery_app:app worker --concurrency=1 --loglevel=info
|
||||
```
|
||||
|
||||
Use `--concurrency=1` to limit resource usage on your dev machine.
|
||||
|
||||
### Verifying the Setup
|
||||
|
||||
1. **Beat** should log `Scheduler: Sending due task reports.scheduler (reports.scheduler)` once per minute
|
||||
2. **Worker** should log `Scheduling alert <name> eta: <timestamp>` for each active report
|
||||
3. Create a test report in **Settings > Alerts & Reports** with a `* * * * *` cron schedule
|
||||
4. Check **http://localhost:8025** (MailHog web UI) for the email within 1-2 minutes
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
| Problem | Solution |
|
||||
|---|---|
|
||||
| Beat shows no output | Ensure `beat_schedule` is defined in your `CeleryConfig` and `--loglevel=info` is set |
|
||||
| "Report Schedule is still working, refusing to re-compute" | Previous executions are stuck. Reset with: `UPDATE report_schedule SET last_state = 'Not triggered' WHERE id = <id>;` |
|
||||
| Task backlog overwhelming the worker | Flush Redis: `redis-cli FLUSHDB`, then restart Beat and Worker |
|
||||
| Screenshot timeout | Ensure your frontend dev server is running and `WEBDRIVER_BASEURL` matches its URL |
|
||||
|
||||
---
|
||||
|
||||
*This documentation is under active development. Check back soon for updates!*
|
||||
|
||||
@@ -63,12 +63,6 @@ by clicking the **Connect** button in the bottom right corner of the modal windo
|
||||
|
||||
Congratulations, you've just added a new data source in Superset!
|
||||
|
||||
### Sharing a Database Connection
|
||||
|
||||
When adding a new database, you can share the connection with other Superset users. Shared connections appear in other users' database lists, making it easier to collaborate on the same data without requiring each user to configure the same connection separately.
|
||||
|
||||
To share a connection, enable the **Share connection with other users** option in the **Advanced** tab of the database connection modal before saving. You can change sharing settings later by editing the database connection.
|
||||
|
||||
### Registering a new table
|
||||
|
||||
Now that you’ve configured a data source, you can select specific tables (called **Datasets** in Superset)
|
||||
@@ -86,22 +80,6 @@ we register the **cleaned_sales_data** table from the **examples** database.
|
||||
|
||||
To finish, click the **Add** button in the bottom right corner. You should now see your dataset in the list of datasets.
|
||||
|
||||
### Organizing Datasets into Folders
|
||||
|
||||
The Datasets list view supports **folders** for organizing datasets into groups. To create and manage folders:
|
||||
|
||||
1. In the **Datasets** list, click the **Folders** panel on the left sidebar.
|
||||
2. Click **+ New Folder** to create a top-level folder, or drag an existing folder to nest it.
|
||||
3. Drag dataset rows onto a folder to move them in, or right-click a dataset and select **Move to folder**.
|
||||
|
||||
Folders are per-user organizational aids — they do not affect dataset access permissions or how other users see the datasets.
|
||||
|
||||
### Uploading Files via the OS File Manager (PWA)
|
||||
|
||||
When Superset is installed as a **Progressive Web App (PWA)** from your browser, your operating system will offer Superset as an option when opening CSV, Excel (`.xls`/`.xlsx`), and Parquet files. Double-clicking or right-clicking a supported file and selecting "Open with Superset" navigates directly to the upload workflow for that file.
|
||||
|
||||
To install Superset as a PWA, look for the install icon in your browser's address bar (Chrome, Edge) when visiting your Superset instance over HTTPS. PWA installation requires HTTPS and a valid manifest — your admin needs to confirm the app manifest is served correctly.
|
||||
|
||||
### Customizing column properties
|
||||
|
||||
Now that you've registered your dataset, you can configure column properties
|
||||
@@ -256,112 +234,6 @@ For example, when running the local development build, the following will disabl
|
||||
Top Nav and remove the Filter Bar:
|
||||
`http://localhost:8088/superset/dashboard/my-dashboard/?standalone=1&show_filters=0`
|
||||
|
||||
### Table Chart Features
|
||||
|
||||
The **Table** chart type has several advanced capabilities worth knowing:
|
||||
|
||||
#### Conditional Formatting
|
||||
|
||||
Conditional formatting rules highlight cells based on their values. Rules can be applied to:
|
||||
- **Numeric columns** — color cells above/below a threshold, or use a gradient across a range
|
||||
- **String columns** — highlight cells matching specific text values or patterns
|
||||
- **Boolean columns** — color cells that are `true` or `false`, or `null`/`not null`
|
||||
|
||||
Each rule has a **"Use gradient"** toggle: enabled applies a varying opacity (lighter = further from threshold), disabled applies a solid fill at full opacity regardless of value.
|
||||
|
||||
#### HTML Rendering in Table Cells
|
||||
|
||||
Table chart cells can render raw HTML, enabling rich formatting such as hyperlinks, colored badges, and icons directly in the data. Enable this per-column in the chart's **Column Configuration** panel by toggling **Render HTML**.
|
||||
|
||||
:::caution
|
||||
Only enable HTML rendering for columns sourced from data you control. Rendering untrusted HTML can expose users to cross-site scripting (XSS) risks.
|
||||
:::
|
||||
|
||||
#### Column Header Tooltips
|
||||
|
||||
Column headers display a tooltip with the column's **Description** from the dataset editor when the user hovers over them. Keep dataset column descriptions up to date to improve chart discoverability.
|
||||
|
||||
#### Display Controls
|
||||
|
||||
In dashboard view mode (without entering Edit mode), charts with configurable display options expose a **Display Controls** panel accessible from the chart's context menu. This surfaces controls such as Time Grain, Time Column, and layer visibility for applicable chart types — making it easy to adjust a chart's view without going to Explore.
|
||||
### AG Grid Interactive Table
|
||||
|
||||
The **AG Grid Interactive Table** chart type is Superset's fully-featured data grid, suitable for large paginated datasets where the standard Table chart is not enough.
|
||||
|
||||
#### Server-Side Column Filters
|
||||
|
||||
AG Grid supports server-side column filters that query the full dataset — not just the loaded page. Filters are applied before data is sent to the browser, so results are correct even across millions of rows.
|
||||
|
||||
**Available filter types:**
|
||||
|
||||
| Column type | Filter options |
|
||||
|---|---|
|
||||
| Text | Contains, equals, starts with, ends with |
|
||||
| Number | Equals, not equal, less than, greater than, between |
|
||||
| Date | Before, after, between, blank |
|
||||
| Set | Select from a list of distinct values |
|
||||
|
||||
**AND / OR logic:** Each column supports combining multiple conditions with AND or OR. Filters from different columns are always combined with AND.
|
||||
|
||||
**Interaction with pagination:** Server-side filters run as WHERE clauses in the underlying SQL query, so pagination always operates over the already-filtered result set.
|
||||
|
||||
#### Time Shift (Time Comparison)
|
||||
|
||||
AG Grid Interactive Table supports **Time Shift** (time comparison), matching the behavior of the standard Table chart. In the **Advanced Analytics** → **Time Comparison** section of the chart configuration, enter a shift expression (e.g., `1 year ago`, `minus 7 days`) to add comparison columns showing values from the offset period. Dashboard-level time range overrides apply to both the base and comparison periods.
|
||||
|
||||
### Dynamic Currency Formatting
|
||||
|
||||
Chart metric values can display currencies dynamically rather than using a fixed currency code. To enable:
|
||||
|
||||
1. Open the dataset editor for your dataset (**Datasets → Edit**).
|
||||
2. In the **Advanced** tab, set **Currency Code Column** to the name of a column in your dataset that contains ISO 4217 currency codes (e.g., `USD`, `EUR`, `GBP`).
|
||||
3. In the Explore chart configuration, open the metric's **Number format** section and select **Auto-detect** for currency.
|
||||
|
||||
When Auto-detect is active, each row uses the currency code from the designated column, so a single chart can display values in multiple currencies — each formatted correctly for its currency.
|
||||
|
||||
### ECharts Option Editor
|
||||
|
||||
For ECharts-based chart types (line, bar, area, scatter, pie, and others), Explore includes an advanced **ECharts Option Editor** that accepts raw JSON overrides for the underlying ECharts configuration.
|
||||
|
||||
Access it via the **Customize** tab → **ECharts Options** section at the bottom of the panel. The JSON you enter is deep-merged on top of Superset's generated ECharts config, so you can override specific options without rewriting the entire config.
|
||||
|
||||
**Example:** override the legend position and add a custom title:
|
||||
|
||||
```json
|
||||
{
|
||||
"legend": { "orient": "vertical", "right": "5%", "top": "middle" },
|
||||
"title": { "text": "My Custom Title", "left": "center" }
|
||||
}
|
||||
```
|
||||
|
||||
:::caution
|
||||
ECharts option overrides bypass Superset's validation layer. Invalid option keys are silently ignored by ECharts. Overrides that conflict with Superset-generated options (e.g., `series`) may produce unexpected results.
|
||||
:::
|
||||
|
||||
### Table Chart: Exporting Filtered Data
|
||||
|
||||
When the **Search Box** is visible in a Table chart, the **Download** action exports only the rows currently visible after the search filter is applied — not the full underlying dataset. This matches the visual output and is intentional. To export the full dataset regardless of search state, use the **Download as CSV** option from the chart's three-dot menu in the dashboard or from the Explore chart toolbar before applying a search filter.
|
||||
|
||||
### Sharing a Specific Tab
|
||||
|
||||
When a dashboard has tabs, each tab gets its own shareable URL. Navigate to the tab you want to share and copy the URL from your browser's address bar — the tab anchor is encoded in the URL so that anyone opening the link lands directly on that tab.
|
||||
|
||||
### Auto-Refresh
|
||||
|
||||
Dashboards can be configured to refresh automatically at a fixed interval without user interaction. Open a dashboard, click the **⋮** (more options) menu in the top-right, and select **Set auto-refresh interval**. Choose an interval (e.g., every 10 seconds, 1 minute, or 10 minutes). The setting is per-session and resets when you close the tab.
|
||||
|
||||
:::note
|
||||
Auto-refresh triggers a full data reload for all charts on the dashboard. For dashboards with expensive queries, choose longer intervals to avoid overloading your database.
|
||||
:::
|
||||
|
||||
### Last Queried Timestamp
|
||||
|
||||
Charts can display a "Last queried at" timestamp showing when the chart data was last fetched. This is useful on auto-refreshing dashboards to confirm data freshness. Enable it in **Dashboard Properties → Styling → Show last queried time**.
|
||||
|
||||
### Saving a Chart to a Specific Tab
|
||||
|
||||
When saving or adding a chart to a dashboard from Explore, you can select which tab it should land on using the tab tree-select dropdown in the "Add to dashboard" modal.
|
||||
|
||||
:::resources
|
||||
- [Dashboard Customization](https://docs.preset.io/docs/dashboard-customization) - Advanced dashboard styling and layout options
|
||||
- [Blog: BI Dashboard Best Practices](https://preset.io/blog/bi-dashboard-best-practices/)
|
||||
|
||||
@@ -1,130 +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.
|
||||
*/}
|
||||
|
||||
---
|
||||
title: Embedding Superset
|
||||
sidebar_position: 6
|
||||
---
|
||||
|
||||
# Embedding Superset
|
||||
|
||||
Superset dashboards can be embedded directly in host applications using the `@superset-ui/embedded-sdk` package.
|
||||
|
||||
:::info Prerequisites
|
||||
- The `EMBEDDED_SUPERSET` feature flag must be enabled.
|
||||
- The embedding domain and allowed origins must be configured by an admin.
|
||||
:::
|
||||
|
||||
## Quick Start
|
||||
|
||||
Install the SDK:
|
||||
|
||||
```bash
|
||||
npm install @superset-ui/embedded-sdk
|
||||
```
|
||||
|
||||
Embed a dashboard:
|
||||
|
||||
```javascript
|
||||
import { embedDashboard } from '@superset-ui/embedded-sdk';
|
||||
|
||||
embedDashboard({
|
||||
id: 'dashboard-uuid-here', // from Dashboard → Embed
|
||||
supersetDomain: 'https://superset.example.com',
|
||||
mountPoint: document.getElementById('superset-container'),
|
||||
fetchGuestToken: () => fetchTokenFromYourBackend(),
|
||||
dashboardUiConfig: {
|
||||
hideTitle: true,
|
||||
filters: { expanded: false },
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
`fetchGuestToken` must return a **guest token** obtained from your server by calling Superset's `/api/v1/security/guest_token/` endpoint with a service account. Do not call this endpoint from client-side code.
|
||||
|
||||
---
|
||||
|
||||
## Callbacks
|
||||
|
||||
### `resolvePermalinkUrl`
|
||||
|
||||
When a user copies a permalink from an embedded dashboard, Superset generates a URL on its own domain. In an embedded context this URL is usually not meaningful to the host application's users — the dashboard is rendered inside the host app, not at the Superset URL.
|
||||
|
||||
The `resolvePermalinkUrl` callback lets the host app intercept permalink generation and return a URL on the host domain instead:
|
||||
|
||||
```javascript
|
||||
embedDashboard({
|
||||
id: 'my-dashboard-uuid',
|
||||
supersetDomain: 'https://superset.example.com',
|
||||
mountPoint: document.getElementById('superset-container'),
|
||||
fetchGuestToken: () => fetchGuestToken(),
|
||||
/**
|
||||
* Called when Superset generates a permalink.
|
||||
* @param {Object} args - { key: string } — the permalink key
|
||||
* @returns {string | null} - your host URL, or null to use Superset's default
|
||||
*/
|
||||
resolvePermalinkUrl: ({ key }) => {
|
||||
return `https://myapp.example.com/dashboard?permalink=${key}`;
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
If the callback returns `null` or is not provided, Superset uses its own permalink URL as a fallback.
|
||||
|
||||
---
|
||||
|
||||
## Feature Flags for Embedded Mode
|
||||
|
||||
### `DISABLE_EMBEDDED_SUPERSET_LOGOUT`
|
||||
|
||||
Hides the logout button when Superset is embedded in a host application. This is useful when the host application manages the session lifecycle and you do not want users to accidentally log out of the embedded Superset session:
|
||||
|
||||
```python
|
||||
# superset_config.py
|
||||
FEATURE_FLAGS = {
|
||||
"EMBEDDED_SUPERSET": True,
|
||||
"DISABLE_EMBEDDED_SUPERSET_LOGOUT": True,
|
||||
}
|
||||
```
|
||||
|
||||
When enabled, the **Logout** menu item is removed from the user avatar dropdown in the embedded view. The session can still be invalidated server-side by revoking the guest token.
|
||||
|
||||
### `EMBEDDED_SUPERSET`
|
||||
|
||||
Must be `True` to enable the embedded SDK and the guest token endpoint. Without this flag, `embedDashboard` will fail to load.
|
||||
|
||||
---
|
||||
|
||||
## URL Parameters
|
||||
|
||||
The following URL parameters can be passed through the `urlParams` option in `dashboardUiConfig` or appended to the embedded iframe URL:
|
||||
|
||||
| Parameter | Values | Effect |
|
||||
|-----------|--------|--------|
|
||||
| `standalone` | `0`, `1`, `2`, `3` | `0`: normal; `1`: hide nav; `2`: hide nav + title; `3`: hide nav + title + tabs |
|
||||
| `show_filters` | `0`, `1` | Show or hide the native filter bar |
|
||||
| `expand_filters` | `0`, `1` | Start with filter bar expanded or collapsed |
|
||||
|
||||
---
|
||||
|
||||
## Security Notes
|
||||
|
||||
- **Guest tokens expire** — their lifetime is controlled by the `GUEST_TOKEN_JWT_EXP_SECONDS` config (default: 5 minutes). Refresh tokens before they expire using a token refresh mechanism in your host app.
|
||||
- **Row-level security** — pass `rls` rules in the guest token request to restrict which rows are visible to the embedded user.
|
||||
- **Allowed domains** — restrict which host origins can embed a dashboard by setting **Allowed Domains** per-dashboard in the *Embed* settings modal. Superset checks the request's `Referer` header against this list before serving the embedded view; an empty list allows any origin, so configure this explicitly for production.
|
||||
@@ -329,27 +329,6 @@ various options in this section, refer to the
|
||||
Lastly, save your chart as Tutorial Resample and add it to the Tutorial Dashboard. Go to the
|
||||
tutorial dashboard to see the four charts side by side and compare the different outputs.
|
||||
|
||||
### SQL Lab Tips
|
||||
|
||||
**Schema and table browser**: The left-side table browser uses a collapsible treeview — click a schema to expand its tables, and click a table to see its columns and sample data inline. This makes navigating large schemas much faster than the previous flat list.
|
||||
|
||||
**Find in editor**: Press **Ctrl+F** (or **Cmd+F** on Mac) to open the Monaco find/replace widget inside the SQL editor without leaving the editor.
|
||||
|
||||
**Resizable panels**: The dividers between the SQL editor, schema browser, and results pane are draggable. Adjust them to match your workflow and screen size.
|
||||
|
||||
**Dialect-aware Format SQL**: The **Format SQL** button applies the SQL dialect of the currently selected database — Trino, Presto, MySQL, PostgreSQL, etc. — rather than a generic formatter. Switch to a different database in the toolbar and re-format to get dialect-specific output. Jinja template syntax (`{{ }}`, `{% %}`) is preserved during formatting and will not cause format errors.
|
||||
|
||||
### Time Range Natural Language Expressions
|
||||
|
||||
The **Custom** time range picker accepts natural language expressions alongside specific dates:
|
||||
|
||||
- **Relative**: `Last 7 days`, `Last month`, `Last quarter`, `Last year`
|
||||
- **Anchored**: `previous calendar week`, `previous calendar month`
|
||||
- **"First of" expressions**: `first day of this week`, `first day of this month`, `first day of this quarter`, `first day of this year`, `first week of this year`
|
||||
- **Offsets**: `30 days ago`, `1 year ago`, `next week`
|
||||
|
||||
These expressions are evaluated at query time, so saved charts always display data relative to the current date.
|
||||
|
||||
:::resources
|
||||
- [Chart Walkthroughs](https://docs.preset.io/docs/chart-walkthroughs) - Detailed guides for most chart types
|
||||
- [Blog: Why Apache ECharts is the Future of Apache Superset](https://preset.io/blog/2021-4-1-why-echarts/)
|
||||
|
||||
@@ -33,29 +33,6 @@ SQL templating must be enabled by your administrator via the `ENABLE_TEMPLATE_PR
|
||||
For advanced configuration options, see the [SQL Templating Configuration Guide](/admin-docs/configuration/sql-templating).
|
||||
:::
|
||||
|
||||
## Using Jinja in Calculated Columns
|
||||
|
||||
Jinja template macros are available in calculated column expressions in the dataset editor — not just in SQL Lab queries and virtual datasets. This allows column expressions to reference the current user or dynamic context.
|
||||
|
||||
**Example: User-scoped calculated column**
|
||||
|
||||
```sql
|
||||
CASE WHEN sales_rep = '{{ current_username() }}' THEN amount ELSE 0 END
|
||||
```
|
||||
|
||||
**Example: Conditional display based on role**
|
||||
|
||||
Because `current_user_roles()` returns a Python list, test role membership with a Jinja
|
||||
conditional at template time rather than matching against the list's string representation:
|
||||
|
||||
```sql
|
||||
{% if 'Finance' in current_user_roles() %}revenue{% else %}NULL{% endif %} AS finance_revenue
|
||||
```
|
||||
|
||||
:::note
|
||||
The `ENABLE_TEMPLATE_PROCESSING` feature flag must be enabled by your administrator for Jinja in calculated columns to work.
|
||||
:::
|
||||
|
||||
## Basic Usage
|
||||
|
||||
Jinja templates use double curly braces `{{ }}` for expressions and `{% %}` for logic blocks.
|
||||
@@ -266,7 +243,6 @@ Using `remove_filter=True` applies the filter in the inner query for better perf
|
||||
- Use `|tojson` to serialize arrays as JSON strings
|
||||
- Test queries with explicit parameter values before relying on filter context
|
||||
- For complex templating needs, ask your administrator about custom Jinja macros
|
||||
- **Format SQL is Jinja-aware**: The "Format SQL" button in SQL Lab correctly preserves `{{ }}` and `{% %}` template syntax and applies your selected database's SQL dialect when formatting.
|
||||
|
||||
:::resources
|
||||
- [Admin Guide: SQL Templating Configuration](/admin-docs/configuration/sql-templating)
|
||||
|
||||
@@ -55,10 +55,9 @@ Ask your AI assistant to browse what's available in your Superset instance:
|
||||
|
||||
Describe the visualization you want and AI creates it for you:
|
||||
|
||||
- **Preview-first workflow** -- by default AI generates an Explore link so you can review the chart before it is saved. Say "save it" to commit permanently
|
||||
- **Create charts from natural language** -- describe what you want to see and AI picks the right chart type, metrics, and dimensions
|
||||
- **Preview before saving** -- `generate_chart` defaults to `save_chart=False`, showing the chart in Explore before it's committed. Ask AI to save once you're satisfied.
|
||||
- **Modify existing charts** -- `update_chart` also supports preview mode so you can review changes before saving (update filters, change chart types, add metrics)
|
||||
- **Preview before saving** -- AI generates a preview so you can review before committing
|
||||
- **Modify existing charts** -- update filters, change chart types, add metrics
|
||||
- **Get Explore links** -- open any chart in Superset's Explore view for further refinement
|
||||
|
||||
**Example prompts:**
|
||||
@@ -66,16 +65,6 @@ Describe the visualization you want and AI creates it for you:
|
||||
> "Update chart 42 to use a line chart instead"
|
||||
> "Give me a link to explore this chart further"
|
||||
|
||||
:::tip Preview-first workflow
|
||||
Charts are **not saved by default**. The workflow is intentionally iterative:
|
||||
|
||||
1. **Explore** — AI generates an Explore link so you can see the chart before it exists in Superset
|
||||
2. **Iterate** — ask the AI to adjust the chart; changes are previewed without touching the database
|
||||
3. **Save** — when you're happy, say "save it" and the chart is permanently stored
|
||||
|
||||
To skip the preview and save immediately, include "and save it" in your prompt.
|
||||
:::
|
||||
|
||||
### Create Dashboards
|
||||
|
||||
Build dashboards from a collection of charts:
|
||||
@@ -87,40 +76,16 @@ Build dashboards from a collection of charts:
|
||||
> "Create a dashboard called 'Q4 Sales Overview' with charts 10, 15, and 22"
|
||||
> "Add the revenue trend chart to the executive dashboard"
|
||||
|
||||
### Browse Databases
|
||||
|
||||
Discover what database connections are configured in your Superset instance:
|
||||
|
||||
- **List databases** -- see all database connections you have access to
|
||||
- **Get database details** -- name, backend type (PostgreSQL, Snowflake, etc.), and connection status
|
||||
|
||||
**Example prompts:**
|
||||
> "What databases are connected to Superset?"
|
||||
> "Show me details about the data warehouse connection"
|
||||
|
||||
### Create Virtual Datasets
|
||||
|
||||
Build ad-hoc SQL datasets that can be used as the basis for charts:
|
||||
|
||||
- **Create virtual datasets** -- write a SQL query and save it as a reusable dataset
|
||||
- **Use immediately in charts** -- the returned dataset ID can be passed directly to chart creation
|
||||
|
||||
**Example prompts:**
|
||||
> "Create a dataset from: SELECT region, SUM(revenue) as total_revenue FROM orders GROUP BY region"
|
||||
> "Make a virtual dataset called 'monthly_signups' from the users table filtered to last 12 months"
|
||||
|
||||
### Run SQL Queries
|
||||
|
||||
Execute SQL directly through your AI assistant:
|
||||
|
||||
- **Run queries** -- execute SQL with full Superset RBAC enforcement (you can only query data your roles allow)
|
||||
- **Open SQL Lab** -- get a link to SQL Lab pre-populated with a query, ready to run and explore
|
||||
- **Save queries** -- save a SQL query to SQL Lab's Saved Queries for later reuse
|
||||
|
||||
**Example prompts:**
|
||||
> "Run this query: SELECT region, SUM(revenue) FROM sales GROUP BY region"
|
||||
> "Open SQL Lab with a query to show the top 10 customers by order count"
|
||||
> "Save this query as 'Weekly Revenue Report'"
|
||||
|
||||
### Analyze Chart Data
|
||||
|
||||
@@ -230,59 +195,27 @@ Ask your admin for the MCP server URL and any authentication tokens you need.
|
||||
|
||||
## Available Tools Reference
|
||||
|
||||
### Exploration & Discovery
|
||||
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `health_check` | Verify the MCP server is running and connected |
|
||||
| `get_instance_info` | Get instance statistics (dataset, chart, dashboard counts) |
|
||||
| `get_schema` | Discover available charts, datasets, and dashboards with schema info |
|
||||
|
||||
### Datasets
|
||||
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `list_datasets` | List datasets with filtering and search |
|
||||
| `get_dataset_info` | Get dataset metadata (columns, metrics, filters) |
|
||||
| `create_virtual_dataset` | Create a virtual dataset from a SQL query |
|
||||
|
||||
### Charts
|
||||
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `list_charts` | List charts with filtering and search |
|
||||
| `get_chart_info` | Get chart metadata and configuration |
|
||||
| `get_chart_data` | Retrieve chart data (JSON, CSV, or Excel) |
|
||||
| `get_chart_preview` | Generate a chart preview (URL, ASCII, table, or Vega-Lite) |
|
||||
| `get_chart_type_schema` | Get the configuration schema for a chart type |
|
||||
| `generate_chart` | Create a new chart from a specification (defaults to preview mode — review before saving) |
|
||||
| `update_chart` | Modify an existing chart's configuration (pass `generate_preview=False` to persist immediately instead of returning a preview URL) |
|
||||
| `generate_chart` | Create a new chart from a specification |
|
||||
| `update_chart` | Modify an existing chart's configuration |
|
||||
| `update_chart_preview` | Update a cached chart preview without saving |
|
||||
| `generate_explore_link` | Generate an Explore URL for interactive visualization |
|
||||
|
||||
### Dashboards
|
||||
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `list_dashboards` | List dashboards with filtering and search |
|
||||
| `get_dashboard_info` | Get dashboard metadata and layout |
|
||||
| `generate_dashboard` | Create a new dashboard with specified charts |
|
||||
| `add_chart_to_existing_dashboard` | Add a chart to an existing dashboard |
|
||||
|
||||
### SQL
|
||||
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `execute_sql` | Run a SQL query with RBAC enforcement |
|
||||
| `save_sql_query` | Persist a SQL query to SQL Lab's saved queries |
|
||||
| `open_sql_lab_with_context` | Open SQL Lab with a pre-populated query |
|
||||
|
||||
### Databases
|
||||
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `list_databases` | List configured database connections |
|
||||
| `get_database_info` | Get details about a specific database connection |
|
||||
| `generate_explore_link` | Generate an Explore URL for interactive visualization |
|
||||
|
||||
---
|
||||
|
||||
|
||||
+16
-16
@@ -40,7 +40,7 @@
|
||||
"version:remove:components": "node scripts/manage-versions.mjs remove components"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^6.1.0",
|
||||
"@ant-design/icons": "^6.1.1",
|
||||
"@docusaurus/core": "3.9.2",
|
||||
"@docusaurus/faster": "^3.9.2",
|
||||
"@docusaurus/plugin-client-redirects": "3.9.2",
|
||||
@@ -55,22 +55,22 @@
|
||||
"@fontsource/inter": "^5.2.8",
|
||||
"@mdx-js/react": "^3.1.1",
|
||||
"@saucelabs/theme-github-codeblock": "^0.3.0",
|
||||
"@storybook/addon-docs": "^8.6.17",
|
||||
"@storybook/addon-docs": "^8.6.18",
|
||||
"@storybook/blocks": "^8.6.15",
|
||||
"@storybook/channels": "^8.6.17",
|
||||
"@storybook/client-logger": "^8.6.17",
|
||||
"@storybook/components": "^8.6.17",
|
||||
"@storybook/core": "^8.6.17",
|
||||
"@storybook/core-events": "^8.6.17",
|
||||
"@storybook/channels": "^8.6.18",
|
||||
"@storybook/client-logger": "^8.6.18",
|
||||
"@storybook/components": "^8.6.18",
|
||||
"@storybook/core": "^8.6.18",
|
||||
"@storybook/core-events": "^8.6.18",
|
||||
"@storybook/csf": "^0.1.13",
|
||||
"@storybook/docs-tools": "^8.6.17",
|
||||
"@storybook/preview-api": "^8.6.17",
|
||||
"@storybook/docs-tools": "^8.6.18",
|
||||
"@storybook/preview-api": "^8.6.18",
|
||||
"@storybook/theming": "^8.6.15",
|
||||
"@superset-ui/core": "^0.20.4",
|
||||
"@swc/core": "^1.15.17",
|
||||
"antd": "^6.3.1",
|
||||
"baseline-browser-mapping": "^2.10.0",
|
||||
"caniuse-lite": "^1.0.30001775",
|
||||
"@swc/core": "^1.15.21",
|
||||
"antd": "^6.3.5",
|
||||
"baseline-browser-mapping": "^2.10.13",
|
||||
"caniuse-lite": "^1.0.30001786",
|
||||
"docusaurus-plugin-openapi-docs": "^4.6.0",
|
||||
"docusaurus-theme-openapi-docs": "^4.6.0",
|
||||
"js-yaml": "^4.1.1",
|
||||
@@ -85,8 +85,8 @@
|
||||
"react-table": "^7.8.0",
|
||||
"remark-import-partial": "^0.0.2",
|
||||
"reselect": "^5.1.1",
|
||||
"storybook": "^8.6.17",
|
||||
"swagger-ui-react": "^5.32.0",
|
||||
"storybook": "^8.6.18",
|
||||
"swagger-ui-react": "^5.32.1",
|
||||
"swc-loader": "^0.2.7",
|
||||
"tinycolor2": "^1.4.2",
|
||||
"unist-util-visit": "^5.1.0"
|
||||
@@ -106,7 +106,7 @@
|
||||
"globals": "^17.4.0",
|
||||
"prettier": "^3.8.1",
|
||||
"typescript": "~5.9.3",
|
||||
"typescript-eslint": "^8.56.1",
|
||||
"typescript-eslint": "^8.58.0",
|
||||
"webpack": "^5.105.4"
|
||||
},
|
||||
"browserslist": {
|
||||
|
||||
Vendored
+12
@@ -51,6 +51,18 @@
|
||||
"lifecycle": "development",
|
||||
"description": "Enable Superset extensions for custom functionality without modifying core"
|
||||
},
|
||||
{
|
||||
"name": "FAB_API_KEY_ENABLED",
|
||||
"default": false,
|
||||
"lifecycle": "development",
|
||||
"description": "Enable API key authentication via FAB SecurityManager When enabled, users can create/manage API keys in the User Info page"
|
||||
},
|
||||
{
|
||||
"name": "GRANULAR_EXPORT_CONTROLS",
|
||||
"default": false,
|
||||
"lifecycle": "development",
|
||||
"description": "Enable granular export controls (can_export_data, can_export_image, can_copy_clipboard) instead of the single can_csv permission"
|
||||
},
|
||||
{
|
||||
"name": "MATRIXIFY",
|
||||
"default": false,
|
||||
|
||||
+635
-803
File diff suppressed because it is too large
Load Diff
Generated
-6
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"name": "superset-release",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {}
|
||||
}
|
||||
@@ -372,7 +372,6 @@ unfixable = []
|
||||
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)
|
||||
"scripts/*" = ["TID251"]
|
||||
"setup.py" = ["TID251"]
|
||||
"superset/config.py" = ["TID251"]
|
||||
|
||||
@@ -86,7 +86,7 @@ cron-descriptor==1.4.5
|
||||
# via apache-superset (pyproject.toml)
|
||||
croniter==6.0.0
|
||||
# via apache-superset (pyproject.toml)
|
||||
cryptography==46.0.5
|
||||
cryptography==46.0.6
|
||||
# via
|
||||
# apache-superset (pyproject.toml)
|
||||
# paramiko
|
||||
@@ -120,7 +120,7 @@ flask==2.3.3
|
||||
# flask-session
|
||||
# flask-sqlalchemy
|
||||
# flask-wtf
|
||||
flask-appbuilder==5.0.2
|
||||
flask-appbuilder==5.2.0
|
||||
# via
|
||||
# apache-superset (pyproject.toml)
|
||||
# apache-superset-core
|
||||
@@ -209,7 +209,7 @@ mako==1.3.10
|
||||
# via
|
||||
# apache-superset (pyproject.toml)
|
||||
# alembic
|
||||
markdown==3.8
|
||||
markdown==3.8.1
|
||||
# via apache-superset (pyproject.toml)
|
||||
markdown-it-py==3.0.0
|
||||
# via rich
|
||||
@@ -279,7 +279,7 @@ parsedatetime==2.6
|
||||
# via apache-superset (pyproject.toml)
|
||||
pgsanity==0.2.9
|
||||
# via apache-superset (pyproject.toml)
|
||||
pillow==11.3.0
|
||||
pillow==12.1.1
|
||||
# via apache-superset (pyproject.toml)
|
||||
platformdirs==4.3.8
|
||||
# via requests-cache
|
||||
@@ -293,7 +293,7 @@ prompt-toolkit==3.0.51
|
||||
# via click-repl
|
||||
pyarrow==16.1.0
|
||||
# via apache-superset (pyproject.toml)
|
||||
pyasn1==0.6.2
|
||||
pyasn1==0.6.3
|
||||
# via
|
||||
# pyasn1-modules
|
||||
# rsa
|
||||
@@ -309,9 +309,9 @@ pydantic-core==2.33.2
|
||||
# via pydantic
|
||||
pygeohash==3.2.2
|
||||
# via apache-superset (pyproject.toml)
|
||||
pygments==2.19.1
|
||||
pygments==2.20.0
|
||||
# via rich
|
||||
pyjwt==2.10.1
|
||||
pyjwt==2.12.0
|
||||
# via
|
||||
# apache-superset (pyproject.toml)
|
||||
# flask-appbuilder
|
||||
|
||||
@@ -178,7 +178,7 @@ croniter==6.0.0
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# apache-superset
|
||||
cryptography==46.0.5
|
||||
cryptography==46.0.6
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# apache-superset
|
||||
@@ -259,7 +259,7 @@ flask==2.3.3
|
||||
# flask-sqlalchemy
|
||||
# flask-testing
|
||||
# flask-wtf
|
||||
flask-appbuilder==5.0.2
|
||||
flask-appbuilder==5.2.0
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# apache-superset
|
||||
@@ -508,7 +508,7 @@ mako==1.3.10
|
||||
# -c requirements/base-constraint.txt
|
||||
# alembic
|
||||
# apache-superset
|
||||
markdown==3.8
|
||||
markdown==3.8.1
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# apache-superset
|
||||
@@ -655,7 +655,7 @@ pgsanity==0.2.9
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# apache-superset
|
||||
pillow==11.3.0
|
||||
pillow==12.1.1
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# apache-superset
|
||||
@@ -716,7 +716,7 @@ pyarrow==16.1.0
|
||||
# apache-superset
|
||||
# db-dtypes
|
||||
# pandas-gbq
|
||||
pyasn1==0.6.2
|
||||
pyasn1==0.6.3
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# pyasn1-modules
|
||||
@@ -756,7 +756,7 @@ pygeohash==3.2.2
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# apache-superset
|
||||
pygments==2.19.1
|
||||
pygments==2.20.0
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# rich
|
||||
@@ -764,7 +764,7 @@ pyhive==0.7.0
|
||||
# via apache-superset
|
||||
pyinstrument==4.4.0
|
||||
# via apache-superset
|
||||
pyjwt==2.10.1
|
||||
pyjwt==2.12.0
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# apache-superset
|
||||
@@ -996,6 +996,8 @@ tabulate==0.9.0
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# apache-superset
|
||||
tomli-w==1.2.0
|
||||
# via apache-superset-extensions-cli
|
||||
tomlkit==0.13.3
|
||||
# via pylint
|
||||
tqdm==4.67.1
|
||||
|
||||
+1
-11
@@ -45,17 +45,7 @@ if [ ${#js_ts_files[@]} -gt 0 ]; then
|
||||
# Skip custom OXC build in pre-commit for speed
|
||||
export SKIP_CUSTOM_OXC=true
|
||||
# Use quiet mode in pre-commit to reduce noise (only show errors)
|
||||
# Capture output so we can treat "No files found" (all files ignored by
|
||||
# ignorePatterns) as success rather than a false-positive failure.
|
||||
output=$(npx oxlint --config oxlint.json --fix --quiet "${js_ts_files[@]}" 2>&1) || {
|
||||
if echo "$output" | grep -q "No files found"; then
|
||||
echo "No files to lint after applying ignore patterns"
|
||||
exit 0
|
||||
fi
|
||||
echo "$output" >&2
|
||||
exit 1
|
||||
}
|
||||
[ -n "$output" ] && echo "$output"
|
||||
npx oxlint --config oxlint.json --fix --quiet "${js_ts_files[@]}"
|
||||
else
|
||||
echo "No JavaScript/TypeScript files to lint"
|
||||
fi
|
||||
|
||||
@@ -18,20 +18,20 @@
|
||||
|
||||
[project]
|
||||
name = "apache-superset-core"
|
||||
version = "0.1.0rc3"
|
||||
version = "0.1.0rc1"
|
||||
description = "Core Python package for building Apache Superset backend extensions and integrations"
|
||||
readme = "README.md"
|
||||
authors = [
|
||||
{ name = "Apache Software Foundation", email = "dev@superset.apache.org" },
|
||||
]
|
||||
license = "Apache-2.0"
|
||||
license-files = ["LICENSE.txt"]
|
||||
license = { file="LICENSE.txt" }
|
||||
requires-python = ">=3.10"
|
||||
keywords = ["superset", "apache", "analytics", "business-intelligence", "extensions", "visualization"]
|
||||
classifiers = [
|
||||
"Development Status :: 3 - Alpha",
|
||||
"Environment :: Web Environment",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: Apache Software License",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
|
||||
Generated
+12
-76
@@ -7025,9 +7025,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
|
||||
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
@@ -7121,15 +7121,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/randombytes": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
||||
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
||||
@@ -7306,26 +7297,6 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/schema-utils": {
|
||||
"version": "4.3.3",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz",
|
||||
@@ -7355,15 +7326,6 @@
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/serialize-javascript": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
|
||||
"integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"randombytes": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/shallow-clone": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz",
|
||||
@@ -7590,15 +7552,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/terser-webpack-plugin": {
|
||||
"version": "5.3.16",
|
||||
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz",
|
||||
"integrity": "sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==",
|
||||
"version": "5.4.0",
|
||||
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.4.0.tgz",
|
||||
"integrity": "sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/trace-mapping": "^0.3.25",
|
||||
"jest-worker": "^27.4.5",
|
||||
"schema-utils": "^4.3.0",
|
||||
"serialize-javascript": "^6.0.2",
|
||||
"terser": "^5.31.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -13159,9 +13120,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
|
||||
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
|
||||
"dev": true
|
||||
},
|
||||
"pify": {
|
||||
@@ -13220,15 +13181,6 @@
|
||||
"integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==",
|
||||
"dev": true
|
||||
},
|
||||
"randombytes": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
||||
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"safe-buffer": "^5.1.0"
|
||||
}
|
||||
},
|
||||
"react-is": {
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
||||
@@ -13359,12 +13311,6 @@
|
||||
"integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==",
|
||||
"dev": true
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"dev": true
|
||||
},
|
||||
"schema-utils": {
|
||||
"version": "4.3.3",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz",
|
||||
@@ -13383,15 +13329,6 @@
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||
"dev": true
|
||||
},
|
||||
"serialize-javascript": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
|
||||
"integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"randombytes": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"shallow-clone": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz",
|
||||
@@ -13563,15 +13500,14 @@
|
||||
}
|
||||
},
|
||||
"terser-webpack-plugin": {
|
||||
"version": "5.3.16",
|
||||
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz",
|
||||
"integrity": "sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==",
|
||||
"version": "5.4.0",
|
||||
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.4.0.tgz",
|
||||
"integrity": "sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@jridgewell/trace-mapping": "^0.3.25",
|
||||
"jest-worker": "^27.4.5",
|
||||
"schema-utils": "^4.3.0",
|
||||
"serialize-javascript": "^6.0.2",
|
||||
"terser": "^5.31.1"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -66,7 +66,7 @@ export type EmbedDashboardParams = {
|
||||
iframeTitle?: string;
|
||||
/** additional iframe sandbox attributes ex (allow-top-navigation, allow-popups-to-escape-sandbox) **/
|
||||
iframeSandboxExtras?: string[];
|
||||
/** Additional Permissions Policy features for the iframe's `allow` attribute (e.g., ['camera', 'microphone']). `fullscreen` and `clipboard-write` are granted by default. **/
|
||||
/** iframe allow attribute for Permissions Policy (e.g., ['clipboard-write', 'fullscreen']) **/
|
||||
iframeAllowExtras?: string[];
|
||||
/** force a specific refererPolicy to be used in the iframe request **/
|
||||
referrerPolicy?: ReferrerPolicy;
|
||||
@@ -233,14 +233,9 @@ export async function embedDashboard({
|
||||
iframe.src = `${supersetDomain}/embedded/${id}${urlParamsString}`;
|
||||
iframe.title = iframeTitle;
|
||||
iframe.style.background = 'transparent';
|
||||
// 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('; '));
|
||||
if (iframeAllowExtras.length > 0) {
|
||||
iframe.setAttribute('allow', iframeAllowExtras.join('; '));
|
||||
}
|
||||
//@ts-ignore
|
||||
mountPoint.replaceChildren(iframe);
|
||||
log('placed the iframe');
|
||||
|
||||
@@ -17,20 +17,20 @@
|
||||
|
||||
[project]
|
||||
name = "apache-superset-extensions-cli"
|
||||
version = "0.1.0rc3"
|
||||
version = "0.1.0rc2"
|
||||
description = "Official command-line interface for building, bundling, and managing Apache Superset extensions"
|
||||
readme = "README.md"
|
||||
authors = [
|
||||
{ name = "Apache Software Foundation", email = "dev@superset.apache.org" },
|
||||
]
|
||||
license = "Apache-2.0"
|
||||
license-files = ["LICENSE.txt"]
|
||||
license = { file="LICENSE.txt" }
|
||||
requires-python = ">=3.10"
|
||||
keywords = ["superset", "apache", "cli", "extensions", "analytics", "business-intelligence", "development-tools"]
|
||||
classifiers = [
|
||||
"Development Status :: 3 - Alpha",
|
||||
"Environment :: Console",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: Apache Software License",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
@@ -49,6 +49,7 @@ dependencies = [
|
||||
"jinja2>=3.1.6",
|
||||
"semver>=3.0.4",
|
||||
"tomli>=2.2.1; python_version < '3.11'",
|
||||
"tomli-w>=1.2.0",
|
||||
"watchdog>=6.0.0",
|
||||
]
|
||||
|
||||
|
||||
@@ -50,6 +50,8 @@ from superset_extensions_cli.utils import (
|
||||
validate_display_name,
|
||||
validate_publisher,
|
||||
validate_technical_name,
|
||||
write_json,
|
||||
write_toml,
|
||||
)
|
||||
|
||||
REMOTE_ENTRY_REGEX = re.compile(r"^remoteEntry\..+\.js$")
|
||||
@@ -292,6 +294,7 @@ def app() -> None:
|
||||
|
||||
@app.command()
|
||||
def validate() -> None:
|
||||
"""Validate the extension structure and metadata consistency."""
|
||||
validate_npm()
|
||||
|
||||
cwd = Path.cwd()
|
||||
@@ -372,12 +375,167 @@ def validate() -> None:
|
||||
click.secho(" Convention requires: frontend/src/index.tsx", fg="yellow")
|
||||
sys.exit(1)
|
||||
|
||||
# Validate version and license consistency across extension.json, frontend, and backend
|
||||
mismatches: list[str] = []
|
||||
frontend_pkg_path = cwd / "frontend" / "package.json"
|
||||
frontend_pkg = None
|
||||
if frontend_pkg_path.is_file():
|
||||
frontend_pkg = read_json(frontend_pkg_path)
|
||||
if frontend_pkg:
|
||||
if frontend_pkg.get("version") != extension.version:
|
||||
mismatches.append(
|
||||
f" frontend/package.json version: {frontend_pkg.get('version')} "
|
||||
f"(expected {extension.version})"
|
||||
)
|
||||
if extension.license and frontend_pkg.get("license") != extension.license:
|
||||
mismatches.append(
|
||||
f" frontend/package.json license: {frontend_pkg.get('license')} "
|
||||
f"(expected {extension.license})"
|
||||
)
|
||||
|
||||
backend_pyproject_path = cwd / "backend" / "pyproject.toml"
|
||||
if backend_pyproject_path.is_file():
|
||||
backend_pyproject = read_toml(backend_pyproject_path)
|
||||
if backend_pyproject:
|
||||
project = backend_pyproject.get("project", {})
|
||||
if project.get("version") != extension.version:
|
||||
mismatches.append(
|
||||
f" backend/pyproject.toml version: {project.get('version')} "
|
||||
f"(expected {extension.version})"
|
||||
)
|
||||
if extension.license and project.get("license") != extension.license:
|
||||
mismatches.append(
|
||||
f" backend/pyproject.toml license: {project.get('license')} "
|
||||
f"(expected {extension.license})"
|
||||
)
|
||||
|
||||
if mismatches:
|
||||
click.secho("❌ Metadata mismatch detected:", err=True, fg="red")
|
||||
for mismatch in mismatches:
|
||||
click.secho(mismatch, err=True, fg="red")
|
||||
click.secho(
|
||||
"Run `superset-extensions update` to sync from extension.json.",
|
||||
fg="yellow",
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
click.secho("✅ Validation successful", fg="green")
|
||||
|
||||
|
||||
@app.command()
|
||||
@click.option(
|
||||
"--version",
|
||||
"version_opt",
|
||||
is_flag=False,
|
||||
flag_value="__prompt__",
|
||||
default=None,
|
||||
help="Set a new version. Prompts for value if none given.",
|
||||
)
|
||||
@click.option(
|
||||
"--license",
|
||||
"license_opt",
|
||||
is_flag=False,
|
||||
flag_value="__prompt__",
|
||||
default=None,
|
||||
help="Set a new license. Prompts for value if none given.",
|
||||
)
|
||||
def update(version_opt: str | None, license_opt: str | None) -> None:
|
||||
"""Update derived and generated files in the extension project."""
|
||||
cwd = Path.cwd()
|
||||
|
||||
extension_json_path = cwd / "extension.json"
|
||||
extension_data = read_json(extension_json_path)
|
||||
if not extension_data:
|
||||
click.secho("❌ extension.json not found.", err=True, fg="red")
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
extension = ExtensionConfig.model_validate(extension_data)
|
||||
except Exception as e:
|
||||
click.secho(f"❌ Invalid extension.json: {e}", err=True, fg="red")
|
||||
sys.exit(1)
|
||||
|
||||
# Resolve version: prompt if flag used without value
|
||||
if version_opt == "__prompt__":
|
||||
version_opt = click.prompt("Version", default=extension.version)
|
||||
target_version = (
|
||||
version_opt
|
||||
if version_opt and version_opt != extension.version
|
||||
else extension.version
|
||||
)
|
||||
|
||||
# Resolve license: prompt if flag used without value
|
||||
if license_opt == "__prompt__":
|
||||
license_opt = click.prompt("License", default=extension.license or "")
|
||||
target_license = (
|
||||
license_opt
|
||||
if license_opt and license_opt != extension.license
|
||||
else extension.license
|
||||
)
|
||||
|
||||
updated: list[str] = []
|
||||
|
||||
# Update extension.json if version or license changed
|
||||
ext_changed = False
|
||||
if version_opt and version_opt != extension.version:
|
||||
extension_data["version"] = target_version
|
||||
ext_changed = True
|
||||
if license_opt and license_opt != extension.license:
|
||||
extension_data["license"] = target_license
|
||||
ext_changed = True
|
||||
if ext_changed:
|
||||
try:
|
||||
ExtensionConfig.model_validate(extension_data)
|
||||
except Exception as e:
|
||||
click.secho(f"❌ Invalid value: {e}", err=True, fg="red")
|
||||
sys.exit(1)
|
||||
write_json(extension_json_path, extension_data)
|
||||
updated.append("extension.json")
|
||||
|
||||
# Update frontend/package.json
|
||||
frontend_pkg_path = cwd / "frontend" / "package.json"
|
||||
if frontend_pkg_path.is_file():
|
||||
frontend_pkg = read_json(frontend_pkg_path)
|
||||
if frontend_pkg:
|
||||
pkg_changed = False
|
||||
if frontend_pkg.get("version") != target_version:
|
||||
frontend_pkg["version"] = target_version
|
||||
pkg_changed = True
|
||||
if target_license and frontend_pkg.get("license") != target_license:
|
||||
frontend_pkg["license"] = target_license
|
||||
pkg_changed = True
|
||||
if pkg_changed:
|
||||
write_json(frontend_pkg_path, frontend_pkg)
|
||||
updated.append("frontend/package.json")
|
||||
|
||||
# Update backend/pyproject.toml
|
||||
backend_pyproject_path = cwd / "backend" / "pyproject.toml"
|
||||
if backend_pyproject_path.is_file():
|
||||
backend_pyproject = read_toml(backend_pyproject_path)
|
||||
if backend_pyproject:
|
||||
project = backend_pyproject.setdefault("project", {})
|
||||
toml_changed = False
|
||||
if project.get("version") != target_version:
|
||||
project["version"] = target_version
|
||||
toml_changed = True
|
||||
if target_license and project.get("license") != target_license:
|
||||
project["license"] = target_license
|
||||
toml_changed = True
|
||||
if toml_changed:
|
||||
write_toml(backend_pyproject_path, backend_pyproject)
|
||||
updated.append("backend/pyproject.toml")
|
||||
|
||||
if updated:
|
||||
for path in updated:
|
||||
click.secho(f"✅ Updated {path}", fg="green")
|
||||
else:
|
||||
click.secho("✅ All files already up to date.", fg="green")
|
||||
|
||||
|
||||
@app.command()
|
||||
@click.pass_context
|
||||
def build(ctx: click.Context) -> None:
|
||||
"""Build extension assets."""
|
||||
ctx.invoke(validate)
|
||||
cwd = Path.cwd()
|
||||
frontend_dir = cwd / "frontend"
|
||||
@@ -413,6 +571,7 @@ def build(ctx: click.Context) -> None:
|
||||
)
|
||||
@click.pass_context
|
||||
def bundle(ctx: click.Context, output: Path | None) -> None:
|
||||
"""Package the extension into a .supx file."""
|
||||
ctx.invoke(build)
|
||||
|
||||
cwd = Path.cwd()
|
||||
@@ -453,6 +612,7 @@ def bundle(ctx: click.Context, output: Path | None) -> None:
|
||||
@app.command()
|
||||
@click.pass_context
|
||||
def dev(ctx: click.Context) -> None:
|
||||
"""Automatically rebuild the extension as files change."""
|
||||
cwd = Path.cwd()
|
||||
frontend_dir = cwd / "frontend"
|
||||
backend_dir = cwd / "backend"
|
||||
@@ -647,6 +807,7 @@ def init(
|
||||
frontend_opt: bool | None,
|
||||
backend_opt: bool | None,
|
||||
) -> None:
|
||||
"""Scaffold a new extension project."""
|
||||
# Get extension names with graceful validation
|
||||
names = prompt_for_extension_info(display_name_opt, publisher_opt, name_opt)
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@ import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import tomli_w
|
||||
|
||||
from superset_core.extensions.constants import (
|
||||
DISPLAY_NAME_PATTERN,
|
||||
PUBLISHER_PATTERN,
|
||||
@@ -109,6 +111,14 @@ def read_json(path: Path) -> dict[str, Any] | None:
|
||||
return json.loads(path.read_text())
|
||||
|
||||
|
||||
def write_json(path: Path, data: dict[str, Any]) -> None:
|
||||
path.write_text(json.dumps(data, indent=2) + "\n")
|
||||
|
||||
|
||||
def write_toml(path: Path, data: dict[str, Any]) -> None:
|
||||
path.write_text(tomli_w.dumps(data))
|
||||
|
||||
|
||||
def _normalize_for_identifiers(name: str) -> str:
|
||||
"""
|
||||
Normalize display name to clean lowercase words.
|
||||
|
||||
@@ -17,10 +17,12 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
import tomli_w
|
||||
from click.testing import CliRunner
|
||||
|
||||
|
||||
@@ -138,3 +140,69 @@ def extension_setup_for_bundling():
|
||||
(backend_dir / "__init__.py").write_text("# init")
|
||||
|
||||
return _setup
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def extension_with_versions():
|
||||
"""Create an extension directory structure with configurable versions and licenses."""
|
||||
|
||||
def _create(
|
||||
base_path: Path,
|
||||
ext_version: str = "1.0.0",
|
||||
frontend_version: str | None = None,
|
||||
backend_version: str | None = None,
|
||||
ext_license: str | None = "Apache-2.0",
|
||||
frontend_license: str | None = None,
|
||||
backend_license: str | None = None,
|
||||
) -> None:
|
||||
extension_json = {
|
||||
"publisher": "test-org",
|
||||
"name": "test-extension",
|
||||
"displayName": "Test Extension",
|
||||
"version": ext_version,
|
||||
"permissions": [],
|
||||
}
|
||||
if ext_license is not None:
|
||||
extension_json["license"] = ext_license
|
||||
(base_path / "extension.json").write_text(json.dumps(extension_json))
|
||||
|
||||
if frontend_version is not None:
|
||||
frontend_dir = base_path / "frontend"
|
||||
frontend_dir.mkdir(exist_ok=True)
|
||||
(frontend_dir / "src").mkdir(exist_ok=True)
|
||||
(frontend_dir / "src" / "index.tsx").write_text("// entry")
|
||||
pkg = {
|
||||
"name": "@test-org/test-extension",
|
||||
"version": frontend_version,
|
||||
}
|
||||
if frontend_license is not None:
|
||||
pkg["license"] = frontend_license
|
||||
elif ext_license is not None:
|
||||
pkg["license"] = ext_license
|
||||
(frontend_dir / "package.json").write_text(json.dumps(pkg, indent=2))
|
||||
|
||||
if backend_version is not None:
|
||||
backend_dir = base_path / "backend"
|
||||
backend_dir.mkdir(exist_ok=True)
|
||||
src_dir = backend_dir / "src" / "test_org" / "test_extension"
|
||||
src_dir.mkdir(parents=True, exist_ok=True)
|
||||
(src_dir / "entrypoint.py").write_text("# entry")
|
||||
project = {
|
||||
"name": "test-org-test-extension",
|
||||
"version": backend_version,
|
||||
}
|
||||
if backend_license is not None:
|
||||
project["license"] = backend_license
|
||||
elif ext_license is not None:
|
||||
project["license"] = ext_license
|
||||
pyproject = {
|
||||
"project": project,
|
||||
"tool": {
|
||||
"apache_superset_extensions": {
|
||||
"build": {"include": ["src/**/*.py"]}
|
||||
}
|
||||
},
|
||||
}
|
||||
(backend_dir / "pyproject.toml").write_text(tomli_w.dumps(pyproject))
|
||||
|
||||
return _create
|
||||
|
||||
@@ -121,7 +121,7 @@ def test_build_command_success_flow(
|
||||
# Setup mocks
|
||||
mock_rebuild_frontend.return_value = "remoteEntry.abc123.js"
|
||||
mock_read_toml.return_value = {
|
||||
"project": {"name": "test"},
|
||||
"project": {"name": "test", "version": "1.0.0"},
|
||||
"tool": {
|
||||
"apache_superset_extensions": {
|
||||
"build": {"include": ["src/test_org/test_extension/**/*.py"]}
|
||||
@@ -162,7 +162,7 @@ def test_build_command_handles_frontend_build_failure(
|
||||
# Setup mocks
|
||||
mock_rebuild_frontend.return_value = None # Indicates failure
|
||||
mock_read_toml.return_value = {
|
||||
"project": {"name": "test"},
|
||||
"project": {"name": "test", "version": "1.0.0"},
|
||||
"tool": {
|
||||
"apache_superset_extensions": {
|
||||
"build": {"include": ["src/test_org/test_extension/**/*.py"]}
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
# 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 pytest
|
||||
from superset_extensions_cli.cli import app
|
||||
from superset_extensions_cli.utils import read_json, read_toml
|
||||
|
||||
|
||||
@pytest.mark.cli
|
||||
def test_update_syncs_versions(
|
||||
cli_runner, isolated_filesystem, extension_with_versions
|
||||
):
|
||||
"""Test update syncs frontend and backend versions from extension.json."""
|
||||
extension_with_versions(
|
||||
isolated_filesystem,
|
||||
ext_version="2.0.0",
|
||||
frontend_version="1.0.0",
|
||||
backend_version="1.0.0",
|
||||
)
|
||||
|
||||
result = cli_runner.invoke(app, ["update"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "Updated frontend/package.json" in result.output
|
||||
assert "Updated backend/pyproject.toml" in result.output
|
||||
|
||||
frontend_pkg = read_json(isolated_filesystem / "frontend" / "package.json")
|
||||
assert frontend_pkg["version"] == "2.0.0"
|
||||
|
||||
backend_pyproject = read_toml(isolated_filesystem / "backend" / "pyproject.toml")
|
||||
assert backend_pyproject["project"]["version"] == "2.0.0"
|
||||
|
||||
|
||||
@pytest.mark.cli
|
||||
def test_update_noop_when_all_match(
|
||||
cli_runner, isolated_filesystem, extension_with_versions
|
||||
):
|
||||
"""Test update reports no changes when everything already matches."""
|
||||
extension_with_versions(
|
||||
isolated_filesystem,
|
||||
ext_version="1.0.0",
|
||||
frontend_version="1.0.0",
|
||||
backend_version="1.0.0",
|
||||
)
|
||||
|
||||
result = cli_runner.invoke(app, ["update"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "All files already up to date" in result.output
|
||||
|
||||
|
||||
@pytest.mark.cli
|
||||
def test_update_fails_without_extension_json(cli_runner, isolated_filesystem):
|
||||
"""Test update fails when extension.json is missing."""
|
||||
result = cli_runner.invoke(app, ["update"])
|
||||
|
||||
assert result.exit_code != 0
|
||||
assert "extension.json not found" in result.output
|
||||
|
||||
|
||||
@pytest.mark.cli
|
||||
def test_update_with_version_flag(
|
||||
cli_runner, isolated_filesystem, extension_with_versions
|
||||
):
|
||||
"""Test --version updates extension.json first, then syncs all files."""
|
||||
extension_with_versions(
|
||||
isolated_filesystem,
|
||||
ext_version="1.0.0",
|
||||
frontend_version="1.0.0",
|
||||
backend_version="1.0.0",
|
||||
)
|
||||
|
||||
result = cli_runner.invoke(app, ["update", "--version", "3.0.0"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "Updated extension.json" in result.output
|
||||
assert "Updated frontend/package.json" in result.output
|
||||
assert "Updated backend/pyproject.toml" in result.output
|
||||
|
||||
ext = read_json(isolated_filesystem / "extension.json")
|
||||
assert ext["version"] == "3.0.0"
|
||||
|
||||
frontend_pkg = read_json(isolated_filesystem / "frontend" / "package.json")
|
||||
assert frontend_pkg["version"] == "3.0.0"
|
||||
|
||||
backend_pyproject = read_toml(isolated_filesystem / "backend" / "pyproject.toml")
|
||||
assert backend_pyproject["project"]["version"] == "3.0.0"
|
||||
|
||||
|
||||
@pytest.mark.cli
|
||||
def test_update_with_license_flag(
|
||||
cli_runner, isolated_filesystem, extension_with_versions
|
||||
):
|
||||
"""Test --license updates license across all files."""
|
||||
extension_with_versions(
|
||||
isolated_filesystem,
|
||||
ext_version="1.0.0",
|
||||
frontend_version="1.0.0",
|
||||
backend_version="1.0.0",
|
||||
ext_license="Apache-2.0",
|
||||
)
|
||||
|
||||
result = cli_runner.invoke(app, ["update", "--license", "MIT"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "Updated extension.json" in result.output
|
||||
assert "Updated frontend/package.json" in result.output
|
||||
assert "Updated backend/pyproject.toml" in result.output
|
||||
|
||||
ext = read_json(isolated_filesystem / "extension.json")
|
||||
assert ext["license"] == "MIT"
|
||||
|
||||
frontend_pkg = read_json(isolated_filesystem / "frontend" / "package.json")
|
||||
assert frontend_pkg["license"] == "MIT"
|
||||
|
||||
backend_pyproject = read_toml(isolated_filesystem / "backend" / "pyproject.toml")
|
||||
assert backend_pyproject["project"]["license"] == "MIT"
|
||||
|
||||
|
||||
@pytest.mark.cli
|
||||
def test_update_version_prompt_default(
|
||||
cli_runner, isolated_filesystem, extension_with_versions
|
||||
):
|
||||
"""Test --version without value prompts with current version as default."""
|
||||
extension_with_versions(
|
||||
isolated_filesystem,
|
||||
ext_version="1.0.0",
|
||||
frontend_version="1.0.0",
|
||||
backend_version="1.0.0",
|
||||
)
|
||||
|
||||
# Hit enter to accept default — nothing should change
|
||||
result = cli_runner.invoke(app, ["update", "--version"], input="\n")
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "All files already up to date" in result.output
|
||||
|
||||
|
||||
@pytest.mark.cli
|
||||
def test_update_rejects_invalid_version(
|
||||
cli_runner, isolated_filesystem, extension_with_versions
|
||||
):
|
||||
"""Test --version with an invalid semver string exits with error."""
|
||||
extension_with_versions(
|
||||
isolated_filesystem,
|
||||
ext_version="1.0.0",
|
||||
)
|
||||
|
||||
result = cli_runner.invoke(app, ["update", "--version", "not-a-version"])
|
||||
|
||||
assert result.exit_code != 0
|
||||
assert "Invalid value" in result.output
|
||||
|
||||
# Verify extension.json was not modified
|
||||
ext = read_json(isolated_filesystem / "extension.json")
|
||||
assert ext["version"] == "1.0.0"
|
||||
@@ -207,3 +207,66 @@ def test_validate_npm_with_empty_version_output_raises_error(mock_run, mock_whic
|
||||
# semver.compare will raise ValueError for empty version
|
||||
with pytest.raises(ValueError):
|
||||
validate_npm()
|
||||
|
||||
|
||||
# Version Consistency Tests
|
||||
@pytest.mark.cli
|
||||
def test_validate_fails_on_version_mismatch(
|
||||
cli_runner, isolated_filesystem, extension_with_versions
|
||||
):
|
||||
"""Test validate fails when frontend/backend versions differ from extension.json."""
|
||||
extension_with_versions(
|
||||
isolated_filesystem,
|
||||
ext_version="2.0.0",
|
||||
frontend_version="1.0.0",
|
||||
backend_version="1.0.0",
|
||||
)
|
||||
|
||||
with patch("superset_extensions_cli.cli.validate_npm"):
|
||||
result = cli_runner.invoke(app, ["validate"])
|
||||
|
||||
assert result.exit_code != 0
|
||||
assert "Metadata mismatch" in result.output
|
||||
assert "superset-extensions update" in result.output
|
||||
|
||||
|
||||
@pytest.mark.cli
|
||||
def test_validate_passes_with_matching_versions(
|
||||
cli_runner, isolated_filesystem, extension_with_versions
|
||||
):
|
||||
"""Test validate passes when all versions match extension.json."""
|
||||
extension_with_versions(
|
||||
isolated_filesystem,
|
||||
ext_version="1.0.0",
|
||||
frontend_version="1.0.0",
|
||||
backend_version="1.0.0",
|
||||
)
|
||||
|
||||
with patch("superset_extensions_cli.cli.validate_npm"):
|
||||
result = cli_runner.invoke(app, ["validate"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "Validation successful" in result.output
|
||||
|
||||
|
||||
@pytest.mark.cli
|
||||
def test_validate_fails_on_license_mismatch(
|
||||
cli_runner, isolated_filesystem, extension_with_versions
|
||||
):
|
||||
"""Test validate fails when frontend/backend licenses differ from extension.json."""
|
||||
extension_with_versions(
|
||||
isolated_filesystem,
|
||||
ext_version="1.0.0",
|
||||
frontend_version="1.0.0",
|
||||
backend_version="1.0.0",
|
||||
ext_license="Apache-2.0",
|
||||
frontend_license="MIT",
|
||||
backend_license="MIT",
|
||||
)
|
||||
|
||||
with patch("superset_extensions_cli.cli.validate_npm"):
|
||||
result = cli_runner.invoke(app, ["validate"])
|
||||
|
||||
assert result.exit_code != 0
|
||||
assert "Metadata mismatch" in result.output
|
||||
assert "license" in result.output
|
||||
|
||||
@@ -20,7 +20,7 @@ from __future__ import annotations
|
||||
import json
|
||||
|
||||
import pytest
|
||||
from superset_extensions_cli.utils import read_json, read_toml
|
||||
from superset_extensions_cli.utils import read_json, read_toml, write_json, write_toml
|
||||
|
||||
|
||||
# Read JSON Tests
|
||||
@@ -269,3 +269,32 @@ def test_read_toml_with_permission_denied(isolated_filesystem):
|
||||
toml_file.chmod(0o644)
|
||||
except (OSError, PermissionError):
|
||||
pass
|
||||
|
||||
|
||||
# Write JSON Tests
|
||||
@pytest.mark.unit
|
||||
def test_write_json_round_trip(isolated_filesystem):
|
||||
"""Test write_json then read_json round-trip preserves content."""
|
||||
data = {"name": "test-extension", "version": "2.0.0", "nested": {"key": "value"}}
|
||||
json_file = isolated_filesystem / "output.json"
|
||||
|
||||
write_json(json_file, data)
|
||||
result = read_json(json_file)
|
||||
|
||||
assert result == data
|
||||
|
||||
|
||||
# Write TOML Tests
|
||||
@pytest.mark.unit
|
||||
def test_write_toml_round_trip(isolated_filesystem):
|
||||
"""Test write_toml then read_toml round-trip preserves content."""
|
||||
data = {
|
||||
"project": {"name": "test-package", "version": "1.0.0"},
|
||||
"tool": {"apache_superset_extensions": {"build": {"include": ["src/**/*.py"]}}},
|
||||
}
|
||||
toml_file = isolated_filesystem / "output.toml"
|
||||
|
||||
write_toml(toml_file, data)
|
||||
result = read_toml(toml_file)
|
||||
|
||||
assert result == data
|
||||
|
||||
@@ -127,7 +127,6 @@ module.exports = {
|
||||
},
|
||||
plugins: [
|
||||
'import',
|
||||
'file-progress',
|
||||
'lodash',
|
||||
'theme-colors',
|
||||
'icons',
|
||||
|
||||
@@ -1,171 +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 { CHART_LIST } from 'cypress/utils/urls';
|
||||
import { setGridMode, toggleBulkSelect } from 'cypress/utils';
|
||||
import {
|
||||
setFilter,
|
||||
interceptBulkDelete,
|
||||
interceptUpdate,
|
||||
interceptDelete,
|
||||
interceptFiltering,
|
||||
interceptFavoriteStatus,
|
||||
} from '../explore/utils';
|
||||
|
||||
function orderAlphabetical() {
|
||||
setFilter('Sort', 'Alphabetical');
|
||||
}
|
||||
|
||||
function openProperties() {
|
||||
cy.get('[aria-label="more"]').eq(0).click();
|
||||
cy.getBySel('chart-list-edit-option').click();
|
||||
}
|
||||
|
||||
function openMenu() {
|
||||
cy.get('[aria-label="more"]').eq(0).click();
|
||||
}
|
||||
|
||||
function confirmDelete() {
|
||||
cy.getBySel('delete-modal-input').type('DELETE');
|
||||
cy.getBySel('modal-confirm-button').click();
|
||||
}
|
||||
|
||||
function visitChartList() {
|
||||
interceptFiltering();
|
||||
interceptFavoriteStatus();
|
||||
cy.visit(CHART_LIST);
|
||||
cy.wait('@filtering');
|
||||
cy.wait('@favoriteStatus');
|
||||
}
|
||||
|
||||
describe('Charts list', () => {
|
||||
describe('common actions', () => {
|
||||
beforeEach(() => {
|
||||
visitChartList();
|
||||
});
|
||||
|
||||
it('should bulk delete correctly', () => {
|
||||
cy.createSampleCharts([0, 1, 2, 3]);
|
||||
|
||||
interceptBulkDelete();
|
||||
toggleBulkSelect();
|
||||
|
||||
// bulk deletes in card-view
|
||||
setGridMode('card');
|
||||
orderAlphabetical();
|
||||
|
||||
cy.getBySel('skeleton-card').should('not.exist');
|
||||
cy.getBySel('styled-card').contains('1 - Sample chart').click();
|
||||
cy.getBySel('styled-card').contains('2 - Sample chart').click();
|
||||
cy.getBySel('bulk-select-action').contains('Delete').click();
|
||||
confirmDelete();
|
||||
cy.wait('@bulkDelete');
|
||||
cy.getBySel('styled-card')
|
||||
.eq(1)
|
||||
.should('not.contain', '1 - Sample chart');
|
||||
cy.getBySel('styled-card')
|
||||
.eq(2)
|
||||
.should('not.contain', '2 - Sample chart');
|
||||
|
||||
// bulk deletes in list-view
|
||||
setGridMode('list');
|
||||
cy.get('.loading').should('not.exist');
|
||||
cy.getBySel('table-row').contains('3 - Sample chart').should('exist');
|
||||
cy.getBySel('table-row').contains('4 - Sample chart').should('exist');
|
||||
cy.get('[data-test="table-row"] input[type="checkbox"]').eq(0).click();
|
||||
cy.get('[data-test="table-row"] input[type="checkbox"]').eq(1).click();
|
||||
cy.getBySel('bulk-select-action').eq(0).contains('Delete').click();
|
||||
confirmDelete();
|
||||
cy.wait('@bulkDelete');
|
||||
cy.get('.loading').should('exist');
|
||||
cy.get('.loading').should('not.exist');
|
||||
cy.getBySel('table-row').eq(0).should('not.contain', '3 - Sample chart');
|
||||
cy.getBySel('table-row').eq(1).should('not.contain', '4 - Sample chart');
|
||||
});
|
||||
|
||||
it('should delete correctly in card mode', () => {
|
||||
cy.createSampleCharts([0, 1]);
|
||||
interceptDelete();
|
||||
|
||||
// deletes in card-view
|
||||
setGridMode('card');
|
||||
orderAlphabetical();
|
||||
|
||||
cy.getBySel('styled-card').contains('1 - Sample chart');
|
||||
openMenu();
|
||||
cy.getBySel('chart-list-delete-option').click();
|
||||
confirmDelete();
|
||||
cy.wait('@delete');
|
||||
cy.getBySel('styled-card')
|
||||
.contains('1 - Sample chart')
|
||||
.should('not.exist');
|
||||
});
|
||||
|
||||
it('should delete correctly in list mode', () => {
|
||||
cy.createSampleCharts([2, 3]);
|
||||
interceptDelete();
|
||||
cy.getBySel('sort-header').contains('Name').click();
|
||||
|
||||
// Modal closes immediately without this
|
||||
cy.wait(2000);
|
||||
|
||||
cy.getBySel('table-row').eq(0).contains('3 - Sample chart');
|
||||
cy.getBySel('delete').eq(0).click();
|
||||
confirmDelete();
|
||||
cy.wait('@delete');
|
||||
cy.get('.loading').should('exist');
|
||||
cy.get('.loading').should('not.exist');
|
||||
cy.getBySel('table-row').eq(0).should('not.contain', '3 - Sample chart');
|
||||
});
|
||||
|
||||
it('should edit correctly', () => {
|
||||
cy.createSampleCharts([0]);
|
||||
interceptUpdate();
|
||||
|
||||
// edits in card-view
|
||||
setGridMode('card');
|
||||
orderAlphabetical();
|
||||
cy.getBySel('skeleton-card').should('not.exist');
|
||||
cy.getBySel('styled-card').eq(0).contains('1 - Sample chart');
|
||||
|
||||
// change title
|
||||
openProperties();
|
||||
cy.getBySel('properties-modal-name-input').type(' | EDITED');
|
||||
cy.get('button:contains("Save")').click();
|
||||
cy.wait('@update');
|
||||
cy.getBySel('styled-card').eq(0).contains('1 - Sample chart | EDITED');
|
||||
|
||||
// edits in list-view
|
||||
setGridMode('list');
|
||||
// Wait for list view to fully render after mode change
|
||||
cy.get('.loading').should('not.exist');
|
||||
cy.getBySel('table-row').should('be.visible');
|
||||
// Target the specific row by chart title to avoid flakiness from row ordering
|
||||
cy.getBySel('table-row')
|
||||
.contains('1 - Sample chart | EDITED')
|
||||
.parents('[data-test="table-row"]')
|
||||
.find('[data-test="edit-alt"]')
|
||||
.click();
|
||||
cy.getBySel('properties-modal-name-input').clear();
|
||||
cy.getBySel('properties-modal-name-input').type('1 - Sample chart');
|
||||
cy.get('button:contains("Save")').click();
|
||||
cy.wait('@update');
|
||||
cy.getBySel('table-row').contains('1 - Sample chart').should('exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,42 +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 { DATASET_LIST_PATH } from 'cypress/utils/urls';
|
||||
|
||||
describe('Dataset list', () => {
|
||||
before(() => {
|
||||
cy.visit(DATASET_LIST_PATH);
|
||||
});
|
||||
|
||||
xit('should open Explore on dataset name click', () => {
|
||||
cy.intercept('**/api/v1/explore/**').as('explore');
|
||||
cy.get('[data-test="listview-table"] [data-test="internal-link"]')
|
||||
.contains('birth_names')
|
||||
.click();
|
||||
cy.wait('@explore');
|
||||
cy.get('[data-test="datasource-control"] .title-select').contains(
|
||||
'birth_names',
|
||||
);
|
||||
cy.get('.metric-option-label').first().contains('COUNT(*)');
|
||||
cy.get('.column-option-label').first().contains('ds');
|
||||
cy.get('[data-test="fast-viz-switcher"] > div:not([role="button"]')
|
||||
.contains('Table')
|
||||
.should('be.visible');
|
||||
});
|
||||
});
|
||||
@@ -23,18 +23,6 @@ export function interceptFiltering() {
|
||||
cy.intercept('GET', `**/api/v1/chart/?q=*`).as('filtering');
|
||||
}
|
||||
|
||||
export function interceptBulkDelete() {
|
||||
cy.intercept('DELETE', `**/api/v1/chart/?q=*`).as('bulkDelete');
|
||||
}
|
||||
|
||||
export function interceptDelete() {
|
||||
cy.intercept('DELETE', `**/api/v1/chart/*`).as('delete');
|
||||
}
|
||||
|
||||
export function interceptFavoriteStatus() {
|
||||
cy.intercept('GET', '**/api/v1/chart/favorite_status/*').as('favoriteStatus');
|
||||
}
|
||||
|
||||
export function interceptUpdate() {
|
||||
cy.intercept('PUT', `**/api/v1/chart/*`).as('update');
|
||||
}
|
||||
@@ -43,32 +31,13 @@ export const interceptV1ChartData = (alias = 'v1Data') => {
|
||||
cy.intercept('**/api/v1/chart/data*').as(alias);
|
||||
};
|
||||
|
||||
export function interceptExploreJson(alias = 'getJson') {
|
||||
cy.intercept('POST', `**/superset/explore_json/**`).as(alias);
|
||||
}
|
||||
|
||||
export const interceptFormDataKey = () => {
|
||||
cy.intercept('POST', '**/api/v1/explore/form_data').as('formDataKey');
|
||||
};
|
||||
|
||||
export function interceptExploreGet() {
|
||||
function interceptExploreGet() {
|
||||
cy.intercept({
|
||||
method: 'GET',
|
||||
url: /.*\/api\/v1\/explore\/\?(form_data_key|dashboard_page_id|slice_id)=.*/,
|
||||
}).as('getExplore');
|
||||
}
|
||||
|
||||
export function setFilter(filter: string, option: string) {
|
||||
interceptFiltering();
|
||||
|
||||
cy.get(`[aria-label^="${filter}"]`).first().click();
|
||||
cy.get(`.ant-select-item-option[title="${option}"]`).first().click({
|
||||
force: true,
|
||||
});
|
||||
|
||||
cy.wait('@filtering');
|
||||
}
|
||||
|
||||
export function saveChartToDashboard(chartName: string, dashboardName: string) {
|
||||
interceptDashboardGet();
|
||||
interceptUpdate();
|
||||
|
||||
@@ -25,28 +25,6 @@ export interface ChartSpec {
|
||||
viz: string;
|
||||
}
|
||||
|
||||
const viewTypeIcons = {
|
||||
card: 'appstore',
|
||||
list: 'unordered-list',
|
||||
};
|
||||
|
||||
export function setGridMode(type: 'card' | 'list') {
|
||||
const icon = viewTypeIcons[type];
|
||||
cy.get(`[aria-label="${icon}"]`).click();
|
||||
}
|
||||
|
||||
export function toggleBulkSelect() {
|
||||
cy.getBySel('bulk-select').click();
|
||||
}
|
||||
|
||||
export function clearAllInputs() {
|
||||
cy.get('body').then($body => {
|
||||
if ($body.find('.ant-select-clear').length) {
|
||||
cy.get('.ant-select-clear').click({ multiple: true, force: true });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const toSlicelike = ($chart: JQuery<HTMLElement>): Slice => {
|
||||
const chartId = $chart.attr('data-test-chart-id');
|
||||
const vizType = $chart.attr('data-test-viz-type');
|
||||
|
||||
@@ -25,8 +25,3 @@ export const SUPPORTED_CHARTS_DASHBOARD =
|
||||
'/superset/dashboard/supported_charts_dash/';
|
||||
export const TABBED_DASHBOARD = '/superset/dashboard/tabbed_dash/';
|
||||
export const DATABASE_LIST = '/databaseview/list';
|
||||
export const DATASET_LIST_PATH = 'tablemodelview/list';
|
||||
export const ALERT_LIST = '/alert/list/';
|
||||
export const REPORT_LIST = '/report/list/';
|
||||
export const LOGIN = '/login/';
|
||||
export const REGISTER = '/register/';
|
||||
|
||||
+30
-30
@@ -3167,9 +3167,9 @@
|
||||
"integrity": "sha512-Fc8Ne62jJlKHiG/ajlonC4Sd66Pq68fFwK4ihJGNZpGqboc324SQk+lRvMzpPRuJOmfrJefdG8/7JdWX4bzJ2Q=="
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"version": "1.1.13",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz",
|
||||
"integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
@@ -4673,9 +4673,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/flatted": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz",
|
||||
"integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==",
|
||||
"version": "3.4.2",
|
||||
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
|
||||
"integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
|
||||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
@@ -5809,9 +5809,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.23",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
|
||||
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
|
||||
"integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q=="
|
||||
},
|
||||
"node_modules/lodash.clonedeep": {
|
||||
"version": "4.5.0",
|
||||
@@ -6930,9 +6930,9 @@
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
|
||||
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
},
|
||||
@@ -8642,9 +8642,9 @@
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "1.10.2",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
|
||||
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
|
||||
"version": "1.10.3",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz",
|
||||
"integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
@@ -11132,9 +11132,9 @@
|
||||
"integrity": "sha512-Fc8Ne62jJlKHiG/ajlonC4Sd66Pq68fFwK4ihJGNZpGqboc324SQk+lRvMzpPRuJOmfrJefdG8/7JdWX4bzJ2Q=="
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"version": "1.1.13",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz",
|
||||
"integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==",
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
@@ -12253,9 +12253,9 @@
|
||||
}
|
||||
},
|
||||
"flatted": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz",
|
||||
"integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==",
|
||||
"version": "3.4.2",
|
||||
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
|
||||
"integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
|
||||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
@@ -13072,9 +13072,9 @@
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.23",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
|
||||
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
|
||||
"integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q=="
|
||||
},
|
||||
"lodash.clonedeep": {
|
||||
"version": "4.5.0",
|
||||
@@ -13825,9 +13825,9 @@
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
|
||||
},
|
||||
"picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
|
||||
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="
|
||||
},
|
||||
"pify": {
|
||||
"version": "2.3.0",
|
||||
@@ -15063,9 +15063,9 @@
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
},
|
||||
"yaml": {
|
||||
"version": "1.10.2",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
|
||||
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
|
||||
"version": "1.10.3",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz",
|
||||
"integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==",
|
||||
"peer": true
|
||||
},
|
||||
"yargs": {
|
||||
|
||||
@@ -69,7 +69,7 @@ module.exports = {
|
||||
],
|
||||
coverageReporters: ['lcov', 'json-summary', 'html', 'text'],
|
||||
transformIgnorePatterns: [
|
||||
'node_modules/(?!d3-(array|interpolate|color|time|scale|time-format|format)|internmap|@mapbox/tiny-sdf|remark-gfm|(?!@ngrx|(?!deck.gl)|d3-scale)|markdown-table|micromark-*.|decode-named-character-reference|character-entities|mdast-util-*.|unist-util-*.|ccount|escape-string-regexp|nanoid|uuid|@rjsf/*.|echarts|zrender|fetch-mock|pretty-ms|parse-ms|ol|@babel/runtime|@emotion|cheerio|cheerio/lib|parse5|dom-serializer|entities|htmlparser2|rehype-sanitize|hast-util-sanitize|unified|unist-.*|hast-.*|rehype-.*|remark-.*|mdast-.*|micromark-.*|parse-entities|property-information|space-separated-tokens|comma-separated-tokens|bail|devlop|zwitch|longest-streak|geostyler|geostyler-.*|react-error-boundary|react-json-tree|react-base16-styling|lodash-es|rbush|quickselect)',
|
||||
'node_modules/(?!d3-(array|interpolate|color|time|scale|time-format|format)|internmap|@mapbox/tiny-sdf|remark-gfm|(?!@ngrx|(?!deck.gl)|d3-scale)|markdown-table|micromark-*.|decode-named-character-reference|character-entities|mdast-util-*.|unist-util-*.|ccount|escape-string-regexp|nanoid|uuid|@rjsf/*.|echarts|zrender|fetch-mock|pretty-ms|parse-ms|ol|@babel/runtime|@emotion|cheerio|cheerio/lib|parse5|dom-serializer|entities|htmlparser2|rehype-sanitize|hast-util-sanitize|unified|unist-.*|hast-.*|rehype-.*|remark-.*|mdast-.*|micromark-.*|parse-entities|property-information|space-separated-tokens|comma-separated-tokens|bail|devlop|zwitch|longest-streak|geostyler|geostyler-.*|(?!geostyler)lodash|react-error-boundary|react-json-tree|react-base16-styling|lodash-es|rbush|quickselect|react-diff-viewer-continued)',
|
||||
],
|
||||
preset: 'ts-jest',
|
||||
transform: {
|
||||
|
||||
@@ -237,8 +237,7 @@
|
||||
"jsx-a11y/no-noninteractive-tabindex": "error",
|
||||
"jsx-a11y/no-redundant-roles": "error",
|
||||
"jsx-a11y/no-static-element-interactions": "off",
|
||||
// TODO: Fix missing aria-selected on tab roles
|
||||
"jsx-a11y/role-has-required-aria-props": "warn",
|
||||
"jsx-a11y/role-has-required-aria-props": "error",
|
||||
"jsx-a11y/role-supports-aria-props": "error",
|
||||
"jsx-a11y/scope": "error",
|
||||
"jsx-a11y/tabindex-no-positive": "error",
|
||||
|
||||
Generated
+5434
-5675
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "superset",
|
||||
"version": "6.1.0",
|
||||
"version": "0.0.0-dev",
|
||||
"description": "Superset is a data exploration platform designed to be visual, intuitive, and interactive.",
|
||||
"keywords": [
|
||||
"big",
|
||||
@@ -162,43 +162,42 @@
|
||||
"content-disposition": "^1.0.1",
|
||||
"d3-color": "^3.1.0",
|
||||
"d3-scale": "^4.0.2",
|
||||
"dayjs": "^1.11.19",
|
||||
"dayjs": "^1.11.20",
|
||||
"dom-to-image-more": "^3.7.2",
|
||||
"dom-to-pdf": "^0.3.2",
|
||||
"echarts": "^5.6.0",
|
||||
"fast-glob": "^3.3.2",
|
||||
"fs-extra": "^11.3.3",
|
||||
"fs-extra": "^11.3.4",
|
||||
"fuse.js": "^7.1.0",
|
||||
"geolib": "^3.3.4",
|
||||
"geostyler": "^14.1.3",
|
||||
"geolib": "^3.3.14",
|
||||
"geostyler": "^18.3.1",
|
||||
"geostyler-data": "^1.1.0",
|
||||
"geostyler-openlayers-parser": "^4.3.0",
|
||||
"geostyler-style": "7.5.0",
|
||||
"geostyler-wfs-parser": "^2.0.3",
|
||||
"google-auth-library": "^10.6.1",
|
||||
"geostyler-openlayers-parser": "^5.4.1",
|
||||
"geostyler-style": "11.0.2",
|
||||
"geostyler-wfs-parser": "^3.0.1",
|
||||
"google-auth-library": "^10.6.2",
|
||||
"immer": "^11.1.4",
|
||||
"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.17.23",
|
||||
"mapbox-gl": "^3.19.0",
|
||||
"markdown-to-jsx": "^9.7.6",
|
||||
"match-sorter": "^6.3.4",
|
||||
"lodash": "^4.18.1",
|
||||
"mapbox-gl": "^3.20.0",
|
||||
"markdown-to-jsx": "^9.7.13",
|
||||
"match-sorter": "^8.2.0",
|
||||
"memoize-one": "^5.2.1",
|
||||
"mousetrap": "^1.6.5",
|
||||
"mustache": "^4.2.0",
|
||||
"nanoid": "^5.1.6",
|
||||
"nanoid": "^5.1.7",
|
||||
"ol": "^10.8.0",
|
||||
"pretty-ms": "^9.3.0",
|
||||
"query-string": "9.3.1",
|
||||
"re-resizable": "^6.11.2",
|
||||
"react": "^17.0.2",
|
||||
"use-sync-external-store": "^1.2.0",
|
||||
"react-arborist": "^3.4.3",
|
||||
"react-checkbox-tree": "^1.8.0",
|
||||
"react-diff-viewer-continued": "^3.4.0",
|
||||
"react-diff-viewer-continued": "^4.2.0",
|
||||
"react-dnd": "^11.1.3",
|
||||
"react-dnd-html5-backend": "^11.1.3",
|
||||
"react-dom": "^17.0.2",
|
||||
@@ -245,19 +244,19 @@
|
||||
"@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.0",
|
||||
"@babel/preset-env": "^7.29.2",
|
||||
"@babel/preset-react": "^7.28.5",
|
||||
"@babel/preset-typescript": "^7.28.5",
|
||||
"@babel/register": "^7.23.7",
|
||||
"@babel/runtime": "^7.28.6",
|
||||
"@babel/runtime-corejs3": "^7.29.0",
|
||||
"@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.58.2",
|
||||
"@playwright/test": "^1.59.1",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.6.2",
|
||||
"@storybook/addon-actions": "^8.6.17",
|
||||
"@storybook/addon-controls": "^8.6.17",
|
||||
@@ -271,8 +270,8 @@
|
||||
"@storybook/test": "^8.6.15",
|
||||
"@storybook/test-runner": "^0.17.0",
|
||||
"@svgr/webpack": "^8.1.0",
|
||||
"@swc/core": "^1.15.18",
|
||||
"@swc/plugin-emotion": "^14.6.0",
|
||||
"@swc/core": "^1.15.24",
|
||||
"@swc/plugin-emotion": "^14.8.0",
|
||||
"@swc/plugin-transform-imports": "^12.5.0",
|
||||
"@testing-library/dom": "^8.20.1",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
@@ -285,9 +284,8 @@
|
||||
"@types/js-levenshtein": "^1.1.3",
|
||||
"@types/json-bigint": "^1.0.4",
|
||||
"@types/mousetrap": "^1.6.15",
|
||||
"@types/node": "^25.3.3",
|
||||
"@types/node": "^25.5.0",
|
||||
"@types/react": "^17.0.83",
|
||||
"@types/use-sync-external-store": "^0.0.6",
|
||||
"@types/react-dom": "^17.0.26",
|
||||
"@types/react-loadable": "^5.5.11",
|
||||
"@types/react-redux": "^7.1.10",
|
||||
@@ -303,14 +301,14 @@
|
||||
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
||||
"@typescript-eslint/parser": "^7.18.0",
|
||||
"babel-jest": "^30.0.2",
|
||||
"babel-loader": "^10.0.0",
|
||||
"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.0",
|
||||
"baseline-browser-mapping": "^2.10.13",
|
||||
"cheerio": "1.2.0",
|
||||
"concurrently": "^9.2.1",
|
||||
"copy-webpack-plugin": "^13.0.1",
|
||||
"copy-webpack-plugin": "^14.0.0",
|
||||
"cross-env": "^10.1.0",
|
||||
"css-loader": "^7.1.4",
|
||||
"css-minimizer-webpack-plugin": "^8.0.0",
|
||||
@@ -319,7 +317,6 @@
|
||||
"eslint-import-resolver-alias": "^1.1.2",
|
||||
"eslint-import-resolver-typescript": "^4.4.4",
|
||||
"eslint-plugin-cypress": "^3.6.0",
|
||||
"eslint-plugin-file-progress": "^1.5.0",
|
||||
"eslint-plugin-i18n-strings": "file:eslint-rules/eslint-plugin-i18n-strings",
|
||||
"eslint-plugin-icons": "file:eslint-rules/eslint-plugin-icons",
|
||||
"eslint-plugin-import": "^2.32.0",
|
||||
@@ -328,9 +325,9 @@
|
||||
"eslint-plugin-no-only-tests": "^3.3.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.1",
|
||||
"eslint-plugin-react-you-might-not-need-an-effect": "^0.9.2",
|
||||
"eslint-plugin-storybook": "^0.8.0",
|
||||
"eslint-plugin-testing-library": "^7.16.0",
|
||||
"eslint-plugin-testing-library": "^7.16.2",
|
||||
"eslint-plugin-theme-colors": "file:eslint-rules/eslint-plugin-theme-colors",
|
||||
"fetch-mock": "^12.6.0",
|
||||
"fork-ts-checker-webpack-plugin": "^9.1.0",
|
||||
@@ -338,33 +335,33 @@
|
||||
"html-webpack-plugin": "^5.6.6",
|
||||
"http-server": "^14.1.1",
|
||||
"imports-loader": "^5.0.0",
|
||||
"jest": "^30.2.0",
|
||||
"jest": "^30.3.0",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"jest-html-reporter": "^4.3.0",
|
||||
"jest-html-reporter": "^4.4.0",
|
||||
"jest-websocket-mock": "^2.5.0",
|
||||
"js-yaml-loader": "^1.2.2",
|
||||
"jsdom": "^28.1.0",
|
||||
"lerna": "^8.2.3",
|
||||
"lightningcss": "^1.31.1",
|
||||
"mini-css-extract-plugin": "^2.10.0",
|
||||
"open-cli": "^8.0.0",
|
||||
"oxlint": "^1.51.0",
|
||||
"jsdom": "^29.0.2",
|
||||
"lerna": "^9.0.4",
|
||||
"lightningcss": "^1.32.0",
|
||||
"mini-css-extract-plugin": "^2.10.2",
|
||||
"open-cli": "^9.0.0",
|
||||
"oxlint": "^1.56.0",
|
||||
"po2json": "^0.4.5",
|
||||
"prettier": "3.8.1",
|
||||
"prettier-plugin-packagejson": "^3.0.0",
|
||||
"prettier-plugin-packagejson": "^3.0.2",
|
||||
"process": "^0.11.10",
|
||||
"react-ace": "^10.1.0",
|
||||
"react-refresh": "^0.18.0",
|
||||
"react-resizable": "^3.1.3",
|
||||
"redux-mock-store": "^1.5.4",
|
||||
"source-map": "^0.7.6",
|
||||
"source-map-support": "^0.5.21",
|
||||
"speed-measure-webpack-plugin": "^1.5.0",
|
||||
"speed-measure-webpack-plugin": "^1.6.0",
|
||||
"storybook": "8.6.17",
|
||||
"style-loader": "^4.0.0",
|
||||
"swc-loader": "^0.2.7",
|
||||
"terser-webpack-plugin": "^5.3.17",
|
||||
"ts-jest": "^29.4.6",
|
||||
"terser-webpack-plugin": "^5.4.0",
|
||||
"thread-loader": "^4.0.4",
|
||||
"ts-jest": "^29.4.9",
|
||||
"tscw-config": "^1.1.2",
|
||||
"tsx": "^4.21.0",
|
||||
"typescript": "5.4.5",
|
||||
@@ -372,7 +369,7 @@
|
||||
"vm-browserify": "^1.1.2",
|
||||
"wait-on": "^9.0.4",
|
||||
"webpack": "^5.105.4",
|
||||
"webpack-bundle-analyzer": "^5.2.0",
|
||||
"webpack-bundle-analyzer": "^5.3.0",
|
||||
"webpack-cli": "^6.0.1",
|
||||
"webpack-dev-server": "^5.2.3",
|
||||
"webpack-manifest-plugin": "^5.0.1",
|
||||
|
||||
@@ -29,14 +29,14 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"chalk": "^5.6.2",
|
||||
"lodash-es": "^4.17.23",
|
||||
"yeoman-generator": "^7.5.1",
|
||||
"lodash-es": "^4.18.1",
|
||||
"yeoman-generator": "^8.1.2",
|
||||
"yosay": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cross-env": "^10.1.0",
|
||||
"fs-extra": "^11.3.3",
|
||||
"jest": "^30.2.0",
|
||||
"fs-extra": "^11.3.4",
|
||||
"jest": "^30.3.0",
|
||||
"yeoman-test": "^11.3.1"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@apache-superset/core",
|
||||
"version": "0.1.0-rc3",
|
||||
"version": "0.1.0-rc1",
|
||||
"description": "This package contains UI elements, APIs, and utility functions used by Superset.",
|
||||
"sideEffects": false,
|
||||
"main": "lib/index.js",
|
||||
@@ -18,14 +18,6 @@
|
||||
"types": "./lib/authentication/index.d.ts",
|
||||
"default": "./lib/authentication/index.js"
|
||||
},
|
||||
"./chat": {
|
||||
"types": "./lib/chat/index.d.ts",
|
||||
"default": "./lib/chat/index.js"
|
||||
},
|
||||
"./navigation": {
|
||||
"types": "./lib/navigation/index.d.ts",
|
||||
"default": "./lib/navigation/index.js"
|
||||
},
|
||||
"./commands": {
|
||||
"types": "./lib/commands/index.d.ts",
|
||||
"default": "./lib/commands/index.js"
|
||||
@@ -78,12 +70,12 @@
|
||||
"files": [
|
||||
"lib"
|
||||
],
|
||||
"author": "Apache Software Foundation",
|
||||
"license": "Apache-2.0",
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.28.6",
|
||||
"@babel/core": "^7.29.0",
|
||||
"@babel/preset-env": "^7.29.0",
|
||||
"@babel/preset-env": "^7.29.2",
|
||||
"@babel/preset-react": "^7.28.5",
|
||||
"@babel/preset-typescript": "^7.28.5",
|
||||
"typescript": "^5.0.0",
|
||||
@@ -110,7 +102,7 @@
|
||||
"react-dom": "^17.0.2",
|
||||
"react-loadable": "^5.5.0",
|
||||
"tinycolor2": "*",
|
||||
"lodash": "^4.17.21",
|
||||
"lodash": "^4.18.1",
|
||||
"antd": "^5.26.0",
|
||||
"jed": "^1.1.1"
|
||||
},
|
||||
|
||||
@@ -1,156 +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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Chat contribution API for Superset extensions.
|
||||
*
|
||||
* Chat is a dedicated contribution type: an extension registers
|
||||
* a chat via {@link registerChat} and the host owns where and how it is
|
||||
* mounted. The host applies singleton resolution — multiple chat extensions
|
||||
* may register, but exactly one is active at a time.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { chat } from '@apache-superset/core';
|
||||
*
|
||||
* chat.registerChat(
|
||||
* { id: 'acme.chat', name: 'Acme Chat' },
|
||||
* AcmeTrigger,
|
||||
* AcmePanel,
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
|
||||
import { ComponentType } from 'react';
|
||||
import type { Disposable, Event } from '../common';
|
||||
|
||||
export interface Chat {
|
||||
/** The unique identifier for the chat. */
|
||||
id: string;
|
||||
/** The display name of the chat. */
|
||||
name: string;
|
||||
/** Optional description of the chat. */
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export type DisplayMode = 'floating' | 'panel';
|
||||
|
||||
/**
|
||||
* Registers a chat provider. Only one chat is active at a time; the most
|
||||
* recently registered chat wins. Disposing the returned Disposable unregisters
|
||||
* the chat.
|
||||
*
|
||||
* @param chat The chat descriptor (id, name).
|
||||
* @param trigger The trigger component — the collapsed bubble entry point.
|
||||
* Owns dynamic state such as unread counts.
|
||||
* @param panel The panel component, rendered in either display mode. In
|
||||
* 'floating' mode it appears as an overlay; in 'panel' mode it is docked
|
||||
* alongside the main content.
|
||||
* @returns A Disposable that unregisters the chat when disposed.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* chat.registerChat(
|
||||
* { id: 'acme.chat', name: 'Acme Chat' },
|
||||
* AcmeTrigger,
|
||||
* AcmePanel,
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
export declare function registerChat(
|
||||
chat: Chat,
|
||||
trigger: ComponentType,
|
||||
panel: ComponentType,
|
||||
): Disposable;
|
||||
|
||||
/**
|
||||
* Returns the active chat descriptor, or undefined if none is registered.
|
||||
*/
|
||||
export declare function getChat(): Chat | undefined;
|
||||
|
||||
/**
|
||||
* Event fired when a chat is registered.
|
||||
*/
|
||||
export declare const onDidRegisterChat: Event<Chat>;
|
||||
|
||||
/**
|
||||
* Event fired when a chat is unregistered.
|
||||
*/
|
||||
export declare const onDidUnregisterChat: Event<Chat>;
|
||||
|
||||
/**
|
||||
* Opens the active chat's panel.
|
||||
*
|
||||
* Acts on whichever chat is active, regardless of which extension calls it.
|
||||
* No-op when no chat is registered or the panel is already open.
|
||||
*/
|
||||
export declare function open(): void;
|
||||
|
||||
/**
|
||||
* Closes the active chat's panel.
|
||||
*
|
||||
* Acts on whichever chat is active, regardless of which extension calls it.
|
||||
* No-op when the panel is not open.
|
||||
*/
|
||||
export declare function close(): void;
|
||||
|
||||
/**
|
||||
* Returns whether the active chat's panel is currently open.
|
||||
*/
|
||||
export declare function isOpen(): boolean;
|
||||
|
||||
/**
|
||||
* Event fired when the chat panel opens. Also fired by the host's own
|
||||
* controls, not only by an extension's open() call.
|
||||
*/
|
||||
export declare const onDidOpen: Event<void>;
|
||||
|
||||
/**
|
||||
* Event fired when the chat panel closes, whether triggered by an extension
|
||||
* or by the host.
|
||||
*/
|
||||
export declare const onDidClose: Event<void>;
|
||||
|
||||
/**
|
||||
* Returns the current display mode.
|
||||
*/
|
||||
export declare function getDisplayMode(): DisplayMode;
|
||||
|
||||
/**
|
||||
* Sets the display mode. The mode is host-global and applies to whichever
|
||||
* chat is active. Use {@link onDidChangeDisplayMode} to observe all changes,
|
||||
* including those triggered by the host.
|
||||
*/
|
||||
export declare function setDisplayMode(displayMode: DisplayMode): void;
|
||||
|
||||
/**
|
||||
* Event fired when the display mode changes, whether triggered by an
|
||||
* extension via setDisplayMode() or by host-provided controls.
|
||||
*/
|
||||
export declare const onDidChangeDisplayMode: Event<DisplayMode>;
|
||||
|
||||
/**
|
||||
* Event fired when the panel is resized in panel mode. Not all hosts provide
|
||||
* a resizer — do not rely on this event firing.
|
||||
*/
|
||||
export declare const onDidResizePanel: Event<{ width: number }>;
|
||||
|
||||
// TODO: client actions API — tool availability functions will be added here
|
||||
// once the client_actions SIP is finalized. The chat namespace is the
|
||||
// intended integration point between the two SIPs.
|
||||
@@ -223,6 +223,8 @@ export interface Extension {
|
||||
dependencies: string[];
|
||||
/** Human-readable description of the extension */
|
||||
description: string;
|
||||
/** List of other extensions that this extension depends on */
|
||||
extensionDependencies: string[];
|
||||
/** Unique identifier for the extension */
|
||||
id: string;
|
||||
/** Human-readable name of the extension */
|
||||
|
||||
@@ -23,10 +23,9 @@
|
||||
* This module defines the aggregate interfaces used by the extension.json
|
||||
* manifest and the `superset-extensions` build command. Individual metadata
|
||||
* types are defined in their respective namespace modules (commands, views,
|
||||
* menus, editors, chat) and re-exported here for the manifest schema.
|
||||
* menus, editors) and re-exported here for the manifest schema.
|
||||
*/
|
||||
|
||||
import { Chat } from '../chat';
|
||||
import { Command } from '../commands';
|
||||
import { View } from '../views';
|
||||
import { Menu } from '../menus';
|
||||
@@ -72,8 +71,7 @@ export interface MenuContributions {
|
||||
}
|
||||
|
||||
/**
|
||||
* Aggregates all contributions (commands, menus, views, editors, and chat)
|
||||
* provided by an extension or module.
|
||||
* Aggregates all contributions (commands, menus, views, and editors) provided by an extension or module.
|
||||
*/
|
||||
export interface Contributions {
|
||||
/** List of commands. */
|
||||
@@ -84,10 +82,4 @@ export interface Contributions {
|
||||
views: ViewContributions;
|
||||
/** List of editors. */
|
||||
editors?: Editor[];
|
||||
/**
|
||||
* The chat contributed by the extension — at most one per extension, since
|
||||
* the host applies singleton resolution and renders exactly one active
|
||||
* chat at a time.
|
||||
*/
|
||||
chat?: Chat;
|
||||
}
|
||||
|
||||
@@ -18,12 +18,10 @@
|
||||
*/
|
||||
export * as common from './common';
|
||||
export * as authentication from './authentication';
|
||||
export * as chat from './chat';
|
||||
export * as commands from './commands';
|
||||
export * as editors from './editors';
|
||||
export * as extensions from './extensions';
|
||||
export * as menus from './menus';
|
||||
export * as navigation from './navigation';
|
||||
export * as sqlLab from './sqlLab';
|
||||
export * as views from './views';
|
||||
export * as contributions from './contributions';
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
* ```
|
||||
*/
|
||||
|
||||
import { Disposable, Event } from '../common';
|
||||
import { Disposable } from '../common';
|
||||
|
||||
/**
|
||||
* Represents a menu item that links a view to a command.
|
||||
@@ -102,37 +102,3 @@ export declare function registerMenuItem(
|
||||
* ```
|
||||
*/
|
||||
export declare function getMenu(location: string): Menu | undefined;
|
||||
|
||||
/**
|
||||
* Event fired when a menu item is registered.
|
||||
*/
|
||||
export interface MenuItemRegisteredEvent {
|
||||
/** The menu item that was registered. */
|
||||
item: MenuItem;
|
||||
/** The location where the item was registered. */
|
||||
location: string;
|
||||
/** The group the item was placed in. */
|
||||
group: 'primary' | 'secondary' | 'context';
|
||||
}
|
||||
|
||||
/**
|
||||
* Event fired when a menu item is unregistered.
|
||||
*/
|
||||
export interface MenuItemUnregisteredEvent {
|
||||
/** The menu item that was unregistered. */
|
||||
item: MenuItem;
|
||||
/** The location where the item was registered. */
|
||||
location: string;
|
||||
/** The group the item was placed in. */
|
||||
group: 'primary' | 'secondary' | 'context';
|
||||
}
|
||||
|
||||
/**
|
||||
* Event fired when a menu item is registered.
|
||||
*/
|
||||
export declare const onDidRegisterMenuItem: Event<MenuItemRegisteredEvent>;
|
||||
|
||||
/**
|
||||
* Event fired when a menu item is unregistered.
|
||||
*/
|
||||
export declare const onDidUnregisterMenuItem: Event<MenuItemUnregisteredEvent>;
|
||||
|
||||
@@ -1,81 +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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Navigation namespace for Superset extensions.
|
||||
*
|
||||
* Exposes the current application surface so extensions can react to route
|
||||
* changes without polling. Entity-level context (chart, dashboard, dataset)
|
||||
* is intentionally not included here — surface-specific namespaces that
|
||||
* resolve entity payloads are introduced in later phases.
|
||||
*/
|
||||
|
||||
import { Event } from '../common';
|
||||
|
||||
/**
|
||||
* The set of top-level application surfaces.
|
||||
*
|
||||
* `'explore'`, `'dashboard'` and `'dataset'` are the single-entity
|
||||
* editing/viewing surfaces. `'chart_list'`, `'dashboard_list'` and
|
||||
* `'dataset_list'` are the browse/list surfaces, distinct from those because no
|
||||
* single entity is active. `'sqllab'` is the SQL editor where
|
||||
* `sqlLab.getCurrentTab()` resolves; `'query_history'` and `'saved_queries'`
|
||||
* are the related SQL Lab browse pages, which are not the editor. `'home'` is
|
||||
* the welcome surface and the fallback for any route not explicitly enumerated.
|
||||
*/
|
||||
export type Page =
|
||||
| 'dashboard'
|
||||
| 'dashboard_list'
|
||||
| 'explore'
|
||||
| 'chart_list'
|
||||
| 'sqllab'
|
||||
| 'query_history'
|
||||
| 'saved_queries'
|
||||
| 'dataset'
|
||||
| 'dataset_list'
|
||||
| 'home';
|
||||
|
||||
/**
|
||||
* Returns the current page surface.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const page = navigation.getPage();
|
||||
* if (page === 'dashboard') {
|
||||
* // react to being on a dashboard surface
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export declare function getPage(): Page;
|
||||
|
||||
/**
|
||||
* Event fired whenever the user navigates to a different surface.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const sub = navigation.onDidChangePage(page => {
|
||||
* if (page === 'dashboard') {
|
||||
* // react to navigating onto a dashboard surface
|
||||
* }
|
||||
* });
|
||||
* // later:
|
||||
* sub.dispose();
|
||||
* ```
|
||||
*/
|
||||
export declare const onDidChangePage: Event<Page>;
|
||||
@@ -29,20 +29,14 @@ import '@fontsource/ibm-plex-mono/600.css';
|
||||
/* eslint-enable import/extensions */
|
||||
|
||||
import { css, useTheme, Global } from '@emotion/react';
|
||||
import { useThemeMode } from './utils/themeUtils';
|
||||
|
||||
export const GlobalStyles = () => {
|
||||
const theme = useTheme();
|
||||
const isDark = useThemeMode();
|
||||
return (
|
||||
<Global
|
||||
key={`global-${theme.colorLink}`}
|
||||
styles={css`
|
||||
// SPA
|
||||
html {
|
||||
color-scheme: ${isDark ? 'dark' : 'light'};
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#app {
|
||||
|
||||
@@ -110,6 +110,26 @@ export interface ColorVariants {
|
||||
}
|
||||
|
||||
export interface SupersetSpecificTokens {
|
||||
// Label variant tokens — Published/Draft (dashboard status)
|
||||
labelPublishedColor?: string;
|
||||
labelPublishedBg?: string;
|
||||
labelPublishedBorderColor?: string;
|
||||
labelPublishedIconColor?: string;
|
||||
labelDraftColor?: string;
|
||||
labelDraftBg?: string;
|
||||
labelDraftBorderColor?: string;
|
||||
labelDraftIconColor?: string;
|
||||
|
||||
// Label variant tokens — Dataset type (Physical/Virtual)
|
||||
labelDatasetPhysicalColor?: string;
|
||||
labelDatasetPhysicalBg?: string;
|
||||
labelDatasetPhysicalBorderColor?: string;
|
||||
labelDatasetPhysicalIconColor?: string;
|
||||
labelDatasetVirtualColor?: string;
|
||||
labelDatasetVirtualBg?: string;
|
||||
labelDatasetVirtualBorderColor?: string;
|
||||
labelDatasetVirtualIconColor?: string;
|
||||
|
||||
// Font-related
|
||||
fontSizeXS: string;
|
||||
fontSizeXXL: string;
|
||||
|
||||
@@ -30,13 +30,13 @@
|
||||
*
|
||||
* views.registerView(
|
||||
* { id: 'my_ext.result_stats', name: 'Result Stats', location: 'sqllab.panels' },
|
||||
* ResultStatsPanel,
|
||||
* () => <ResultStatsPanel />,
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
|
||||
import { ComponentType } from 'react';
|
||||
import { Disposable, Event } from '../common';
|
||||
import { ReactElement } from 'react';
|
||||
import { Disposable } from '../common';
|
||||
|
||||
/**
|
||||
* Represents a contributed view in the application.
|
||||
@@ -58,7 +58,7 @@ export interface View {
|
||||
*
|
||||
* @param view The view descriptor (id and name).
|
||||
* @param location The location where this view should appear (e.g. "sqllab.panels").
|
||||
* @param component The React component to render at that location.
|
||||
* @param provider A function that returns the React element to render.
|
||||
* @returns A Disposable that unregisters the view when disposed.
|
||||
*
|
||||
* @example
|
||||
@@ -66,14 +66,14 @@ export interface View {
|
||||
* views.registerView(
|
||||
* { id: 'my_ext.result_stats', name: 'Result Stats' },
|
||||
* 'sqllab.panels',
|
||||
* ResultStatsPanel,
|
||||
* () => <ResultStatsPanel />,
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
export declare function registerView(
|
||||
view: View,
|
||||
location: string,
|
||||
component: ComponentType,
|
||||
provider: () => ReactElement,
|
||||
): Disposable;
|
||||
|
||||
/**
|
||||
@@ -88,33 +88,3 @@ export declare function registerView(
|
||||
* ```
|
||||
*/
|
||||
export declare function getViews(location: string): View[] | undefined;
|
||||
|
||||
/**
|
||||
* Event fired when a view is registered.
|
||||
*/
|
||||
export interface ViewRegisteredEvent {
|
||||
/** The descriptor of the view that was registered. */
|
||||
view: View;
|
||||
/** The location where the view was registered. */
|
||||
location: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event fired when a view is unregistered.
|
||||
*/
|
||||
export interface ViewUnregisteredEvent {
|
||||
/** The descriptor of the view that was unregistered. */
|
||||
view: View;
|
||||
/** The location where the view was registered. */
|
||||
location: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event fired when a view is registered.
|
||||
*/
|
||||
export declare const onDidRegisterView: Event<ViewRegisteredEvent>;
|
||||
|
||||
/**
|
||||
* Event fired when a view is unregistered.
|
||||
*/
|
||||
export declare const onDidUnregisterView: Event<ViewUnregisteredEvent>;
|
||||
|
||||
@@ -26,11 +26,11 @@
|
||||
"dependencies": {
|
||||
"@apache-superset/core": "*",
|
||||
"@types/react": "*",
|
||||
"lodash": "^4.17.23",
|
||||
"lodash": "^4.18.1",
|
||||
"tinycolor2": "*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ant-design/icons": "^5.2.6",
|
||||
"@ant-design/icons": "^5.6.1",
|
||||
"@emotion/react": "^11.4.1",
|
||||
"@superset-ui/core": "*",
|
||||
"@testing-library/dom": "^8.20.1",
|
||||
|
||||
@@ -24,39 +24,40 @@
|
||||
"lib"
|
||||
],
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^6.1.1",
|
||||
"@apache-superset/core": "*",
|
||||
"@ant-design/icons": "^5.2.6",
|
||||
"@babel/runtime": "^7.28.6",
|
||||
"@babel/runtime": "^7.29.2",
|
||||
"@types/json-bigint": "^1.0.4",
|
||||
"@visx/responsive": "^3.12.0",
|
||||
"ace-builds": "^1.43.6",
|
||||
"ag-grid-community": "35.0.1",
|
||||
"ag-grid-react": "35.0.1",
|
||||
"brace": "^0.11.1",
|
||||
"classnames": "^2.5.1",
|
||||
"core-js": "^3.49.0",
|
||||
"csstype": "^3.2.3",
|
||||
"core-js": "^3.48.0",
|
||||
"d3-format": "^3.1.2",
|
||||
"dayjs": "^1.11.19",
|
||||
"d3-interpolate": "^3.0.1",
|
||||
"d3-scale": "^4.0.2",
|
||||
"d3-time": "^3.1.0",
|
||||
"d3-time-format": "^4.1.0",
|
||||
"dompurify": "^3.3.1",
|
||||
"dayjs": "^1.11.20",
|
||||
"dompurify": "^3.3.3",
|
||||
"fetch-retry": "^6.0.0",
|
||||
"handlebars": "^4.7.8",
|
||||
"handlebars": "^4.7.9",
|
||||
"jed": "^1.1.1",
|
||||
"lodash": "^4.17.23",
|
||||
"lodash": "^4.18.1",
|
||||
"math-expression-evaluator": "^2.0.7",
|
||||
"pretty-ms": "^9.3.0",
|
||||
"re-resizable": "^6.11.2",
|
||||
"react-ace": "^14.0.1",
|
||||
"react-js-cron": "^5.2.0",
|
||||
"react-draggable": "^4.5.0",
|
||||
"react-resize-detector": "^7.1.2",
|
||||
"react-syntax-highlighter": "^16.1.1",
|
||||
"react-ultimate-pagination": "^1.3.2",
|
||||
"react-error-boundary": "6.0.0",
|
||||
"react-js-cron": "^5.2.0",
|
||||
"react-markdown": "^8.0.7",
|
||||
"react-resize-detector": "^7.1.2",
|
||||
"react-syntax-highlighter": "^16.1.0",
|
||||
"react-ultimate-pagination": "^1.3.2",
|
||||
"regenerator-runtime": "^0.14.1",
|
||||
"rehype-raw": "^7.0.0",
|
||||
"rehype-sanitize": "^6.0.0",
|
||||
@@ -64,7 +65,6 @@
|
||||
"reselect": "^5.1.1",
|
||||
"rison": "^0.1.1",
|
||||
"seedrandom": "^3.0.5",
|
||||
"@visx/responsive": "^3.12.0",
|
||||
"xss": "^1.0.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -74,12 +74,12 @@
|
||||
"@types/d3-scale": "^2.1.1",
|
||||
"@types/d3-time": "^3.0.4",
|
||||
"@types/d3-time-format": "^4.0.3",
|
||||
"@types/react-table": "^7.7.20",
|
||||
"@types/react-syntax-highlighter": "^15.5.13",
|
||||
"@types/jquery": "^3.5.33",
|
||||
"@types/lodash": "^4.17.24",
|
||||
"@types/node": "^25.3.3",
|
||||
"@types/node": "^25.5.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",
|
||||
@@ -88,7 +88,6 @@
|
||||
"timezone-mock": "1.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"antd": "^5.26.0",
|
||||
"@emotion/cache": "^11.4.0",
|
||||
"@emotion/react": "^11.4.1",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
@@ -101,6 +100,7 @@
|
||||
"@types/react-loadable": "*",
|
||||
"@types/react-window": "^1.8.8",
|
||||
"@types/tinycolor2": "*",
|
||||
"antd": "^5.26.0",
|
||||
"nanoid": "^5.0.9",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
|
||||
+10
-12
@@ -33,22 +33,20 @@ import type { PlaceholderProps } from './types';
|
||||
function DefaultPlaceholder({
|
||||
width,
|
||||
height,
|
||||
showLoadingForImport = true,
|
||||
showLoadingForImport = false,
|
||||
placeholderStyle: style,
|
||||
}: PlaceholderProps) {
|
||||
if (showLoadingForImport) {
|
||||
return (
|
||||
return (
|
||||
// since `width` defaults to 100%, we can display the placeholder once
|
||||
// height is specified.
|
||||
(height && (
|
||||
<div key="async-asm-placeholder" style={{ width, height, ...style }}>
|
||||
<Loading position="floating" size="s" />
|
||||
{showLoadingForImport && <Loading position="floating" />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (height) {
|
||||
return (
|
||||
<div key="async-asm-placeholder" style={{ width, height, ...style }} />
|
||||
);
|
||||
}
|
||||
return null;
|
||||
)) ||
|
||||
// `|| null` is for in case of height=0.
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+1
-41
@@ -16,7 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { render, screen, fireEvent } from '../../spec';
|
||||
import { render, screen } from '../../spec';
|
||||
import CodeSyntaxHighlighter from './index';
|
||||
|
||||
// Simple mock that just returns the content
|
||||
@@ -153,44 +153,4 @@ describe('CodeSyntaxHighlighter', () => {
|
||||
|
||||
expect(screen.getByText('SELECT * FROM users;')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('shows copy button by default', () => {
|
||||
render(
|
||||
<CodeSyntaxHighlighter language="sql">SELECT 1;</CodeSyntaxHighlighter>,
|
||||
);
|
||||
|
||||
expect(screen.getByTitle('Copy to clipboard')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('hides copy button when showCopyButton is false', () => {
|
||||
render(
|
||||
<CodeSyntaxHighlighter language="sql" showCopyButton={false}>
|
||||
SELECT 1;
|
||||
</CodeSyntaxHighlighter>,
|
||||
);
|
||||
|
||||
expect(screen.queryByTitle('Copy to clipboard')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('copy button does not throw when clipboard API is unavailable', () => {
|
||||
const originalClipboard = navigator.clipboard;
|
||||
Object.defineProperty(navigator, 'clipboard', {
|
||||
value: undefined,
|
||||
configurable: true,
|
||||
});
|
||||
document.execCommand = jest.fn().mockReturnValue(true);
|
||||
|
||||
render(
|
||||
<CodeSyntaxHighlighter language="sql">SELECT 1;</CodeSyntaxHighlighter>,
|
||||
);
|
||||
|
||||
expect(() =>
|
||||
fireEvent.click(screen.getByTitle('Copy to clipboard')),
|
||||
).not.toThrow();
|
||||
|
||||
Object.defineProperty(navigator, 'clipboard', {
|
||||
value: originalClipboard,
|
||||
configurable: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+19
-88
@@ -16,14 +16,11 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import SyntaxHighlighterBase from 'react-syntax-highlighter/dist/cjs/light';
|
||||
import github from 'react-syntax-highlighter/dist/cjs/styles/hljs/github';
|
||||
import tomorrow from 'react-syntax-highlighter/dist/cjs/styles/hljs/tomorrow-night';
|
||||
import { css, isThemeDark, useTheme } from '@apache-superset/core/theme';
|
||||
import { t } from '@apache-superset/core/translation';
|
||||
import copyTextToClipboard from '../../utils/copy';
|
||||
import { Icons } from '../Icons';
|
||||
import { isThemeDark, useTheme } from '@apache-superset/core/theme';
|
||||
|
||||
export type SupportedLanguage = 'sql' | 'htmlbars' | 'markdown' | 'json';
|
||||
|
||||
@@ -34,7 +31,6 @@ export interface CodeSyntaxHighlighterProps {
|
||||
showLineNumbers?: boolean;
|
||||
wrapLines?: boolean;
|
||||
style?: any; // Override theme style if needed
|
||||
showCopyButton?: boolean;
|
||||
}
|
||||
|
||||
// Track which languages have been registered to avoid duplicate registrations
|
||||
@@ -80,14 +76,11 @@ export const CodeSyntaxHighlighter: React.FC<CodeSyntaxHighlighterProps> = ({
|
||||
showLineNumbers = false,
|
||||
wrapLines = true,
|
||||
style: overrideStyle,
|
||||
showCopyButton = true,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const [isLanguageReady, setIsLanguageReady] = useState(
|
||||
registeredLanguages.has(language),
|
||||
);
|
||||
const [copied, setCopied] = useState(false);
|
||||
const copyTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const loadLanguage = async () => {
|
||||
@@ -100,21 +93,6 @@ export const CodeSyntaxHighlighter: React.FC<CodeSyntaxHighlighterProps> = ({
|
||||
loadLanguage();
|
||||
}, [language]);
|
||||
|
||||
useEffect(
|
||||
() => () => {
|
||||
if (copyTimeoutRef.current) clearTimeout(copyTimeoutRef.current);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const handleCopy = useCallback(() => {
|
||||
copyTextToClipboard(() => Promise.resolve(children)).then(() => {
|
||||
if (copyTimeoutRef.current) clearTimeout(copyTimeoutRef.current);
|
||||
setCopied(true);
|
||||
copyTimeoutRef.current = setTimeout(() => setCopied(false), 1500);
|
||||
});
|
||||
}, [children]);
|
||||
|
||||
const isDark = isThemeDark(theme);
|
||||
const themeStyle = overrideStyle || (isDark ? tomorrow : github);
|
||||
|
||||
@@ -126,79 +104,32 @@ export const CodeSyntaxHighlighter: React.FC<CodeSyntaxHighlighterProps> = ({
|
||||
...customStyle,
|
||||
};
|
||||
|
||||
const copyButton = showCopyButton && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
handleCopy();
|
||||
}}
|
||||
title={copied ? t('Copied!') : t('Copy to clipboard')}
|
||||
css={css`
|
||||
position: absolute;
|
||||
top: ${theme.sizeUnit}px;
|
||||
right: ${theme.sizeUnit}px;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: ${theme.sizeUnit}px;
|
||||
color: ${copied ? theme.colorSuccess : theme.colorTextSecondary};
|
||||
line-height: 1;
|
||||
border-radius: ${theme.borderRadius}px;
|
||||
&:hover {
|
||||
color: ${copied ? theme.colorSuccess : theme.colorText};
|
||||
background: ${theme.colorBgTextHover};
|
||||
}
|
||||
`}
|
||||
>
|
||||
{copied ? (
|
||||
<Icons.CheckOutlined style={{ fontSize: theme.fontSizeSM }} />
|
||||
) : (
|
||||
<Icons.CopyOutlined style={{ fontSize: theme.fontSizeSM }} />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
|
||||
// Show a simple pre-formatted text while language is loading
|
||||
if (!isLanguageReady) {
|
||||
return (
|
||||
<div
|
||||
css={css`
|
||||
position: relative;
|
||||
`}
|
||||
<pre
|
||||
style={{
|
||||
...defaultCustomStyle,
|
||||
fontFamily: 'monospace',
|
||||
whiteSpace: 'pre-wrap',
|
||||
margin: 0,
|
||||
}}
|
||||
>
|
||||
{copyButton}
|
||||
<pre
|
||||
style={{
|
||||
...defaultCustomStyle,
|
||||
fontFamily: 'monospace',
|
||||
whiteSpace: 'pre-wrap',
|
||||
margin: 0,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</pre>
|
||||
</div>
|
||||
{children}
|
||||
</pre>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
css={css`
|
||||
position: relative;
|
||||
`}
|
||||
<SyntaxHighlighterBase
|
||||
language={language}
|
||||
style={themeStyle}
|
||||
customStyle={defaultCustomStyle}
|
||||
showLineNumbers={showLineNumbers}
|
||||
wrapLines={wrapLines}
|
||||
>
|
||||
{copyButton}
|
||||
<SyntaxHighlighterBase
|
||||
language={language}
|
||||
style={themeStyle}
|
||||
customStyle={defaultCustomStyle}
|
||||
showLineNumbers={showLineNumbers}
|
||||
wrapLines={wrapLines}
|
||||
>
|
||||
{children}
|
||||
</SyntaxHighlighterBase>
|
||||
</div>
|
||||
{children}
|
||||
</SyntaxHighlighterBase>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
+6
-6
@@ -17,10 +17,10 @@ specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
<svg viewBox="0 0 119 78" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M99.3193 1H20.6639C19.0349 1 17.7143 2.32057 17.7143 3.94958V59.0084C17.7143 60.6374 19.0349 61.958 20.6639 61.958H99.3193C100.948 61.958 102.269 60.6374 102.269 59.0084V3.94958C102.269 2.32057 100.948 1 99.3193 1Z" stroke="currentColor" stroke-width="2"/>
|
||||
<path d="M95.3866 4.93277H24.5966C22.9676 4.93277 21.647 6.25335 21.647 7.88235V15.7479C21.647 17.3769 22.9676 18.6975 24.5966 18.6975H95.3866C97.0156 18.6975 98.3361 17.3769 98.3361 15.7479V7.88235C98.3361 6.25335 97.0156 4.93277 95.3866 4.93277Z" stroke="currentColor" stroke-width="2"/>
|
||||
<path d="M56.0588 22.6303H24.5966C22.9676 22.6303 21.647 23.9508 21.647 25.5798V55.0756C21.647 56.7046 22.9676 58.0252 24.5966 58.0252H56.0588C57.6878 58.0252 59.0084 56.7046 59.0084 55.0756V25.5798C59.0084 23.9508 57.6878 22.6303 56.0588 22.6303Z" stroke="currentColor" stroke-width="2"/>
|
||||
<path d="M95.3865 36.395H65.8907C64.2617 36.395 62.9412 37.7155 62.9412 39.3445V55.0756C62.9412 56.7046 64.2617 58.0252 65.8907 58.0252H95.3865C97.0155 58.0252 98.3361 56.7046 98.3361 55.0756V39.3445C98.3361 37.7155 97.0155 36.395 95.3865 36.395Z" stroke="currentColor" stroke-width="2"/>
|
||||
<path d="M95.3865 22.6303H65.8907C64.2617 22.6303 62.9412 23.9508 62.9412 25.5798V29.5126C62.9412 31.1416 64.2617 32.4622 65.8907 32.4622H95.3865C97.0155 32.4622 98.3361 31.1416 98.3361 29.5126V25.5798C98.3361 23.9508 97.0155 22.6303 95.3865 22.6303Z" stroke="currentColor" stroke-width="2"/>
|
||||
<path d="M17.2227 59.0086C17.2228 60.9091 18.7637 62.45 20.6641 62.45H99.3193C101.22 62.45 102.761 60.9091 102.761 59.0086V53.9891L103.374 54.1453C107.981 55.3221 111.718 56.6931 114.315 58.2137C116.877 59.7134 118.491 61.4572 118.491 63.4324C118.491 64.5056 118.01 65.513 117.177 66.4373C116.346 67.3587 115.143 68.2232 113.635 69.032C110.618 70.6501 106.286 72.0925 100.975 73.2977C90.343 75.7099 75.6797 77.1971 59.5 77.1971C43.3203 77.1971 28.657 75.7099 18.0254 73.2977C12.7137 72.0925 8.38162 70.6501 5.36523 69.032C3.85742 68.2232 2.65424 67.3587 1.82324 66.4373C0.989641 65.513 0.508789 64.5056 0.508789 63.4324C0.508977 61.389 2.23587 59.5926 4.96484 58.0535C7.73449 56.4916 11.717 55.0896 16.6152 53.8992L17.2227 53.7518V59.0086Z" fill="currentColor" fill-opacity="0.25" stroke="currentColor"/>
|
||||
<rect x="17" y="1" width="86" height="62" rx="3" stroke="currentColor" stroke-width="2"/>
|
||||
<rect x="21" y="5" width="78" height="14" rx="3" stroke="currentColor" stroke-width="2"/>
|
||||
<rect x="21" y="23" width="38" height="36" rx="3" stroke="currentColor" stroke-width="2"/>
|
||||
<rect x="63" y="37" width="36" height="22" rx="3" stroke="currentColor" stroke-width="2"/>
|
||||
<rect x="63" y="23" width="36" height="10" rx="3" stroke="currentColor" stroke-width="2"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M16 55.2892C6.07439 57.7013 0 60.9397 0 64.5C0 71.9559 26.6391 78 59.5 78C92.3609 78 119 71.9559 119 64.5C119 61.0609 113.332 57.9221 104 55.5383V60C104 62.2092 102.209 64 100 64H20C17.7909 64 16 62.2092 16 60V55.2892Z" fill="currentColor" fill-opacity="0.25" stroke="currentColor"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 1.6 KiB |
+2
-2
@@ -17,6 +17,6 @@ specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
<svg viewBox="0 0 119 76" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M82.7969 1.34302L102.269 23.5966V60.958C102.269 63.13 100.508 64.8908 98.3361 64.8908H20.6639C18.4919 64.8908 16.7311 63.13 16.7311 60.958V23.5966L36.203 1.34302C36.9498 0.489553 38.0286 0 39.1627 0H79.8372C80.9713 0 82.0502 0.489554 82.7969 1.34302ZM100.303 25.563V60.958C100.303 62.044 99.4225 62.9244 98.3361 62.9244H20.6639C19.5778 62.9244 18.6975 62.044 18.6975 60.958V25.563H35.6575C38.2369 25.563 40.3277 27.6539 40.3277 30.2332C40.3277 33.8984 43.299 36.8698 46.9643 36.8698H72.0357C75.7009 36.8698 78.6723 33.8984 78.6723 30.2332C78.6723 27.6539 80.7631 25.563 83.3424 25.563H100.303ZM99.6556 23.5966L81.3171 2.6379C80.9437 2.21116 80.4043 1.96639 79.8372 1.96639H39.1627C38.5956 1.96639 38.0562 2.21116 37.6828 2.6379L19.3439 23.5966H35.6575C39.3228 23.5966 42.2941 26.568 42.2941 30.2332C42.2941 32.8125 44.385 34.9034 46.9643 34.9034H72.0357C74.615 34.9034 76.7059 32.8125 76.7059 30.2332C76.7059 26.568 79.6772 23.5966 83.3424 23.5966H99.6556Z" fill="currentColor"/>
|
||||
<path d="M102.385 51.9159C107.283 53.1063 111.266 54.5083 114.035 56.0702C116.764 57.6093 118.491 59.4058 118.491 61.4492C118.491 62.5223 118.01 63.5298 117.177 64.454C116.346 65.3754 115.143 66.2399 113.635 67.0488C110.618 68.6668 106.286 70.1092 100.975 71.3144C90.343 73.7266 75.6797 75.2138 59.5 75.2138C43.3203 75.2138 28.657 73.7266 18.0254 71.3144C12.7137 70.1092 8.38162 68.6668 5.36523 67.0488C3.85743 66.2399 2.65424 65.3754 1.82324 64.454C0.989642 63.5298 0.508789 62.5223 0.508789 61.4492C0.508976 59.4057 2.23587 57.6093 4.96484 56.0702C7.73449 54.5083 11.717 53.1063 16.6152 51.9159L17.2227 51.7685V60.9579C17.2227 62.8584 18.7636 64.3993 20.6641 64.3993H98.3359C100.236 64.3993 101.777 62.8584 101.777 60.9579V51.7685L102.385 51.9159Z" fill="currentColor" stroke="currentColor" fill-opacity="0.25" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M83.1952 1.36598L103 24V62C103 64.2091 101.209 66 99 66H20C17.7909 66 16 64.2091 16 62V24L35.8048 1.36598C36.5643 0.497921 37.6616 0 38.8151 0H80.1849C81.3384 0 82.4357 0.497922 83.1952 1.36598ZM101 26V62C101 63.1046 100.105 64 99 64H20C18.8954 64 18 63.1046 18 62V26H35.25C37.8734 26 40 28.1266 40 30.75C40 34.4779 43.0221 37.5 46.75 37.5H72.25C75.9779 37.5 79 34.4779 79 30.75C79 28.1266 81.1266 26 83.75 26H101ZM100.342 24L81.6901 2.68299C81.3103 2.24896 80.7617 2 80.1849 2H38.8151C38.2383 2 37.6897 2.24896 37.3099 2.68299L18.6575 24H35.25C38.9779 24 42 27.0221 42 30.75C42 33.3734 44.1266 35.5 46.75 35.5H72.25C74.8734 35.5 77 33.3734 77 30.75C77 27.0221 80.0221 24 83.75 24H100.342Z" fill="currentColor"/>
|
||||
<path d="M16 53.2891C6.07439 55.7012 0 58.9396 0 62.4999C0 69.9557 26.6391 75.9999 59.5 75.9999C92.3609 75.9999 119 69.9557 119 62.4999C119 58.9396 112.926 55.7012 103 53.2891V61.9999C103 64.209 101.209 65.9999 99 65.9999H20C17.7909 65.9999 16 64.209 16 61.9999V53.2891Z" fill="currentColor" stroke="currentColor" fill-opacity="0.25" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 1.9 KiB |
+2
-2
@@ -17,6 +17,6 @@ specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
<svg viewBox="0 0 119 78" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M59.2172 1.54918C59.4582 0.816949 60.525 0.81693 60.7662 1.54918L67.4261 21.777C67.8015 22.9173 68.8726 23.6776 70.0684 23.6776H91.6205C92.4352 23.6776 92.71 24.6673 92.1034 25.1023L74.6673 37.6038C73.6922 38.3029 73.2776 39.5495 73.6539 40.6922L80.3138 60.9201C80.5391 61.6042 79.7302 62.2787 79.0563 61.7956L61.6204 49.2941C60.6483 48.5971 59.3349 48.5971 58.3629 49.2941L40.9269 61.7956C40.253 62.2787 39.4441 61.6042 39.6694 60.9201L46.3294 40.6922C46.7056 39.5495 46.291 38.3029 45.3159 37.6038L27.8798 25.1023C27.2732 24.6673 27.548 23.6776 28.3628 23.6776H49.9149C51.1106 23.6776 52.1817 22.9173 52.5571 21.777L59.2172 1.54918Z" stroke="currentColor" stroke-width="2"/>
|
||||
<path d="M59.5 49.6685C60.052 49.6685 60.6021 49.6699 61.1504 49.6733L61.3066 49.6743L61.4336 49.7651L78.7695 62.1948C79.8103 62.9411 81.1618 61.9247 80.7803 60.7661L77.5625 50.9927L77.333 50.2935L78.0674 50.3491C89.7005 51.2299 99.7327 52.9176 106.87 55.1284C110.435 56.2328 113.307 57.4759 115.298 58.8335C117.27 60.1787 118.491 61.7173 118.491 63.4331C118.491 64.5061 118.01 65.5129 117.177 66.437C116.346 67.3584 115.143 68.2239 113.635 69.0327C110.618 70.6507 106.286 72.0922 100.975 73.2974C90.343 75.7096 75.6797 77.1978 59.5 77.1978C43.3203 77.1978 28.657 75.7096 18.0254 73.2974C12.7139 72.0922 8.3816 70.6507 5.36523 69.0327C3.85739 68.2239 2.65424 67.3584 1.82324 66.437C0.989783 65.5129 0.508887 64.5061 0.508789 63.4331C0.508789 61.6928 1.7644 60.135 3.78906 58.7749C5.83338 57.4017 8.78191 56.1452 12.4404 55.0327C19.7642 52.8057 30.0482 51.1211 41.9424 50.2749L42.6738 50.2231L42.4443 50.9194L39.2021 60.7661C38.8207 61.9247 40.1722 62.9409 41.2129 62.1948L58.5557 49.7612L58.6836 49.6694L58.8408 49.6685C59.0602 49.668 59.2799 49.6685 59.5 49.6685Z" fill="currentColor" fill-opacity="0.25" stroke="currentColor"/>
|
||||
<path d="M59.2123 1.55857C59.4575 0.81382 60.5425 0.813801 60.7878 1.55857L67.5616 22.1322C67.9434 23.292 69.0328 24.0653 70.249 24.0653L92.1695 24.0653C92.9982 24.0653 93.2777 25.0719 92.6607 25.5143L74.9266 38.2295C73.9348 38.9406 73.5131 40.2085 73.8958 41.3707L80.6696 61.9444C80.8987 62.6402 80.076 63.3262 79.3906 62.8348L61.6566 50.1196C60.6679 49.4107 59.3321 49.4107 58.3434 50.1196L40.6094 62.8348C39.924 63.3262 39.1013 62.6402 39.3304 61.9444L46.1042 41.3707C46.4869 40.2085 46.0652 38.9406 45.0734 38.2295L27.3393 25.5143C26.7223 25.0719 27.0018 24.0653 27.8305 24.0653L49.751 24.0653C50.9672 24.0653 52.0566 23.292 52.4384 22.1322L59.2123 1.55857Z" stroke="currentColor" stroke-width="2"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M41.6782 51.616C17.5239 53.3343 0 58.4528 0 64.5C0 71.9558 26.6391 78 59.5 78C92.3609 78 119 71.9558 119 64.5C119 58.5387 101.97 53.4798 78.3466 51.6913L81.6194 61.6317C82.1664 63.2928 80.2398 64.6741 78.808 63.6475L61.1757 51.0053C60.619 51.0018 60.0604 51 59.5 51C59.2765 51 59.0534 51.0003 58.8305 51.0008L41.192 63.6475C39.7602 64.6741 37.8337 63.2928 38.3806 61.6317L41.6782 51.616Z" fill="currentColor" fill-opacity="0.25" stroke="currentColor"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.0 KiB |
+128
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* 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 { screen } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
import { supersetTheme } from '@apache-superset/core/theme';
|
||||
import { DatasetTypeLabel } from './DatasetTypeLabel';
|
||||
import { renderWithTheme } from './testUtils';
|
||||
|
||||
test('renders "Physical" text for physical dataset', () => {
|
||||
renderWithTheme(<DatasetTypeLabel datasetType="physical" />);
|
||||
expect(screen.getByText('Physical')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('renders "Virtual" text for virtual dataset', () => {
|
||||
renderWithTheme(<DatasetTypeLabel datasetType="virtual" />);
|
||||
expect(screen.getByText('Virtual')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('uses default primary color for physical label', () => {
|
||||
renderWithTheme(<DatasetTypeLabel datasetType="physical" />);
|
||||
const tag = screen
|
||||
.getByText('Physical')
|
||||
.closest('[data-test="dataset-type-label"]');
|
||||
expect(tag).toHaveStyle({ color: supersetTheme.colorPrimaryText });
|
||||
});
|
||||
|
||||
test('uses default color for virtual label', () => {
|
||||
renderWithTheme(<DatasetTypeLabel datasetType="virtual" />);
|
||||
const tag = screen
|
||||
.getByText('Virtual')
|
||||
.closest('[data-test="dataset-type-label"]');
|
||||
expect(tag).toHaveStyle({ color: supersetTheme.colorPrimary });
|
||||
});
|
||||
|
||||
test('applies custom labelDatasetPhysical tokens when set', () => {
|
||||
renderWithTheme(<DatasetTypeLabel datasetType="physical" />, {
|
||||
labelDatasetPhysicalColor: '#111111',
|
||||
labelDatasetPhysicalBg: '#222222',
|
||||
labelDatasetPhysicalBorderColor: '#333333',
|
||||
});
|
||||
const tag = screen
|
||||
.getByText('Physical')
|
||||
.closest('[data-test="dataset-type-label"]');
|
||||
expect(tag).toHaveStyle({
|
||||
color: '#111111',
|
||||
backgroundColor: '#222222',
|
||||
borderColor: '#333333',
|
||||
});
|
||||
});
|
||||
|
||||
test('applies custom labelDatasetVirtual tokens when set', () => {
|
||||
renderWithTheme(<DatasetTypeLabel datasetType="virtual" />, {
|
||||
labelDatasetVirtualColor: '#444444',
|
||||
labelDatasetVirtualBg: '#555555',
|
||||
labelDatasetVirtualBorderColor: '#666666',
|
||||
});
|
||||
const tag = screen
|
||||
.getByText('Virtual')
|
||||
.closest('[data-test="dataset-type-label"]');
|
||||
expect(tag).toHaveStyle({
|
||||
color: '#444444',
|
||||
backgroundColor: '#555555',
|
||||
borderColor: '#666666',
|
||||
});
|
||||
});
|
||||
|
||||
test('applies custom labelDatasetPhysicalIconColor to icon', () => {
|
||||
const { container } = renderWithTheme(
|
||||
<DatasetTypeLabel datasetType="physical" />,
|
||||
{ labelDatasetPhysicalIconColor: '#aabbcc' },
|
||||
);
|
||||
const svg = container.querySelector('[role="img"]');
|
||||
expect(svg).toHaveStyle({ color: '#aabbcc' });
|
||||
});
|
||||
|
||||
test('applies custom labelDatasetVirtualIconColor to icon', () => {
|
||||
const { container } = renderWithTheme(
|
||||
<DatasetTypeLabel datasetType="virtual" />,
|
||||
{ labelDatasetVirtualIconColor: '#ddeeff' },
|
||||
);
|
||||
const svg = container.querySelector('[role="img"]');
|
||||
expect(svg).toHaveStyle({ color: '#ddeeff' });
|
||||
});
|
||||
|
||||
test('uses default colorPrimary for physical dataset icon', () => {
|
||||
const { container } = renderWithTheme(
|
||||
<DatasetTypeLabel datasetType="physical" />,
|
||||
);
|
||||
const svg = container.querySelector('[role="img"]');
|
||||
expect(svg).toHaveStyle({ color: supersetTheme.colorPrimary });
|
||||
});
|
||||
|
||||
test('virtual dataset icon has no explicit icon color by default', () => {
|
||||
const { container } = renderWithTheme(
|
||||
<DatasetTypeLabel datasetType="virtual" />,
|
||||
);
|
||||
const svg = container.querySelector('[role="img"]') as HTMLElement;
|
||||
expect(svg.style.color).toBe('');
|
||||
});
|
||||
|
||||
test('partial token override uses custom bg with default color fallback', () => {
|
||||
renderWithTheme(<DatasetTypeLabel datasetType="physical" />, {
|
||||
labelDatasetPhysicalBg: '#ff0000',
|
||||
});
|
||||
const tag = screen
|
||||
.getByText('Physical')
|
||||
.closest('[data-test="dataset-type-label"]');
|
||||
expect(tag).toHaveStyle({
|
||||
backgroundColor: '#ff0000',
|
||||
color: supersetTheme.colorPrimaryText,
|
||||
});
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user