mirror of
https://github.com/apache/superset.git
synced 2026-08-29 03:21:14 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f0f55fbed3 | ||
|
|
b4c57ae9a5 | ||
|
|
4589a78e52 | ||
|
|
c027d8ca6e | ||
|
|
e053b6d92d | ||
|
|
65532b6bdd | ||
|
|
05e8d24d9d | ||
|
|
fec70404c4 | ||
|
|
72c6f95053 | ||
|
|
d46e7986a2 | ||
|
|
889d2a6a22 | ||
|
|
75044575a1 | ||
|
|
4028ff1e05 | ||
|
|
67a193f1d9 | ||
|
|
cd2638e27c | ||
|
|
58b37d0fa6 | ||
|
|
782104964e | ||
|
|
24e1c544ad | ||
|
|
1333ed12c7 | ||
|
|
dff961a2f9 | ||
|
|
03ebf2add4 | ||
|
|
ce94f79a09 | ||
|
|
80928bdb6c | ||
|
|
6ce414928e | ||
|
|
f2d0b592c0 | ||
|
|
5a7fa2d1c6 | ||
|
|
c0e32663a4 | ||
|
|
fa36e2565a | ||
|
|
eb91af8244 | ||
|
|
051105098c | ||
|
|
8c5be889d9 | ||
|
|
b733b57e9e | ||
|
|
13e97ba913 | ||
|
|
f9e43a37d8 | ||
|
|
5ff44c5ed6 | ||
|
|
7b145a520c | ||
|
|
9f62aace7e | ||
|
|
20be48c085 | ||
|
|
e1c7a51674 | ||
|
|
ecbd6578b0 | ||
|
|
ea3206b076 | ||
|
|
478f612f25 | ||
|
|
a16bc0d94f | ||
|
|
bcc6af6c5f | ||
|
|
94b59420b7 | ||
|
|
6814a89230 | ||
|
|
ebbe2e8714 | ||
|
|
b8acf4b207 | ||
|
|
ba5d5091b9 | ||
|
|
8c88c0d3ac | ||
|
|
e9e652d22d | ||
|
|
8f82d851cf | ||
|
|
83934c038e | ||
|
|
35fc36a97e | ||
|
|
88c4e689e3 | ||
|
|
29f4a05eb7 | ||
|
|
323741b043 | ||
|
|
5c3c6362ae | ||
|
|
c1b6ff340b | ||
|
|
05830ebc77 | ||
|
|
74dd690102 | ||
|
|
22027ec24c | ||
|
|
a3072c0bf6 | ||
|
|
6d8cbbfe8d | ||
|
|
926e0e6a92 | ||
|
|
d013bc7a21 | ||
|
|
87c48cc473 | ||
|
|
c2266f860c | ||
|
|
e8aea6b6df | ||
|
|
bde1c483b0 | ||
|
|
ff1d9f9e09 | ||
|
|
76151beade | ||
|
|
905a35d05f | ||
|
|
fde0fbd315 |
@@ -5,6 +5,10 @@ updates:
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
groups:
|
||||
security:
|
||||
applies-to: "security-updates"
|
||||
patterns: ["*"]
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
@@ -61,6 +65,9 @@ updates:
|
||||
- npm
|
||||
- dependabot
|
||||
groups:
|
||||
security:
|
||||
applies-to: "security-updates"
|
||||
patterns: ["*"]
|
||||
rjsf:
|
||||
patterns:
|
||||
- "@rjsf/*"
|
||||
@@ -98,6 +105,10 @@ updates:
|
||||
labels:
|
||||
- pip
|
||||
- dependabot
|
||||
groups:
|
||||
security:
|
||||
applies-to: "security-updates"
|
||||
patterns: ["*"]
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
@@ -105,6 +116,10 @@ updates:
|
||||
directory: ".github/actions"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
groups:
|
||||
security:
|
||||
applies-to: "security-updates"
|
||||
patterns: ["*"]
|
||||
open-pull-requests-limit: 10
|
||||
versioning-strategy: increase
|
||||
cooldown:
|
||||
@@ -115,6 +130,9 @@ updates:
|
||||
schedule:
|
||||
interval: "daily"
|
||||
groups:
|
||||
security:
|
||||
applies-to: "security-updates"
|
||||
patterns: ["*"]
|
||||
storybook:
|
||||
patterns:
|
||||
- "@storybook/*"
|
||||
@@ -142,6 +160,10 @@ updates:
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
groups:
|
||||
security:
|
||||
applies-to: "security-updates"
|
||||
patterns: ["*"]
|
||||
versioning-strategy: increase
|
||||
cooldown:
|
||||
default-days: 7
|
||||
@@ -153,6 +175,10 @@ updates:
|
||||
labels:
|
||||
- npm
|
||||
- dependabot
|
||||
groups:
|
||||
security:
|
||||
applies-to: "security-updates"
|
||||
patterns: ["*"]
|
||||
open-pull-requests-limit: 10
|
||||
versioning-strategy: increase
|
||||
cooldown:
|
||||
|
||||
@@ -141,7 +141,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@b6e2e70617bc3265edd6dab6c906732b2f1ae151 # v21
|
||||
uses: dawidd6/action-download-artifact@d63b86af1b34672e53c440b1b83979861906bad7 # v24
|
||||
continue-on-error: true
|
||||
with:
|
||||
workflow: superset-python-integrationtest.yml
|
||||
@@ -150,7 +150,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@b6e2e70617bc3265edd6dab6c906732b2f1ae151 # v21
|
||||
uses: dawidd6/action-download-artifact@d63b86af1b34672e53c440b1b83979861906bad7 # v24
|
||||
continue-on-error: true
|
||||
with:
|
||||
workflow: superset-python-integrationtest.yml
|
||||
|
||||
@@ -140,7 +140,7 @@ jobs:
|
||||
run: |
|
||||
yarn install --check-cache
|
||||
- name: Download database diagnostics from integration tests
|
||||
uses: dawidd6/action-download-artifact@b6e2e70617bc3265edd6dab6c906732b2f1ae151 # v21
|
||||
uses: dawidd6/action-download-artifact@d63b86af1b34672e53c440b1b83979861906bad7 # v24
|
||||
with:
|
||||
workflow: superset-python-integrationtest.yml
|
||||
run_id: ${{ github.event.workflow_run.id }}
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
# db_engine_specs tests against real databases (testcontainers)
|
||||
name: Testcontainers
|
||||
|
||||
# Spins up real Docker containers (see tests/testcontainers/ for the current
|
||||
# dialect list) via testcontainers-python, which catches real dialect/driver
|
||||
# regressions -- the kind mocked db_engine_specs unit tests structurally
|
||||
# cannot, e.g. apache/superset#42899 (Trino emitting OFFSET before LIMIT).
|
||||
# Runs on a nightly cron (catches drift from a driver's own releases, not
|
||||
# just from Superset's changes) and on pull_request, scoped via `paths` to
|
||||
# only PRs that actually touch this test suite or the workflow itself, so
|
||||
# unrelated PRs across the repo are never affected.
|
||||
#
|
||||
# A matrix entry can set `nightly_only: true` to run only on the cron (or a
|
||||
# manual workflow_dispatch), never on pull_request -- for a dialect whose
|
||||
# image is too heavy (a multi-service cluster, a many-GB image, a slow
|
||||
# licensed installer) to justify adding its wall-clock/resource cost to
|
||||
# every PR that merely touches this suite. Omit the field entirely for a
|
||||
# normal dialect; it isn't nightly-only by default.
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 5 * * *"
|
||||
workflow_dispatch: {}
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/testcontainers.yml"
|
||||
- "tests/testcontainers/**"
|
||||
- "superset/db_engine_specs/**"
|
||||
- "pyproject.toml"
|
||||
- "requirements/development.in"
|
||||
- "requirements/development.txt"
|
||||
|
||||
concurrency:
|
||||
# Scoped by ref, not just workflow name -- otherwise every PR run and the
|
||||
# nightly cron share one group, and starting the workflow on another PR
|
||||
# (or the nightly firing mid-PR-run) cancels an unrelated in-progress run.
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
testcontainers:
|
||||
runs-on: ubuntu-26.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
# One job per dialect rather than one job for the whole suite: a
|
||||
# single slow container would otherwise inflate the wall-clock
|
||||
# time for every dialect, not just its own. Running in parallel
|
||||
# means the suite's total time is bounded by the slowest dialect,
|
||||
# not the sum of all of them. Db2's first-boot init is documented
|
||||
# upstream as notably slow (a real instance bring-up, not just a
|
||||
# process start) and untested locally here (no arm64 image), so
|
||||
# it gets a wider timeout margin than the rest until real CI data
|
||||
# says otherwise.
|
||||
- dialect: cockroachdb
|
||||
timeout: 10
|
||||
- dialect: crate
|
||||
timeout: 10
|
||||
- dialect: trino
|
||||
timeout: 10
|
||||
- dialect: mssql
|
||||
timeout: 10
|
||||
- dialect: elasticsearch
|
||||
timeout: 10
|
||||
- dialect: oracle
|
||||
timeout: 15
|
||||
- dialect: db2
|
||||
timeout: 25
|
||||
- dialect: mariadb
|
||||
timeout: 10
|
||||
- dialect: timescaledb
|
||||
timeout: 10
|
||||
- dialect: yugabytedb
|
||||
timeout: 10
|
||||
- dialect: monetdb
|
||||
timeout: 10
|
||||
- dialect: mongodb
|
||||
timeout: 10
|
||||
- dialect: postgres
|
||||
timeout: 10
|
||||
- dialect: mysql
|
||||
timeout: 10
|
||||
- dialect: clickhouse
|
||||
timeout: 10
|
||||
# StarRocks' allin1-ubuntu image brings up both FE and BE in one
|
||||
# container, which is a heavier bring-up than a single-process
|
||||
# database -- wider margin until real CI data says otherwise.
|
||||
- dialect: starrocks
|
||||
timeout: 15
|
||||
- dialect: databend
|
||||
timeout: 10
|
||||
- dialect: risingwave
|
||||
timeout: 10
|
||||
- dialect: firebird
|
||||
timeout: 10
|
||||
- dialect: ydb
|
||||
timeout: 10
|
||||
# OceanBase bootstraps a distributed-style cluster even in
|
||||
# single-node MODE=MINI -- too heavy for every PR's CI budget, so
|
||||
# it runs on the nightly cron / manual dispatch only.
|
||||
- dialect: oceanbase
|
||||
timeout: 20
|
||||
nightly_only: true
|
||||
timeout-minutes: ${{ matrix.timeout }}
|
||||
env:
|
||||
PYTHONPATH: ${{ github.workspace }}
|
||||
SUPERSET_TESTENV: true
|
||||
SUPERSET_SECRET_KEY: not-a-secret
|
||||
# This job's matrix installs exactly one dialect's testcontainers
|
||||
# driver for exactly this job, so treat that driver as required: a
|
||||
# broken/missing import should fail the job, not silently skip to a
|
||||
# misleadingly green, zero-tests-run result. See _driver.py.
|
||||
SUPERSET_TESTCONTAINERS_STRICT: true
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Setup Python
|
||||
uses: ./.github/actions/setup-backend/
|
||||
with:
|
||||
python-version: current
|
||||
- name: Install db2 driver (ibm-db-sa)
|
||||
# ibm-db (the db2 DBAPI) ships no Linux arm64 wheel, so it's kept out
|
||||
# of the baseline dev install (requirements/development.in) to avoid
|
||||
# breaking the multi-platform dev Docker image build. Install it here
|
||||
# instead, only for this leg of the matrix.
|
||||
if: matrix.dialect == 'db2'
|
||||
run: uv pip install --system -e .[db2]
|
||||
- name: Install oceanbase driver (oceanbase_py)
|
||||
# oceanbase_py pins sqlalchemy-utils>=0.38.3,<0.39, which conflicts
|
||||
# outright with Superset's own sqlalchemy-utils==0.42.1 pin -- kept
|
||||
# out of the baseline dev install for the same reason as db2 above.
|
||||
# Installed as its own standalone package (not via `-e .[oceanbase]`)
|
||||
# so --no-deps only skips *oceanbase_py's* dependencies -- applied
|
||||
# to `-e .[oceanbase]` instead, --no-deps blocks pip from installing
|
||||
# anything the extras marker pulls in, including oceanbase_py
|
||||
# itself, which "succeeds" without actually installing it
|
||||
# (confirmed on real CI: the install step reported success, but the
|
||||
# module was still missing). This job only needs oceanbase_py's
|
||||
# dialect module importable, not its sqlalchemy-utils dependency
|
||||
# satisfied, since nothing here calls into it.
|
||||
if: matrix.dialect == 'oceanbase'
|
||||
run: uv pip install --system --no-deps "oceanbase_py>=0.0.1.2"
|
||||
- name: Install Firebird client library (libfbclient2)
|
||||
# sqlalchemy-firebird's driver (firebird-driver) is a pure-Python
|
||||
# ctypes wrapper (its wheel is py3-none-any) that dynamically loads
|
||||
# the native Firebird client library from the host at import time
|
||||
# -- it doesn't bundle that library itself, so it has to come from
|
||||
# the system package manager, only for this leg of the matrix.
|
||||
if: matrix.dialect == 'firebird'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libfbclient2
|
||||
- name: Run testcontainers db_engine_specs tests (${{ matrix.dialect }})
|
||||
# A job-level `if:` can't reference `matrix` (only github/inputs/
|
||||
# needs/vars are available there), so the nightly_only skip has to
|
||||
# live on the step instead. A dialect without `nightly_only` set
|
||||
# evaluates the left side true (unset is null, and `null != true`
|
||||
# is true) and always runs; one WITH it set only runs on the cron
|
||||
# or a manual dispatch, never on pull_request.
|
||||
if: >-
|
||||
matrix.nightly_only != true ||
|
||||
github.event_name == 'schedule' ||
|
||||
github.event_name == 'workflow_dispatch'
|
||||
run: |
|
||||
pytest --durations-min=2 -v -m testcontainers \
|
||||
./tests/testcontainers/db_engine_specs/test_${{ matrix.dialect }}.py \
|
||||
--junit-xml=test-results/junit-testcontainers-${{ matrix.dialect }}.xml
|
||||
- name: Upload JUnit test results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: junit-results-testcontainers-${{ matrix.dialect }}
|
||||
path: test-results/
|
||||
retention-days: 7
|
||||
|
||||
actions-timeline:
|
||||
needs: [testcontainers]
|
||||
if: always()
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
actions: read
|
||||
steps:
|
||||
- uses: Kesin11/actions-timeline@57fc93f20c6da7fbc14063c6d24a2a5627c799ad # v3.2.0
|
||||
@@ -25,6 +25,7 @@ assists people when migrating to a new version.
|
||||
## Next
|
||||
|
||||
- `SAMPLES_ROW_LIMIT` is now the default for `/datasource/samples` requests without a valid explicit `per_page`, rather than a hard per-request ceiling; explicit limits are honored up to the existing global row-limit ceiling, matching `/chart/data` SAMPLES requests.
|
||||
- The `cockroachdb` extra (`pip install apache-superset[cockroachdb]`) now installs `sqlalchemy-cockroachdb` instead of the abandoned `cockroachdb` package, whose SQLAlchemy dialect could not be imported under SQLAlchemy 2.0. Existing environments with the old package installed must `pip uninstall cockroachdb` before reinstalling the extra -- both packages register the same `cockroachdb` SQLAlchemy dialect entry point, so leaving the old one in place can still load the abandoned implementation.
|
||||
|
||||
### MCP tool results preserve stored string values
|
||||
|
||||
|
||||
@@ -14,6 +14,20 @@ Users can configure automated alerts and reports to send dashboards or charts to
|
||||
|
||||
Alerts and reports are disabled by default. To turn them on, you'll need to change configuration settings and install a suitable headless browser in your environment.
|
||||
|
||||
## Content Format Options
|
||||
|
||||
When scheduling an alert or report, you can choose the format used to deliver the dashboard or chart:
|
||||
|
||||
- **PDF** – a full-page screenshot rendered as a PDF attachment. Available for both dashboards and charts.
|
||||
- **PNG** – a screenshot delivered as an attachment. Emails embed the image inline in the message body; Slack and webhook recipients receive it as an uploaded file. Available for both dashboards and charts.
|
||||
- **CSV** – chart data attached as a `.csv` file. Available for charts only.
|
||||
- **XLSX (Excel)** – chart data attached as a `.xlsx` file. Available for charts only. If the chart's data spans multiple server-paginated files, email delivery detects the bundle and renames the attachment to `.zip`; Slack and webhook deliveries always name the file with an `.xlsx` extension even when the contents are a multi-file ZIP archive.
|
||||
- **Text** – chart data embedded directly in the email or Slack message body. Available only for charts using a text-based visualization type (e.g. Table, Pivot Table, Paired t-test).
|
||||
|
||||
Dashboard reports and alerts are limited to the PDF and PNG formats; the CSV, XLSX, and Text options are only available when scheduling a report or alert for an individual chart.
|
||||
|
||||
For alerts (not scheduled reports), PNG/PDF screenshots and chart CSV/XLSX data are only generated when the `ALERTS_ATTACH_REPORTS` [feature flag](/admin-docs/configuration/configuring-superset#feature-flags) is enabled (the default); with it disabled, an alert notification is still sent, but without the attachment.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Commons
|
||||
|
||||
@@ -76,6 +76,27 @@ 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`.
|
||||
|
||||
Native filter option queries (the dropdown values for native filters) go through this same
|
||||
chart-data cache, but their freshness needs often differ from regular chart queries, especially for
|
||||
datasets whose visible values change frequently, including RLS-constrained datasets. Set
|
||||
`NATIVE_FILTER_OPTIONS_CACHE_TIMEOUT` in `superset_config.py` to give these queries a dedicated
|
||||
timeout, checked before the chart/dataset/database chain and the `DATA_CACHE_CONFIG` default above:
|
||||
|
||||
```python
|
||||
NATIVE_FILTER_OPTIONS_CACHE_TIMEOUT = 60 # seconds
|
||||
```
|
||||
|
||||
- `None` (default): native filter option queries fall through to the normal
|
||||
chart/dataset/database/`DATA_CACHE_CONFIG` resolution chain.
|
||||
- `-1`: disables caching for native filter option queries entirely.
|
||||
- `0`: passed directly to the cache backend; behavior is backend-specific, so use `-1` if the intent
|
||||
is to disable caching.
|
||||
- A positive integer: cache native filter option queries for that many seconds.
|
||||
|
||||
This setting only applies to requests detected as native filter option queries. It takes precedence
|
||||
over the per-chart/dataset/database timeouts, but not over an explicit per-request
|
||||
`custom_cache_timeout` override (e.g. "Force refresh").
|
||||
|
||||
## Limiting Cached Result Size
|
||||
|
||||
Very large chart or SQL query results can flood the cache backend (Redis/Memcached), evicting many
|
||||
|
||||
@@ -83,6 +83,26 @@ The optional username flag **-u** sets the user used for the datasource import.
|
||||
superset import_datasources -p <path / filename> -u 'admin'
|
||||
```
|
||||
|
||||
## Importing a Directory of Assets
|
||||
|
||||
The `import_directory` command imports a directory of exported assets (databases, datasets,
|
||||
dashboards, charts) in the same layout produced by the ZIP-based export. Saved queries and tags
|
||||
included in a full export are not imported by this command:
|
||||
|
||||
```bash
|
||||
superset import_directory <path / directory>
|
||||
```
|
||||
|
||||
As with `import_datasources`, the optional username flag **-u** sets the user assigned as the
|
||||
owner of the imported assets. The default is 'admin'. Example:
|
||||
|
||||
```bash
|
||||
superset import_directory <path / directory> -u 'admin'
|
||||
```
|
||||
|
||||
If the specified user does not exist, the command fails immediately with an error rather than
|
||||
importing the assets without an owner.
|
||||
|
||||
## 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:
|
||||
|
||||
@@ -505,6 +505,8 @@ All MCP settings go in `superset_config.py`. Defaults are defined in `superset/m
|
||||
| `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). |
|
||||
| `MCP_DISABLED_CHART_PLUGINS` | `frozenset()` | Set of chart type plugin names (e.g. `"handlebars"`) to hide from `generate_chart`. Does not affect `get_chart_type_schema`. See [Disabling chart type plugins](#disabling-chart-type-plugins). |
|
||||
| `MCP_CHART_PLUGIN_ENABLED_FUNC` | `None` | Callable `(chart_type: str) -> bool` evaluated per registry lookup for dynamic enable/disable decisions. Takes precedence over `MCP_DISABLED_CHART_PLUGINS` when set. See [Disabling chart type plugins](#disabling-chart-type-plugins). |
|
||||
|
||||
### Authentication
|
||||
|
||||
@@ -893,6 +895,39 @@ MCP_DISABLED_TOOLS = {"extensions.myorg.myextension.some_tool"}
|
||||
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.
|
||||
:::
|
||||
|
||||
## Disabling chart type plugins
|
||||
|
||||
The `generate_chart` tool dispatches per chart type (`xy`, `table`, `pie`, `pivot_table`, `mixed_timeseries`, `handlebars`, `big_number`, `histogram`, `box_plot`, `waterfall`) to a registered chart type plugin. Two settings let operators enable or disable individual chart type plugins at runtime, without a code deploy.
|
||||
|
||||
### Static deny-list
|
||||
|
||||
Set `MCP_DISABLED_CHART_PLUGINS` in your `superset_config.py` to a set of chart type names:
|
||||
|
||||
```python
|
||||
# superset_config.py
|
||||
|
||||
# Emergency kill switch: hide "handlebars" from all callers
|
||||
MCP_DISABLED_CHART_PLUGINS = {"handlebars"}
|
||||
```
|
||||
|
||||
Disabled chart types stay registered but are filtered out at lookup time: they're never listed in `generate_chart`'s supported chart types, and `generate_chart` calls for them are rejected. `get_chart_type_schema` consults its own static schema/example map rather than the registry filter, so a disabled chart type's schema remains queryable through that tool even though `generate_chart` will reject it.
|
||||
|
||||
### Dynamic predicate
|
||||
|
||||
For per-request control (A/B tests, gradual rollout, entitlement checks), set `MCP_CHART_PLUGIN_ENABLED_FUNC` to a callable. It's evaluated as `enabled_func(chart_type: str) -> bool` on every registry lookup, and it takes precedence over `MCP_DISABLED_CHART_PLUGINS` when set:
|
||||
|
||||
```python
|
||||
# superset_config.py
|
||||
from flask import g
|
||||
|
||||
|
||||
def MCP_CHART_PLUGIN_ENABLED_FUNC(chart_type: str) -> bool:
|
||||
flags = getattr(g, "feature_flags", {})
|
||||
return flags.get(f"mcp_chart_{chart_type}", True)
|
||||
```
|
||||
|
||||
The callable must be cheap and in-process (consult already-loaded feature flags or request-local context) -- do not perform network I/O per call. If it raises, the registry fails closed (the plugin is hidden) and logs a warning.
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
- **Use TLS** for all production MCP endpoints -- place the server behind a reverse proxy with HTTPS
|
||||
|
||||
@@ -88,6 +88,27 @@ THEME_DARK = {
|
||||
# - OS preference detection is automatically enabled
|
||||
```
|
||||
|
||||
### Default Theme Mode
|
||||
|
||||
By default, Superset mimics the visitor's OS/browser preference (light or dark) for
|
||||
sessions that don't have a saved user preference. Use `THEME_DEFAULT_MODE` to override
|
||||
that starting point instance-wide for the standard application:
|
||||
|
||||
```python
|
||||
# Default theme mode for sessions without a saved user preference.
|
||||
# One of "default" (always light), "dark" (always dark), or "system" (mimic OS preference).
|
||||
THEME_DEFAULT_MODE = "dark"
|
||||
```
|
||||
|
||||
- `"system"` (the default) preserves the existing behavior of following the OS/browser
|
||||
preference, provided a dark theme is configured (`THEME_DARK` is not `None`). If no dark
|
||||
theme is available, Superset always starts in light mode regardless of this setting.
|
||||
- `"default"` or `"dark"` forces that starting mode for first-time visitors; users can still switch themes manually afterward if both `THEME_DEFAULT` and `THEME_DARK` are available.
|
||||
- A user's own saved preference, once they toggle the theme switcher, always takes precedence over `THEME_DEFAULT_MODE`.
|
||||
- `THEME_DEFAULT_MODE` has no effect on embedded dashboards: the embed SDK sets the
|
||||
starting mode via its own `themeMode` URL parameter, which takes precedence and falls
|
||||
back to light mode when the parameter is absent.
|
||||
|
||||
### App Branding
|
||||
|
||||
The application name shown in the browser title bar and navigation can be
|
||||
|
||||
@@ -243,6 +243,42 @@ Each subject in the response includes flat scalar ids (`user_id`, `role_id`, `gr
|
||||
than a nested object, so callers can match directly on whichever id they already have — only the
|
||||
id field matching the subject's `type` is populated; the others are `null`.
|
||||
|
||||
#### Extending Editorship with EXTRA_EDITORS_RESOLVER
|
||||
|
||||
Deployments that grant edit access to a dashboard or chart through a mechanism outside
|
||||
Superset's own Subject-based `editors` list — for example, a folder-permission system or an
|
||||
internal directory service — can plug that logic in with `EXTRA_EDITORS_RESOLVER`:
|
||||
|
||||
```python
|
||||
def extra_editors_resolver(resource):
|
||||
# `resource` is the Dashboard or Slice instance being checked.
|
||||
# Return Subject instances, raw subject ids, or dicts with an `id` key.
|
||||
return [...]
|
||||
|
||||
|
||||
EXTRA_EDITORS_RESOLVER = extra_editors_resolver
|
||||
```
|
||||
|
||||
The resolver's result is unioned with the resource's own `editors` for editorship checks: it
|
||||
feeds `is_editor`, `raise_for_editorship`, save-as, and soft-delete restore. When
|
||||
`EXTRA_EDITORS_RESOLVER` is configured, the usual lockout-prevention behavior — automatically
|
||||
re-adding a non-admin who removes themselves from a resource's editors — is skipped, since the
|
||||
deployment has its own way of keeping the resource editable. This skip is global to the setting,
|
||||
not per-resource: it still applies on a resource where the resolver currently returns no
|
||||
subjects, so a resolver that can't guarantee an alternate editor for every resource can let the
|
||||
last editor remove themselves and leave it uneditable by non-admins.
|
||||
|
||||
Resolved subject ids are also surfaced as `extra_editors` in the chart and dashboard `GET`
|
||||
responses, so API clients can distinguish resolver-granted editorship from the resource's own
|
||||
`editors` list. This field is attached after serialization and isn't part of the OpenAPI response
|
||||
schema, so generated API clients won't see it as a typed field.
|
||||
|
||||
Because the resolver is arbitrary per-deployment Python rather than a SQL-expressible condition,
|
||||
editorship it grants cannot be reflected in list-view filtering (for example, the soft-deleted
|
||||
archive is scoped to editors via a SQL query). It does still run once per row on chart and
|
||||
dashboard list responses to populate `extra_editors`, so a slow or unavailable external resolver
|
||||
affects ordinary list requests, not just direct per-object checks.
|
||||
|
||||
### Dashboard Access Control
|
||||
|
||||
Access to dashboards is managed via editors (subjects that have edit permissions to the dashboard).
|
||||
@@ -583,6 +619,28 @@ SELECT * FROM (
|
||||
queries run against tables that have associated datasets with RLS filters will then have
|
||||
the appropriate predicates injected automatically.
|
||||
|
||||
#### RLS Indicator in the Dataset List and Explore
|
||||
|
||||
When a dataset has one or more RLS filters that apply to it, Superset shows a lock
|
||||
icon badge next to the dataset name in the **Datasets** list and next to the dataset
|
||||
selector in **Explore**. Hovering over the badge shows a tooltip listing each
|
||||
applicable filter's name, filter type (Regular or Base), group key (if any), assigned
|
||||
subjects (labeled "Roles" in the tooltip, but may include users and groups too), and
|
||||
clause.
|
||||
|
||||
This badge also surfaces filters that are inherited from the physical tables
|
||||
referenced by a virtual (SQL-based) dataset, as described above. Inherited filters
|
||||
are marked "from underlying table" in the tooltip, and a summary note is shown
|
||||
whenever any of the listed filters are inherited rather than assigned directly to
|
||||
the dataset. Inherited-filter detection depends on Superset's SQL parser being able
|
||||
to identify the referenced tables and match them to a physical dataset by name,
|
||||
schema, and database, so it's best-effort: unparseable or unmatched references won't
|
||||
surface a filter on the badge even if one would apply at query time.
|
||||
|
||||
The badge is a visibility aid only — it does not change which filters are applied to
|
||||
a query. Use the RLS REST API described below if you need to confirm exactly which
|
||||
filters affect a dataset.
|
||||
|
||||
#### Checking RLS Filters via the API
|
||||
|
||||
You can use the RLS REST API to audit which filters are configured and which datasets
|
||||
|
||||
@@ -406,6 +406,18 @@ ECharts option overrides bypass Superset's validation layer. Invalid option keys
|
||||
|
||||
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.
|
||||
|
||||
### Exporting a Chart as an Image or PDF
|
||||
|
||||
Alongside the raw-data export options (CSV, JSON, Excel), a chart's three-dot menu — in a dashboard or from the Explore chart toolbar — offers a few ways to export a visual snapshot of the chart:
|
||||
|
||||
- **Export screenshot (jpeg)** — a single-click JPEG screenshot of the chart.
|
||||
- **Export screenshot (png)** — opens a submenu with **Transparent background** and **Solid background** options. The solid option uses the current theme's background color. PNG produces a higher-quality image than the JPEG export.
|
||||
- **Export as PDF** — downloads the chart as a PDF file.
|
||||
|
||||
The dropdown menu is briefly hidden while the screenshot or PDF is being captured so it doesn't appear in the exported file. In Explore, these image and PDF options are available from the **Export All Data** submenu, and also from the **Export current view** submenu when the chart type supports current-view export.
|
||||
|
||||
These menu items respect your permissions: the dashboard export menu only appears if you can download, and the image/PDF options are disabled if you lack image-export permission.
|
||||
|
||||
### 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.
|
||||
|
||||
@@ -88,6 +88,21 @@ embedDashboard({
|
||||
|
||||
If the callback returns `null` or is not provided, Superset uses its own permalink URL as a fallback.
|
||||
|
||||
### Permalink origin rewriting
|
||||
|
||||
This rewrite only applies to the non-embedded permalink path — it has no effect on embedded dashboards. When Superset is not embedded, it rewrites the origin of any permalink URL it generates to `window.location.origin` before showing it to the user, which keeps a proxied or subdirectory-deployed Superset from handing out a permalink that points at an internal hostname the user's browser can't reach.
|
||||
|
||||
When Superset **is** embedded, this rewrite is skipped entirely regardless of the flag below: a `resolvePermalinkUrl` callback's return value is used as-is, and if no callback is provided (or it fails), the backend-supplied URL is also returned as-is.
|
||||
|
||||
If your reverse proxy correctly forwards `X-Forwarded-Host` and you'd rather non-embedded permalinks carry the backend's literal origin, opt out of the rewrite with `EMBEDDED_DISABLE_PERMALINK_ORIGIN_REWRITE`:
|
||||
|
||||
```python
|
||||
# superset_config.py
|
||||
EMBEDDED_DISABLE_PERMALINK_ORIGIN_REWRITE = True
|
||||
```
|
||||
|
||||
This defaults to `False` (rewrite enabled) and only affects non-embedded permalinks. Flipping the default would regress the common proxied/subdirectory deployment by exposing an unreachable internal host in copied permalinks.
|
||||
|
||||
---
|
||||
|
||||
## Feature Flags for Embedded Mode
|
||||
|
||||
@@ -248,11 +248,12 @@ Ask your admin for the MCP server URL and any authentication tokens you need.
|
||||
|
||||
### 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 |
|
||||
| Tool | Description |
|
||||
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `list_datasets` | List datasets with filtering and search |
|
||||
| `get_dataset_info` | Get dataset metadata (columns, metrics, schema details) |
|
||||
| `create_virtual_dataset` | Create a virtual dataset from a SQL query |
|
||||
| `update_dataset_metric` | Update a saved metric's expression, name, verbose_name, or format (affects every chart using it; requires dataset ownership) |
|
||||
|
||||
### Charts
|
||||
|
||||
|
||||
+3
-3
@@ -58,11 +58,11 @@
|
||||
"@fontsource/inter": "^5.3.0",
|
||||
"@mdx-js/react": "^3.1.1",
|
||||
"@saucelabs/theme-github-codeblock": "^0.3.0",
|
||||
"@storybook/addon-docs": "^10.5.9",
|
||||
"@storybook/addon-docs": "^10.5.10",
|
||||
"@superset-ui/core": "^0.20.4",
|
||||
"@swc/core": "^1.16.1",
|
||||
"antd": "^6.6.1",
|
||||
"baseline-browser-mapping": "^2.11.15",
|
||||
"baseline-browser-mapping": "^2.11.16",
|
||||
"caniuse-lite": "^1.0.30001809",
|
||||
"docusaurus-plugin-openapi-docs": "^5.2.0",
|
||||
"docusaurus-theme-openapi-docs": "^5.2.0",
|
||||
@@ -77,7 +77,7 @@
|
||||
"react-table": "^7.8.0",
|
||||
"remark-import-partial": "^0.0.2",
|
||||
"reselect": "^5.2.0",
|
||||
"storybook": "^10.5.9",
|
||||
"storybook": "^10.5.10",
|
||||
"swagger-ui-react": "^5.32.14",
|
||||
"swc-loader": "^0.2.7",
|
||||
"tinycolor2": "^1.4.2",
|
||||
|
||||
+141
-134
@@ -2145,10 +2145,10 @@
|
||||
utility-types "^3.10.0"
|
||||
webpack "^5.88.1"
|
||||
|
||||
"@emnapi/core@1.11.1":
|
||||
version "1.11.1"
|
||||
resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.11.1.tgz#b9e1064f3a6b1631e241e638eb48d736bfd372a6"
|
||||
integrity sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==
|
||||
"@emnapi/core@1.11.0":
|
||||
version "1.11.0"
|
||||
resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.11.0.tgz#8a655042dbbb10d0266670c9903c34a7001c705b"
|
||||
integrity sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q==
|
||||
dependencies:
|
||||
"@emnapi/wasi-threads" "1.2.2"
|
||||
tslib "^2.4.0"
|
||||
@@ -2169,10 +2169,10 @@
|
||||
"@emnapi/wasi-threads" "1.2.2"
|
||||
tslib "^2.4.0"
|
||||
|
||||
"@emnapi/runtime@1.11.1":
|
||||
version "1.11.1"
|
||||
resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.11.1.tgz#58f1f3d5d81a9b12f793ab688c96371901027c24"
|
||||
integrity sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==
|
||||
"@emnapi/runtime@1.11.0":
|
||||
version "1.11.0"
|
||||
resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.11.0.tgz#ce16b3674ff7266bbf50f9668bde8a04f3014d4e"
|
||||
integrity sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg==
|
||||
dependencies:
|
||||
tslib "^2.4.0"
|
||||
|
||||
@@ -2934,13 +2934,20 @@
|
||||
"@emnapi/runtime" "^1.5.0"
|
||||
"@tybys/wasm-util" "^0.10.1"
|
||||
|
||||
"@napi-rs/wasm-runtime@^1.1.4", "@napi-rs/wasm-runtime@^1.1.6":
|
||||
"@napi-rs/wasm-runtime@^1.1.4":
|
||||
version "1.1.6"
|
||||
resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz#ed33806d0f9be98dc76d0c3d4fd872fda701b5d5"
|
||||
integrity sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==
|
||||
dependencies:
|
||||
"@tybys/wasm-util" "^0.10.3"
|
||||
|
||||
"@napi-rs/wasm-runtime@^1.1.5":
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.3.tgz#97e3d45d7424dc5da1d4e32f3bf3b292f6c1b44c"
|
||||
integrity sha512-UMduMbqO5s5zF2NkNacMT/yK5Y5QiKvWr2+50bzIIxFDwVJ2h49b+oyjaCGPhJxd2/gC2x39EHv/gHVuu36x2Q==
|
||||
dependencies:
|
||||
"@tybys/wasm-util" "^0.10.3"
|
||||
|
||||
"@noble/hashes@1.4.0":
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426"
|
||||
@@ -3076,104 +3083,104 @@
|
||||
resolved "https://registry.yarnpkg.com/@oxc-project/types/-/types-0.127.0.tgz#8374fcdfb4a641861218daa5700c447c00b66663"
|
||||
integrity sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==
|
||||
|
||||
"@oxc-resolver/binding-android-arm-eabi@11.23.0":
|
||||
version "11.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-android-arm-eabi/-/binding-android-arm-eabi-11.23.0.tgz#31b3087c2c8a9d100ae22aced377c03befc87efa"
|
||||
integrity sha512-8IJyWRLVAyhTfe9/TIEbQqSQnl5rUqYJrUOS6Dkr+Mq9FGHMxDGeiEmwkBqCvDP5KckpPh/GYSgbag66O6JsCw==
|
||||
"@oxc-resolver/binding-android-arm-eabi@11.21.2":
|
||||
version "11.21.2"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-android-arm-eabi/-/binding-android-arm-eabi-11.21.2.tgz#ef832062e3a2f0c7604a8312c34eacf23a7bd7e3"
|
||||
integrity sha512-xQoCRv+gKax9KTdwdaQNnAFOai8neay7g3jExDIORzhbrejwGSJaZNTdOJHR5ziLg2joMxOCMOFMo4zuxza2uQ==
|
||||
|
||||
"@oxc-resolver/binding-android-arm64@11.23.0":
|
||||
version "11.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-android-arm64/-/binding-android-arm64-11.23.0.tgz#744d8c82d37189ddb76f911b6fbd9915d8090a9b"
|
||||
integrity sha512-pprVojnNhHxupwTT2gdeUlkxll6XEvWWBk3oVicOSNVWQC99OBnDhMQDoirqnzrE1bScQSMS2JgPpqdlrhz/Fg==
|
||||
"@oxc-resolver/binding-android-arm64@11.21.2":
|
||||
version "11.21.2"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-android-arm64/-/binding-android-arm64-11.21.2.tgz#be6093b76621981c57833172a7fa9b5b0753daef"
|
||||
integrity sha512-HF5oiE2L05yInPYCFD/4uxSrEZW4SuIfn99Y6L1xnJnzl066JR+MJs2rIdstw8A2MPlAKH+13dpFPNycjqzvGg==
|
||||
|
||||
"@oxc-resolver/binding-darwin-arm64@11.23.0":
|
||||
version "11.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-darwin-arm64/-/binding-darwin-arm64-11.23.0.tgz#a57f71c22d3d39da3ce08fbd672a25673ad38364"
|
||||
integrity sha512-mbIrWIMAJeytyee36OyUP5XH92TP7FaKaQ2m5AjokKy7STgjrhRt7SMXqpqLjhGm6Xn721Xmsg6H3Rtd9YQETw==
|
||||
"@oxc-resolver/binding-darwin-arm64@11.21.2":
|
||||
version "11.21.2"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-darwin-arm64/-/binding-darwin-arm64-11.21.2.tgz#b5ec0b6774ef9c60bb3e9237b39b603aac39f75f"
|
||||
integrity sha512-UX4u49CVCAD8QZNELaW8eMGgMAGwFWYEPbvNsh+3r/gs4NX3KfpiACMVwRQT0EuH3uat9hM5Zl+Ppm9pJD8tgg==
|
||||
|
||||
"@oxc-resolver/binding-darwin-x64@11.23.0":
|
||||
version "11.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-11.23.0.tgz#ca28168b5aca8003c2a114731ffc556f04ac40e4"
|
||||
integrity sha512-UnIphmZ1LazUCr9DXWaKYWtKDefPMbgLsywaoYxRqVCNHhq4MM6d2q1Nz1i9Vzxt5i+cE2nRUYpAUHr/lijNYA==
|
||||
"@oxc-resolver/binding-darwin-x64@11.21.2":
|
||||
version "11.21.2"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-11.21.2.tgz#79dbeeca048fda1de9c71169b08924535369a14d"
|
||||
integrity sha512-J9xPx7YBkrRmJ+xl561ztnMWEc1aOyjEIxBiGX1dVb3u7bGSnfObfcZk+Pd+uM0HZAPNsQ1xvD8j52A/uOSqNQ==
|
||||
|
||||
"@oxc-resolver/binding-freebsd-x64@11.23.0":
|
||||
version "11.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-11.23.0.tgz#b956c5b3eda65e1302e291c185d7fca10fbfd7fb"
|
||||
integrity sha512-aaZ/cSEYFkSxgS2hOrobT6RQcsWNviOX8dW6CEkVx2/UYkAf9MeHbjl3W0usWV53rVV//ndBdn2nb1y7jsu4lw==
|
||||
"@oxc-resolver/binding-freebsd-x64@11.21.2":
|
||||
version "11.21.2"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-11.21.2.tgz#8d49e6cb2269597b6ef0bc90c5b23e68d946f8c3"
|
||||
integrity sha512-fRlt7OvSaQkWj6+EDTVxawVxOlqJB2QSnBfkeCyK4RTvsGctbw3BiH2Tb7DzMs7bikc4BRBpvWP5zF9K8b54Zg==
|
||||
|
||||
"@oxc-resolver/binding-linux-arm-gnueabihf@11.23.0":
|
||||
version "11.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-11.23.0.tgz#f59878badc556b023a12980f8fd4ca363f8065e2"
|
||||
integrity sha512-IoJLvO5SjLSVMaq83BNTrPCb1FppvoJc1IhZ5CoUVl3PykUBku7D+LK1j0GSurhJcIc6zfjghsvaZNpq5ev6Mg==
|
||||
"@oxc-resolver/binding-linux-arm-gnueabihf@11.21.2":
|
||||
version "11.21.2"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-11.21.2.tgz#84413d558915ecf81da5914336b9fe9a70a84acd"
|
||||
integrity sha512-gK+vPUcPQITkGwBKpZGrcDHSlU6eDGl7AQacxS2CEKAZIBHWkOVFeJwLZ4tYnA1acJqRM5lt7yYwPCVqGHIJ7A==
|
||||
|
||||
"@oxc-resolver/binding-linux-arm-musleabihf@11.23.0":
|
||||
version "11.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-11.23.0.tgz#19e4cd124f6fb0f17c2693525422be493edabf39"
|
||||
integrity sha512-vskFpwg44T/LFsfjSCnVZ5ygcuqzPC1yUzVEiKa8BgHAQz0+QLQQW3EGWLPVi8EXFghzjR4EtgPBtOhCjU4jdw==
|
||||
"@oxc-resolver/binding-linux-arm-musleabihf@11.21.2":
|
||||
version "11.21.2"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-11.21.2.tgz#af2554d8984592926ec92fd838bd01e3e14f2f53"
|
||||
integrity sha512-L/Rgas7SrOKy/z7IH+HxSFRqVO4PuLDKLEGKvnhoCBBq3UJ0YzGBou3qMzaAGgkJwsIvX2pBF+7ojzfUhLiZxQ==
|
||||
|
||||
"@oxc-resolver/binding-linux-arm64-gnu@11.23.0":
|
||||
version "11.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-11.23.0.tgz#770c64d6f976c6374de3314942616b6c8f2afb05"
|
||||
integrity sha512-//TcHVhrChyw5RYtgts6WO7KcWq9387c1Z5Zvhqpk/ktAbyaRYgBZrpSY1GDCFq50ASt6B6jhh+JxB1rB45IAg==
|
||||
"@oxc-resolver/binding-linux-arm64-gnu@11.21.2":
|
||||
version "11.21.2"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-11.21.2.tgz#453a2e5d1d858d7e5bff0ddde584ec46560a37b6"
|
||||
integrity sha512-CUEYvlX1Fk7E9kUMzuswru1J9HLxMwnpDeQGjQuI4ZH+iNCoa2X9T+pvyzrbsgl7WnIeTFTlNlHsRfVdf5g9/g==
|
||||
|
||||
"@oxc-resolver/binding-linux-arm64-musl@11.23.0":
|
||||
version "11.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-11.23.0.tgz#88640f695e6ca9f71641b59f2996d5bb3eaaea42"
|
||||
integrity sha512-ZFqlwiTf7CXLLSGyAR9tYiO33LiaeIEXW+xm42d8mnUGpDgPltyrCGYtQezyMMEXvjhOgCz1X+i7sbDTJEx+bg==
|
||||
"@oxc-resolver/binding-linux-arm64-musl@11.21.2":
|
||||
version "11.21.2"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-11.21.2.tgz#4e04fb8d43aa3baa037f88a292b0eee386dfd742"
|
||||
integrity sha512-ViN1ZibQyxwC67GpoP33oo+S9UyUnkog13vzQb9+v9bCNvrVzJuk0MRdacjtv/9xfRMVF/eqHFyl8YOeykI10Q==
|
||||
|
||||
"@oxc-resolver/binding-linux-ppc64-gnu@11.23.0":
|
||||
version "11.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-11.23.0.tgz#7c2a0c45ab65354b89f8777c806fcfebe09f12e8"
|
||||
integrity sha512-oZ5LeN5+H1R19dRjTAxKrxQguH+AsemHcnthEfFxf4OjmBSty2doHLeSmMunKy3zpTHJQ3lh3Af+dNS+W6dYeA==
|
||||
"@oxc-resolver/binding-linux-ppc64-gnu@11.21.2":
|
||||
version "11.21.2"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-11.21.2.tgz#662de504b88a9d7c745808514bfdb2a4e9f53760"
|
||||
integrity sha512-CU1sCqWnhGqYD5I1HedHk5pujrn7ssDkNB/AEQd/pd3D/EojVSgJUlpbafwIbyhia3PgIfkvdFpRPWSALzVumA==
|
||||
|
||||
"@oxc-resolver/binding-linux-riscv64-gnu@11.23.0":
|
||||
version "11.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-11.23.0.tgz#fb502eb40ca7576d826672ba9f6c6ba9bb87094d"
|
||||
integrity sha512-O4ciFDyX5ebQd0qkb1bjAIg8IEfiLT03GbSeylwlwlUMK9KwBWaALwrxSbc0Msaz4U6iPj+T9eRXpD5mxBfmvA==
|
||||
"@oxc-resolver/binding-linux-riscv64-gnu@11.21.2":
|
||||
version "11.21.2"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-11.21.2.tgz#72e7d1edb906d42a2ad339a3a6be5b097c00a0a0"
|
||||
integrity sha512-jWVyZtIHca4Gb96x7dag+y69vlei7ffjrsveLkmf2ZhqEAz6ZSBnY1GWvgZUaZlwZP62A3xD092BW97Q5VGc+g==
|
||||
|
||||
"@oxc-resolver/binding-linux-riscv64-musl@11.23.0":
|
||||
version "11.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-11.23.0.tgz#2159fe2807e1356d9149a151d06cd9a0c7253224"
|
||||
integrity sha512-P3o8Y9kISYjcxadmbO+94ThRwLhwGuDAbA7dcdd4+YLpfeF+mmobz8fXf4NmSdfSqjyRSkceJDBRZha9NVYkiQ==
|
||||
"@oxc-resolver/binding-linux-riscv64-musl@11.21.2":
|
||||
version "11.21.2"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-11.21.2.tgz#cf5788f4dd858ae6a27d1252b0b662b39a4bbf2d"
|
||||
integrity sha512-LF29obFqNgBUgDX7rmUK7M4D0JQG5LxhYzn3xXmECcHU9aQAdWG7NiY052qybtesEdwHQXKNTWYQ7mTsybNvWg==
|
||||
|
||||
"@oxc-resolver/binding-linux-s390x-gnu@11.23.0":
|
||||
version "11.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-11.23.0.tgz#1fb7c19af499a5701d53f2aa38e841f153d7cf86"
|
||||
integrity sha512-oj03m1E3RmTFczKhcKJDzHaEDKJnPIsDcQFVxBJsSdXGSuIPdt5TvcM332FfMQgzI6yDJqyl4InrnFfXrmUTKQ==
|
||||
"@oxc-resolver/binding-linux-s390x-gnu@11.21.2":
|
||||
version "11.21.2"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-11.21.2.tgz#ec3933ccb443b68eb9b879b7f25489fcde11cc53"
|
||||
integrity sha512-+NYcm+cCHBbtdQQ3A4phQTSuVRYnNHz7wrl9XRAPEovcdoqi0mb1K5ZOl+jN54ZD+q1zz3V0vltbFJmzecJKmw==
|
||||
|
||||
"@oxc-resolver/binding-linux-x64-gnu@11.23.0":
|
||||
version "11.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-11.23.0.tgz#580644712d78ebe1c1d4b7077db992ce149d46e8"
|
||||
integrity sha512-BqJxbSC8FdP7mSuSpRePTGHm0hXWV+dfz//f7SjsteZncLaBgWTBmi/OZNv7sX6CyG/Pt/eJkPorP+DkMOhMwQ==
|
||||
"@oxc-resolver/binding-linux-x64-gnu@11.21.2":
|
||||
version "11.21.2"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-11.21.2.tgz#9cb87e203ef40631d7ff2cd1a8a6c145a5595054"
|
||||
integrity sha512-UQqZDdG2r2HhAOsZEgufkIWHPQ886IUyuJQkoZByvzhW8j51R4UNzGBJFkTiTnLhQnggwJRdJgFWK4uY6ZVIMw==
|
||||
|
||||
"@oxc-resolver/binding-linux-x64-musl@11.23.0":
|
||||
version "11.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-11.23.0.tgz#709b86ff4c0a43bfeb4d7e5cc7334ee77780c5ae"
|
||||
integrity sha512-utmw+VmUrW4K8LI5/6jhg4aGYKJHOIjQ9syYOOA6pF3w7haKu4r4enTe2U0C04/HbUvkq/Zif43xFsKW1Pnq9w==
|
||||
"@oxc-resolver/binding-linux-x64-musl@11.21.2":
|
||||
version "11.21.2"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-11.21.2.tgz#a1ebb07307eccb2b150dc6bcac5a575cf3b647bc"
|
||||
integrity sha512-3Q9PMRjalWkT6NZ4jfujuqTCFwoWErg3y3BnOgb544B8IMw4PktiwWOigMfOHNRLMghZeJ7hpfpZf4CP7rV7Og==
|
||||
|
||||
"@oxc-resolver/binding-openharmony-arm64@11.23.0":
|
||||
version "11.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-openharmony-arm64/-/binding-openharmony-arm64-11.23.0.tgz#580331f88fbdbc7b518a93e2ab43a7692e24f7f4"
|
||||
integrity sha512-V6lbRrthHa4TbvsLjPtg+EkXT1tRY+s4I8rYLXUfiHlZzGx3sLv1EH9CEOOevjvUYHLsbe/gqCIc73XnQfPb9A==
|
||||
"@oxc-resolver/binding-openharmony-arm64@11.21.2":
|
||||
version "11.21.2"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-openharmony-arm64/-/binding-openharmony-arm64-11.21.2.tgz#aba2e14c823e3356acfb8bce06be97d5aa5fcab8"
|
||||
integrity sha512-Eljeq3ndtyKhM+Es8LITi4Zl2htzuRZcrPMF3kMCsrILztvU6AjZ3FuEhHHKobPUB9rMpzLpJP5bDqXI7r+iog==
|
||||
|
||||
"@oxc-resolver/binding-wasm32-wasi@11.23.0":
|
||||
version "11.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-11.23.0.tgz#4d5f1300d1a4ef18b933329ad7c6b395bac28580"
|
||||
integrity sha512-gRoOxQPdnAmIAjxcuQNBxfihvx+wjTaQM/9/eP12xwnGNawOG/+Zz9RHN4WNSxT45b5CrscK4NB8aPh+oZQXAQ==
|
||||
"@oxc-resolver/binding-wasm32-wasi@11.21.2":
|
||||
version "11.21.2"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-11.21.2.tgz#8840346c1b8a3028b1b3575abb2e45fed529d3d6"
|
||||
integrity sha512-HGDbNsIywqc4LxU38+CTJNnB/6BF7rheWOJ259b4eE0aEnelYblC8x+1tEd63bp31fYne63RQz9Jb4yVnC7Yig==
|
||||
dependencies:
|
||||
"@emnapi/core" "1.11.1"
|
||||
"@emnapi/runtime" "1.11.1"
|
||||
"@napi-rs/wasm-runtime" "^1.1.6"
|
||||
"@emnapi/core" "1.11.0"
|
||||
"@emnapi/runtime" "1.11.0"
|
||||
"@napi-rs/wasm-runtime" "^1.1.5"
|
||||
|
||||
"@oxc-resolver/binding-win32-arm64-msvc@11.23.0":
|
||||
version "11.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-11.23.0.tgz#87145652fdf60741d07b893150d61fa06e2a1d15"
|
||||
integrity sha512-CgTGMYsJVe1eUiCdJTpGw21svXw79ITsemN1h0hcNkiswasDbN5MoibSLY+gRMWP5syfEz5iffrjZnwEP8xeUA==
|
||||
"@oxc-resolver/binding-win32-arm64-msvc@11.21.2":
|
||||
version "11.21.2"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-11.21.2.tgz#c987ab635ffe553e6230255a07c0576686f5ab45"
|
||||
integrity sha512-iWx25CBEgH49iE9q5coEGI/jb1jl5kkCY9z6U5Og67xCkQ/WFMDc2J5U78+AE91SUxM2NqSLhJC8/PLfWnImww==
|
||||
|
||||
"@oxc-resolver/binding-win32-x64-msvc@11.23.0":
|
||||
version "11.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-11.23.0.tgz#8b66dbfa7b796139e719063fc0e44084e80a1c15"
|
||||
integrity sha512-gUGJpr+Rn6zMxm5juApV0K3U845i8t47o8k+rbO0BHbi4PoJIfSPeQmrE2dgohQm2g5k6iviNFyXCGqvmaYUpw==
|
||||
"@oxc-resolver/binding-win32-x64-msvc@11.21.2":
|
||||
version "11.21.2"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-11.21.2.tgz#dbdf12721396ef4c31899d0963b002df32f57050"
|
||||
integrity sha512-VPoCAhKvCQTlG7vxqaBXcmuvbh77BfnGXj8g0pbvVXpm1F/R8rDVwqIWcEbMzrI1JvJlm8v7T9uMVQb6UctMRg==
|
||||
|
||||
"@oxfmt/binding-android-arm-eabi@0.64.0":
|
||||
version "0.64.0"
|
||||
@@ -4122,23 +4129,23 @@
|
||||
resolved "https://registry.yarnpkg.com/@standard-schema/utils/-/utils-0.3.0.tgz#3d5e608f16c2390c10528e98e59aef6bf73cae7b"
|
||||
integrity sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==
|
||||
|
||||
"@storybook/addon-docs@^10.5.9":
|
||||
version "10.5.9"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-docs/-/addon-docs-10.5.9.tgz#6d871977f7ad833dc142d12ee43490105dec4d07"
|
||||
integrity sha512-8sFsMkZYrrdqCLdV+hnwTwDF7RaBsBPRwl4wfc8ve9Q/7Yhi5REe/Xjvd8x1yn6fBPPw9tnID9dx6Agdnr81fw==
|
||||
"@storybook/addon-docs@^10.5.10":
|
||||
version "10.5.10"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-docs/-/addon-docs-10.5.10.tgz#c65d1d4a6d1e2de50f0decaf82ea57583f220b30"
|
||||
integrity sha512-06JoK3/a7FWI/6GzuidJP9iHp1/Vejboe6lzS1jW+d8ItpecriBt+oXh1VNmUM7i7PjI6pZnet+j51QnLyeOoQ==
|
||||
dependencies:
|
||||
"@mdx-js/react" "^3.0.0"
|
||||
"@storybook/csf-plugin" "10.5.9"
|
||||
"@storybook/csf-plugin" "10.5.10"
|
||||
"@storybook/icons" "^2.0.2"
|
||||
"@storybook/react-dom-shim" "10.5.9"
|
||||
"@storybook/react-dom-shim" "10.5.10"
|
||||
react "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
react-dom "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
ts-dedent "^2.0.0"
|
||||
|
||||
"@storybook/csf-plugin@10.5.9":
|
||||
version "10.5.9"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/csf-plugin/-/csf-plugin-10.5.9.tgz#805e4c93a1704b220351d62bb0c74ce3b78c5e10"
|
||||
integrity sha512-4H5QIHQVtQYCuL43GCRLGjNQhZpQg9gL03ja0DV80kO2Dn9LEt6ol87bSnSjn4VDgcAXtgTzXFvRLknfVgAAqg==
|
||||
"@storybook/csf-plugin@10.5.10":
|
||||
version "10.5.10"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/csf-plugin/-/csf-plugin-10.5.10.tgz#c65da1cdfe0a11795a14c518e9f8f828b8d54b3b"
|
||||
integrity sha512-TaCLBrqVEr767+w58QDotDUiCTuE5cyRJuRcDlsKQyUIyGBv+lYD3lu8wBiVCYxgIjB/gu9HmqiC+0yx1rHzaw==
|
||||
dependencies:
|
||||
unplugin "^2.3.5"
|
||||
|
||||
@@ -4152,10 +4159,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@storybook/icons/-/icons-2.1.0.tgz#edfc2450a39c5e780f28c6cbc49acd7bff59b41a"
|
||||
integrity sha512-Fxh9vYpX9bQqFeHRiY8h2ApeRGDzRSMLwJwNZ/AIRqnyOKHxRKL+yFe+ctEkVJmuptRE9u1Hrn8ZZNHyfDKKNg==
|
||||
|
||||
"@storybook/react-dom-shim@10.5.9":
|
||||
version "10.5.9"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/react-dom-shim/-/react-dom-shim-10.5.9.tgz#549793845bb8b966acd36002d33d7b54cc5c92d4"
|
||||
integrity sha512-7qZD6CSa64p1m/zX9tG4ALcEHlEk0Bx+5++4RL3MFlRCgCFfAomXsCHTzF0RyF8cI4vl+hS7Y0ryjQchVs6UQA==
|
||||
"@storybook/react-dom-shim@10.5.10":
|
||||
version "10.5.10"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/react-dom-shim/-/react-dom-shim-10.5.10.tgz#8ddcad879d8804a9bbc09a5d0ea10f8356f01ed6"
|
||||
integrity sha512-rbu62ILo/VE3iXKmu+kWXFpD1H1Lwi0f19q/x7JnDsD2dxKS9w5znLEqPIq2qxpzi/wjjIb2iUP1cRG1d/9W5A==
|
||||
|
||||
"@superset-ui/core@^0.20.4":
|
||||
version "0.20.4"
|
||||
@@ -6522,10 +6529,10 @@ base64-js@^1.3.1, base64-js@^1.5.1:
|
||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
|
||||
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
|
||||
|
||||
baseline-browser-mapping@^2.10.38, baseline-browser-mapping@^2.11.15, baseline-browser-mapping@^2.9.19:
|
||||
version "2.11.15"
|
||||
resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.11.15.tgz#9c0cac93d7d304f3d61bb41088a102cd62e68676"
|
||||
integrity sha512-FwMjJJ7HnyZpWe+oWxegG0fezZyBZUagI5LZEoO3GCbtbKNwRfMH9Ue5d5v01PNePBy1QSfPSDTTeVL0Hb9EzA==
|
||||
baseline-browser-mapping@^2.10.38, baseline-browser-mapping@^2.11.16, baseline-browser-mapping@^2.9.19:
|
||||
version "2.11.16"
|
||||
resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.11.16.tgz#0fa19a4ece2e34439ecaa3fdca8a59acfbd287fb"
|
||||
integrity sha512-H/bNPUFHewJHyCTdjn1n3Pit5+2GmWT6mmeHImPX+8MA9NA6b67jO4gYmi4jTbCJb2otq34KMZnovndDPqJwhQ==
|
||||
|
||||
batch@0.6.1:
|
||||
version "0.6.1"
|
||||
@@ -12237,30 +12244,30 @@ oxc-parser@^0.127.0:
|
||||
"@oxc-parser/binding-win32-ia32-msvc" "0.127.0"
|
||||
"@oxc-parser/binding-win32-x64-msvc" "0.127.0"
|
||||
|
||||
oxc-resolver@^11.19.1:
|
||||
version "11.23.0"
|
||||
resolved "https://registry.yarnpkg.com/oxc-resolver/-/oxc-resolver-11.23.0.tgz#bb9e32fa028dbde0584b5964dbc59de2878ac171"
|
||||
integrity sha512-f0+l598CJMOLnYPXsXxttJALH0ljtivdRMKtvHhxRuWa5FYmw5+qODARl8oYjMC/brpzKcrpdORsOBrTqhBZ9A==
|
||||
oxc-resolver@11.21.2:
|
||||
version "11.21.2"
|
||||
resolved "https://registry.yarnpkg.com/oxc-resolver/-/oxc-resolver-11.21.2.tgz#06f49557c98adb97133d85362797c460ef028057"
|
||||
integrity sha512-w5tLwYN3Zo24w5EeWJjJWZOwhYqTtC8PS2B1tIt7BZUuqTIcU07sQValbDw+rq7+AuAGzOHklgK+ifsy4lpXfw==
|
||||
optionalDependencies:
|
||||
"@oxc-resolver/binding-android-arm-eabi" "11.23.0"
|
||||
"@oxc-resolver/binding-android-arm64" "11.23.0"
|
||||
"@oxc-resolver/binding-darwin-arm64" "11.23.0"
|
||||
"@oxc-resolver/binding-darwin-x64" "11.23.0"
|
||||
"@oxc-resolver/binding-freebsd-x64" "11.23.0"
|
||||
"@oxc-resolver/binding-linux-arm-gnueabihf" "11.23.0"
|
||||
"@oxc-resolver/binding-linux-arm-musleabihf" "11.23.0"
|
||||
"@oxc-resolver/binding-linux-arm64-gnu" "11.23.0"
|
||||
"@oxc-resolver/binding-linux-arm64-musl" "11.23.0"
|
||||
"@oxc-resolver/binding-linux-ppc64-gnu" "11.23.0"
|
||||
"@oxc-resolver/binding-linux-riscv64-gnu" "11.23.0"
|
||||
"@oxc-resolver/binding-linux-riscv64-musl" "11.23.0"
|
||||
"@oxc-resolver/binding-linux-s390x-gnu" "11.23.0"
|
||||
"@oxc-resolver/binding-linux-x64-gnu" "11.23.0"
|
||||
"@oxc-resolver/binding-linux-x64-musl" "11.23.0"
|
||||
"@oxc-resolver/binding-openharmony-arm64" "11.23.0"
|
||||
"@oxc-resolver/binding-wasm32-wasi" "11.23.0"
|
||||
"@oxc-resolver/binding-win32-arm64-msvc" "11.23.0"
|
||||
"@oxc-resolver/binding-win32-x64-msvc" "11.23.0"
|
||||
"@oxc-resolver/binding-android-arm-eabi" "11.21.2"
|
||||
"@oxc-resolver/binding-android-arm64" "11.21.2"
|
||||
"@oxc-resolver/binding-darwin-arm64" "11.21.2"
|
||||
"@oxc-resolver/binding-darwin-x64" "11.21.2"
|
||||
"@oxc-resolver/binding-freebsd-x64" "11.21.2"
|
||||
"@oxc-resolver/binding-linux-arm-gnueabihf" "11.21.2"
|
||||
"@oxc-resolver/binding-linux-arm-musleabihf" "11.21.2"
|
||||
"@oxc-resolver/binding-linux-arm64-gnu" "11.21.2"
|
||||
"@oxc-resolver/binding-linux-arm64-musl" "11.21.2"
|
||||
"@oxc-resolver/binding-linux-ppc64-gnu" "11.21.2"
|
||||
"@oxc-resolver/binding-linux-riscv64-gnu" "11.21.2"
|
||||
"@oxc-resolver/binding-linux-riscv64-musl" "11.21.2"
|
||||
"@oxc-resolver/binding-linux-s390x-gnu" "11.21.2"
|
||||
"@oxc-resolver/binding-linux-x64-gnu" "11.21.2"
|
||||
"@oxc-resolver/binding-linux-x64-musl" "11.21.2"
|
||||
"@oxc-resolver/binding-openharmony-arm64" "11.21.2"
|
||||
"@oxc-resolver/binding-wasm32-wasi" "11.21.2"
|
||||
"@oxc-resolver/binding-win32-arm64-msvc" "11.21.2"
|
||||
"@oxc-resolver/binding-win32-x64-msvc" "11.21.2"
|
||||
|
||||
oxfmt@^0.64.0:
|
||||
version "0.64.0"
|
||||
@@ -14783,10 +14790,10 @@ stop-iteration-iterator@^1.1.0:
|
||||
es-errors "^1.3.0"
|
||||
internal-slot "^1.1.0"
|
||||
|
||||
storybook@^10.5.9:
|
||||
version "10.5.9"
|
||||
resolved "https://registry.yarnpkg.com/storybook/-/storybook-10.5.9.tgz#61f476fd73785dcf09e9198ddf404b9b8c06964a"
|
||||
integrity sha512-UfdMKSjEhIKr8LbqYyIE5r7vT/drL/PxN75YaouJ+UG0FssEy6cf49OdTF3kstAqVMHskc+zEqyRoiQHZXHwgA==
|
||||
storybook@^10.5.10:
|
||||
version "10.5.10"
|
||||
resolved "https://registry.yarnpkg.com/storybook/-/storybook-10.5.10.tgz#97e9b4a71b4df7732e82d64edffe07f9e8d70083"
|
||||
integrity sha512-Rz8k9ejFHsi7lbtJTaxZlhCUz4GkbJIKEoKDjXeLfr/ZhXip73E6keKxW0KH8iGeKiCqHAbJCV4YIQrxTOLiig==
|
||||
dependencies:
|
||||
"@storybook/global" "^5.0.0"
|
||||
"@storybook/icons" "^2.0.2"
|
||||
@@ -14800,7 +14807,7 @@ storybook@^10.5.9:
|
||||
jsonc-parser "^3.3.1"
|
||||
open "^10.2.0"
|
||||
oxc-parser "^0.127.0"
|
||||
oxc-resolver "^11.19.1"
|
||||
oxc-resolver "11.21.2"
|
||||
recast "^0.23.5"
|
||||
semver "^7.7.3"
|
||||
use-sync-external-store "^1.5.0"
|
||||
|
||||
+32
-8
@@ -141,8 +141,17 @@ bigquery = [
|
||||
"sqlalchemy-bigquery>=1.17.2",
|
||||
"google-cloud-bigquery>=3.42.3",
|
||||
]
|
||||
clickhouse = ["clickhouse-connect>=1.7.1, <2.0"]
|
||||
cockroachdb = ["cockroachdb>=0.3.5, <0.4"]
|
||||
clickhouse = ["clickhouse-connect>=1.7.2, <2.0"]
|
||||
# The `cockroachdb` PyPI package (last released 2021) is abandoned and its
|
||||
# SQLAlchemy dialect cannot even import under SQLAlchemy 2.0 (it references
|
||||
# sqlalchemy.dialects.postgresql.psycopg2.PGCompiler_psycopg2, removed in
|
||||
# 2.0). sqlalchemy-cockroachdb is the actively maintained replacement,
|
||||
# already linked from CockroachDbEngineSpec.metadata's docs_url, and
|
||||
# registers the same `cockroachdb` SQLAlchemy dialect entry point.
|
||||
# sqlalchemy-cockroachdb itself declares no DBAPI dependency (its own docs
|
||||
# require picking one), so pull in the same psycopg2-binary pin as the
|
||||
# `postgres` extra -- CockroachDB speaks the Postgres wire protocol.
|
||||
cockroachdb = ["sqlalchemy-cockroachdb>=2.0.0, <3", "psycopg2-binary==2.9.12"]
|
||||
crate = ["sqlalchemy-cratedb>=0.43.1, <1"]
|
||||
# sqlalchemy-d1's only release (0.1.0, Nov 2025) pins sqlalchemy<2,>=1.4,
|
||||
# explicitly excluding SQLAlchemy 2.0. See superset/db_engine_specs/d1.py's
|
||||
@@ -190,7 +199,7 @@ fastmcp = [
|
||||
# tiktoken backs the response-size-guard token estimator. Without
|
||||
# it, the middleware falls back to a coarser character-based
|
||||
# heuristic that under-counts JSON-heavy MCP responses.
|
||||
"tiktoken>=0.13.0,<1.0",
|
||||
"tiktoken>=0.14.0,<1.0",
|
||||
]
|
||||
# sqlalchemy-firebird >=2.0.0 unconditionally requires SQLAlchemy 2.0 on
|
||||
# Python >=3.8 (which covers Superset's >=3.11 floor), with no dual-compat
|
||||
@@ -213,6 +222,11 @@ impala = ["impyla>=0.24.0, <0.25"]
|
||||
# superset/db_engine_specs/kusto.py's known_incompatibilities metadata.
|
||||
kusto = ["sqlalchemy-kusto>=3.1.2, <4"]
|
||||
kylin = ["kylinpy>=2.8.4, <2.9"]
|
||||
# MariaDB is a MySQL fork implementing the same wire protocol - connects via
|
||||
# the plain mysql dialect, same driver as mysql.
|
||||
mariadb = ["apache-superset[mysql]"]
|
||||
monetdb = ["sqlalchemy-monetdb>=2.1.0, <3", "pymonetdb>=1.9.1, <2"]
|
||||
mongodb = ["pymongosql>=0.7.3, <1"]
|
||||
mssql = ["pymssql>=2.3.13, <3"]
|
||||
# motherduck is an alias for duckdb - MotherDuck works via the duckdb driver
|
||||
motherduck = ["apache-superset[duckdb]"]
|
||||
@@ -236,7 +250,7 @@ pinot = ["pinotdb>=5.0.0, <10.0.0"]
|
||||
playwright = ["playwright>=1.62.0, <2"]
|
||||
postgres = ["psycopg2-binary==2.9.12"]
|
||||
presto = ["pyhive[presto]>=0.6.5"]
|
||||
trino = ["trino>=0.338.0"]
|
||||
trino = ["trino>=0.339.0"]
|
||||
prophet = ["prophet>=1.4.0, <2"]
|
||||
# sqlalchemy-redshift cuts hard from SQLAlchemy 1.4-only (0.8.x) to 2.0-only
|
||||
# (>=1.0.0) with no dual-compat release. Bumped now that Superset's own
|
||||
@@ -250,7 +264,7 @@ risingwave = ["sqlalchemy-risingwave>=2.0.0"]
|
||||
shillelagh = ["shillelagh[all]>=1.4.5, <2"]
|
||||
singlestore = ["sqlalchemy-singlestoredb>=1.2.1, <2"]
|
||||
snowflake = ["snowflake-sqlalchemy>=1.11.0, <2"]
|
||||
sqlite = ["syntaqlite>=0.7.0,<0.8.0"]
|
||||
sqlite = ["syntaqlite>=0.9.0,<0.10.0"]
|
||||
spark = [
|
||||
"pyhive[hive_pure_sasl]>=0.7",
|
||||
"tableschema",
|
||||
@@ -260,7 +274,10 @@ tdengine = [
|
||||
"taospy>=2.8.10",
|
||||
"taos-ws-py>=0.7.0"
|
||||
]
|
||||
teradata = ["teradatasql>=20.0.0.65"]
|
||||
teradata = ["teradatasql>=20.0.0.66"]
|
||||
# TimescaleDB is a genuine Postgres extension, not a fork - connects via the
|
||||
# plain postgresql dialect, same driver as postgres.
|
||||
timescaledb = ["apache-superset[postgres]"]
|
||||
thumbnails = [] # deprecated, will be removed in 7.0
|
||||
vertica = ["sqlalchemy-vertica-python>= 0.6.3, < 0.7"]
|
||||
netezza = ["nzalchemy>= 11.1.2, < 11.2"]
|
||||
@@ -268,11 +285,18 @@ starrocks = ["starrocks>=1.3.4, <2"]
|
||||
doris = ["pydoris>=1.2.0, <2.0.0"]
|
||||
oceanbase = ["oceanbase_py>=0.0.1.2"]
|
||||
ydb = ["ydb-sqlalchemy>=0.1.22", "ydb-sqlglot-plugin>=0.2.8"]
|
||||
# YugabyteDB's YSQL layer is fully Postgres-wire compatible - connects via
|
||||
# the plain postgresql dialect, same driver as postgres.
|
||||
yugabytedb = ["apache-superset[postgres]"]
|
||||
development = [
|
||||
# no bounds for apache-superset-extensions-cli until a stable version
|
||||
"apache-superset-extensions-cli",
|
||||
"boto3",
|
||||
"docker",
|
||||
# 7.0.0 raises `docker.errors.DockerException: ... Not supported URL
|
||||
# scheme http+docker` against the requests/urllib3 versions pinned
|
||||
# elsewhere in this file -- breaks testcontainers (tests/testcontainers/)
|
||||
# before any container even starts. 7.2.0 is confirmed working.
|
||||
"docker>=7.2.0",
|
||||
"flask-testing",
|
||||
"freezegun",
|
||||
"grpcio>=1.82.1",
|
||||
@@ -294,7 +318,7 @@ development = [
|
||||
"ruff",
|
||||
"sqloxide",
|
||||
"statsd",
|
||||
"syntaqlite>=0.7.0,<0.8.0",
|
||||
"syntaqlite>=0.9.0,<0.10.0",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
|
||||
+7
-1
@@ -19,7 +19,13 @@ testpaths =
|
||||
tests
|
||||
python_files = *_test.py test_*.py *_tests.py *viz/utils.py
|
||||
# `-p no:warnings` temporarily disabled in favor of more finely tuned `filterwarnings`.
|
||||
#addopts = -p no:warnings
|
||||
# `not testcontainers` excludes tests/testcontainers/ by default: those spin up
|
||||
# real Docker containers, and `testpaths = tests` would otherwise pull them into
|
||||
# every plain `pytest` run. The dedicated CI job (testcontainers.yml) overrides
|
||||
# this with an explicit `-m testcontainers` to run them.
|
||||
addopts = -m "not testcontainers"
|
||||
markers =
|
||||
testcontainers: exercises a real database via testcontainers-python (needs Docker); excluded by default, see .github/workflows/testcontainers.yml
|
||||
asyncio_mode = auto
|
||||
|
||||
# `ignore` is effectively equivalent to `-p no:warnings`.
|
||||
|
||||
@@ -168,11 +168,11 @@ greenlet==3.5.4
|
||||
# apache-superset (pyproject.toml)
|
||||
# shillelagh
|
||||
# sqlalchemy
|
||||
gunicorn==26.0.0
|
||||
gunicorn==26.2.0
|
||||
# via apache-superset (pyproject.toml)
|
||||
hashids==1.3.1
|
||||
# via apache-superset (pyproject.toml)
|
||||
holidays==0.102
|
||||
holidays==0.103
|
||||
# via apache-superset (pyproject.toml)
|
||||
humanize==4.12.3
|
||||
# via apache-superset (pyproject.toml)
|
||||
@@ -263,7 +263,6 @@ packaging==25.0
|
||||
# apache-superset (pyproject.toml)
|
||||
# apispec
|
||||
# deprecation
|
||||
# gunicorn
|
||||
# kombu
|
||||
# limits
|
||||
# shillelagh
|
||||
|
||||
@@ -16,5 +16,32 @@
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
-e .[development,bigquery,druid,duckdb,fastmcp,gevent,gsheets,mysql,postgres,presto,prophet,trino,thumbnails]
|
||||
-e .[development,bigquery,clickhouse,cockroachdb,crate,databend,druid,duckdb,elasticsearch,fastmcp,firebird,gevent,gsheets,monetdb,mongodb,mssql,mysql,oracle,postgres,presto,prophet,risingwave,starrocks,trino,thumbnails,ydb]
|
||||
-e ./superset-extensions-cli[test]
|
||||
# testcontainers-backed db_engine_specs tests (tests/testcontainers/) --
|
||||
# see .github/workflows/testcontainers.yml
|
||||
#
|
||||
# `db2` (the `ibm-db-sa`/`ibm-db` driver) and `oceanbase` (the `oceanbase_py`
|
||||
# driver) are both deliberately left out of the baseline dev install above:
|
||||
# `ibm-db` ships no Linux arm64 wheel, breaking the multi-platform
|
||||
# (amd64+arm64) dev Docker image build; `oceanbase_py` pins
|
||||
# `sqlalchemy-utils>=0.38.3,<0.39`, which conflicts outright with Superset's
|
||||
# own `sqlalchemy-utils==0.42.1` pin -- there's no version of both that can
|
||||
# coexist in one resolved environment. Both testcontainers CI jobs install
|
||||
# their driver on demand instead, only for their own matrix leg -- see
|
||||
# .github/workflows/testcontainers.yml.
|
||||
#
|
||||
# mariadb/timescaledb/yugabytedb need no testcontainers extra of their own:
|
||||
# they reuse the postgres/mysql container classes pointed at a different
|
||||
# image, and psycopg2-binary/mysqlclient are already pulled in above via
|
||||
# the postgres/mysql extras. Plain postgres/mysql obviously need nothing
|
||||
# extra either. clickhouse and starrocks also need no testcontainers extra:
|
||||
# ClickHouseContainer has no driver import of its own (clickhouse-connect,
|
||||
# pulled in above via the clickhouse extra, is all the test needs), and
|
||||
# StarRocks has no dedicated testcontainers module at all -- its test uses
|
||||
# a generic DockerContainer plus the same mysqlclient the mysql extra
|
||||
# already provides. databend/risingwave/firebird/ydb are the same story:
|
||||
# none has a dedicated testcontainers module, so each test uses a generic
|
||||
# DockerContainer plus whatever driver its own extra above already
|
||||
# provides.
|
||||
testcontainers[cockroachdb,cratedb,mongodb,mssql,mysql,oracle,postgres,trino]>=4.15.0,<5
|
||||
|
||||
+161
-12
@@ -12,10 +12,17 @@
|
||||
# apache-superset
|
||||
aiofile==3.9.0
|
||||
# via py-key-value-aio
|
||||
aiohappyeyeballs==2.7.1
|
||||
# via aiohttp
|
||||
aiohttp==3.14.3
|
||||
# via ydb
|
||||
aiosignal==1.4.0
|
||||
# via aiohttp
|
||||
alembic==1.15.2
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# flask-migrate
|
||||
# starrocks
|
||||
amqp==5.3.1
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
@@ -24,6 +31,8 @@ annotated-types==0.7.0
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# pydantic
|
||||
antlr4-python3-runtime==4.13.2
|
||||
# via pymongosql
|
||||
anyio==4.11.0
|
||||
# via
|
||||
# httpx
|
||||
@@ -42,9 +51,12 @@ apsw==3.50.1.0
|
||||
# shillelagh
|
||||
astroid==3.3.10
|
||||
# via pylint
|
||||
asyncmy2==0.2.21
|
||||
# via starrocks
|
||||
attrs==25.3.0
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# aiohttp
|
||||
# cattrs
|
||||
# cyclopts
|
||||
# jsonschema
|
||||
@@ -65,6 +77,7 @@ backports-tarfile==1.2.0
|
||||
backports-zstd==1.6.0
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# clickhouse-connect
|
||||
# flask-compress
|
||||
bcrypt==4.3.0
|
||||
# via
|
||||
@@ -117,8 +130,11 @@ celery==5.6.3
|
||||
certifi==2026.5.20
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# clickhouse-connect
|
||||
# elasticsearch
|
||||
# httpcore
|
||||
# httpx
|
||||
# opensearch-py
|
||||
# requests
|
||||
cffi==2.0.0
|
||||
# via
|
||||
@@ -160,6 +176,8 @@ click-repl==0.3.0
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# celery
|
||||
clickhouse-connect==1.7.2
|
||||
# via apache-superset
|
||||
cmdstanpy==1.1.0
|
||||
# via prophet
|
||||
colorama==0.4.6
|
||||
@@ -171,6 +189,8 @@ contourpy==1.0.7
|
||||
# via matplotlib
|
||||
coverage==7.6.8
|
||||
# via pytest-cov
|
||||
crate==2.2.1
|
||||
# via sqlalchemy-cratedb
|
||||
cron-descriptor==1.4.5
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
@@ -186,14 +206,20 @@ cryptography==50.0.0
|
||||
# authlib
|
||||
# google-auth
|
||||
# joserfc
|
||||
# oracledb
|
||||
# paramiko
|
||||
# pyjwt
|
||||
# pymysql
|
||||
# pyopenssl
|
||||
# secretstorage
|
||||
cycler==0.12.1
|
||||
# via matplotlib
|
||||
cyclopts==4.2.4
|
||||
# via fastmcp-slim
|
||||
databend-driver==0.34.2
|
||||
# via databend-sqlalchemy
|
||||
databend-sqlalchemy==0.5.5
|
||||
# via apache-superset
|
||||
db-dtypes==1.3.1
|
||||
# via pandas-gbq
|
||||
defusedxml==0.7.1
|
||||
@@ -216,8 +242,11 @@ dnspython==2.7.0
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# email-validator
|
||||
docker==7.0.0
|
||||
# via apache-superset
|
||||
# pymongo
|
||||
docker==7.2.0
|
||||
# via
|
||||
# apache-superset
|
||||
# testcontainers
|
||||
docstring-parser==0.17.0
|
||||
# via cyclopts
|
||||
docutils==0.22.2
|
||||
@@ -228,6 +257,10 @@ duckdb==1.5.5
|
||||
# duckdb-engine
|
||||
duckdb-engine==0.17.0
|
||||
# via apache-superset
|
||||
elasticsearch==7.17.13
|
||||
# via elasticsearch-dbapi
|
||||
elasticsearch-dbapi==0.2.13
|
||||
# via apache-superset
|
||||
email-validator==2.2.0
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
@@ -237,6 +270,8 @@ et-xmlfile==2.0.0
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# openpyxl
|
||||
events==0.5
|
||||
# via opensearch-py
|
||||
exceptiongroup==1.3.0
|
||||
# via fastmcp-slim
|
||||
fastmcp==3.4.7
|
||||
@@ -247,6 +282,10 @@ filelock==3.20.3
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# virtualenv
|
||||
firebird-base==2.0.3
|
||||
# via firebird-driver
|
||||
firebird-driver==2.0.3
|
||||
# via sqlalchemy-firebird
|
||||
flask==2.3.3
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
@@ -327,12 +366,18 @@ fonttools==4.60.2
|
||||
# via matplotlib
|
||||
freezegun==1.5.1
|
||||
# via apache-superset
|
||||
frozenlist==1.8.0
|
||||
# via
|
||||
# aiohttp
|
||||
# aiosignal
|
||||
future==1.0.0
|
||||
# via pyhive
|
||||
geographiclib==2.0
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# geopy
|
||||
geojson==3.3.0
|
||||
# via sqlalchemy-cratedb
|
||||
geopy==2.4.1
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
@@ -389,9 +434,10 @@ grpcio==1.83.0
|
||||
# apache-superset
|
||||
# google-api-core
|
||||
# grpcio-status
|
||||
# ydb
|
||||
grpcio-status==1.60.1
|
||||
# via google-api-core
|
||||
gunicorn==26.0.0
|
||||
gunicorn==26.2.0
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# apache-superset
|
||||
@@ -403,7 +449,7 @@ hashids==1.3.1
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# apache-superset
|
||||
holidays==0.102
|
||||
holidays==0.103
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# apache-superset
|
||||
@@ -414,6 +460,7 @@ httpx==0.28.1
|
||||
# via
|
||||
# fastmcp-slim
|
||||
# mcp
|
||||
# testcontainers
|
||||
httpx-sse==0.4.1
|
||||
# via mcp
|
||||
humanize==4.12.3
|
||||
@@ -430,6 +477,7 @@ idna==3.15
|
||||
# httpx
|
||||
# requests
|
||||
# url-normalize
|
||||
# yarl
|
||||
importlib-metadata==8.7.0
|
||||
# via
|
||||
# keyring
|
||||
@@ -468,6 +516,7 @@ jmespath==1.1.0
|
||||
# via
|
||||
# boto3
|
||||
# botocore
|
||||
# pymongosql
|
||||
joserfc==1.7.2
|
||||
# via fastmcp-slim
|
||||
jsonpath-ng==1.8.0
|
||||
@@ -500,6 +549,8 @@ kombu==5.6.2
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# celery
|
||||
lark==1.3.1
|
||||
# via starrocks
|
||||
lazy-object-proxy==1.10.0
|
||||
# via openapi-spec-validator
|
||||
limits==5.1.0
|
||||
@@ -507,7 +558,9 @@ limits==5.1.0
|
||||
# -c requirements/base-constraint.txt
|
||||
# flask-limiter
|
||||
lz4==4.4.5
|
||||
# via trino
|
||||
# via
|
||||
# clickhouse-connect
|
||||
# trino
|
||||
mako==1.4.1
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
@@ -567,6 +620,10 @@ msgspec==0.19.0
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# flask-session
|
||||
multidict==6.7.1
|
||||
# via
|
||||
# aiohttp
|
||||
# yarl
|
||||
mysqlclient==2.2.8
|
||||
# via apache-superset
|
||||
nh3==0.3.6
|
||||
@@ -605,14 +662,22 @@ openpyxl==3.1.5
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# pandas
|
||||
opensearch-py==2.8.0
|
||||
# via elasticsearch-dbapi
|
||||
opentelemetry-api==1.39.1
|
||||
# via fastmcp-slim
|
||||
oracledb==4.0.2
|
||||
# via
|
||||
# apache-superset
|
||||
# testcontainers
|
||||
ordered-set==4.1.0
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# flask-limiter
|
||||
orjson==3.11.9
|
||||
# via trino
|
||||
# via
|
||||
# crate
|
||||
# trino
|
||||
packaging==25.0
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
@@ -620,11 +685,10 @@ packaging==25.0
|
||||
# apispec
|
||||
# db-dtypes
|
||||
# deprecation
|
||||
# docker
|
||||
# duckdb-engine
|
||||
# elasticsearch-dbapi
|
||||
# fastmcp-slim
|
||||
# google-cloud-bigquery
|
||||
# gunicorn
|
||||
# kombu
|
||||
# limits
|
||||
# matplotlib
|
||||
@@ -632,6 +696,8 @@ packaging==25.0
|
||||
# pytest
|
||||
# shillelagh
|
||||
# sqlalchemy-bigquery
|
||||
# sqlalchemy-firebird
|
||||
# ydb
|
||||
pandas==2.3.3
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
@@ -693,16 +759,22 @@ prompt-toolkit==3.0.51
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# click-repl
|
||||
propcache==0.5.2
|
||||
# via
|
||||
# aiohttp
|
||||
# yarl
|
||||
prophet==1.4.0
|
||||
# via apache-superset
|
||||
proto-plus==1.25.0
|
||||
# via google-api-core
|
||||
protobuf==5.29.6
|
||||
# via
|
||||
# firebird-base
|
||||
# google-api-core
|
||||
# googleapis-common-protos
|
||||
# grpcio-status
|
||||
# proto-plus
|
||||
# ydb
|
||||
psutil==6.1.0
|
||||
# via
|
||||
# apache-superset
|
||||
@@ -776,6 +848,24 @@ pyjwt==2.13.0
|
||||
# mcp
|
||||
pylint==3.3.7
|
||||
# via apache-superset
|
||||
pymonetdb==1.9.1
|
||||
# via
|
||||
# apache-superset
|
||||
# sqlalchemy-monetdb
|
||||
pymongo==4.17.0
|
||||
# via
|
||||
# pymongosql
|
||||
# testcontainers
|
||||
pymongosql==0.7.3
|
||||
# via apache-superset
|
||||
pymssql==2.3.13
|
||||
# via
|
||||
# apache-superset
|
||||
# testcontainers
|
||||
pymysql==1.2.0
|
||||
# via
|
||||
# starrocks
|
||||
# testcontainers
|
||||
pynacl==1.6.2
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
@@ -820,11 +910,13 @@ python-dateutil==2.9.0.post0
|
||||
# botocore
|
||||
# celery
|
||||
# croniter
|
||||
# firebird-driver
|
||||
# flask-appbuilder
|
||||
# freezegun
|
||||
# google-cloud-bigquery
|
||||
# holidays
|
||||
# matplotlib
|
||||
# opensearch-py
|
||||
# pandas
|
||||
# pyhive
|
||||
# shillelagh
|
||||
@@ -835,6 +927,7 @@ python-dotenv==1.2.2
|
||||
# apache-superset
|
||||
# fastmcp-slim
|
||||
# pydantic-settings
|
||||
# testcontainers
|
||||
python-ldap==3.4.7
|
||||
# via apache-superset
|
||||
python-multipart==0.0.29
|
||||
@@ -878,6 +971,7 @@ requests==2.33.0
|
||||
# google-api-core
|
||||
# google-cloud-bigquery
|
||||
# jsonschema-path
|
||||
# opensearch-py
|
||||
# pydruid
|
||||
# pyhive
|
||||
# requests-cache
|
||||
@@ -956,20 +1050,42 @@ sqlalchemy==2.0.52
|
||||
# alembic
|
||||
# apache-superset
|
||||
# apache-superset-core
|
||||
# databend-sqlalchemy
|
||||
# duckdb-engine
|
||||
# elasticsearch-dbapi
|
||||
# flask-appbuilder
|
||||
# flask-sqlalchemy
|
||||
# marshmallow-sqlalchemy
|
||||
# shillelagh
|
||||
# sqlalchemy-bigquery
|
||||
# sqlalchemy-cockroachdb
|
||||
# sqlalchemy-continuum
|
||||
# sqlalchemy-cratedb
|
||||
# sqlalchemy-firebird
|
||||
# sqlalchemy-monetdb
|
||||
# sqlalchemy-risingwave
|
||||
# sqlalchemy-utils
|
||||
# starrocks
|
||||
# testcontainers
|
||||
# ydb-sqlalchemy
|
||||
sqlalchemy-bigquery==1.17.2
|
||||
# via apache-superset
|
||||
sqlalchemy-cockroachdb==2.0.4
|
||||
# via apache-superset
|
||||
sqlalchemy-continuum==1.7.0
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# apache-superset
|
||||
sqlalchemy-cratedb==0.43.1
|
||||
# via
|
||||
# apache-superset
|
||||
# testcontainers
|
||||
sqlalchemy-firebird==2.2.0
|
||||
# via apache-superset
|
||||
sqlalchemy-monetdb==2.1.0
|
||||
# via apache-superset
|
||||
sqlalchemy-risingwave==2.1.0
|
||||
# via apache-superset
|
||||
sqlalchemy-utils==0.42.1
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
@@ -981,6 +1097,7 @@ sqlglot==30.17.0
|
||||
# -c requirements/base-constraint.txt
|
||||
# apache-superset
|
||||
# apache-superset-core
|
||||
# ydb-sqlglot-plugin
|
||||
sqloxide==0.1.51
|
||||
# via apache-superset
|
||||
sse-starlette==3.0.2
|
||||
@@ -993,15 +1110,19 @@ starlette==1.3.1
|
||||
# via
|
||||
# fastmcp-slim
|
||||
# mcp
|
||||
starrocks==1.3.4
|
||||
# via apache-superset
|
||||
statsd==4.0.1
|
||||
# via apache-superset
|
||||
syntaqlite==0.7.1
|
||||
syntaqlite==0.9.0
|
||||
# via apache-superset
|
||||
tabulate==0.10.0
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# apache-superset
|
||||
tiktoken==0.13.0
|
||||
testcontainers==4.15.0
|
||||
# via -r requirements/development.in
|
||||
tiktoken==0.14.0
|
||||
# via apache-superset
|
||||
tomli-w==1.2.0
|
||||
# via apache-superset-extensions-cli
|
||||
@@ -1011,11 +1132,15 @@ tqdm==4.67.1
|
||||
# via
|
||||
# cmdstanpy
|
||||
# prophet
|
||||
trino==0.338.0
|
||||
# via apache-superset
|
||||
trino==0.339.0
|
||||
# via
|
||||
# apache-superset
|
||||
# testcontainers
|
||||
typing-extensions==4.16.0
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# aiohttp
|
||||
# aiosignal
|
||||
# alembic
|
||||
# anyio
|
||||
# apache-superset
|
||||
@@ -1027,6 +1152,7 @@ typing-extensions==4.16.0
|
||||
# limits
|
||||
# mcp
|
||||
# opentelemetry-api
|
||||
# oracledb
|
||||
# py-key-value-aio
|
||||
# pydantic
|
||||
# pydantic-core
|
||||
@@ -1035,6 +1161,7 @@ typing-extensions==4.16.0
|
||||
# shillelagh
|
||||
# sqlalchemy
|
||||
# starlette
|
||||
# testcontainers
|
||||
# typing-inspection
|
||||
typing-inspection==0.4.2
|
||||
# via
|
||||
@@ -1062,13 +1189,22 @@ urllib3==2.7.0
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# botocore
|
||||
# clickhouse-connect
|
||||
# crate
|
||||
# docker
|
||||
# elasticsearch
|
||||
# opensearch-py
|
||||
# requests
|
||||
# requests-cache
|
||||
# testcontainers
|
||||
uvicorn==0.37.0
|
||||
# via
|
||||
# fastmcp-slim
|
||||
# mcp
|
||||
verlib2==0.3.2
|
||||
# via
|
||||
# crate
|
||||
# sqlalchemy-cratedb
|
||||
vine==5.1.0
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
@@ -1102,6 +1238,7 @@ wrapt==1.17.2
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# deprecated
|
||||
# testcontainers
|
||||
wtforms==3.2.2
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
@@ -1122,6 +1259,18 @@ xlsxwriter==3.2.9
|
||||
# -c requirements/base-constraint.txt
|
||||
# apache-superset
|
||||
# pandas
|
||||
yarl==1.24.5
|
||||
# via aiohttp
|
||||
ydb==3.31.4
|
||||
# via
|
||||
# ydb-dbapi
|
||||
# ydb-sqlalchemy
|
||||
ydb-dbapi==0.1.23
|
||||
# via ydb-sqlalchemy
|
||||
ydb-sqlalchemy==0.1.22
|
||||
# via apache-superset
|
||||
ydb-sqlglot-plugin==0.2.8
|
||||
# via apache-superset
|
||||
zipp==3.23.0
|
||||
# via importlib-metadata
|
||||
zope-event==5.0
|
||||
|
||||
@@ -113,7 +113,7 @@
|
||||
// === Import plugin rules ===
|
||||
"import/named": "error",
|
||||
"import/export": "error",
|
||||
"import/no-named-as-default": "error",
|
||||
"import/no-named-as-default": "warn",
|
||||
"import/no-named-as-default-member": "error",
|
||||
"import/no-mutable-exports": "error",
|
||||
"import/no-amd": "error",
|
||||
|
||||
Generated
+205
-168
@@ -180,9 +180,9 @@
|
||||
"@istanbuljs/nyc-config-typescript": "^1.0.1",
|
||||
"@playwright/test": "^1.62.1",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.6.2",
|
||||
"@storybook/addon-docs": "10.5.9",
|
||||
"@storybook/addon-links": "10.5.9",
|
||||
"@storybook/react-webpack5": "10.5.9",
|
||||
"@storybook/addon-docs": "10.5.10",
|
||||
"@storybook/addon-links": "10.5.10",
|
||||
"@storybook/react-webpack5": "10.5.10",
|
||||
"@storybook/test-runner": "0.24.4",
|
||||
"@svgr/webpack": "^8.1.0",
|
||||
"@swc/core": "^1.16.1",
|
||||
@@ -218,7 +218,7 @@
|
||||
"babel-loader": "^10.1.1",
|
||||
"babel-plugin-dynamic-import-node": "^2.3.3",
|
||||
"babel-plugin-jsx-remove-data-test-id": "^3.0.0",
|
||||
"baseline-browser-mapping": "^2.11.15",
|
||||
"baseline-browser-mapping": "^2.11.16",
|
||||
"cheerio": "1.2.0",
|
||||
"concurrently": "^10.0.5",
|
||||
"copy-webpack-plugin": "^14.0.0",
|
||||
@@ -235,7 +235,7 @@
|
||||
"eslint-plugin-no-only-tests": "^3.4.0",
|
||||
"eslint-plugin-react-prefer-function-component": "^5.0.0",
|
||||
"eslint-plugin-react-you-might-not-need-an-effect": "^1.0.1",
|
||||
"eslint-plugin-storybook": "10.5.9",
|
||||
"eslint-plugin-storybook": "10.5.10",
|
||||
"eslint-plugin-testing-library": "^7.16.2",
|
||||
"eslint-plugin-theme-colors": "file:eslint-rules/eslint-plugin-theme-colors",
|
||||
"fetch-mock": "^12.6.0",
|
||||
@@ -266,7 +266,7 @@
|
||||
"source-map": "^0.8.0",
|
||||
"source-map-support": "^0.5.21",
|
||||
"speed-measure-webpack-plugin": "^1.6.0",
|
||||
"storybook": "10.5.9",
|
||||
"storybook": "10.5.10",
|
||||
"style-loader": "^4.0.0",
|
||||
"stylelint": "^17.14.1",
|
||||
"swc-loader": "^0.2.7",
|
||||
@@ -8117,9 +8117,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@oxc-resolver/binding-android-arm-eabi": {
|
||||
"version": "11.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm-eabi/-/binding-android-arm-eabi-11.20.0.tgz",
|
||||
"integrity": "sha512-IjfWOXRgJFNdORDl+Uf1aibNgZY2guOD3zmOhx1BGVb/MIiqlFTdmjpQNplSN58lhWehnX4UNqC3QwpUo8pjJg==",
|
||||
"version": "11.21.2",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm-eabi/-/binding-android-arm-eabi-11.21.2.tgz",
|
||||
"integrity": "sha512-xQoCRv+gKax9KTdwdaQNnAFOai8neay7g3jExDIORzhbrejwGSJaZNTdOJHR5ziLg2joMxOCMOFMo4zuxza2uQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -8131,9 +8131,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@oxc-resolver/binding-android-arm64": {
|
||||
"version": "11.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm64/-/binding-android-arm64-11.20.0.tgz",
|
||||
"integrity": "sha512-QqslZAuFQG8Q9xm7JuIn8JUbvywhSBMVhuQHtYW+auirZJloS41oxUUaBXk7uUhZJgp44c5zQLeVvmFaDQB+2Q==",
|
||||
"version": "11.21.2",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm64/-/binding-android-arm64-11.21.2.tgz",
|
||||
"integrity": "sha512-HF5oiE2L05yInPYCFD/4uxSrEZW4SuIfn99Y6L1xnJnzl066JR+MJs2rIdstw8A2MPlAKH+13dpFPNycjqzvGg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -8145,9 +8145,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@oxc-resolver/binding-darwin-arm64": {
|
||||
"version": "11.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-arm64/-/binding-darwin-arm64-11.20.0.tgz",
|
||||
"integrity": "sha512-MUcavykj2ewlR+kc5arpg4tC2RvzJkUxWtNv74pf7lcNk00GpIpN43vXMj+j6r4eMmfZhlb8hueKoIb8e9kAGQ==",
|
||||
"version": "11.21.2",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-arm64/-/binding-darwin-arm64-11.21.2.tgz",
|
||||
"integrity": "sha512-UX4u49CVCAD8QZNELaW8eMGgMAGwFWYEPbvNsh+3r/gs4NX3KfpiACMVwRQT0EuH3uat9hM5Zl+Ppm9pJD8tgg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -8159,9 +8159,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@oxc-resolver/binding-darwin-x64": {
|
||||
"version": "11.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-11.20.0.tgz",
|
||||
"integrity": "sha512-BGB16nRUK5Etiv//ihPyzj8Lj1px0mhh4YIfe0FDf045ywknfSm0GEbiRESpr6Q4K82AvnyaRIhhluHByvS4bg==",
|
||||
"version": "11.21.2",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-11.21.2.tgz",
|
||||
"integrity": "sha512-J9xPx7YBkrRmJ+xl561ztnMWEc1aOyjEIxBiGX1dVb3u7bGSnfObfcZk+Pd+uM0HZAPNsQ1xvD8j52A/uOSqNQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -8173,9 +8173,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@oxc-resolver/binding-freebsd-x64": {
|
||||
"version": "11.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-11.20.0.tgz",
|
||||
"integrity": "sha512-JZgtePaqj3qmD5XFHJaSLWzHRxQu0LaPkdoM1KJXYADvAaa83ijXHclV3ej3CueeW0wxfIAbGCZVP45J0CA7uQ==",
|
||||
"version": "11.21.2",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-11.21.2.tgz",
|
||||
"integrity": "sha512-fRlt7OvSaQkWj6+EDTVxawVxOlqJB2QSnBfkeCyK4RTvsGctbw3BiH2Tb7DzMs7bikc4BRBpvWP5zF9K8b54Zg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -8187,9 +8187,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@oxc-resolver/binding-linux-arm-gnueabihf": {
|
||||
"version": "11.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-11.20.0.tgz",
|
||||
"integrity": "sha512-hOQ/p3ry3v3SchUBXicrrnszaI/UmYzM4wtS4RGfwgVUX7a+HbyQSzJ5aOzu+o6XZkFkS3ZXN4PZAzhOb77OSg==",
|
||||
"version": "11.21.2",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-11.21.2.tgz",
|
||||
"integrity": "sha512-gK+vPUcPQITkGwBKpZGrcDHSlU6eDGl7AQacxS2CEKAZIBHWkOVFeJwLZ4tYnA1acJqRM5lt7yYwPCVqGHIJ7A==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -8201,9 +8201,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@oxc-resolver/binding-linux-arm-musleabihf": {
|
||||
"version": "11.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-11.20.0.tgz",
|
||||
"integrity": "sha512-2ArPksaw0AqeuGBfoS715VF+JvJQAhD2niWgjE5hVO+L+nAfikVQopvngCMX9x4BD8itWoQ3dnikrQyl5Ho5Jg==",
|
||||
"version": "11.21.2",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-11.21.2.tgz",
|
||||
"integrity": "sha512-L/Rgas7SrOKy/z7IH+HxSFRqVO4PuLDKLEGKvnhoCBBq3UJ0YzGBou3qMzaAGgkJwsIvX2pBF+7ojzfUhLiZxQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -8215,13 +8215,16 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@oxc-resolver/binding-linux-arm64-gnu": {
|
||||
"version": "11.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-11.20.0.tgz",
|
||||
"integrity": "sha512-0bJnmYFp62JdZ4nVMDUZ/C58BCZOCcqgKtnUlp7L9Ojf/czIN+3j72YlLPeWLkzlr6SlYvIQA4SGV/HyO0d+qg==",
|
||||
"version": "11.21.2",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-11.21.2.tgz",
|
||||
"integrity": "sha512-CUEYvlX1Fk7E9kUMzuswru1J9HLxMwnpDeQGjQuI4ZH+iNCoa2X9T+pvyzrbsgl7WnIeTFTlNlHsRfVdf5g9/g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -8229,13 +8232,16 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@oxc-resolver/binding-linux-arm64-musl": {
|
||||
"version": "11.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-11.20.0.tgz",
|
||||
"integrity": "sha512-wKHHzPKZo7Ufhv/Bt6yxT7FOgnIgW4gwXcJUipkShGp68W3wGVqvr1Sr0fY65lN0Oy6y41+g2kIDvkgZaMMUkw==",
|
||||
"version": "11.21.2",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-11.21.2.tgz",
|
||||
"integrity": "sha512-ViN1ZibQyxwC67GpoP33oo+S9UyUnkog13vzQb9+v9bCNvrVzJuk0MRdacjtv/9xfRMVF/eqHFyl8YOeykI10Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -8243,13 +8249,16 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@oxc-resolver/binding-linux-ppc64-gnu": {
|
||||
"version": "11.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-11.20.0.tgz",
|
||||
"integrity": "sha512-RN8goF7Ie0B79L4i4G6OeBocTgSC56vJbQ65VJje+oXnldVpLnOU7j/AQ/dP94TcCS+Yh6WG8u3Qt4ETteXFNQ==",
|
||||
"version": "11.21.2",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-11.21.2.tgz",
|
||||
"integrity": "sha512-CU1sCqWnhGqYD5I1HedHk5pujrn7ssDkNB/AEQd/pd3D/EojVSgJUlpbafwIbyhia3PgIfkvdFpRPWSALzVumA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -8257,13 +8266,16 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@oxc-resolver/binding-linux-riscv64-gnu": {
|
||||
"version": "11.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-11.20.0.tgz",
|
||||
"integrity": "sha512-5l1yU6/xQEqLZRzxqmMxJfWPslpwCmBsdDGaBvABPehxquCXDC7dd7oraNdKSJUMDXSM7VvVj8H2D2FTjU7oWw==",
|
||||
"version": "11.21.2",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-11.21.2.tgz",
|
||||
"integrity": "sha512-jWVyZtIHca4Gb96x7dag+y69vlei7ffjrsveLkmf2ZhqEAz6ZSBnY1GWvgZUaZlwZP62A3xD092BW97Q5VGc+g==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -8271,13 +8283,16 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@oxc-resolver/binding-linux-riscv64-musl": {
|
||||
"version": "11.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-11.20.0.tgz",
|
||||
"integrity": "sha512-xHEvkbgz6UC+A3JOyDQy76LkUaxsNSfIr3/GV8slwZsnuooJiIB34gzJfsyvR4JdCYNUUPsRJc/w/oWkODu+hg==",
|
||||
"version": "11.21.2",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-11.21.2.tgz",
|
||||
"integrity": "sha512-LF29obFqNgBUgDX7rmUK7M4D0JQG5LxhYzn3xXmECcHU9aQAdWG7NiY052qybtesEdwHQXKNTWYQ7mTsybNvWg==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -8285,13 +8300,16 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@oxc-resolver/binding-linux-s390x-gnu": {
|
||||
"version": "11.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-11.20.0.tgz",
|
||||
"integrity": "sha512-aWPDUUmSeyHvlW+SoEUd+JIJsQhVhu6a5tBpDRMu058naPAchTgAVGCFy35zjbnFlt0i8hLWziff6HX0D3LU4g==",
|
||||
"version": "11.21.2",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-11.21.2.tgz",
|
||||
"integrity": "sha512-+NYcm+cCHBbtdQQ3A4phQTSuVRYnNHz7wrl9XRAPEovcdoqi0mb1K5ZOl+jN54ZD+q1zz3V0vltbFJmzecJKmw==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -8299,13 +8317,16 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@oxc-resolver/binding-linux-x64-gnu": {
|
||||
"version": "11.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-11.20.0.tgz",
|
||||
"integrity": "sha512-x2YeSimvhJjKLVD8KSu8f/rqU1potcdEMkApIPJqjZWN7c2Fpt4g2X32WDg1p+XDAmyT7nuQGe0vnhvXeLbH+g==",
|
||||
"version": "11.21.2",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-11.21.2.tgz",
|
||||
"integrity": "sha512-UQqZDdG2r2HhAOsZEgufkIWHPQ886IUyuJQkoZByvzhW8j51R4UNzGBJFkTiTnLhQnggwJRdJgFWK4uY6ZVIMw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -8313,13 +8334,16 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@oxc-resolver/binding-linux-x64-musl": {
|
||||
"version": "11.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-11.20.0.tgz",
|
||||
"integrity": "sha512-kcRLEIxpZefeYfLChjpgFf3ilBzRDZ+yobMrpRsQlSrxuFGtm3U6PMU7AaEpMqo3NfDGVyJJseAjnRLzMFHjwQ==",
|
||||
"version": "11.21.2",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-11.21.2.tgz",
|
||||
"integrity": "sha512-3Q9PMRjalWkT6NZ4jfujuqTCFwoWErg3y3BnOgb544B8IMw4PktiwWOigMfOHNRLMghZeJ7hpfpZf4CP7rV7Og==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -8327,9 +8351,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@oxc-resolver/binding-openharmony-arm64": {
|
||||
"version": "11.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-openharmony-arm64/-/binding-openharmony-arm64-11.20.0.tgz",
|
||||
"integrity": "sha512-HHcfnApSZGtKhTiHqe8OZruOZe5XuFQH5/E0Yhj3u8fnFvzkM4/k6WjacUf4SvA0SPEAbfbgYmVPuo0VX/fIBQ==",
|
||||
"version": "11.21.2",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-openharmony-arm64/-/binding-openharmony-arm64-11.21.2.tgz",
|
||||
"integrity": "sha512-Eljeq3ndtyKhM+Es8LITi4Zl2htzuRZcrPMF3kMCsrILztvU6AjZ3FuEhHHKobPUB9rMpzLpJP5bDqXI7r+iog==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -8341,9 +8365,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@oxc-resolver/binding-wasm32-wasi": {
|
||||
"version": "11.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-11.20.0.tgz",
|
||||
"integrity": "sha512-Tn0y1XOFYHNfK1wp1Z5QK8Rcld/bsOwRISQXfqAZ5IBpv8Gz1IvV39fUWNprqNdRizgcvFhOzWwFun2zkJsyBg==",
|
||||
"version": "11.21.2",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-11.21.2.tgz",
|
||||
"integrity": "sha512-HGDbNsIywqc4LxU38+CTJNnB/6BF7rheWOJ259b4eE0aEnelYblC8x+1tEd63bp31fYne63RQz9Jb4yVnC7Yig==",
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
@@ -8351,30 +8375,41 @@
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/core": "1.10.0",
|
||||
"@emnapi/runtime": "1.10.0",
|
||||
"@napi-rs/wasm-runtime": "^1.1.4"
|
||||
"@emnapi/core": "1.11.0",
|
||||
"@emnapi/runtime": "1.11.0",
|
||||
"@napi-rs/wasm-runtime": "^1.1.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@oxc-resolver/binding-wasm32-wasi/node_modules/@emnapi/core": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
|
||||
"integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
|
||||
"version": "1.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.0.tgz",
|
||||
"integrity": "sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/wasi-threads": "1.2.1",
|
||||
"@emnapi/wasi-threads": "1.2.2",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@oxc-resolver/binding-wasm32-wasi/node_modules/@emnapi/runtime": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
|
||||
"integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
|
||||
"version": "1.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.0.tgz",
|
||||
"integrity": "sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@oxc-resolver/binding-wasm32-wasi/node_modules/@emnapi/wasi-threads": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz",
|
||||
"integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
@@ -8383,28 +8418,31 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@oxc-resolver/binding-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz",
|
||||
"integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==",
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.3.tgz",
|
||||
"integrity": "sha512-UMduMbqO5s5zF2NkNacMT/yK5Y5QiKvWr2+50bzIIxFDwVJ2h49b+oyjaCGPhJxd2/gC2x39EHv/gHVuu36x2Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@tybys/wasm-util": "^0.10.1"
|
||||
"@tybys/wasm-util": "^0.10.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.13.0 || >=23.5.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emnapi/core": "^1.7.1",
|
||||
"@emnapi/runtime": "^1.7.1"
|
||||
"@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4",
|
||||
"@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@oxc-resolver/binding-wasm32-wasi/node_modules/@tybys/wasm-util": {
|
||||
"version": "0.10.2",
|
||||
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz",
|
||||
"integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==",
|
||||
"version": "0.10.3",
|
||||
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz",
|
||||
"integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
@@ -8413,9 +8451,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@oxc-resolver/binding-win32-arm64-msvc": {
|
||||
"version": "11.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-11.20.0.tgz",
|
||||
"integrity": "sha512-qPi25YNPe4YenS8MgsQU2+bIFHxxpLx1LVna2444cEHqNPhNjvWf9zqj4aWE43H9LpAsTmkkAlA3eL5ElBU3mA==",
|
||||
"version": "11.21.2",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-11.21.2.tgz",
|
||||
"integrity": "sha512-iWx25CBEgH49iE9q5coEGI/jb1jl5kkCY9z6U5Og67xCkQ/WFMDc2J5U78+AE91SUxM2NqSLhJC8/PLfWnImww==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -8427,9 +8465,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@oxc-resolver/binding-win32-x64-msvc": {
|
||||
"version": "11.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-11.20.0.tgz",
|
||||
"integrity": "sha512-Wb14jWEW8huH6It9F6sXd9vrYmIS7pMrgkU6sxpLxkP+9z+wRgs71hUEhRpcn8FOXAFa27FVWfY2tRpbfTzfLw==",
|
||||
"version": "11.21.2",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-11.21.2.tgz",
|
||||
"integrity": "sha512-VPoCAhKvCQTlG7vxqaBXcmuvbh77BfnGXj8g0pbvVXpm1F/R8rDVwqIWcEbMzrI1JvJlm8v7T9uMVQb6UctMRg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -10765,16 +10803,16 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@storybook/addon-docs": {
|
||||
"version": "10.5.9",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-10.5.9.tgz",
|
||||
"integrity": "sha512-8sFsMkZYrrdqCLdV+hnwTwDF7RaBsBPRwl4wfc8ve9Q/7Yhi5REe/Xjvd8x1yn6fBPPw9tnID9dx6Agdnr81fw==",
|
||||
"version": "10.5.10",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-10.5.10.tgz",
|
||||
"integrity": "sha512-06JoK3/a7FWI/6GzuidJP9iHp1/Vejboe6lzS1jW+d8ItpecriBt+oXh1VNmUM7i7PjI6pZnet+j51QnLyeOoQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@mdx-js/react": "^3.0.0",
|
||||
"@storybook/csf-plugin": "10.5.9",
|
||||
"@storybook/csf-plugin": "10.5.10",
|
||||
"@storybook/icons": "^2.0.2",
|
||||
"@storybook/react-dom-shim": "10.5.9",
|
||||
"@storybook/react-dom-shim": "10.5.10",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"ts-dedent": "^2.0.0"
|
||||
@@ -10785,7 +10823,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"storybook": "10.5.9"
|
||||
"storybook": "10.5.10"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
@@ -10794,9 +10832,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/addon-docs/node_modules/@storybook/csf-plugin": {
|
||||
"version": "10.5.9",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-10.5.9.tgz",
|
||||
"integrity": "sha512-4H5QIHQVtQYCuL43GCRLGjNQhZpQg9gL03ja0DV80kO2Dn9LEt6ol87bSnSjn4VDgcAXtgTzXFvRLknfVgAAqg==",
|
||||
"version": "10.5.10",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-10.5.10.tgz",
|
||||
"integrity": "sha512-TaCLBrqVEr767+w58QDotDUiCTuE5cyRJuRcDlsKQyUIyGBv+lYD3lu8wBiVCYxgIjB/gu9HmqiC+0yx1rHzaw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -10809,7 +10847,7 @@
|
||||
"peerDependencies": {
|
||||
"esbuild": "*",
|
||||
"rollup": "*",
|
||||
"storybook": "10.5.9",
|
||||
"storybook": "10.5.10",
|
||||
"vite": "*",
|
||||
"webpack": "*"
|
||||
},
|
||||
@@ -10829,9 +10867,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/addon-docs/node_modules/@storybook/react-dom-shim": {
|
||||
"version": "10.5.9",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-10.5.9.tgz",
|
||||
"integrity": "sha512-7qZD6CSa64p1m/zX9tG4ALcEHlEk0Bx+5++4RL3MFlRCgCFfAomXsCHTzF0RyF8cI4vl+hS7Y0ryjQchVs6UQA==",
|
||||
"version": "10.5.10",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-10.5.10.tgz",
|
||||
"integrity": "sha512-rbu62ILo/VE3iXKmu+kWXFpD1H1Lwi0f19q/x7JnDsD2dxKS9w5znLEqPIq2qxpzi/wjjIb2iUP1cRG1d/9W5A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
@@ -10843,7 +10881,7 @@
|
||||
"@types/react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"storybook": "10.5.9"
|
||||
"storybook": "10.5.10"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
@@ -10855,9 +10893,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/addon-links": {
|
||||
"version": "10.5.9",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/addon-links/-/addon-links-10.5.9.tgz",
|
||||
"integrity": "sha512-ZDbPl6ia6hqjoV+CpQU3DjkXpc0TxUq6+y/rFD8w21dJMdqNWzY8zajHC8r4CfTWANjM1pGPUYisWtTKi1MxZw==",
|
||||
"version": "10.5.10",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/addon-links/-/addon-links-10.5.10.tgz",
|
||||
"integrity": "sha512-wZQX26vSKEBuQB+EKHuAoeDh0XiUDrN2GMX2lhhj2KpJ/di4OyXFaHXspXOXWkjNtyMTRd0QkCHQqKx4LY3E6w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -10870,7 +10908,7 @@
|
||||
"peerDependencies": {
|
||||
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"storybook": "10.5.9"
|
||||
"storybook": "10.5.10"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
@@ -10964,15 +11002,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/react-webpack5": {
|
||||
"version": "10.5.9",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/react-webpack5/-/react-webpack5-10.5.9.tgz",
|
||||
"integrity": "sha512-mrCJub/WAt6RAU1P+bpBvcdTHMHePXLuNM+TuJl0Sl3r9Ta0YjDvtdODweud9sTEpVN3ao/fk0LpiaDMfLd84w==",
|
||||
"version": "10.5.10",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/react-webpack5/-/react-webpack5-10.5.10.tgz",
|
||||
"integrity": "sha512-PpC2CYbeqKGDYpG64PR/wkjo+Sm9EKWvdE0ZaKHBQb1PfC0p/TUxHy8XfXQp/4atjhET0VqSUDc0TP/ii1bGxw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@storybook/builder-webpack5": "10.5.9",
|
||||
"@storybook/preset-react-webpack": "10.5.9",
|
||||
"@storybook/react": "10.5.9"
|
||||
"@storybook/builder-webpack5": "10.5.10",
|
||||
"@storybook/preset-react-webpack": "10.5.10",
|
||||
"@storybook/react": "10.5.10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
@@ -10981,7 +11019,7 @@
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"storybook": "10.5.9",
|
||||
"storybook": "10.5.10",
|
||||
"typescript": ">= 4.9.x"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
@@ -10991,13 +11029,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/react-webpack5/node_modules/@storybook/builder-webpack5": {
|
||||
"version": "10.5.9",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/builder-webpack5/-/builder-webpack5-10.5.9.tgz",
|
||||
"integrity": "sha512-XTLC95jP75V9NfhoUzDNKDCn9r0ZqXz4ZhrNzQXVDz4vNWkjU3/uDL6Ywh9WFu6Xrb+onQqSR9hlHjS/DOZj7Q==",
|
||||
"version": "10.5.10",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/builder-webpack5/-/builder-webpack5-10.5.10.tgz",
|
||||
"integrity": "sha512-9ZBRusBwZStoCj3Wm4wVpSpKV8OlLMpadEQ+vvOVrtDQdVJWM5xdLQpqrG21r/cJC4ZX53ZpbrDL+NMsIp1Rkw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@storybook/core-webpack": "10.5.9",
|
||||
"@storybook/core-webpack": "10.5.10",
|
||||
"case-sensitive-paths-webpack-plugin": "^2.4.0",
|
||||
"cjs-module-lexer": "^1.2.3",
|
||||
"css-loader": "^7.1.2",
|
||||
@@ -11019,7 +11057,7 @@
|
||||
"url": "https://opencollective.com/storybook"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"storybook": "10.5.9"
|
||||
"storybook": "10.5.10"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
@@ -11028,9 +11066,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/react-webpack5/node_modules/@storybook/builder-webpack5/node_modules/@storybook/core-webpack": {
|
||||
"version": "10.5.9",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/core-webpack/-/core-webpack-10.5.9.tgz",
|
||||
"integrity": "sha512-YmXR9RJdQpH8EtWEIjLTr5LMGiCXSmZp/A9UkATY5xHM4qG2QwJB++jU2wv4nGDyaPiUcbzT2PQSXN6RXhiUZA==",
|
||||
"version": "10.5.10",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/core-webpack/-/core-webpack-10.5.10.tgz",
|
||||
"integrity": "sha512-CiUo4AZRKm3nVb6KWw+yfV6g6VSTA97msiVN9bJlBOFS1dax2ulrRNJnHJxR/U1AT51hONfU4dZOpCXRwaobTA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -11041,17 +11079,17 @@
|
||||
"url": "https://opencollective.com/storybook"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"storybook": "10.5.9"
|
||||
"storybook": "10.5.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/react-webpack5/node_modules/@storybook/preset-react-webpack": {
|
||||
"version": "10.5.9",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/preset-react-webpack/-/preset-react-webpack-10.5.9.tgz",
|
||||
"integrity": "sha512-LOr8SoM2CejVCHeLyxbdRMiKuQb2q95CE5D75oK3+513mMZ8LxVPVApxy8B6FvFYNe9CTqVK+95jETefOqabTw==",
|
||||
"version": "10.5.10",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/preset-react-webpack/-/preset-react-webpack-10.5.10.tgz",
|
||||
"integrity": "sha512-erAyPdNKqzOQOQVHm/kMSO4Nrr/g279KoA5LyzMOTJAQJHjDdsqmYdGXRDVfcnDL+H+Qm9pWaP+re2S8YG6JNg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@storybook/core-webpack": "10.5.9",
|
||||
"@storybook/core-webpack": "10.5.10",
|
||||
"@storybook/react-docgen-typescript-plugin": "1.0.6--canary.9.0c3f3b7.0",
|
||||
"@types/semver": "^7.7.1",
|
||||
"magic-string": "^0.30.5",
|
||||
@@ -11068,7 +11106,7 @@
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"storybook": "10.5.9"
|
||||
"storybook": "10.5.10"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
@@ -11077,9 +11115,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/react-webpack5/node_modules/@storybook/preset-react-webpack/node_modules/@storybook/core-webpack": {
|
||||
"version": "10.5.9",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/core-webpack/-/core-webpack-10.5.9.tgz",
|
||||
"integrity": "sha512-YmXR9RJdQpH8EtWEIjLTr5LMGiCXSmZp/A9UkATY5xHM4qG2QwJB++jU2wv4nGDyaPiUcbzT2PQSXN6RXhiUZA==",
|
||||
"version": "10.5.10",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/core-webpack/-/core-webpack-10.5.10.tgz",
|
||||
"integrity": "sha512-CiUo4AZRKm3nVb6KWw+yfV6g6VSTA97msiVN9bJlBOFS1dax2ulrRNJnHJxR/U1AT51hONfU4dZOpCXRwaobTA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -11090,18 +11128,18 @@
|
||||
"url": "https://opencollective.com/storybook"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"storybook": "10.5.9"
|
||||
"storybook": "10.5.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/react-webpack5/node_modules/@storybook/react": {
|
||||
"version": "10.5.9",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/react/-/react-10.5.9.tgz",
|
||||
"integrity": "sha512-kApGOuNT26NkpioTsr1iT/Q2c44tA7OIsNUSyFqtT7W8k3fRn/jQWfrDegYxty0WG0wxdNMZq2ndRfOOF8HaHw==",
|
||||
"version": "10.5.10",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/react/-/react-10.5.10.tgz",
|
||||
"integrity": "sha512-4MBV5e1SXIMfPynLHzr+Mp0dwGv/FW1bklWAsS4ynBOAbC98W9p/I9vqBnUctsvE3BJkhzHQQyPwMHL5tTcHVA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@storybook/global": "^5.0.0",
|
||||
"@storybook/react-dom-shim": "10.5.9",
|
||||
"@storybook/react-dom-shim": "10.5.10",
|
||||
"react-docgen": "^8.0.2",
|
||||
"react-docgen-typescript": "^2.2.2"
|
||||
},
|
||||
@@ -11114,7 +11152,7 @@
|
||||
"@types/react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"storybook": "10.5.9",
|
||||
"storybook": "10.5.10",
|
||||
"typescript": ">= 4.9.x"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
@@ -11130,9 +11168,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/react-webpack5/node_modules/@storybook/react/node_modules/@storybook/react-dom-shim": {
|
||||
"version": "10.5.9",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-10.5.9.tgz",
|
||||
"integrity": "sha512-7qZD6CSa64p1m/zX9tG4ALcEHlEk0Bx+5++4RL3MFlRCgCFfAomXsCHTzF0RyF8cI4vl+hS7Y0ryjQchVs6UQA==",
|
||||
"version": "10.5.10",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-10.5.10.tgz",
|
||||
"integrity": "sha512-rbu62ILo/VE3iXKmu+kWXFpD1H1Lwi0f19q/x7JnDsD2dxKS9w5znLEqPIq2qxpzi/wjjIb2iUP1cRG1d/9W5A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
@@ -11144,7 +11182,7 @@
|
||||
"@types/react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"storybook": "10.5.9"
|
||||
"storybook": "10.5.10"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
@@ -15715,9 +15753,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/baseline-browser-mapping": {
|
||||
"version": "2.11.15",
|
||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.15.tgz",
|
||||
"integrity": "sha512-FwMjJJ7HnyZpWe+oWxegG0fezZyBZUagI5LZEoO3GCbtbKNwRfMH9Ue5d5v01PNePBy1QSfPSDTTeVL0Hb9EzA==",
|
||||
"version": "2.11.16",
|
||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.16.tgz",
|
||||
"integrity": "sha512-H/bNPUFHewJHyCTdjn1n3Pit5+2GmWT6mmeHImPX+8MA9NA6b67jO4gYmi4jTbCJb2otq34KMZnovndDPqJwhQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
@@ -20202,9 +20240,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-storybook": {
|
||||
"version": "10.5.9",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-storybook/-/eslint-plugin-storybook-10.5.9.tgz",
|
||||
"integrity": "sha512-4Hrqccy/zttV0S/32TSUbqtizzj4sbkgYvv7UzHveH58v96PhrO+fSpOFAqYvzNDzdOU6smKwObFtzX+RZqj4w==",
|
||||
"version": "10.5.10",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-storybook/-/eslint-plugin-storybook-10.5.10.tgz",
|
||||
"integrity": "sha512-NeOu3axmhNZfRuHRciFH0a/OVgvtAJLRHAwiJjcGkPAcElNmwEPp+pqoiwxytx7kHInz7mVVoBfPwVc8kPmOLw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -20212,8 +20250,7 @@
|
||||
"@typescript-eslint/utils": "^8.60.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": ">=8",
|
||||
"storybook": "10.5.9"
|
||||
"eslint": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-testing-library": {
|
||||
@@ -31822,34 +31859,34 @@
|
||||
}
|
||||
},
|
||||
"node_modules/oxc-resolver": {
|
||||
"version": "11.20.0",
|
||||
"resolved": "https://registry.npmjs.org/oxc-resolver/-/oxc-resolver-11.20.0.tgz",
|
||||
"integrity": "sha512-CblytBiV/a/ZXY34dsVU2NxhIOxMXst8CvDCtyBelVITgd7PLrKzbEbA6oKLdPjvDKDzCiW48qzmzZ+mYaqn+g==",
|
||||
"version": "11.21.2",
|
||||
"resolved": "https://registry.npmjs.org/oxc-resolver/-/oxc-resolver-11.21.2.tgz",
|
||||
"integrity": "sha512-w5tLwYN3Zo24w5EeWJjJWZOwhYqTtC8PS2B1tIt7BZUuqTIcU07sQValbDw+rq7+AuAGzOHklgK+ifsy4lpXfw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/Boshen"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@oxc-resolver/binding-android-arm-eabi": "11.20.0",
|
||||
"@oxc-resolver/binding-android-arm64": "11.20.0",
|
||||
"@oxc-resolver/binding-darwin-arm64": "11.20.0",
|
||||
"@oxc-resolver/binding-darwin-x64": "11.20.0",
|
||||
"@oxc-resolver/binding-freebsd-x64": "11.20.0",
|
||||
"@oxc-resolver/binding-linux-arm-gnueabihf": "11.20.0",
|
||||
"@oxc-resolver/binding-linux-arm-musleabihf": "11.20.0",
|
||||
"@oxc-resolver/binding-linux-arm64-gnu": "11.20.0",
|
||||
"@oxc-resolver/binding-linux-arm64-musl": "11.20.0",
|
||||
"@oxc-resolver/binding-linux-ppc64-gnu": "11.20.0",
|
||||
"@oxc-resolver/binding-linux-riscv64-gnu": "11.20.0",
|
||||
"@oxc-resolver/binding-linux-riscv64-musl": "11.20.0",
|
||||
"@oxc-resolver/binding-linux-s390x-gnu": "11.20.0",
|
||||
"@oxc-resolver/binding-linux-x64-gnu": "11.20.0",
|
||||
"@oxc-resolver/binding-linux-x64-musl": "11.20.0",
|
||||
"@oxc-resolver/binding-openharmony-arm64": "11.20.0",
|
||||
"@oxc-resolver/binding-wasm32-wasi": "11.20.0",
|
||||
"@oxc-resolver/binding-win32-arm64-msvc": "11.20.0",
|
||||
"@oxc-resolver/binding-win32-x64-msvc": "11.20.0"
|
||||
"@oxc-resolver/binding-android-arm-eabi": "11.21.2",
|
||||
"@oxc-resolver/binding-android-arm64": "11.21.2",
|
||||
"@oxc-resolver/binding-darwin-arm64": "11.21.2",
|
||||
"@oxc-resolver/binding-darwin-x64": "11.21.2",
|
||||
"@oxc-resolver/binding-freebsd-x64": "11.21.2",
|
||||
"@oxc-resolver/binding-linux-arm-gnueabihf": "11.21.2",
|
||||
"@oxc-resolver/binding-linux-arm-musleabihf": "11.21.2",
|
||||
"@oxc-resolver/binding-linux-arm64-gnu": "11.21.2",
|
||||
"@oxc-resolver/binding-linux-arm64-musl": "11.21.2",
|
||||
"@oxc-resolver/binding-linux-ppc64-gnu": "11.21.2",
|
||||
"@oxc-resolver/binding-linux-riscv64-gnu": "11.21.2",
|
||||
"@oxc-resolver/binding-linux-riscv64-musl": "11.21.2",
|
||||
"@oxc-resolver/binding-linux-s390x-gnu": "11.21.2",
|
||||
"@oxc-resolver/binding-linux-x64-gnu": "11.21.2",
|
||||
"@oxc-resolver/binding-linux-x64-musl": "11.21.2",
|
||||
"@oxc-resolver/binding-openharmony-arm64": "11.21.2",
|
||||
"@oxc-resolver/binding-wasm32-wasi": "11.21.2",
|
||||
"@oxc-resolver/binding-win32-arm64-msvc": "11.21.2",
|
||||
"@oxc-resolver/binding-win32-x64-msvc": "11.21.2"
|
||||
}
|
||||
},
|
||||
"node_modules/oxfmt": {
|
||||
@@ -37700,9 +37737,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/storybook": {
|
||||
"version": "10.5.9",
|
||||
"resolved": "https://registry.npmjs.org/storybook/-/storybook-10.5.9.tgz",
|
||||
"integrity": "sha512-UfdMKSjEhIKr8LbqYyIE5r7vT/drL/PxN75YaouJ+UG0FssEy6cf49OdTF3kstAqVMHskc+zEqyRoiQHZXHwgA==",
|
||||
"version": "10.5.10",
|
||||
"resolved": "https://registry.npmjs.org/storybook/-/storybook-10.5.10.tgz",
|
||||
"integrity": "sha512-Rz8k9ejFHsi7lbtJTaxZlhCUz4GkbJIKEoKDjXeLfr/ZhXip73E6keKxW0KH8iGeKiCqHAbJCV4YIQrxTOLiig==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -37718,7 +37755,7 @@
|
||||
"jsonc-parser": "^3.3.1",
|
||||
"open": "^10.2.0",
|
||||
"oxc-parser": "^0.127.0",
|
||||
"oxc-resolver": "^11.19.1",
|
||||
"oxc-resolver": "11.21.2",
|
||||
"recast": "^0.23.5",
|
||||
"semver": "^7.7.3",
|
||||
"use-sync-external-store": "^1.5.0",
|
||||
|
||||
@@ -257,9 +257,9 @@
|
||||
"@istanbuljs/nyc-config-typescript": "^1.0.1",
|
||||
"@playwright/test": "^1.62.1",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.6.2",
|
||||
"@storybook/addon-docs": "10.5.9",
|
||||
"@storybook/addon-links": "10.5.9",
|
||||
"@storybook/react-webpack5": "10.5.9",
|
||||
"@storybook/addon-docs": "10.5.10",
|
||||
"@storybook/addon-links": "10.5.10",
|
||||
"@storybook/react-webpack5": "10.5.10",
|
||||
"@storybook/test-runner": "0.24.4",
|
||||
"@svgr/webpack": "^8.1.0",
|
||||
"@swc/core": "^1.16.1",
|
||||
@@ -295,7 +295,7 @@
|
||||
"babel-loader": "^10.1.1",
|
||||
"babel-plugin-dynamic-import-node": "^2.3.3",
|
||||
"babel-plugin-jsx-remove-data-test-id": "^3.0.0",
|
||||
"baseline-browser-mapping": "^2.11.15",
|
||||
"baseline-browser-mapping": "^2.11.16",
|
||||
"cheerio": "1.2.0",
|
||||
"concurrently": "^10.0.5",
|
||||
"copy-webpack-plugin": "^14.0.0",
|
||||
@@ -312,7 +312,7 @@
|
||||
"eslint-plugin-no-only-tests": "^3.4.0",
|
||||
"eslint-plugin-react-prefer-function-component": "^5.0.0",
|
||||
"eslint-plugin-react-you-might-not-need-an-effect": "^1.0.1",
|
||||
"eslint-plugin-storybook": "10.5.9",
|
||||
"eslint-plugin-storybook": "10.5.10",
|
||||
"eslint-plugin-testing-library": "^7.16.2",
|
||||
"eslint-plugin-theme-colors": "file:eslint-rules/eslint-plugin-theme-colors",
|
||||
"fetch-mock": "^12.6.0",
|
||||
@@ -343,7 +343,7 @@
|
||||
"source-map": "^0.8.0",
|
||||
"source-map-support": "^0.5.21",
|
||||
"speed-measure-webpack-plugin": "^1.6.0",
|
||||
"storybook": "10.5.9",
|
||||
"storybook": "10.5.10",
|
||||
"style-loader": "^4.0.0",
|
||||
"stylelint": "^17.14.1",
|
||||
"swc-loader": "^0.2.7",
|
||||
|
||||
+28
@@ -246,6 +246,34 @@ test('wraps component with proper container div', () => {
|
||||
expect(wrapper).toHaveAttribute('data-themed-ag-grid', 'true');
|
||||
});
|
||||
|
||||
test('applies non-transparent backgrounds to native menus, tooltips and overlays', () => {
|
||||
const customTheme = {
|
||||
...supersetTheme,
|
||||
colorBgElevated: '#f2f2f2',
|
||||
};
|
||||
|
||||
render(
|
||||
<ThemeProvider theme={customTheme}>
|
||||
<ThemedAgGridReact rowData={mockRowData} columnDefs={mockColumnDefs} />
|
||||
</ThemeProvider>,
|
||||
);
|
||||
|
||||
const agGrid = screen.getByTestId('ag-grid-react');
|
||||
const theme = JSON.parse(agGrid.getAttribute('data-theme') || '{}');
|
||||
|
||||
// ag-grid's own context/column menus, side bar, tooltips and overlays are
|
||||
// rendered against these params rather than `backgroundColor` (which is
|
||||
// intentionally 'transparent' so the surrounding app shows through the
|
||||
// grid body). Without explicit values they inherit transparency too,
|
||||
// making native menus/popups unreadable.
|
||||
expect(theme.chromeBackgroundColor).toBe('#f2f2f2');
|
||||
expect(theme.menuBackgroundColor).toBe('#f2f2f2');
|
||||
expect(theme.menuBorder).toBe(true);
|
||||
expect(theme.sideBarBackgroundColor).toBe('#f2f2f2');
|
||||
expect(theme.tooltipBackgroundColor).toBe('#f2f2f2');
|
||||
expect(theme.modalOverlayBackgroundColor).toBe('#f2f2f2');
|
||||
});
|
||||
|
||||
test('handles missing theme gracefully', () => {
|
||||
const incompleteTheme = {
|
||||
...supersetTheme,
|
||||
|
||||
+11
@@ -104,6 +104,17 @@ export const ThemedAgGridReact = forwardRef<
|
||||
foregroundColor: theme.colorText,
|
||||
browserColorScheme: isDarkMode ? 'dark' : 'light',
|
||||
|
||||
// Native menus, popups, side bar, tooltips and loading/no-rows overlays
|
||||
// are rendered against these params rather than `backgroundColor`
|
||||
// (which is intentionally transparent). Without explicit values they
|
||||
// inherit transparency too, making them unreadable.
|
||||
chromeBackgroundColor: theme.colorBgElevated,
|
||||
menuBackgroundColor: theme.colorBgElevated,
|
||||
menuBorder: true,
|
||||
sideBarBackgroundColor: theme.colorBgElevated,
|
||||
tooltipBackgroundColor: theme.colorBgElevated,
|
||||
modalOverlayBackgroundColor: theme.colorBgElevated,
|
||||
|
||||
// Header styling
|
||||
headerBackgroundColor: theme.colorFillTertiary,
|
||||
headerTextColor: theme.colorTextHeading,
|
||||
|
||||
@@ -201,6 +201,31 @@ function createCustomizeSection(
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
name: `label_position${controlSuffix}`,
|
||||
config: {
|
||||
type: 'SelectControl',
|
||||
freeForm: false,
|
||||
label: t('Label Position'),
|
||||
choices: [
|
||||
['auto', t('Auto')],
|
||||
['top', t('Top')],
|
||||
['inside', t('Inside')],
|
||||
['bottom', t('Bottom')],
|
||||
['left', t('Left')],
|
||||
['right', t('Right')],
|
||||
],
|
||||
default: 'auto',
|
||||
renderTrigger: true,
|
||||
description: t(
|
||||
'Position of the data label relative to the data point',
|
||||
),
|
||||
visibility: ({ controls }: ControlPanelsContainerProps) =>
|
||||
Boolean(controls?.[`show_value${controlSuffix}`]?.value),
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
name: `only_total${controlSuffix}`,
|
||||
|
||||
@@ -189,6 +189,8 @@ export default function transformProps(
|
||||
showLegend,
|
||||
showValue,
|
||||
showValueB,
|
||||
labelPosition,
|
||||
labelPositionB,
|
||||
onlyTotal,
|
||||
onlyTotalB,
|
||||
stack,
|
||||
@@ -533,6 +535,7 @@ export default function transformProps(
|
||||
thresholdValues,
|
||||
timeShiftColor,
|
||||
theme,
|
||||
labelPosition,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -621,6 +624,7 @@ export default function transformProps(
|
||||
thresholdValues: thresholdValuesB,
|
||||
timeShiftColor,
|
||||
theme,
|
||||
labelPosition: labelPositionB,
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
ContextMenuTransformedProps,
|
||||
CrossFilterTransformedProps,
|
||||
EchartsTimeseriesSeriesType,
|
||||
LabelPositionEnum,
|
||||
LegendFormData,
|
||||
StackType,
|
||||
TitleFormData,
|
||||
@@ -86,6 +87,18 @@ export type EchartsMixedTimeseriesFormData = QueryFormData & {
|
||||
seriesTypeB: EchartsTimeseriesSeriesType;
|
||||
showValue: boolean;
|
||||
showValueB: boolean;
|
||||
/**
|
||||
* Where the data label sits relative to its data point on query A, applied
|
||||
* when `showValue` is on.
|
||||
*
|
||||
* `'auto'` keeps the orientation-aware default the chart used before this
|
||||
* control existed: `Right` for a horizontal chart, `Top` otherwise. It is
|
||||
* also the value every chart saved before then resolves to, so the default
|
||||
* must stay `'auto'` for those to keep rendering as they did.
|
||||
*/
|
||||
labelPosition?: LabelPositionEnum | 'auto';
|
||||
/** The same, for query B. Resolved independently of {@link labelPosition}. */
|
||||
labelPositionB?: LabelPositionEnum | 'auto';
|
||||
stack: StackType;
|
||||
stackB: StackType;
|
||||
yAxisIndex?: number;
|
||||
@@ -130,6 +143,8 @@ export const DEFAULT_FORM_DATA: EchartsMixedTimeseriesFormData = {
|
||||
seriesTypeB: TIMESERIES_DEFAULTS.seriesType,
|
||||
showValue: TIMESERIES_DEFAULTS.showValue,
|
||||
showValueB: TIMESERIES_DEFAULTS.showValue,
|
||||
labelPosition: 'auto',
|
||||
labelPositionB: 'auto',
|
||||
stack: TIMESERIES_DEFAULTS.stack,
|
||||
stackB: TIMESERIES_DEFAULTS.stack,
|
||||
yAxisIndex: 0,
|
||||
|
||||
@@ -86,6 +86,7 @@ export const DEFAULT_FORM_DATA: EchartsTimeseriesFormData = {
|
||||
xAxisLabelInterval: defaultXAxis.xAxisLabelInterval,
|
||||
groupby: [],
|
||||
showValue: false,
|
||||
labelPosition: 'auto',
|
||||
onlyTotal: false,
|
||||
percentageThreshold: 0,
|
||||
orientation: OrientationType.Vertical,
|
||||
|
||||
@@ -292,6 +292,7 @@ export default function transformProps(
|
||||
showLegend,
|
||||
showValue,
|
||||
size,
|
||||
labelPosition,
|
||||
colorByPrimaryAxis,
|
||||
sliceId,
|
||||
sortSeriesType,
|
||||
@@ -752,6 +753,7 @@ export default function transformProps(
|
||||
theme,
|
||||
hasDimensions: (groupBy?.length ?? 0) > 0,
|
||||
colorByPrimaryAxis,
|
||||
labelPosition,
|
||||
},
|
||||
);
|
||||
if (transformedSeries) {
|
||||
|
||||
@@ -51,6 +51,7 @@ import { extractForecastSeriesContext } from '../utils/forecast';
|
||||
import {
|
||||
EchartsTimeseriesSeriesType,
|
||||
ForecastSeriesEnum,
|
||||
LabelPositionEnum,
|
||||
LegendOrientation,
|
||||
OrientationType,
|
||||
StackType,
|
||||
@@ -170,6 +171,7 @@ export const getBaselineSeriesForStream = (
|
||||
export function transformNegativeLabelsPosition(
|
||||
series: SeriesOption,
|
||||
isHorizontal: boolean,
|
||||
labelPosition?: string,
|
||||
): TimeseriesDataRecord[] {
|
||||
/*
|
||||
* Adjusts label position for negative values in bar series
|
||||
@@ -185,7 +187,10 @@ export function transformNegativeLabelsPosition(
|
||||
? {
|
||||
value,
|
||||
label: {
|
||||
position: 'outside',
|
||||
position:
|
||||
labelPosition && labelPosition !== 'auto'
|
||||
? labelPosition
|
||||
: 'outside',
|
||||
},
|
||||
}
|
||||
: value;
|
||||
@@ -255,6 +260,7 @@ export function transformSeries(
|
||||
theme?: SupersetTheme;
|
||||
hasDimensions?: boolean;
|
||||
colorByPrimaryAxis?: boolean;
|
||||
labelPosition?: string;
|
||||
},
|
||||
): SeriesOption | undefined {
|
||||
const { name, data } = series;
|
||||
@@ -287,6 +293,7 @@ export function transformSeries(
|
||||
timeShiftColor,
|
||||
theme,
|
||||
colorByPrimaryAxis = false,
|
||||
labelPosition,
|
||||
} = opts;
|
||||
const contexts = seriesContexts[name || ''] || [];
|
||||
const hasForecast =
|
||||
@@ -406,7 +413,13 @@ export function transformSeries(
|
||||
),
|
||||
}
|
||||
: seriesType === 'bar' && !stack
|
||||
? { data: transformNegativeLabelsPosition(series, isHorizontal) }
|
||||
? {
|
||||
data: transformNegativeLabelsPosition(
|
||||
series,
|
||||
isHorizontal,
|
||||
labelPosition,
|
||||
),
|
||||
}
|
||||
: null
|
||||
: null),
|
||||
connectNulls,
|
||||
@@ -443,7 +456,12 @@ export function transformSeries(
|
||||
symbolSize: symbolSizeFn ?? markerSize,
|
||||
label: {
|
||||
show: !!showValue,
|
||||
position: isHorizontal ? 'right' : 'top',
|
||||
position: (labelPosition === 'auto' || !labelPosition
|
||||
? isHorizontal
|
||||
? LabelPositionEnum.Right
|
||||
: LabelPositionEnum.Top
|
||||
: labelPosition) as LabelPositionEnum,
|
||||
...(plotType === 'bar' ? { overflow: 'truncate' } : {}),
|
||||
color: theme?.colorText,
|
||||
textBorderWidth: 0,
|
||||
formatter: (params: any) => {
|
||||
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
BaseTransformedProps,
|
||||
ContextMenuTransformedProps,
|
||||
CrossFilterTransformedProps,
|
||||
LabelPositionEnum,
|
||||
LegendFormData,
|
||||
StackType,
|
||||
TitleFormData,
|
||||
@@ -99,6 +100,16 @@ export type EchartsTimeseriesFormData = QueryFormData & {
|
||||
xAxisLabelRotation: number;
|
||||
xAxisLabelInterval: number | string;
|
||||
showValue: boolean;
|
||||
/**
|
||||
* Where the data label sits relative to its data point, applied when
|
||||
* `showValue` is on.
|
||||
*
|
||||
* `'auto'` keeps the orientation-aware default the chart used before this
|
||||
* control existed: `Right` for a horizontal chart, `Top` otherwise. It is
|
||||
* also the value every chart saved before then resolves to, so the default
|
||||
* must stay `'auto'` for those to keep rendering as they did.
|
||||
*/
|
||||
labelPosition?: LabelPositionEnum | 'auto';
|
||||
onlyTotal: boolean;
|
||||
showExtraControls: boolean;
|
||||
percentageThreshold: number;
|
||||
|
||||
@@ -140,6 +140,28 @@ export const showValueControl: ControlSetItem = {
|
||||
},
|
||||
};
|
||||
|
||||
export const labelPositionControl: ControlSetItem = {
|
||||
name: 'label_position',
|
||||
config: {
|
||||
type: 'SelectControl',
|
||||
freeForm: false,
|
||||
label: t('Label Position'),
|
||||
choices: [
|
||||
['auto', t('Auto')],
|
||||
['top', t('Top')],
|
||||
['inside', t('Inside')],
|
||||
['bottom', t('Bottom')],
|
||||
['left', t('Left')],
|
||||
['right', t('Right')],
|
||||
],
|
||||
default: 'auto',
|
||||
renderTrigger: true,
|
||||
description: t('Position of the data label relative to the data point'),
|
||||
visibility: ({ controls }: ControlPanelsContainerProps) =>
|
||||
Boolean(controls?.show_value?.value),
|
||||
},
|
||||
};
|
||||
|
||||
export const colorByPrimaryAxisControl: ControlSetItem = {
|
||||
name: 'color_by_primary_axis',
|
||||
config: {
|
||||
@@ -219,6 +241,7 @@ export const percentageThresholdControl: ControlSetItem = {
|
||||
|
||||
export const showValueSection: ControlSetRow[] = [
|
||||
[showValueControl],
|
||||
[labelPositionControl],
|
||||
[stackControl],
|
||||
[onlyTotalControl],
|
||||
[percentageThresholdControl],
|
||||
@@ -230,11 +253,13 @@ export const colorByPrimaryAxisSection: ControlSetRow[] = [
|
||||
|
||||
export const showValueSectionWithoutStack: ControlSetRow[] = [
|
||||
[showValueControl],
|
||||
[labelPositionControl],
|
||||
[onlyTotalControl],
|
||||
];
|
||||
|
||||
export const showValueSectionWithoutStream: ControlSetRow[] = [
|
||||
[showValueControl],
|
||||
[labelPositionControl],
|
||||
[stackControlWithoutStream],
|
||||
[onlyTotalControl],
|
||||
[percentageThresholdControl],
|
||||
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* 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 { ControlPanelsContainerProps } from '@superset-ui/chart-controls/types';
|
||||
import controlPanel from '../../src/MixedTimeseries/controlPanel';
|
||||
|
||||
// Narrow shape of the controls under test: enough to exercise `visibility`
|
||||
// without reaching for `any`.
|
||||
type VisibilityControl = {
|
||||
name: string;
|
||||
config: { visibility: (props: ControlPanelsContainerProps) => boolean };
|
||||
};
|
||||
|
||||
const config = controlPanel;
|
||||
|
||||
const getControl = (controlName: string) => {
|
||||
for (const section of config.controlPanelSections) {
|
||||
if (section && section.controlSetRows) {
|
||||
for (const row of section.controlSetRows) {
|
||||
for (const control of row) {
|
||||
if (
|
||||
typeof control === 'object' &&
|
||||
control !== null &&
|
||||
'name' in control &&
|
||||
control.name === controlName
|
||||
) {
|
||||
return control;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// Mock getStandardizedControls
|
||||
jest.mock('@superset-ui/chart-controls', () => {
|
||||
const actual = jest.requireActual('@superset-ui/chart-controls');
|
||||
return {
|
||||
...actual,
|
||||
getStandardizedControls: jest.fn(() => ({
|
||||
popAllMetrics: jest.fn(() => []),
|
||||
popAllColumns: jest.fn(() => []),
|
||||
})),
|
||||
};
|
||||
});
|
||||
|
||||
test('should have correct visibility for label_position', () => {
|
||||
const labelPositionCtrl = getControl(
|
||||
'label_position',
|
||||
) as unknown as VisibilityControl;
|
||||
expect(labelPositionCtrl).toBeDefined();
|
||||
expect(labelPositionCtrl.config.visibility).toBeDefined();
|
||||
|
||||
expect(
|
||||
labelPositionCtrl.config.visibility({
|
||||
controls: {
|
||||
show_value: { value: true },
|
||||
},
|
||||
} as unknown as ControlPanelsContainerProps),
|
||||
).toBe(true);
|
||||
|
||||
expect(
|
||||
labelPositionCtrl.config.visibility({
|
||||
controls: {
|
||||
show_value: { value: false },
|
||||
},
|
||||
} as unknown as ControlPanelsContainerProps),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
test('should have correct visibility for label_positionB', () => {
|
||||
const labelPositionBCtrl = getControl(
|
||||
'label_positionB',
|
||||
) as unknown as VisibilityControl;
|
||||
expect(labelPositionBCtrl).toBeDefined();
|
||||
expect(labelPositionBCtrl.config.visibility).toBeDefined();
|
||||
|
||||
expect(
|
||||
labelPositionBCtrl.config.visibility({
|
||||
controls: {
|
||||
show_valueB: { value: true },
|
||||
},
|
||||
} as unknown as ControlPanelsContainerProps),
|
||||
).toBe(true);
|
||||
|
||||
expect(
|
||||
labelPositionBCtrl.config.visibility({
|
||||
controls: {
|
||||
show_valueB: { value: false },
|
||||
},
|
||||
} as unknown as ControlPanelsContainerProps),
|
||||
).toBe(false);
|
||||
});
|
||||
+54
-1
@@ -34,6 +34,7 @@ import {
|
||||
LegendOrientation,
|
||||
LegendType,
|
||||
EchartsTimeseriesSeriesType,
|
||||
LabelPositionEnum,
|
||||
} from '../../src';
|
||||
import transformProps from '../../src/MixedTimeseries/transformProps';
|
||||
import {
|
||||
@@ -42,7 +43,7 @@ import {
|
||||
EchartsMixedTimeseriesProps,
|
||||
} from '../../src/MixedTimeseries/types';
|
||||
import { createEchartsTimeseriesTestChartProps } from '../helpers';
|
||||
import type { SeriesOption } from 'echarts';
|
||||
import type { BarSeriesOption, LineSeriesOption, SeriesOption } from 'echarts';
|
||||
|
||||
type LabelFormatterParams = {
|
||||
value: [number, number];
|
||||
@@ -433,6 +434,58 @@ test('keeps bar value label clipping aligned with the assigned y-axis', () => {
|
||||
expect(formatSeriesLabel(barSeries, [timestamp, 0.5])).toBe('');
|
||||
});
|
||||
|
||||
test('threads labelPosition and labelPositionB to series A and B', () => {
|
||||
const timestamp = 1704067200000;
|
||||
const queryAData = createTestQueryData(
|
||||
[{ __timestamp: timestamp, lineMetric: 0.25 }],
|
||||
{
|
||||
colnames: ['__timestamp', 'lineMetric'],
|
||||
coltypes: [GenericDataType.Temporal, GenericDataType.Numeric],
|
||||
label_map: { lineMetric: ['lineMetric'] },
|
||||
},
|
||||
);
|
||||
const queryBData = createTestQueryData(
|
||||
[{ __timestamp: timestamp, barMetric: 0.5 }],
|
||||
{
|
||||
colnames: ['__timestamp', 'barMetric'],
|
||||
coltypes: [GenericDataType.Temporal, GenericDataType.Numeric],
|
||||
label_map: { 'barMetric (1)': ['barMetric'] },
|
||||
},
|
||||
);
|
||||
const chartProps = createEchartsTimeseriesTestChartProps<
|
||||
EchartsMixedTimeseriesFormData,
|
||||
EchartsMixedTimeseriesProps
|
||||
>({
|
||||
...MIXED_TIMESERIES_CHART_PROPS_DEFAULTS,
|
||||
defaultQueriesData: [queryAData, queryBData],
|
||||
formData: {
|
||||
...formData,
|
||||
groupby: [],
|
||||
groupbyB: [],
|
||||
metrics: ['lineMetric'],
|
||||
metricsB: ['barMetric'],
|
||||
showValue: true,
|
||||
showValueB: true,
|
||||
labelPosition: LabelPositionEnum.Inside,
|
||||
labelPositionB: LabelPositionEnum.Bottom,
|
||||
stack: null,
|
||||
stackB: null,
|
||||
x_axis: '__timestamp',
|
||||
},
|
||||
queriesData: [queryAData, queryBData],
|
||||
});
|
||||
|
||||
const { echartOptions } = transformProps(chartProps);
|
||||
// `SeriesOption` is a union across every echarts series type and does not
|
||||
// carry `label`; the two series under test are a line and a bar.
|
||||
const series = echartOptions.series as (LineSeriesOption | BarSeriesOption)[];
|
||||
const seriesA = series.find(s => s.name === 'lineMetric');
|
||||
const seriesB = series.find(s => s.name === 'barMetric');
|
||||
|
||||
expect(seriesA?.label?.position).toBe(LabelPositionEnum.Inside);
|
||||
expect(seriesB?.label?.position).toBe(LabelPositionEnum.Bottom);
|
||||
});
|
||||
|
||||
describe('legend sorting', () => {
|
||||
const getChartProps = (overrides = {}) =>
|
||||
createEchartsTimeseriesTestChartProps<
|
||||
|
||||
+46
@@ -25,6 +25,13 @@ import {
|
||||
} from '../../../src/constants';
|
||||
import { OrientationType } from '../../../src/Timeseries/types';
|
||||
|
||||
// Narrow shape of the control under test: enough to exercise `visibility`
|
||||
// without reaching for `any`.
|
||||
type VisibilityControl = {
|
||||
name: string;
|
||||
config: { visibility: (props: ControlPanelsContainerProps) => boolean };
|
||||
};
|
||||
|
||||
const config = controlPanel;
|
||||
|
||||
const getControl = (controlName: string) => {
|
||||
@@ -292,3 +299,42 @@ test('x_axis_time_format should be hidden for numeric columns', () => {
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
test('should have visibility function for label_position', () => {
|
||||
const labelPositionCtrl = getControl(
|
||||
'label_position',
|
||||
) as unknown as VisibilityControl;
|
||||
expect(labelPositionCtrl).toBeDefined();
|
||||
expect(labelPositionCtrl.config.visibility).toBeDefined();
|
||||
expect(typeof labelPositionCtrl.config.visibility).toBe('function');
|
||||
|
||||
expect(
|
||||
labelPositionCtrl.config.visibility({
|
||||
controls: {
|
||||
show_value: { value: true },
|
||||
show_valueB: { value: false },
|
||||
},
|
||||
} as unknown as ControlPanelsContainerProps),
|
||||
).toBe(true);
|
||||
|
||||
// Visibility follows `show_value` alone. No Timeseries panel defines
|
||||
// `show_valueB` — Mixed declares its own suffixed controls — so it must not
|
||||
// reveal the control on its own.
|
||||
expect(
|
||||
labelPositionCtrl.config.visibility({
|
||||
controls: {
|
||||
show_value: { value: false },
|
||||
show_valueB: { value: true },
|
||||
},
|
||||
} as unknown as ControlPanelsContainerProps),
|
||||
).toBe(false);
|
||||
|
||||
expect(
|
||||
labelPositionCtrl.config.visibility({
|
||||
controls: {
|
||||
show_value: { value: false },
|
||||
show_valueB: { value: false },
|
||||
},
|
||||
} as unknown as ControlPanelsContainerProps),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
+141
@@ -48,6 +48,7 @@ import {
|
||||
LegendOrientation,
|
||||
LegendType,
|
||||
EchartsTimeseriesChartProps,
|
||||
LabelPositionEnum,
|
||||
} from '../../src/types';
|
||||
import { DEFAULT_FORM_DATA } from '../../src/Timeseries/constants';
|
||||
import { createEchartsTimeseriesTestChartProps } from '../helpers';
|
||||
@@ -747,6 +748,146 @@ describe('Does transformProps transform series correctly', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('should respect labelPosition configuration', () => {
|
||||
const chartProps = createTestChartProps({
|
||||
formData: {
|
||||
...formData,
|
||||
labelPosition: LabelPositionEnum.InsideBottom,
|
||||
},
|
||||
queriesData,
|
||||
});
|
||||
|
||||
const transformedSeries = transformProps(chartProps).echartOptions
|
||||
.series as any[];
|
||||
|
||||
transformedSeries.forEach(series => {
|
||||
expect(series.label.position).toBe('insideBottom');
|
||||
expect(series.label.overflow).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
test('should default to top when labelPosition is auto and orientation is vertical', () => {
|
||||
const chartProps = createTestChartProps({
|
||||
formData: {
|
||||
...formData,
|
||||
labelPosition: 'auto',
|
||||
orientation: OrientationType.Vertical,
|
||||
},
|
||||
queriesData,
|
||||
});
|
||||
|
||||
const transformedSeries = transformProps(chartProps).echartOptions
|
||||
.series as any[];
|
||||
|
||||
transformedSeries.forEach(series => {
|
||||
expect(series.label.position).toBe('top');
|
||||
expect(series.label.overflow).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
test('should default to right when labelPosition is auto and orientation is horizontal', () => {
|
||||
const chartProps = createTestChartProps({
|
||||
formData: {
|
||||
...formData,
|
||||
labelPosition: 'auto',
|
||||
orientation: OrientationType.Horizontal,
|
||||
},
|
||||
queriesData,
|
||||
});
|
||||
|
||||
const transformedSeries = transformProps(chartProps).echartOptions
|
||||
.series as any[];
|
||||
|
||||
transformedSeries.forEach(series => {
|
||||
expect(series.label.position).toBe('right');
|
||||
expect(series.label.overflow).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
test('should default to right when labelPosition is unset and orientation is horizontal', () => {
|
||||
const chartProps = createTestChartProps({
|
||||
formData: {
|
||||
...formData,
|
||||
labelPosition: undefined,
|
||||
orientation: OrientationType.Horizontal,
|
||||
},
|
||||
queriesData,
|
||||
});
|
||||
|
||||
const transformedSeries = transformProps(chartProps).echartOptions
|
||||
.series as any[];
|
||||
|
||||
transformedSeries.forEach(series => {
|
||||
expect(series.label.position).toBe('right');
|
||||
expect(series.label.overflow).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
test('should set overflow: truncate only for bar series', () => {
|
||||
const barChartProps = createTestChartProps({
|
||||
formData: {
|
||||
...formData,
|
||||
seriesType: EchartsTimeseriesSeriesType.Bar,
|
||||
},
|
||||
queriesData,
|
||||
});
|
||||
const lineChartProps = createTestChartProps({
|
||||
formData: {
|
||||
...formData,
|
||||
seriesType: EchartsTimeseriesSeriesType.Line,
|
||||
},
|
||||
queriesData,
|
||||
});
|
||||
|
||||
const barSeries = transformProps(barChartProps).echartOptions
|
||||
.series as any[];
|
||||
const lineSeries = transformProps(lineChartProps).echartOptions
|
||||
.series as any[];
|
||||
|
||||
barSeries.forEach(series => {
|
||||
expect(series.label.overflow).toBe('truncate');
|
||||
});
|
||||
lineSeries.forEach(series => {
|
||||
expect(series.label.overflow).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
test('should respect labelPosition for negative values in unstacked bar charts', () => {
|
||||
const negativeQueriesData = [
|
||||
createTestQueryData(
|
||||
createTestData(
|
||||
[
|
||||
{
|
||||
'San Francisco': -1,
|
||||
'New York': 2,
|
||||
},
|
||||
],
|
||||
{ intervalMs: 300000000 },
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
const chartProps = createTestChartProps({
|
||||
formData: {
|
||||
...formData,
|
||||
seriesType: EchartsTimeseriesSeriesType.Bar,
|
||||
stack: false,
|
||||
labelPosition: LabelPositionEnum.Inside,
|
||||
},
|
||||
queriesData: negativeQueriesData,
|
||||
});
|
||||
|
||||
const transformedSeries = transformProps(chartProps).echartOptions
|
||||
.series as any[];
|
||||
|
||||
expect(transformedSeries[0].data[0]).toEqual({
|
||||
value: [expect.any(Number), -1],
|
||||
label: {
|
||||
position: 'inside',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('should show only totals when onlyTotal is true', () => {
|
||||
const chartProps = createTestChartProps({
|
||||
formData: { ...formData, onlyTotal: true },
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import { render, screen } from '@testing-library/react';
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import '@testing-library/jest-dom';
|
||||
import { supersetTheme, ThemeProvider } from '@apache-superset/core/theme';
|
||||
import type { ReactElement } from 'react';
|
||||
import Tooltip from './Tooltip';
|
||||
|
||||
const renderWithTheme = (component: ReactElement) =>
|
||||
render(<ThemeProvider theme={supersetTheme}>{component}</ThemeProvider>);
|
||||
|
||||
const tooltip = {
|
||||
x: 10,
|
||||
y: 20,
|
||||
content: <span data-test="tooltip-content">Fremont: 42</span>,
|
||||
};
|
||||
|
||||
test('renders an opaque, themed surface for the default variant', () => {
|
||||
renderWithTheme(<Tooltip tooltip={tooltip} />);
|
||||
|
||||
const container = screen.getByTestId('tooltip-content').parentElement;
|
||||
expect(container).toHaveStyle({
|
||||
background: supersetTheme.colorBgElevated,
|
||||
color: supersetTheme.colorText,
|
||||
});
|
||||
});
|
||||
|
||||
test('renders an opaque, themed surface for the custom (handlebars) variant, see #41154', () => {
|
||||
renderWithTheme(<Tooltip tooltip={tooltip} variant="custom" />);
|
||||
|
||||
// Custom tooltip content comes from a user-supplied handlebars template, so
|
||||
// the container must supply its own background instead of letting the map
|
||||
// show through. See https://github.com/apache/superset/issues/41154
|
||||
const container = screen.getByTestId('tooltip-content').parentElement;
|
||||
expect(container).toHaveStyle({
|
||||
background: supersetTheme.colorBgElevated,
|
||||
color: supersetTheme.colorText,
|
||||
'border-radius': `${supersetTheme.borderRadius}px`,
|
||||
padding: `${supersetTheme.sizeUnit * 2}px`,
|
||||
});
|
||||
});
|
||||
|
||||
test('renders nothing without a tooltip', () => {
|
||||
const { container } = renderWithTheme(<Tooltip tooltip={null} />);
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
@@ -44,23 +44,15 @@ const StyledDiv = styled.div<{
|
||||
left: ${left}px;
|
||||
z-index: 9;
|
||||
pointer-events: none;
|
||||
${
|
||||
variant === 'default'
|
||||
? `
|
||||
padding: ${theme.sizeUnit * 2}px;
|
||||
margin: ${theme.sizeUnit * 2}px;
|
||||
background: ${theme.colorBgElevated};
|
||||
color: ${theme.colorText};
|
||||
max-width: 300px;
|
||||
font-size: ${theme.fontSizeSM}px;
|
||||
border: 1px solid ${theme.colorBorder};
|
||||
border-radius: ${theme.borderRadius}px;
|
||||
box-shadow: ${theme.boxShadowSecondary};
|
||||
`
|
||||
: `
|
||||
margin: ${theme.sizeUnit * 3}px;
|
||||
`
|
||||
}
|
||||
padding: ${theme.sizeUnit * 2}px;
|
||||
margin: ${variant === 'default' ? theme.sizeUnit * 2 : theme.sizeUnit * 3}px;
|
||||
background: ${theme.colorBgElevated};
|
||||
color: ${theme.colorText};
|
||||
max-width: 300px;
|
||||
font-size: ${theme.fontSizeSM}px;
|
||||
border: 1px solid ${theme.colorBorder};
|
||||
border-radius: ${theme.borderRadius}px;
|
||||
box-shadow: ${theme.boxShadowSecondary};
|
||||
`}
|
||||
`;
|
||||
|
||||
|
||||
+1
-12
@@ -22,7 +22,7 @@ import { t } from '@apache-superset/core/translation';
|
||||
import { isDefined, NativeFilterScope } from '@superset-ui/core';
|
||||
import { Alert } from '@apache-superset/core/components';
|
||||
import { css, styled, useTheme } from '@apache-superset/core/theme';
|
||||
import { Select, Tooltip } from '@superset-ui/core/components';
|
||||
import { Select } from '@superset-ui/core/components';
|
||||
import { noOp } from 'src/utils/common';
|
||||
import ScopingTree from 'src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FilterScope/ScopingTree';
|
||||
import {
|
||||
@@ -33,7 +33,6 @@ import {
|
||||
} from 'src/dashboard/types';
|
||||
import { CHART_TYPE } from 'src/dashboard/util/componentTypes';
|
||||
import type { SelectOptionsType } from '@superset-ui/core/components';
|
||||
import { Icons } from '@superset-ui/core/components/Icons';
|
||||
import { NEW_CHART_SCOPING_ID } from './constants';
|
||||
|
||||
interface ScopingTreePanelProps {
|
||||
@@ -109,16 +108,6 @@ const ChartSelect = ({
|
||||
margin-bottom: 0;
|
||||
`}
|
||||
>{`${t('Chart')} *`}</InfoText>
|
||||
<Tooltip title={t('Tooltip')} placement="top">
|
||||
<Icons.InfoCircleOutlined
|
||||
iconSize="xs"
|
||||
css={css`
|
||||
& > span {
|
||||
line-height: 0;
|
||||
}
|
||||
`}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Select
|
||||
data-test="select-chart"
|
||||
|
||||
+32
@@ -207,6 +207,38 @@ describe('AdhocFilter', () => {
|
||||
expect(adhocFilter10.isValid()).toBe(true);
|
||||
});
|
||||
|
||||
test('is invalid when a comparator-taking operator has no comparator', () => {
|
||||
// A comparator that was never set, or that was cleared through the value
|
||||
// Select's clear affordance, is `undefined` rather than `null` or `[]`.
|
||||
const adhocFilter1 = new AdhocFilter({
|
||||
expressionType: ExpressionTypes.Simple,
|
||||
subject: 'is_intro',
|
||||
operator: 'IN',
|
||||
comparator: undefined,
|
||||
clause: Clauses.Where,
|
||||
});
|
||||
expect(adhocFilter1.isValid()).toBe(false);
|
||||
|
||||
const adhocFilter2 = new AdhocFilter({
|
||||
expressionType: ExpressionTypes.Simple,
|
||||
subject: 'is_intro',
|
||||
operator: '==',
|
||||
comparator: undefined,
|
||||
clause: Clauses.Where,
|
||||
});
|
||||
expect(adhocFilter2.isValid()).toBe(false);
|
||||
|
||||
// `false` is a legitimate boolean comparator, not a missing value
|
||||
const adhocFilter3 = new AdhocFilter({
|
||||
expressionType: ExpressionTypes.Simple,
|
||||
subject: 'is_intro',
|
||||
operator: '==',
|
||||
comparator: false,
|
||||
clause: Clauses.Where,
|
||||
});
|
||||
expect(adhocFilter3.isValid()).toBe(true);
|
||||
});
|
||||
|
||||
test('can translate from simple expressions to sql expressions', () => {
|
||||
const adhocFilter1 = new AdhocFilter({
|
||||
expressionType: ExpressionTypes.Simple,
|
||||
|
||||
+4
-2
@@ -182,8 +182,10 @@ export default class AdhocFilter {
|
||||
// A non-empty array of values ('IN' or 'NOT IN' clauses)
|
||||
return this.comparator.length > 0;
|
||||
}
|
||||
// A value has been selected or typed
|
||||
return this.comparator !== null;
|
||||
// A value has been selected or typed. An unset comparator is
|
||||
// `undefined` rather than `null`: picking a new subject resets it, and
|
||||
// the value Select's clear affordance emits `undefined` too.
|
||||
return this.comparator != null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+23
@@ -181,6 +181,29 @@ describe('AdhocFilterEditPopover', () => {
|
||||
expect(saveButton).toBeDisabled();
|
||||
});
|
||||
|
||||
test('disables save button when a boolean column has no value selected', async () => {
|
||||
const booleanColumn = { type: 'BOOL', column_name: 'is_intro' };
|
||||
renderPopover({
|
||||
adhocFilter: new AdhocFilter({
|
||||
expressionType: ExpressionTypes.Simple,
|
||||
clause: Clauses.Where,
|
||||
}),
|
||||
options: [booleanColumn],
|
||||
datasource: { columns: [booleanColumn], filter_select: false },
|
||||
});
|
||||
|
||||
// Picking the subject resets the comparator to `undefined`; the value
|
||||
// control is then left untouched, mirroring the reported repro.
|
||||
await userEvent.click(screen.getByTestId('select-element'));
|
||||
await userEvent.click(
|
||||
await screen.findByRole('option', { name: /is_intro/ }),
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.getByTestId('adhoc-filter-edit-popover-save-button'),
|
||||
).toBeDisabled();
|
||||
});
|
||||
|
||||
test('initiates resize when resize handle is dragged', async () => {
|
||||
const onResize = jest.fn();
|
||||
renderPopover({ onResize });
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* 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 type { WorkBook } from 'xlsx';
|
||||
import { getNumberFormatterRegistry } from '@superset-ui/core';
|
||||
import exportPivotExcel from './downloadAsPivotExcel';
|
||||
|
||||
const mockWriteFile = jest.fn();
|
||||
|
||||
jest.mock('xlsx', () => {
|
||||
const actual = jest.requireActual('xlsx');
|
||||
return {
|
||||
...actual,
|
||||
writeFile: (...args: unknown[]) => mockWriteFile(...args),
|
||||
};
|
||||
});
|
||||
|
||||
// Renders a single-row pivot table with the given cell values, runs the
|
||||
// export, and returns the resulting sheet so each test only has to state
|
||||
// its input cells and assertions.
|
||||
function exportRowAndGetSheet(cells: string[]): WorkBook['Sheets'][string] {
|
||||
document.body.innerHTML = `
|
||||
<table id="pivot-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
${cells.map(cell => `<td>${cell}</td>`).join('\n ')}
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
`;
|
||||
|
||||
exportPivotExcel('#pivot-table', 'export');
|
||||
|
||||
const workbook = mockWriteFile.mock.calls.at(-1)?.[0] as WorkBook;
|
||||
return workbook.Sheets[workbook.SheetNames[0]];
|
||||
}
|
||||
|
||||
test('preserves locale-formatted numbers exactly as rendered, without SheetJS reinterpreting them', () => {
|
||||
const sheet = exportRowAndGetSheet(['1.234,56', '12,50%', '3.500']);
|
||||
|
||||
expect(mockWriteFile).toHaveBeenCalledTimes(1);
|
||||
|
||||
// These are Spanish-locale D3_FORMAT strings ("." as thousands separator,
|
||||
// "," as decimal separator). Each must survive the export untouched, as a
|
||||
// text cell, rather than being silently reparsed as a different number
|
||||
// (SheetJS's default HTML table parsing would otherwise turn "1.234,56"
|
||||
// into the number 1.23456, "3.500" into 3.5, and "12,50%" into 12.5).
|
||||
expect(sheet.A1).toMatchObject({ t: 's', v: '1.234,56' });
|
||||
expect(sheet.B1).toMatchObject({ t: 's', v: '12,50%' });
|
||||
expect(sheet.C1).toMatchObject({ t: 's', v: '3.500' });
|
||||
});
|
||||
|
||||
test('restores unambiguous plain numbers to native Excel numeric cells', () => {
|
||||
const sheet = exportRowAndGetSheet(['42', '-3.5', '3.500', '1,234']);
|
||||
|
||||
// "42" and "-3.5" round-trip exactly through Number(), so they're
|
||||
// unambiguous under any locale and are restored to real numbers.
|
||||
expect(sheet.A1).toMatchObject({ t: 'n', v: 42 });
|
||||
expect(sheet.B1).toMatchObject({ t: 'n', v: -3.5 });
|
||||
// "3.500" (trailing zero padding) and "1,234" (grouped thousands) don't
|
||||
// round-trip, so they stay as text rather than risk misparsing them.
|
||||
expect(sheet.C1).toMatchObject({ t: 's', v: '3.500' });
|
||||
expect(sheet.D1).toMatchObject({ t: 's', v: '1,234' });
|
||||
});
|
||||
|
||||
test('does not restore grouped-thousands numbers under a "." thousands-separator locale', () => {
|
||||
const registry = getNumberFormatterRegistry();
|
||||
const original = registry.d3Format;
|
||||
registry.setD3Format({ decimal: ',', thousands: '.', grouping: [3] });
|
||||
|
||||
try {
|
||||
// Under a Spanish-style D3_FORMAT, "1.234" is the plain integer 1234
|
||||
// rendered with a "." group separator, not the decimal 1.234. It also
|
||||
// round-trips cleanly through Number(), so without the locale check it
|
||||
// would be misrestored to the number 1.234, silently corrupting the
|
||||
// value this PR exists to preserve. "42" has no "." and still round
|
||||
// trips safely, so it's still restored.
|
||||
const sheet = exportRowAndGetSheet(['1.234', '42']);
|
||||
expect(sheet.A1).toMatchObject({ t: 's', v: '1.234' });
|
||||
expect(sheet.B1).toMatchObject({ t: 'n', v: 42 });
|
||||
} finally {
|
||||
registry.setD3Format(original);
|
||||
}
|
||||
});
|
||||
|
||||
test('leaves date-shaped strings as text rather than reinterpreting them as dates', () => {
|
||||
const sheet = exportRowAndGetSheet([
|
||||
'2024-01-01',
|
||||
'2024-01-01 13:45:30',
|
||||
'not-a-date',
|
||||
]);
|
||||
|
||||
// A rendered string can't be reliably classified as a genuine date rather
|
||||
// than a coincidentally date-shaped formatted number (e.g. a custom
|
||||
// D3_FORMAT grouping/thousands locale can render a plain metric like
|
||||
// 20240101 as "2024-01-01"), so date-shaped cells are left exactly as
|
||||
// rendered instead of being reinterpreted as native Excel dates.
|
||||
expect(sheet.A1).toMatchObject({ t: 's', v: '2024-01-01' });
|
||||
expect(sheet.B1).toMatchObject({ t: 's', v: '2024-01-01 13:45:30' });
|
||||
expect(sheet.C1).toMatchObject({ t: 's', v: 'not-a-date' });
|
||||
});
|
||||
@@ -16,13 +16,63 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { getNumberFormatterRegistry } from '@superset-ui/core';
|
||||
import { utils, writeFile } from 'xlsx';
|
||||
import type { WorkSheet } from 'xlsx';
|
||||
|
||||
// `raw: true` (used below) keeps every table cell as text, so ordinary
|
||||
// numbers lose their native Excel type along with the locale-formatted
|
||||
// values. A cell's text is only restored to a real number when it is
|
||||
// unambiguous under the active D3_FORMAT locale: a plain number that
|
||||
// round-trips losslessly through Number() (e.g. "42" or "-3.5"). Restoring
|
||||
// those can't reintroduce the misparsing raw: true guards against. Anything
|
||||
// else (grouped thousands, percent suffixes, trailing zero padding,
|
||||
// date-shaped text, other D3_FORMAT output, etc.) stays as text, exactly as
|
||||
// rendered: a rendered string can't be reliably classified as a genuine date
|
||||
// rather than a coincidentally date-shaped formatted number (e.g. a custom
|
||||
// D3_FORMAT grouping/thousands locale can render a plain metric like
|
||||
// 20240101 as "2024-01-01"), so cells are never reinterpreted as dates.
|
||||
//
|
||||
// Number()'s round-trip check assumes "." is a decimal point, which isn't
|
||||
// true under every locale: a Spanish D3_FORMAT (thousands: '.') renders the
|
||||
// plain integer 1234 as "1.234", which also round-trips through Number() as
|
||||
// the decimal 1.234. When the active locale uses "." as its thousands
|
||||
// separator, a cell containing "." can't be trusted as an unambiguous
|
||||
// decimal, so it's left as text instead.
|
||||
function restoreUnambiguousNumbers(sheet: WorkSheet): void {
|
||||
const { thousands } = getNumberFormatterRegistry().d3Format;
|
||||
Object.keys(sheet).forEach(cellRef => {
|
||||
if (cellRef.startsWith('!')) {
|
||||
return;
|
||||
}
|
||||
const cell = sheet[cellRef];
|
||||
if (!cell || cell.t !== 's' || typeof cell.v !== 'string') {
|
||||
return;
|
||||
}
|
||||
if (thousands === '.' && cell.v.includes('.')) {
|
||||
return;
|
||||
}
|
||||
const value = Number(cell.v);
|
||||
if (cell.v !== '' && Number.isFinite(value) && String(value) === cell.v) {
|
||||
cell.t = 'n';
|
||||
cell.v = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default function exportPivotExcel(
|
||||
tableSelector: string,
|
||||
fileName: string,
|
||||
) {
|
||||
const table = document.querySelector(tableSelector);
|
||||
const workbook = utils.table_to_book(table);
|
||||
// `raw: true` keeps every cell as the literal text rendered in the DOM.
|
||||
// Without it, SheetJS tries to infer numbers/dates from the displayed
|
||||
// string, which mangles values that were formatted using a non-US
|
||||
// D3_FORMAT (e.g. "1.234,56" gets misread as a date or truncated number).
|
||||
const workbook = utils.table_to_book(table, { raw: true });
|
||||
const sheet = workbook.Sheets[workbook.SheetNames[0]];
|
||||
if (sheet) {
|
||||
restoreUnambiguousNumbers(sheet);
|
||||
}
|
||||
writeFile(workbook, `${fileName}.xlsx`);
|
||||
}
|
||||
|
||||
+3
-11
@@ -21,7 +21,7 @@ from io import BytesIO
|
||||
from typing import Any, cast, Optional
|
||||
from zipfile import is_zipfile, ZipFile
|
||||
|
||||
from flask import current_app, redirect, request, Response, send_file, url_for
|
||||
from flask import current_app, redirect, request, Response, url_for
|
||||
from flask_appbuilder.api import expose, protect, rison as parse_rison, safe
|
||||
from flask_appbuilder.hooks import before_request
|
||||
from flask_appbuilder.models.sqla.interface import SQLAInterface
|
||||
@@ -103,7 +103,7 @@ from superset.subjects.filters import (
|
||||
from superset.tasks.thumbnails import cache_chart_thumbnail
|
||||
from superset.tasks.utils import get_current_user
|
||||
from superset.utils import json
|
||||
from superset.utils.core import sanitize_cookie_token
|
||||
from superset.utils.core import send_export_zip
|
||||
from superset.utils.screenshots import (
|
||||
ChartScreenshot,
|
||||
DEFAULT_CHART_WINDOW_SIZE,
|
||||
@@ -1293,15 +1293,7 @@ class ChartRestApi(SoftDeleteApiMixin, BaseSupersetModelRestApi):
|
||||
return self.response_404()
|
||||
buf.seek(0)
|
||||
|
||||
response = send_file(
|
||||
buf,
|
||||
mimetype="application/zip",
|
||||
as_attachment=True,
|
||||
download_name=filename,
|
||||
)
|
||||
if token := sanitize_cookie_token(request.args.get("token")):
|
||||
response.set_cookie(token, "done", max_age=600)
|
||||
return response
|
||||
return send_export_zip(buf, filename)
|
||||
|
||||
@expose("/favorite_status/", methods=("GET",))
|
||||
@protect()
|
||||
|
||||
@@ -18,11 +18,13 @@ import logging
|
||||
from functools import partial
|
||||
from typing import Any, Optional
|
||||
|
||||
from jinja2.exceptions import TemplateError
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
|
||||
from superset import db
|
||||
from superset.commands.explore.permalink.base import BaseExplorePermalinkCommand
|
||||
from superset.daos.key_value import KeyValueDAO
|
||||
from superset.exceptions import SupersetTemplateException
|
||||
from superset.explore.permalink.exceptions import ExplorePermalinkCreateFailedError
|
||||
from superset.explore.utils import check_access as check_chart_access
|
||||
from superset.key_value.exceptions import (
|
||||
@@ -58,7 +60,10 @@ class CreateExplorePermalinkCommand(BaseExplorePermalinkCommand):
|
||||
d_id, d_type = self.datasource.split("__")
|
||||
datasource_id = int(d_id)
|
||||
datasource_type = DatasourceType(d_type)
|
||||
check_chart_access(datasource_id, self.chart_id, datasource_type)
|
||||
try:
|
||||
check_chart_access(datasource_id, self.chart_id, datasource_type)
|
||||
except TemplateError as ex:
|
||||
raise SupersetTemplateException(str(ex)) from ex
|
||||
value = {
|
||||
"chartId": self.chart_id,
|
||||
"datasourceId": datasource_id,
|
||||
|
||||
@@ -17,11 +17,13 @@
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from jinja2.exceptions import TemplateError
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
|
||||
from superset.commands.dataset.exceptions import DatasetNotFoundError
|
||||
from superset.commands.explore.permalink.base import BaseExplorePermalinkCommand
|
||||
from superset.daos.key_value import KeyValueDAO
|
||||
from superset.exceptions import SupersetTemplateException
|
||||
from superset.explore.permalink.exceptions import ExplorePermalinkGetFailedError
|
||||
from superset.explore.permalink.types import ExplorePermalinkValue
|
||||
from superset.explore.utils import check_access as check_chart_access
|
||||
@@ -54,7 +56,10 @@ class GetExplorePermalinkCommand(BaseExplorePermalinkCommand):
|
||||
datasource_type = DatasourceType(
|
||||
value.get("datasourceType", DatasourceType.TABLE)
|
||||
)
|
||||
check_chart_access(datasource_id, chart_id, datasource_type)
|
||||
try:
|
||||
check_chart_access(datasource_id, chart_id, datasource_type)
|
||||
except TemplateError as ex:
|
||||
raise SupersetTemplateException(str(ex)) from ex
|
||||
return value
|
||||
return None
|
||||
except (
|
||||
|
||||
@@ -18,13 +18,15 @@ import logging
|
||||
from functools import partial
|
||||
from typing import Any
|
||||
|
||||
from jinja2.exceptions import TemplateError
|
||||
|
||||
from superset import security_manager
|
||||
from superset.commands.base import BaseCommand, CreateMixin
|
||||
from superset.commands.tag.exceptions import TagCreateFailedError, TagInvalidError
|
||||
from superset.commands.tag.utils import to_object_model, to_object_type
|
||||
from superset.commands.utils import current_user_can_modify_object
|
||||
from superset.daos.tag import TagDAO
|
||||
from superset.exceptions import SupersetSecurityException
|
||||
from superset.exceptions import SupersetParseError, SupersetSecurityException
|
||||
from superset.tags.models import ObjectType, TagType
|
||||
from superset.utils.decorators import on_error, transaction
|
||||
|
||||
@@ -95,8 +97,30 @@ class CreateCustomTagCommand(CreateMixin, BaseCommand):
|
||||
)
|
||||
)
|
||||
except SupersetSecurityException:
|
||||
# A routine, expected authorization denial; swallowed silently by
|
||||
# design (no logging) and surfaced to the caller as a validation
|
||||
# failure rather than an unhandled 500.
|
||||
exceptions.append(
|
||||
TagCreateFailedError(f"Access denied for {object_type} {object_id}")
|
||||
TagCreateFailedError(
|
||||
f"Could not validate access for {object_type} {object_id}"
|
||||
)
|
||||
)
|
||||
except (TemplateError, SupersetParseError) as ex:
|
||||
# Authorizing a saved query parses its Jinja-templated SQL to resolve
|
||||
# table references. Malformed Jinja (TemplateError) or an
|
||||
# unresolvable partition macro (SupersetParseError) is a validation
|
||||
# failure, not an unhandled 500 -- but unlike an access denial it is
|
||||
# genuinely unexpected, so log it for server-side visibility and
|
||||
# preserve the underlying error text instead of discarding it.
|
||||
logger.warning(
|
||||
"Could not parse query %s while validating tag access: %s",
|
||||
object_id,
|
||||
str(ex),
|
||||
)
|
||||
exceptions.append(
|
||||
TagCreateFailedError(
|
||||
f"Could not validate access for {object_type} {object_id}: {ex}"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
||||
+16
-1
@@ -38,7 +38,7 @@ from flask_appbuilder.models.filters import BaseFilter
|
||||
from flask_appbuilder.models.sqla.interface import SQLAInterface
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy import asc, cast, desc, false, or_, Text
|
||||
from sqlalchemy.exc import SQLAlchemyError, StatementError
|
||||
from sqlalchemy.exc import OperationalError, SQLAlchemyError, StatementError
|
||||
from sqlalchemy.ext.hybrid import hybrid_property
|
||||
from sqlalchemy.inspection import inspect
|
||||
from sqlalchemy.orm import ColumnProperty, joinedload, Query, RelationshipProperty
|
||||
@@ -255,6 +255,11 @@ class BaseDAO(CoreBaseDAO[T], Generic[T]):
|
||||
filter = uuid_column == model_id_or_uuid
|
||||
try:
|
||||
return query.filter(filter).one_or_none()
|
||||
except OperationalError:
|
||||
# A transient connection-level failure (e.g. the server dropping the
|
||||
# connection mid-query) surfaces as OperationalError, a StatementError
|
||||
# subclass. Let it propagate instead of masking it as a "not found".
|
||||
raise
|
||||
except StatementError:
|
||||
# can happen if neither uuid nor int is passed
|
||||
return None
|
||||
@@ -341,6 +346,11 @@ class BaseDAO(CoreBaseDAO[T], Generic[T]):
|
||||
|
||||
try:
|
||||
return query.filter(column == converted_value).one_or_none()
|
||||
except OperationalError:
|
||||
# A transient connection-level failure (e.g. the server dropping the
|
||||
# connection mid-query) surfaces as OperationalError, a StatementError
|
||||
# subclass. Let it propagate instead of masking it as a "not found".
|
||||
raise
|
||||
except StatementError:
|
||||
# can happen if int is passed instead of a string or similar
|
||||
return None
|
||||
@@ -433,6 +443,11 @@ class BaseDAO(CoreBaseDAO[T], Generic[T]):
|
||||
|
||||
try:
|
||||
results = query.all()
|
||||
except OperationalError:
|
||||
# A transient connection-level failure (e.g. the server dropping the
|
||||
# connection mid-query) surfaces as OperationalError. Let it propagate
|
||||
# as a 5xx instead of masking it as a 400 "record doesn't exist".
|
||||
raise
|
||||
except SQLAlchemyError as ex:
|
||||
model_name = cls.model_cls.__name__ if cls.model_cls else "Unknown"
|
||||
raise DAOFindFailedError(
|
||||
|
||||
@@ -24,7 +24,7 @@ from typing import Any, Callable, cast
|
||||
from zipfile import is_zipfile, ZipFile
|
||||
|
||||
import rison
|
||||
from flask import current_app, g, redirect, request, Response, send_file, url_for
|
||||
from flask import current_app, g, redirect, request, Response, url_for
|
||||
from flask_appbuilder import permission_name
|
||||
from flask_appbuilder.api import (
|
||||
expose,
|
||||
@@ -162,7 +162,7 @@ from superset.tasks.thumbnails import (
|
||||
)
|
||||
from superset.tasks.utils import get_current_user
|
||||
from superset.utils import json
|
||||
from superset.utils.core import parse_boolean_string, sanitize_cookie_token
|
||||
from superset.utils.core import parse_boolean_string, send_export_zip
|
||||
from superset.utils.file import get_filename
|
||||
from superset.utils.pdf import build_pdf_from_screenshots
|
||||
from superset.utils.screenshots import (
|
||||
@@ -1629,15 +1629,7 @@ class DashboardRestApi(
|
||||
return self.response_404()
|
||||
buf.seek(0)
|
||||
|
||||
response = send_file(
|
||||
buf,
|
||||
mimetype="application/zip",
|
||||
as_attachment=True,
|
||||
download_name=filename,
|
||||
)
|
||||
if token := sanitize_cookie_token(request.args.get("token")):
|
||||
response.set_cookie(token, "done", max_age=600)
|
||||
return response
|
||||
return send_export_zip(buf, filename)
|
||||
|
||||
@expose("/<pk>/export_as_example/", methods=("GET",))
|
||||
@protect()
|
||||
@@ -1720,15 +1712,7 @@ class DashboardRestApi(
|
||||
|
||||
filename = f"{safe_name}_example.zip"
|
||||
|
||||
response = send_file(
|
||||
buf,
|
||||
mimetype="application/zip",
|
||||
as_attachment=True,
|
||||
download_name=filename,
|
||||
)
|
||||
if token := sanitize_cookie_token(request.args.get("token")):
|
||||
response.set_cookie(token, "done", max_age=600)
|
||||
return response
|
||||
return send_export_zip(buf, filename)
|
||||
|
||||
@expose("/<pk>/export_xlsx/", methods=("POST",))
|
||||
@protect()
|
||||
|
||||
@@ -31,7 +31,6 @@ from flask import (
|
||||
render_template,
|
||||
request,
|
||||
Response,
|
||||
send_file,
|
||||
)
|
||||
from flask_appbuilder.api import expose, protect, rison as parse_rison, safe
|
||||
from flask_appbuilder.models.sqla.interface import SQLAInterface
|
||||
@@ -131,7 +130,7 @@ from superset.utils.core import (
|
||||
error_msg_from_exception,
|
||||
get_username,
|
||||
parse_js_uri_path_item,
|
||||
sanitize_cookie_token,
|
||||
send_export_zip,
|
||||
)
|
||||
from superset.utils.decorators import transaction
|
||||
from superset.utils.oauth2 import decode_oauth2_state
|
||||
@@ -1572,15 +1571,7 @@ class DatabaseRestApi(BaseSupersetModelRestApi):
|
||||
return self.response_404()
|
||||
buf.seek(0)
|
||||
|
||||
response = send_file(
|
||||
buf,
|
||||
mimetype="application/zip",
|
||||
as_attachment=True,
|
||||
download_name=filename,
|
||||
)
|
||||
if token := sanitize_cookie_token(request.args.get("token")):
|
||||
response.set_cookie(token, "done", max_age=600)
|
||||
return response
|
||||
return send_export_zip(buf, filename)
|
||||
|
||||
@expose("/import/", methods=("POST",))
|
||||
@protect()
|
||||
|
||||
@@ -23,7 +23,7 @@ from io import BytesIO
|
||||
from typing import Any, Callable
|
||||
from zipfile import is_zipfile, ZipFile
|
||||
|
||||
from flask import request, Response, send_file
|
||||
from flask import request, Response
|
||||
from flask_appbuilder import permission_name
|
||||
from flask_appbuilder.api import expose, protect, rison as parse_rison, safe
|
||||
from flask_appbuilder.api.schemas import get_item_schema
|
||||
@@ -93,7 +93,7 @@ from superset.exceptions import (
|
||||
from superset.jinja_context import BaseTemplateProcessor, get_template_processor
|
||||
from superset.subjects.filters import FilterRelatedSubjects, subject_type_filter
|
||||
from superset.utils import json
|
||||
from superset.utils.core import parse_boolean_string, sanitize_cookie_token
|
||||
from superset.utils.core import parse_boolean_string, send_export_zip
|
||||
from superset.versioning.api_helpers import (
|
||||
current_entity_etag_uuid,
|
||||
current_entity_version_info,
|
||||
@@ -811,15 +811,7 @@ class DatasetRestApi(SoftDeleteApiMixin, BaseSupersetModelRestApi):
|
||||
return self.response_404()
|
||||
buf.seek(0)
|
||||
|
||||
response = send_file(
|
||||
buf,
|
||||
mimetype="application/zip",
|
||||
as_attachment=True,
|
||||
download_name=filename,
|
||||
)
|
||||
if token := sanitize_cookie_token(request.args.get("token")):
|
||||
response.set_cookie(token, "done", max_age=600)
|
||||
return response
|
||||
return send_export_zip(buf, filename)
|
||||
|
||||
@expose("/duplicate", methods=("POST",))
|
||||
@protect()
|
||||
|
||||
@@ -41,6 +41,7 @@ import sqlalchemy.dialects
|
||||
from flask import current_app as app
|
||||
from sqlalchemy.engine.default import DefaultDialect
|
||||
from sqlalchemy.exc import NoSuchModuleError
|
||||
from sqlalchemy.sql import compiler as sqla_compiler
|
||||
|
||||
from superset import feature_flag_manager
|
||||
from superset.db_engine_specs.base import BaseEngineSpec
|
||||
@@ -159,6 +160,16 @@ def get_available_engine_specs() -> dict[type[BaseEngineSpec], set[str]]: # noq
|
||||
continue
|
||||
|
||||
# installed 3rd-party dialects
|
||||
#
|
||||
# `ep.load()` runs arbitrary module-level code in the third-party package.
|
||||
# Some dialects (e.g. sqlalchemy-monetdb) mutate SQLAlchemy's shared,
|
||||
# process-global `compiler.OPERATORS` mapping in place on import instead
|
||||
# of subclassing it, which would otherwise silently change SQL rendering
|
||||
# (e.g. `!=` -> `<>`) for every dialect for the rest of the process, not
|
||||
# just the misbehaving one. Snapshot/restore around each load so a
|
||||
# buggy connector can't leak global compiler state into unrelated
|
||||
# dialects just because it was enumerated here.
|
||||
operators_snapshot = dict(sqla_compiler.OPERATORS)
|
||||
for ep in entry_points(group="sqlalchemy.dialects"):
|
||||
try:
|
||||
dialect = ep.load()
|
||||
@@ -193,6 +204,10 @@ def get_available_engine_specs() -> dict[type[BaseEngineSpec], set[str]]: # noq
|
||||
if isinstance(driver, bytes):
|
||||
driver = driver.decode()
|
||||
drivers[backend].add(driver)
|
||||
finally:
|
||||
if sqla_compiler.OPERATORS != operators_snapshot:
|
||||
sqla_compiler.OPERATORS.clear()
|
||||
sqla_compiler.OPERATORS.update(operators_snapshot)
|
||||
|
||||
dbs_denylist = app.config["DBS_AVAILABLE_DENYLIST"]
|
||||
if not feature_flag_manager.is_feature_enabled("ENABLE_SUPERSET_META_DB"):
|
||||
|
||||
@@ -746,18 +746,18 @@ class BaseEngineSpec: # pylint: disable=too-many-public-methods
|
||||
@classmethod
|
||||
def encrypted_extra_sensitive_field_paths(cls) -> set[str]:
|
||||
"""
|
||||
Returns a set of paths for fields that should be masked in the
|
||||
``masked_encrypted_extra`` JSON.
|
||||
Returns a set of JSONPath expressions for fields that should be masked
|
||||
in the ``masked_encrypted_extra`` JSON.
|
||||
|
||||
:param cls: Description
|
||||
:return: Description
|
||||
:rtype: set[str]
|
||||
The OAuth2 client secret is always included, since
|
||||
``Database.get_oauth2_config`` reads ``oauth2_client_info`` from the
|
||||
``encrypted_extra`` of any database regardless of its engine, so engine
|
||||
specs that override ``encrypted_extra_sensitive_fields`` cannot
|
||||
accidentally expose it.
|
||||
"""
|
||||
return (
|
||||
set(cls.encrypted_extra_sensitive_fields)
|
||||
if isinstance(cls.encrypted_extra_sensitive_fields, dict)
|
||||
else cls.encrypted_extra_sensitive_fields
|
||||
)
|
||||
return set(cls.encrypted_extra_sensitive_fields) | {
|
||||
"$.oauth2_client_info.secret"
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_rls_method(cls) -> RLSMethod:
|
||||
@@ -2870,7 +2870,7 @@ class BaseEngineSpec: # pylint: disable=too-many-public-methods
|
||||
corresponding entry is updated, otherwise the old value is used (see
|
||||
`unmask_encrypted_extra` below).
|
||||
"""
|
||||
if encrypted_extra is None or not cls.encrypted_extra_sensitive_fields:
|
||||
if encrypted_extra is None:
|
||||
return encrypted_extra
|
||||
|
||||
try:
|
||||
|
||||
@@ -44,7 +44,10 @@ class CockroachDbEngineSpec(PostgresEngineSpec):
|
||||
DatabaseCategory.TRADITIONAL_RDBMS,
|
||||
DatabaseCategory.OPEN_SOURCE,
|
||||
],
|
||||
"pypi_packages": ["cockroachdb"],
|
||||
# sqlalchemy-cockroachdb declares no DBAPI dependency of its own (see
|
||||
# the comment on the `cockroachdb` extra in pyproject.toml), so a
|
||||
# plain `cockroachdb://` URL also needs psycopg2 installed to connect.
|
||||
"pypi_packages": ["sqlalchemy-cockroachdb", "psycopg2-binary"],
|
||||
"connection_string": "cockroachdb://root@{hostname}:{port}/{database}?sslmode=disable",
|
||||
"default_port": 26257,
|
||||
"docs_url": "https://github.com/cockroachdb/sqlalchemy-cockroachdb",
|
||||
|
||||
@@ -176,7 +176,7 @@ def create_slices(tbl: SqlaTable) -> tuple[list[Slice], list[Slice]]:
|
||||
"compare_lag": "10",
|
||||
"compare_suffix": "o10Y",
|
||||
"limit": "25",
|
||||
"granularity_sqla": "ds",
|
||||
"granularity": "ds",
|
||||
"groupby": [],
|
||||
"row_limit": current_app.config["ROW_LIMIT"],
|
||||
"time_range": "100 years ago : now",
|
||||
@@ -213,7 +213,7 @@ def create_slices(tbl: SqlaTable) -> tuple[list[Slice], list[Slice]]:
|
||||
params=get_slice_json(
|
||||
defaults,
|
||||
viz_type="big_number",
|
||||
granularity_sqla="ds",
|
||||
granularity="ds",
|
||||
compare_lag="5",
|
||||
compare_suffix="over 5Y",
|
||||
metric=metric,
|
||||
@@ -237,7 +237,7 @@ def create_slices(tbl: SqlaTable) -> tuple[list[Slice], list[Slice]]:
|
||||
defaults,
|
||||
viz_type="echarts_timeseries_line",
|
||||
groupby=["name"],
|
||||
granularity_sqla="ds",
|
||||
granularity="ds",
|
||||
rich_tooltip=True,
|
||||
show_legend=True,
|
||||
metrics=metrics,
|
||||
@@ -420,7 +420,7 @@ def create_slices(tbl: SqlaTable) -> tuple[list[Slice], list[Slice]]:
|
||||
}
|
||||
],
|
||||
metrics_b=["sum__num"],
|
||||
granularity_sqla="ds",
|
||||
granularity="ds",
|
||||
yAxisIndex=0,
|
||||
yAxisIndexB=1,
|
||||
),
|
||||
@@ -474,7 +474,7 @@ def create_slices(tbl: SqlaTable) -> tuple[list[Slice], list[Slice]]:
|
||||
"label": "SUM(num_california)",
|
||||
},
|
||||
viz_type="big_number_total",
|
||||
granularity_sqla="ds",
|
||||
granularity="ds",
|
||||
),
|
||||
editors=[],
|
||||
),
|
||||
@@ -496,7 +496,7 @@ def create_slices(tbl: SqlaTable) -> tuple[list[Slice], list[Slice]]:
|
||||
}
|
||||
],
|
||||
viz_type="echarts_timeseries_line",
|
||||
granularity_sqla="ds",
|
||||
granularity="ds",
|
||||
groupby=["name"],
|
||||
series_limit_metric={
|
||||
"expressionType": "SIMPLE",
|
||||
@@ -542,7 +542,7 @@ def create_slices(tbl: SqlaTable) -> tuple[list[Slice], list[Slice]]:
|
||||
defaults,
|
||||
metric=metric,
|
||||
viz_type="big_number_total",
|
||||
granularity_sqla="ds",
|
||||
granularity="ds",
|
||||
adhoc_filters=[gen_filter("gender", "girl")],
|
||||
subheader="total female participants",
|
||||
),
|
||||
|
||||
@@ -28,7 +28,7 @@ params:
|
||||
subject: gender
|
||||
compare_lag: '10'
|
||||
compare_suffix: o10Y
|
||||
granularity_sqla: ds
|
||||
granularity: ds
|
||||
groupby: []
|
||||
limit: '100'
|
||||
markup_type: markdown
|
||||
|
||||
@@ -28,7 +28,7 @@ params:
|
||||
subject: gender
|
||||
compare_lag: '10'
|
||||
compare_suffix: o10Y
|
||||
granularity_sqla: ds
|
||||
granularity: ds
|
||||
groupby:
|
||||
- name
|
||||
limit: '25'
|
||||
|
||||
@@ -22,7 +22,7 @@ description: null
|
||||
params:
|
||||
compare_lag: '10'
|
||||
compare_suffix: o10Y
|
||||
granularity_sqla: ds
|
||||
granularity: ds
|
||||
groupby:
|
||||
- gender
|
||||
limit: '25'
|
||||
|
||||
@@ -30,7 +30,7 @@ params:
|
||||
subject: state
|
||||
compare_lag: '10'
|
||||
compare_suffix: o10Y
|
||||
granularity_sqla: ds
|
||||
granularity: ds
|
||||
groupby:
|
||||
- state
|
||||
limit: '25'
|
||||
|
||||
@@ -28,7 +28,7 @@ params:
|
||||
subject: gender
|
||||
compare_lag: '10'
|
||||
compare_suffix: o10Y
|
||||
granularity_sqla: ds
|
||||
granularity: ds
|
||||
groupby: []
|
||||
limit: '100'
|
||||
markup_type: markdown
|
||||
|
||||
@@ -28,7 +28,7 @@ params:
|
||||
subject: gender
|
||||
compare_lag: '10'
|
||||
compare_suffix: o10Y
|
||||
granularity_sqla: ds
|
||||
granularity: ds
|
||||
groupby:
|
||||
- name
|
||||
limit: '25'
|
||||
|
||||
@@ -22,7 +22,7 @@ description: null
|
||||
params:
|
||||
compare_lag: '5'
|
||||
compare_suffix: over 5Y
|
||||
granularity_sqla: ds
|
||||
granularity: ds
|
||||
groupby: []
|
||||
limit: '25'
|
||||
markup_type: markdown
|
||||
|
||||
@@ -22,7 +22,7 @@ description: null
|
||||
params:
|
||||
compare_lag: '10'
|
||||
compare_suffix: o10Y
|
||||
granularity_sqla: ds
|
||||
granularity: ds
|
||||
groupby: []
|
||||
groupbyColumns:
|
||||
- state
|
||||
|
||||
@@ -29,7 +29,7 @@ params:
|
||||
compare_lag: '10'
|
||||
compare_suffix: o10Y
|
||||
comparison_type: values
|
||||
granularity_sqla: ds
|
||||
granularity: ds
|
||||
groupby:
|
||||
- name
|
||||
limit: 10
|
||||
|
||||
@@ -29,7 +29,7 @@ params:
|
||||
compare_lag: '10'
|
||||
compare_suffix: o10Y
|
||||
comparison_type: values
|
||||
granularity_sqla: ds
|
||||
granularity: ds
|
||||
groupby:
|
||||
- name
|
||||
limit: 10
|
||||
|
||||
@@ -22,7 +22,7 @@ description: null
|
||||
params:
|
||||
compare_lag: '10'
|
||||
compare_suffix: o10Y
|
||||
granularity_sqla: ds
|
||||
granularity: ds
|
||||
groupby:
|
||||
- name
|
||||
limit: '25'
|
||||
|
||||
@@ -154,7 +154,7 @@ def create_slices(tbl: BaseDatasource) -> list[Slice]:
|
||||
"compare_lag": "10",
|
||||
"compare_suffix": "o10Y",
|
||||
"limit": "25",
|
||||
"granularity_sqla": "year",
|
||||
"granularity": "year",
|
||||
"groupby": [],
|
||||
"row_limit": current_app.config["ROW_LIMIT"],
|
||||
"since": "2014-01-01",
|
||||
|
||||
@@ -24,7 +24,7 @@ params:
|
||||
compare_suffix: o10Y
|
||||
country_fieldtype: cca3
|
||||
entity: country_code
|
||||
granularity_sqla: year
|
||||
granularity: year
|
||||
groupby:
|
||||
- region
|
||||
limit: '25'
|
||||
|
||||
@@ -24,7 +24,7 @@ params:
|
||||
compare_suffix: o10Y
|
||||
country_fieldtype: cca3
|
||||
entity: country_code
|
||||
granularity_sqla: year
|
||||
granularity: year
|
||||
groupby:
|
||||
- country_name
|
||||
limit: '25'
|
||||
|
||||
@@ -43,7 +43,7 @@ params:
|
||||
subject: country_code
|
||||
color_scheme: supersetColors
|
||||
entity: country_name
|
||||
granularity_sqla: year
|
||||
granularity: year
|
||||
legendOrientation: top
|
||||
legendType: scroll
|
||||
max_bubble_size: '50'
|
||||
|
||||
@@ -24,7 +24,7 @@ params:
|
||||
compare_suffix: o10Y
|
||||
country_fieldtype: cca3
|
||||
entity: country_code
|
||||
granularity_sqla: year
|
||||
granularity: year
|
||||
groupby:
|
||||
- country_name
|
||||
limit: '25'
|
||||
|
||||
@@ -24,7 +24,7 @@ params:
|
||||
compare_suffix: o10Y
|
||||
country_fieldtype: cca3
|
||||
entity: country_code
|
||||
granularity_sqla: year
|
||||
granularity: year
|
||||
groupby: []
|
||||
limit: '25'
|
||||
markup_type: markdown
|
||||
|
||||
@@ -27,7 +27,7 @@ params:
|
||||
compare_suffix: o10Y
|
||||
country_fieldtype: cca3
|
||||
entity: country_code
|
||||
granularity_sqla: year
|
||||
granularity: year
|
||||
groupby: []
|
||||
limit: '25'
|
||||
markup_type: markdown
|
||||
|
||||
@@ -24,7 +24,7 @@ params:
|
||||
compare_suffix: o10Y
|
||||
country_fieldtype: cca3
|
||||
entity: country_code
|
||||
granularity_sqla: year
|
||||
granularity: year
|
||||
groupby:
|
||||
- region
|
||||
- country_code
|
||||
|
||||
@@ -24,7 +24,7 @@ params:
|
||||
compare_suffix: o10Y
|
||||
country_fieldtype: cca3
|
||||
entity: country_code
|
||||
granularity_sqla: year
|
||||
granularity: year
|
||||
groupby:
|
||||
- region
|
||||
limit: '25'
|
||||
|
||||
@@ -24,7 +24,7 @@ params:
|
||||
compare_suffix: over 10Y
|
||||
country_fieldtype: cca3
|
||||
entity: country_code
|
||||
granularity_sqla: year
|
||||
granularity: year
|
||||
groupby: []
|
||||
limit: '25'
|
||||
markup_type: markdown
|
||||
|
||||
@@ -31,6 +31,7 @@ from superset.commands.dataset.exceptions import (
|
||||
from superset.commands.explore.permalink.create import CreateExplorePermalinkCommand
|
||||
from superset.commands.explore.permalink.get import GetExplorePermalinkCommand
|
||||
from superset.constants import MODEL_API_RW_METHOD_PERMISSION_MAP
|
||||
from superset.exceptions import SupersetTemplateException
|
||||
from superset.explore.permalink.exceptions import ExplorePermalinkInvalidStateError
|
||||
from superset.explore.permalink.schemas import ExplorePermalinkStateSchema
|
||||
from superset.extensions import event_logger
|
||||
@@ -107,6 +108,8 @@ class ExplorePermalinkRestApi(BaseSupersetApi):
|
||||
return self.response(403, message=str(ex))
|
||||
except (ChartNotFoundError, DatasetNotFoundError) as ex:
|
||||
return self.response(404, message=str(ex))
|
||||
except SupersetTemplateException as ex:
|
||||
return self.response(ex.status, message=str(ex))
|
||||
|
||||
@expose("/permalink/<string:key>", methods=("GET",))
|
||||
@protect()
|
||||
@@ -162,3 +165,5 @@ class ExplorePermalinkRestApi(BaseSupersetApi):
|
||||
return self.response(403, message=str(ex))
|
||||
except (ChartNotFoundError, DatasetNotFoundError) as ex:
|
||||
return self.response(404, message=str(ex))
|
||||
except SupersetTemplateException as ex:
|
||||
return self.response(ex.status, message=str(ex))
|
||||
|
||||
@@ -18,7 +18,7 @@ from datetime import datetime
|
||||
from io import BytesIO
|
||||
from zipfile import is_zipfile, ZipFile
|
||||
|
||||
from flask import request, Response, send_file
|
||||
from flask import request, Response
|
||||
from flask_appbuilder.api import expose, protect
|
||||
|
||||
from superset.commands.export.assets import ExportAssetsCommand
|
||||
@@ -30,7 +30,7 @@ from superset.commands.importers.v1.assets import ImportAssetsCommand
|
||||
from superset.commands.importers.v1.utils import get_contents_from_bundle
|
||||
from superset.extensions import event_logger
|
||||
from superset.utils import json
|
||||
from superset.utils.core import parse_boolean_string
|
||||
from superset.utils.core import parse_boolean_string, send_export_zip
|
||||
from superset.views.base_api import BaseSupersetApi, requires_form_data, statsd_metrics
|
||||
|
||||
|
||||
@@ -84,13 +84,7 @@ class ImportExportRestApi(BaseSupersetApi):
|
||||
fp.write(file_content().encode())
|
||||
buf.seek(0)
|
||||
|
||||
response = send_file(
|
||||
buf,
|
||||
mimetype="application/zip",
|
||||
as_attachment=True,
|
||||
download_name=filename,
|
||||
)
|
||||
return response
|
||||
return send_export_zip(buf, filename)
|
||||
|
||||
@expose("/import/", methods=("POST",))
|
||||
@protect()
|
||||
|
||||
@@ -119,6 +119,7 @@ from superset.extensions import event_logger
|
||||
title="My new tool",
|
||||
readOnlyHint=True,
|
||||
destructiveHint=False,
|
||||
openWorldHint=False,
|
||||
),
|
||||
)
|
||||
async def my_new_tool(request: MyRequest, ctx: Context) -> MyResponse:
|
||||
@@ -139,6 +140,8 @@ async def my_new_tool(request: MyRequest, ctx: Context) -> MyResponse:
|
||||
title="Create something",
|
||||
readOnlyHint=False,
|
||||
destructiveHint=False,
|
||||
idempotentHint=False,
|
||||
openWorldHint=False,
|
||||
),
|
||||
)
|
||||
async def create_something(request: CreateRequest, ctx: Context) -> CreateResponse:
|
||||
@@ -210,8 +213,10 @@ The `@tool` decorator from `superset_core.mcp.decorators` accepts:
|
||||
```python
|
||||
annotations=ToolAnnotations(
|
||||
title="Human-readable title",
|
||||
readOnlyHint=True, # Whether tool only reads data
|
||||
destructiveHint=False, # Whether tool has destructive side effects
|
||||
readOnlyHint=False, # Whether tool only reads data
|
||||
destructiveHint=False, # Whether tool has destructive side effects
|
||||
idempotentHint=False, # Whether repeated mutating calls have additional effects
|
||||
openWorldHint=False, # Whether tool interacts with external entities
|
||||
)
|
||||
```
|
||||
|
||||
@@ -572,7 +577,9 @@ async def test_my_tool_success(mcp_server):
|
||||
|
||||
### 4. Missing ToolAnnotations
|
||||
**Problem**: Tool lacks MCP directory compliance metadata.
|
||||
**Solution**: Always include `annotations=ToolAnnotations(title=..., readOnlyHint=..., destructiveHint=...)`.
|
||||
**Solution**: Always include `title`, `readOnlyHint`, `destructiveHint`, and
|
||||
`openWorldHint`. Mutating tools (`readOnlyHint=False`) must also include
|
||||
`idempotentHint`.
|
||||
|
||||
### 5. Using `Optional` Instead of Union Syntax
|
||||
**Problem**: Old-style `Optional[T]` is not Python 3.10+ style.
|
||||
|
||||
@@ -42,6 +42,7 @@ logger = logging.getLogger(__name__)
|
||||
title="Get annotation layer info",
|
||||
readOnlyHint=True,
|
||||
destructiveHint=False,
|
||||
openWorldHint=False,
|
||||
),
|
||||
)
|
||||
async def get_annotation_layer_info(
|
||||
|
||||
@@ -41,6 +41,7 @@ logger = logging.getLogger(__name__)
|
||||
title="Get annotation info",
|
||||
readOnlyHint=True,
|
||||
destructiveHint=False,
|
||||
openWorldHint=False,
|
||||
),
|
||||
)
|
||||
async def get_layer_annotation_info(
|
||||
|
||||
@@ -49,6 +49,7 @@ _SORTABLE_LAYER_COLUMNS = ["id", "name", "changed_on", "created_on"]
|
||||
title="List annotation layers",
|
||||
readOnlyHint=True,
|
||||
destructiveHint=False,
|
||||
openWorldHint=False,
|
||||
),
|
||||
)
|
||||
async def list_annotation_layers(
|
||||
|
||||
@@ -56,6 +56,7 @@ _SORTABLE_ANNOTATION_COLUMNS = ["id", "short_descr", "start_dttm", "end_dttm"]
|
||||
title="List annotations in a layer",
|
||||
readOnlyHint=True,
|
||||
destructiveHint=False,
|
||||
openWorldHint=False,
|
||||
),
|
||||
)
|
||||
async def list_layer_annotations(
|
||||
|
||||
@@ -125,8 +125,8 @@ Available tools:
|
||||
|
||||
Dashboard Management:
|
||||
- list_dashboards: List dashboards with advanced filters (1-based pagination; deleted_state='only'/'include' surfaces trashed dashboards the caller may restore)
|
||||
- get_dashboard_info: Get detailed dashboard information by ID
|
||||
- get_dashboard_layout: Get parsed tabs and chart positions for a dashboard (companion to get_dashboard_info when its omitted_fields hint flags position_json)
|
||||
- get_dashboard_info: Resolve a dashboard by ID/UUID/slug or shared /dashboard/p/<key>/ permalink, including its active-tab and filter state
|
||||
- get_dashboard_layout: Get parsed tabs and chart positions by dashboard identifier or shared permalink, including the permalink's active-tab and filter context
|
||||
- get_dashboard_datasets: List the datasets used by a dashboard's charts, with columns and metrics (context for configuring native filters)
|
||||
- generate_dashboard: Create a dashboard from chart IDs (requires write access)
|
||||
- update_dashboard: Update an existing dashboard's title/description/slug/published/layout/theme/CSS (requires write access; editorship-checked per-instance)
|
||||
|
||||
@@ -37,13 +37,26 @@ if TYPE_CHECKING:
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# extra_form_data override targets that the query object actually reads. Note
|
||||
# that ``time_grain`` is deliberately absent: the query object has no such field
|
||||
# and nothing downstream consumes it, matching the REST path, where
|
||||
# form_data_query_context reads only ``time_grain_sqla``. Listing it here would
|
||||
# write a key that ChartDataQueryObjectSchema (``unknown = EXCLUDE``) discards.
|
||||
QUERY_CONTEXT_EXTRA_FORM_DATA_OVERRIDE_KEYS = {
|
||||
"granularity",
|
||||
"time_grain",
|
||||
"time_grain_sqla",
|
||||
"time_range",
|
||||
}
|
||||
|
||||
# Of the keys above, these are not query object fields: the query object carries
|
||||
# the time grain inside ``extras`` (see ChartDataExtrasSchema), mirroring how
|
||||
# form_data is translated in superset.common.form_data_query_context. Writing
|
||||
# them at the top level instead means ChartDataQueryObjectSchema, which is
|
||||
# configured with ``unknown = EXCLUDE``, silently drops the override.
|
||||
QUERY_CONTEXT_EXTRA_FORM_DATA_EXTRAS_KEYS = {
|
||||
"time_grain_sqla",
|
||||
}
|
||||
|
||||
|
||||
class ChartNotOnDashboardError(ValueError):
|
||||
"""Raised when a chart is not part of the given dashboard's slices."""
|
||||
@@ -249,7 +262,10 @@ def merge_form_data_filters_into_query(
|
||||
and key in form_data
|
||||
and form_data[key] is not None
|
||||
):
|
||||
query[key] = form_data[key]
|
||||
if key in QUERY_CONTEXT_EXTRA_FORM_DATA_EXTRAS_KEYS:
|
||||
query["extras"] = {**(query.get("extras") or {}), key: form_data[key]}
|
||||
else:
|
||||
query[key] = form_data[key]
|
||||
|
||||
for clause in ("where", "having"):
|
||||
if additional_clause := form_data.get(clause):
|
||||
|
||||
@@ -69,6 +69,8 @@ def _routes_to_soft_delete() -> bool:
|
||||
title="Delete chart",
|
||||
readOnlyHint=False,
|
||||
destructiveHint=True,
|
||||
idempotentHint=False,
|
||||
openWorldHint=False,
|
||||
),
|
||||
)
|
||||
async def delete_chart(
|
||||
|
||||
@@ -71,6 +71,8 @@ __all__ = ["CompileResult", "_compile_chart", "validate_and_compile", "generate_
|
||||
title="Create chart",
|
||||
readOnlyHint=False,
|
||||
destructiveHint=False,
|
||||
idempotentHint=False,
|
||||
openWorldHint=False,
|
||||
),
|
||||
)
|
||||
async def generate_chart( # noqa: C901
|
||||
|
||||
@@ -82,6 +82,31 @@ def _requested_filter_columns(extra_form_data: dict[str, Any] | None) -> set[str
|
||||
return columns
|
||||
|
||||
|
||||
def _rejected_columns_in_query(query: Any) -> set[str]:
|
||||
"""Return the rejected filter column names reported by one query payload.
|
||||
|
||||
``_materialize_full_payload`` converts the datasource's raw
|
||||
``rejected_filter_columns`` list into the ``rejected_filters`` entries
|
||||
(``{"reason": ..., "column": ...}``) that every consumer of a chart-data
|
||||
payload sees, so that is the primary shape to read. The raw key is still
|
||||
accepted for payloads captured before that conversion.
|
||||
"""
|
||||
if not isinstance(query, dict):
|
||||
return set()
|
||||
|
||||
columns = {
|
||||
column
|
||||
for entry in query.get("rejected_filters", [])
|
||||
if isinstance(entry, dict) and isinstance(column := entry.get("column"), str)
|
||||
}
|
||||
columns.update(
|
||||
column
|
||||
for column in query.get("rejected_filter_columns", [])
|
||||
if isinstance(column, str)
|
||||
)
|
||||
return columns
|
||||
|
||||
|
||||
def _rejected_requested_filter_columns(
|
||||
result: Any, extra_form_data: dict[str, Any] | None
|
||||
) -> list[str]:
|
||||
@@ -92,8 +117,7 @@ def _rejected_requested_filter_columns(
|
||||
rejected = {
|
||||
column
|
||||
for query in result.get("queries", [])
|
||||
for column in query.get("rejected_filter_columns", [])
|
||||
if isinstance(column, str)
|
||||
for column in _rejected_columns_in_query(query)
|
||||
}
|
||||
return sorted(requested & rejected)
|
||||
|
||||
@@ -316,6 +340,7 @@ def _build_query_results(
|
||||
title="Get chart data",
|
||||
readOnlyHint=True,
|
||||
destructiveHint=False,
|
||||
openWorldHint=False,
|
||||
),
|
||||
)
|
||||
async def get_chart_data( # noqa: C901
|
||||
|
||||
@@ -201,6 +201,7 @@ def _apply_unsaved_state_override(result: ChartInfo, form_data_key: str) -> None
|
||||
title="Get chart info",
|
||||
readOnlyHint=True,
|
||||
destructiveHint=False,
|
||||
openWorldHint=False,
|
||||
),
|
||||
)
|
||||
async def get_chart_info(
|
||||
|
||||
@@ -1399,6 +1399,7 @@ async def _get_chart_preview_internal( # noqa: C901
|
||||
title="Get chart preview",
|
||||
readOnlyHint=True,
|
||||
destructiveHint=False,
|
||||
openWorldHint=False,
|
||||
),
|
||||
)
|
||||
async def get_chart_preview(
|
||||
|
||||
@@ -393,6 +393,7 @@ def _extract_sql_from_result(
|
||||
title="Get chart SQL",
|
||||
readOnlyHint=True,
|
||||
destructiveHint=False,
|
||||
openWorldHint=False,
|
||||
),
|
||||
)
|
||||
async def get_chart_sql(
|
||||
|
||||
@@ -228,6 +228,7 @@ def _get_chart_type_schema_impl(
|
||||
title="Get chart type schema",
|
||||
readOnlyHint=True,
|
||||
destructiveHint=False,
|
||||
openWorldHint=False,
|
||||
),
|
||||
)
|
||||
def get_chart_type_schema(
|
||||
|
||||
@@ -71,6 +71,7 @@ _DEFAULT_LIST_CHARTS_REQUEST = ListChartsRequest()
|
||||
title="List charts",
|
||||
readOnlyHint=True,
|
||||
destructiveHint=False,
|
||||
openWorldHint=False,
|
||||
),
|
||||
)
|
||||
async def list_charts(
|
||||
|
||||
@@ -76,6 +76,8 @@ def _rollback() -> None:
|
||||
title="Restore chart",
|
||||
readOnlyHint=False,
|
||||
destructiveHint=False,
|
||||
idempotentHint=False,
|
||||
openWorldHint=False,
|
||||
),
|
||||
)
|
||||
async def restore_chart(
|
||||
|
||||
@@ -476,6 +476,8 @@ def _create_preview_url(
|
||||
title="Update chart",
|
||||
readOnlyHint=False,
|
||||
destructiveHint=True,
|
||||
idempotentHint=False,
|
||||
openWorldHint=False,
|
||||
),
|
||||
)
|
||||
async def update_chart( # noqa: C901
|
||||
|
||||
@@ -157,6 +157,8 @@ def _preserve_previous_adhoc_filters(
|
||||
title="Update chart preview",
|
||||
readOnlyHint=False,
|
||||
destructiveHint=False,
|
||||
idempotentHint=False,
|
||||
openWorldHint=False,
|
||||
),
|
||||
)
|
||||
def update_chart_preview( # noqa: C901
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
# 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.
|
||||
|
||||
"""Helpers for resolving dashboard permalink keys and shared URLs."""
|
||||
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
from typing import Callable, Generic, TypeVar
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from flask import g, has_request_context
|
||||
|
||||
from superset.commands.dashboard.exceptions import DashboardAccessDeniedError
|
||||
from superset.commands.dashboard.permalink.get import GetDashboardPermalinkCommand
|
||||
from superset.dashboards.permalink.exceptions import DashboardPermalinkGetFailedError
|
||||
from superset.dashboards.permalink.types import DashboardPermalinkValue
|
||||
from superset.mcp_service.auth import load_user_with_relationships
|
||||
from superset.mcp_service.dashboard.schemas import (
|
||||
redact_filter_state_data_model_metadata,
|
||||
)
|
||||
from superset.mcp_service.privacy import user_can_view_data_model_metadata
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
LookupResultT = TypeVar("LookupResultT")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DashboardLookupResult(Generic[LookupResultT]):
|
||||
"""Result of resolving either a dashboard identifier or permalink."""
|
||||
|
||||
result: LookupResultT | None
|
||||
permalink_key: str | None = None
|
||||
permalink_value: DashboardPermalinkValue | None = None
|
||||
resolved_from_permalink: bool = False
|
||||
"""True when the dashboard itself was selected from the permalink."""
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DashboardPermalinkState:
|
||||
"""Permalink state belonging to a resolved dashboard."""
|
||||
|
||||
key: str
|
||||
state: dict[str, object]
|
||||
|
||||
|
||||
def extract_dashboard_permalink_key(value: str) -> str:
|
||||
"""Return a key from a dashboard permalink URL, or the bare input."""
|
||||
path_parts = [part for part in urlparse(value).path.split("/") if part]
|
||||
if len(path_parts) >= 3 and path_parts[-3:-1] == ["dashboard", "p"]:
|
||||
return path_parts[-1]
|
||||
return value
|
||||
|
||||
|
||||
def refresh_request_user_for_permalink_access() -> None:
|
||||
"""Reload the request user before permalink access checks."""
|
||||
if not has_request_context() or not getattr(g, "user", None):
|
||||
return
|
||||
current_user = g.user
|
||||
if getattr(current_user, "is_anonymous", False):
|
||||
return
|
||||
username = getattr(current_user, "username", None)
|
||||
email = getattr(current_user, "email", None)
|
||||
if not username and not email:
|
||||
return
|
||||
refreshed_user = (
|
||||
load_user_with_relationships(username=username)
|
||||
if username
|
||||
else load_user_with_relationships(email=email)
|
||||
)
|
||||
if refreshed_user is not None:
|
||||
g.user = refreshed_user
|
||||
|
||||
|
||||
def get_dashboard_permalink(
|
||||
key_or_url: str,
|
||||
) -> tuple[str, DashboardPermalinkValue] | None:
|
||||
"""Resolve a dashboard permalink key or shared URL, returning its state."""
|
||||
key = extract_dashboard_permalink_key(key_or_url)
|
||||
refresh_request_user_for_permalink_access()
|
||||
try:
|
||||
value = GetDashboardPermalinkCommand(key).run()
|
||||
except (DashboardAccessDeniedError, DashboardPermalinkGetFailedError) as ex:
|
||||
logger.info("Dashboard permalink could not be resolved: %s", ex)
|
||||
return None
|
||||
return (key, value) if value else None
|
||||
|
||||
|
||||
def lookup_dashboard_reference(
|
||||
*,
|
||||
identifier: int | str | None,
|
||||
permalink_key: str | None,
|
||||
lookup: Callable[[int | str], LookupResultT],
|
||||
is_found: Callable[[LookupResultT], bool],
|
||||
) -> DashboardLookupResult[LookupResultT]:
|
||||
"""Look up a dashboard while preserving identifier precedence.
|
||||
|
||||
A supplied identifier selects the dashboard and an explicit permalink only
|
||||
contributes state. Shared permalink URLs and permalink-only requests select
|
||||
the dashboard embedded in the permalink. Ambiguous bare strings use normal
|
||||
identifier lookup first, then fall back to permalink resolution.
|
||||
"""
|
||||
key = permalink_key
|
||||
identifier_is_permalink_url = False
|
||||
if isinstance(identifier, str):
|
||||
extracted_key = extract_dashboard_permalink_key(identifier)
|
||||
identifier_is_permalink_url = extracted_key != identifier
|
||||
if identifier_is_permalink_url:
|
||||
key = extracted_key
|
||||
|
||||
if identifier is not None and not identifier_is_permalink_url:
|
||||
result = lookup(identifier)
|
||||
if is_found(result):
|
||||
resolved = get_dashboard_permalink(key) if key else None
|
||||
return DashboardLookupResult(
|
||||
result=result,
|
||||
permalink_key=resolved[0] if resolved else key,
|
||||
permalink_value=resolved[1] if resolved else None,
|
||||
)
|
||||
if permalink_key is not None or not isinstance(identifier, str):
|
||||
return DashboardLookupResult(result=result, permalink_key=key)
|
||||
else:
|
||||
result = None
|
||||
|
||||
reference = key or (identifier if isinstance(identifier, str) else None)
|
||||
resolved = get_dashboard_permalink(reference) if reference else None
|
||||
if resolved is None:
|
||||
return DashboardLookupResult(result=result, permalink_key=reference)
|
||||
key, value = resolved
|
||||
return DashboardLookupResult(
|
||||
result=lookup(value["dashboardId"]),
|
||||
permalink_key=key,
|
||||
permalink_value=value,
|
||||
resolved_from_permalink=True,
|
||||
)
|
||||
|
||||
|
||||
def get_matching_dashboard_permalink_state(
|
||||
lookup_result: DashboardLookupResult[LookupResultT],
|
||||
dashboard_id: int | None,
|
||||
dashboard_uuid: str | None = None,
|
||||
dashboard_slug: str | None = None,
|
||||
) -> DashboardPermalinkState | None:
|
||||
"""Return the permalink state when it belongs to the dashboard.
|
||||
|
||||
``CreateDashboardPermalinkCommand`` stores ``dashboardId`` as the dashboard
|
||||
UUID string, while older permalinks may hold a numeric ID or a slug, so the
|
||||
reference is compared against every identifier the dashboard answers to.
|
||||
"""
|
||||
value = lookup_result.permalink_value
|
||||
key = lookup_result.permalink_key
|
||||
if value is None or key is None:
|
||||
return None
|
||||
if not lookup_result.resolved_from_permalink:
|
||||
# The identifier selected the dashboard, so the permalink only
|
||||
# contributes state when it points at that same dashboard.
|
||||
reference = value.get("dashboardId")
|
||||
known_identifiers = {
|
||||
str(candidate)
|
||||
for candidate in (dashboard_id, dashboard_uuid, dashboard_slug)
|
||||
if candidate is not None
|
||||
}
|
||||
if reference is None or str(reference) not in known_identifiers:
|
||||
return None
|
||||
|
||||
raw_state = value.get("state")
|
||||
state: dict[str, object] = dict(raw_state) if isinstance(raw_state, dict) else {}
|
||||
if not user_can_view_data_model_metadata():
|
||||
state = redact_filter_state_data_model_metadata(state)
|
||||
return DashboardPermalinkState(key=key, state=state)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user