Compare commits
5 Commits
remove-leg
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f759949f1 | ||
|
|
26b6f7bb5d | ||
|
|
a10c3f0b1d | ||
|
|
3095d7b07f | ||
|
|
a8e2a340f1 |
2
.github/CODEOWNERS
vendored
@@ -34,7 +34,7 @@
|
||||
|
||||
**/*.geojson @villebro @rusackas
|
||||
**/*.ipynb @villebro @rusackas
|
||||
/superset-frontend/plugins/plugin-chart-country-map/ @villebro @rusackas
|
||||
/superset-frontend/plugins/legacy-plugin-chart-country-map/ @villebro @rusackas
|
||||
|
||||
# Notify translation maintainers of changes to translations
|
||||
|
||||
|
||||
2
.github/workflows/codeql-analysis.yml
vendored
@@ -64,7 +64,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1
|
||||
uses: github/codeql-action/init@e0647621c2984b5ed2f768cb892365bf2a616ad1 # v4.37.2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
|
||||
1
.gitignore
vendored
@@ -138,7 +138,6 @@ PROJECT.md
|
||||
.aider*
|
||||
.claude_rc*
|
||||
.claude/settings.local.json
|
||||
.claude/worktrees/
|
||||
.env.local
|
||||
oxc-custom-build/
|
||||
*.code-workspace
|
||||
|
||||
86
UPDATING.md
@@ -193,10 +193,6 @@ are the intended model going forward; deprecating and removing implicit viewersh
|
||||
in a later major version.
|
||||
|
||||
- [41044](https://github.com/apache/superset/issues/41044): Removes the deprecated `AVOID_COLORS_COLLISION` feature flag (it defaulted to `True`). Color-collision avoidance is now permanently enabled; any config override setting it to `False` is ignored.
|
||||
- [41714](https://github.com/apache/superset/pull/41714): **Breaking — the legacy `explore_json` chart-data pipeline is removed** at its long-declared `5.0.0` EOL. The `/superset/explore_json/` and `/superset/explore_json/data/<cache_key>` endpoints, `superset/viz.py`, the `Slice.viz` property, the `get_viz` factory, the `load_explore_json_into_cache` celery task and the `viz=` overload of `security_manager.raise_for_access` are gone. Anything importing `superset.viz` must migrate to the QueryContext / `pandas_postprocessing` pipeline behind `/api/v1/chart/data`. All 15 remaining legacy charts were migrated first: most keep their `viz_type` and renderer (no action needed for saved charts), while saved nvd3 Bubble charts are auto-migrated to the ECharts Bubble Chart (`bubble_v2`) and saved "Time-series Percent Change" (`compare`) charts to the ECharts Line Chart — the nvd3 renderer's interactive percent re-basing is not preserved. The deck.gl Multiple Layers chart now fetches its layers entirely client-side; its initial autozoom falls back to the saved viewport, and dashboard filter badges no longer aggregate child-layer filter metadata.
|
||||
|
||||
- [41714](https://github.com/apache/superset/pull/41714): Charts migrated in place keep a `NULL` saved query context until they are next opened in Explore (which regenerates it automatically) or re-saved. Until then, cache warm-up and annotation layers referencing such a chart report an actionable error rather than warming/rendering; opening the chart once resolves it.
|
||||
- [41714](https://github.com/apache/superset/pull/41714): **Breaking for third-party viz plugins** — the `useLegacyApi` field of `ChartMetadata` in `@superset-ui/core` is removed. Plugins that set it must provide a `buildQuery` and consume `/api/v1/chart/data`. The migrated first-party packages also drop their `legacy-` prefix: `@superset-ui/legacy-plugin-chart-{calendar,chord,country-map,horizon,paired-t-test,parallel-coordinates,partition,rose,world-map}` → `@superset-ui/plugin-chart-*`, and `@superset-ui/legacy-preset-chart-nvd3` → `@superset-ui/preset-chart-nvd3`. The `can_explore_json` permission is no longer created or granted; custom roles referencing it should switch to the `can_read` permissions on `Chart`.
|
||||
|
||||
- [41813](https://github.com/apache/superset/pull/41813): `redis` (the Python client, `redis-py`) is bumped from 5.3.1 to 8.0.1. redis-py 8 changes several connection defaults; Superset's own Redis-backed features (`GLOBAL_ASYNC_QUERIES_CACHE_BACKEND`, `DISTRIBUTED_COORDINATION_CONFIG`, and the MCP Redis store) explicitly pin the pre-upgrade behavior so this bump is a no-op for them: the wire protocol stays RESP2 (not the new RESP3 default, which requires Redis/Sentinel 6+ to speak `HELLO`) and there is still no socket timeout by default (redis-py 8 defaults to 5s, which could otherwise newly time out large cached payloads or slow networks). The no-timeout default can now be overridden via two new config keys, `CACHE_REDIS_SOCKET_TIMEOUT` / `CACHE_REDIS_SOCKET_CONNECT_TIMEOUT`, on any `CacheConfig` dict using `CACHE_TYPE: RedisCache` or `RedisSentinelCache`. Separately, redis-py 6+ changed the default for `ssl_check_hostname` from `False` to `True` for SSL connections using `ssl_cert_reqs="required"` (the default) — this is a security improvement, so it has **not** been reverted; deployments with `CACHE_REDIS_SSL=True` whose certificates lack a hostname matching the connection address should set `CACHE_REDIS_SSL_CERT_REQS="none"` (disables cert verification entirely, matching hostname-check bypass) or replace the certificate. General-purpose cache/results backends configured via `CACHE_CONFIG` / `DATA_CACHE_CONFIG` / `RESULTS_BACKEND` with `CACHE_TYPE: RedisCache` go through `flask-caching`'s own Redis backend (outside Superset's code) and are subject to the same new defaults; pass `socket_timeout` / `protocol` via `CACHE_OPTIONS` there if needed. Celery broker and result-backend connections (built by `kombu`, also outside Superset's code) keep their no-socket-timeout behavior (`kombu` passes `socket_timeout=None` explicitly) but do **not** pin the wire protocol, so they follow redis-py's RESP3 default — which requires a Redis server new enough to speak `HELLO` (Redis 6+). Deployments using a pre-6.0 Redis server (EOL) as a Celery broker should upgrade the server before taking this bump.
|
||||
|
||||
@@ -216,7 +212,7 @@ in a later major version.
|
||||
|
||||
- **`SqlaTable.sql_url` query-string format.** `SqlaTable.sql_url` now URL-encodes `table_name` and joins it as a query parameter rather than concatenating a second `?`. Previously, with `Database.sql_url` returning `/sqllab/?dbid=<id>`, the concatenation produced `/sqllab/?dbid=<id>?table_name=<raw>` — a malformed second `?` that broke the query parser. External code that parsed the legacy `<base>?table_name=<raw>` shape now sees properly percent-encoded values (e.g. `/` → `%2F`, ` ` → `+` or `%20`); decode with `urllib.parse.parse_qsl`.
|
||||
|
||||
- **New config flag `EMBEDDED_DISABLE_PERMALINK_ORIGIN_REWRITE` (default `False`).** Share/permalink URLs now substitute `window.location.origin` for the backend-supplied origin so a proxied or subdirectory-deployed Superset never hands the user an unreachable internal hostname. Operators whose reverse proxy correctly forwards `X-Forwarded-Host` _and_ who want permalinks to carry the backend's literal origin can opt out by setting `EMBEDDED_DISABLE_PERMALINK_ORIGIN_REWRITE = True` in `superset_config.py`. Default `False` (rewrite is on); flipping the default would regress the dominant proxied/subdir deployment to an unreachable host.
|
||||
- **New config flag `EMBEDDED_DISABLE_PERMALINK_ORIGIN_REWRITE` (default `False`).** Share/permalink URLs now substitute `window.location.origin` for the backend-supplied origin so a proxied or subdirectory-deployed Superset never hands the user an unreachable internal hostname. Operators whose reverse proxy correctly forwards `X-Forwarded-Host` *and* who want permalinks to carry the backend's literal origin can opt out by setting `EMBEDDED_DISABLE_PERMALINK_ORIGIN_REWRITE = True` in `superset_config.py`. Default `False` (rewrite is on); flipping the default would regress the dominant proxied/subdir deployment to an unreachable host.
|
||||
|
||||
- [41651](https://github.com/apache/superset/pull/41651): **New do-not-translate standard for translation catalogs.** Strings that must stay identical to the source — icon names (e.g. `bolt`), enum/option values (`step-after`), SQL keywords, API field names (`error_message`), code constants, and example placeholders — are now marked with a `#. do-not-translate` extracted comment. The list lives in the `superset/translations/do-not-translate.txt` registry; `scripts/translations/apply_do_not_translate.py` stamps the marker onto `messages.pot` during `babel_update.sh`, and `pybabel update` propagates it to every `.po`, so the status is consistent across all languages. The AI backfill (`backfill_po.py`) and translators leave these entries untranslated (source fallback). The legacy per-catalog convention (a `# Не переводить` translator comment in the `ru` catalog) is still honored for back-compat but is superseded by this standard; contributors adding new machine-read strings should add the msgid to the registry rather than annotating individual catalogs.
|
||||
|
||||
@@ -253,7 +249,7 @@ Theme tokens are unaffected — antd 6 removed none of the tokens Superset expos
|
||||
|
||||
### Guest-token RLS rules reject unknown fields
|
||||
|
||||
The `rls` rules passed to `POST /api/v1/security/guest_token/` are now validated strictly: a rule may only contain `dataset` and `clause`. Previously unknown fields were silently dropped, so a mistyped or legacy scope key (most commonly `datasource` instead of `dataset`) produced a rule with no `dataset`, which is treated as a _global_ rule applied to every dataset the embedded resource can reach. Such a request now returns HTTP 400 identifying the offending field instead of issuing a token with an unintended global rule. Integrators that were sending extra fields in RLS rules must remove them; valid dataset-scoped (`{"dataset": 41, "clause": "..."}`) and global (`{"clause": "..."}`) rules are unaffected.
|
||||
The `rls` rules passed to `POST /api/v1/security/guest_token/` are now validated strictly: a rule may only contain `dataset` and `clause`. Previously unknown fields were silently dropped, so a mistyped or legacy scope key (most commonly `datasource` instead of `dataset`) produced a rule with no `dataset`, which is treated as a *global* rule applied to every dataset the embedded resource can reach. Such a request now returns HTTP 400 identifying the offending field instead of issuing a token with an unintended global rule. Integrators that were sending extra fields in RLS rules must remove them; valid dataset-scoped (`{"dataset": 41, "clause": "..."}`) and global (`{"clause": "..."}`) rules are unaffected.
|
||||
|
||||
### MCP service requires `MCP_JWT_AUDIENCE` when JWT auth is enabled
|
||||
|
||||
@@ -337,7 +333,6 @@ ALTER TABLE tagged_object DROP CONSTRAINT <constraint_name>;
|
||||
-- MySQL: find names via `SHOW CREATE TABLE tagged_object;`
|
||||
ALTER TABLE tagged_object DROP FOREIGN KEY <constraint_name>;
|
||||
```
|
||||
|
||||
### Entity version-history infrastructure (gated off by default)
|
||||
|
||||
Introduces the schema and SQLAlchemy-Continuum wiring that captures version history for charts, dashboards, and datasets, plus read-only `GET /api/v1/{chart,dashboard,dataset}/<uuid>/versions/` endpoints. This ships **inert**: a new config flag `ENABLE_VERSIONING_CAPTURE` defaults to `False`, so no save writes any version rows and the endpoints return empty. It is an operational kill-switch (a release toggle that becomes a permanent ops switch), not a feature flag — set it to `True` to enable capture once validated. The migration is additive; existing entity `PUT` responses gain `old_version_uuid` / `new_version_uuid` body fields and an `ETag` header (both null/absent when capture is off).
|
||||
@@ -354,12 +349,12 @@ These are behavior changes that take effect on upgrade regardless of `ENABLE_VER
|
||||
|
||||
A read-only companion to the version-history endpoints: each entity type gains a `GET /api/v1/{chart,dashboard,dataset}/<uuid>/activity/` endpoint returning a chronological, access-filtered stream of edits — the entity's own edits plus, for charts and dashboards, transitive edits to related entities during their association windows. Datasets have no related layer in V2, so `include=related` returns an empty stream for a dataset and `include=all` reduces to the dataset's own edits.
|
||||
|
||||
| Param | Type | Default | Purpose |
|
||||
| -------------------- | ---------------------------- | ---------- | ------------------------------------------------------------------------------------------------------ |
|
||||
| `since` / `until` | ISO 8601 | — | Bound `issued_at` |
|
||||
| `include` | `self` \| `related` \| `all` | `all` | Own edits, related edits, or both |
|
||||
| `q` | string | — | Case-insensitive search over the full history, applied before pagination (so `count` reflects matches) |
|
||||
| `page` / `page_size` | integer | `0` / `25` | Pagination (`page_size` clamped to 200) |
|
||||
| Param | Type | Default | Purpose |
|
||||
|---|---|---|---|
|
||||
| `since` / `until` | ISO 8601 | — | Bound `issued_at` |
|
||||
| `include` | `self` \| `related` \| `all` | `all` | Own edits, related edits, or both |
|
||||
| `q` | string | — | Case-insensitive search over the full history, applied before pagination (so `count` reflects matches) |
|
||||
| `page` / `page_size` | integer | `0` / `25` | Pagination (`page_size` clamped to 200) |
|
||||
|
||||
Authorization reuses the resource's `can_read` permission and per-object `raise_for_access`; related-entity rows are visibility-filtered to what the caller may see. The stream is empty unless version capture is on (`ENABLE_VERSIONING_CAPTURE`).
|
||||
|
||||
@@ -420,7 +415,6 @@ Operators can tune or disable the policy via config:
|
||||
### Data uploads bounded by UPLOAD_MAX_FILE_SIZE_BYTES
|
||||
|
||||
Single data-file uploads (CSV, Excel, columnar) are now bounded by the `UPLOAD_MAX_FILE_SIZE_BYTES` config option, which defaults to `100 * 1024 * 1024` (100 MB). Files larger than this are rejected with a `413` before their contents are buffered into memory. Set `UPLOAD_MAX_FILE_SIZE_BYTES = None` to disable the check and restore unbounded uploads.
|
||||
|
||||
### Currency symbol position follows the locale when unset
|
||||
|
||||
When a chart's currency control leaves the **Prefix or suffix** field empty, the currency symbol position is now derived from the deployment locale's own convention via `Intl.NumberFormat` instead of always defaulting to a suffix. For example, under the default `en-US` locale `USD`, `GBP`, and `EUR` render as a prefix (`$ 1,000`), while eurozone locales such as `fr-FR` render `EUR` as a suffix (`1 000 €`). An explicit Prefix/Suffix selection is always honored and is unaffected.
|
||||
@@ -546,7 +540,7 @@ SQLALCHEMY_ENCRYPTED_FIELD_ENGINE = "aes"
|
||||
```bash
|
||||
superset re-encrypt-secrets --engine aes-gcm
|
||||
```
|
||||
A live instance keeps writing _new_ secrets as AES-CBC during the window between step 2 and the restart in step 4; this second pass sweeps those up (it is idempotent, so already-migrated values are skipped).
|
||||
A live instance keeps writing *new* secrets as AES-CBC during the window between step 2 and the restart in step 4; this second pass sweeps those up (it is idempotent, so already-migrated values are skipped).
|
||||
|
||||
Schedule the cutover in a quiet window. Runtime reads use only the single configured engine, so in a multi-worker deployment there is an unavoidable brief decrypt-outage between the migration commit and the last worker restarting with the new config — each migrator run is transactional, but the fleet-wide cutover is not zero-downtime.
|
||||
|
||||
@@ -574,11 +568,11 @@ With the flag enabled: `DELETE /api/v1/dataset/<id>` no longer hard-deletes the
|
||||
|
||||
**Schema migration:** the migration adds a nullable `deleted_at` column and an index on it (`ix_tables_deleted_at`) to the `tables` table. The column add is instant; the index build runs inline (no `CONCURRENTLY`) and may briefly block writes on the `tables` table (INSERT/UPDATE/DELETE are queued while the index builds; reads are unaffected) on large Postgres deployments. MySQL InnoDB builds the index online (no blocking). Production deployments with many thousands of datasets should run this migration during a maintenance window.
|
||||
|
||||
**Rollback note:** if the application code is rolled back after datasets have been soft-deleted, the older code path's visibility filter no longer applies and previously hidden rows become visible to the older code. Pair the rollback with a data decision (restore the rows, hard-delete them, or also downgrade the migration) rather than assuming the old hard-delete semantics still hold. **Downgrading the migration destroys the deletion markers**: `downgrade()` drops the `deleted_at` column, so any not-yet-restored soft-deleted datasets silently become live, active datasets with no record they were ever deleted. Reconcile the trash (restore or hard-delete each row) _before_ downgrading, and disable the `SOFT_DELETE` flag first so no new soft deletes land mid-rollback.
|
||||
**Rollback note:** if the application code is rolled back after datasets have been soft-deleted, the older code path's visibility filter no longer applies and previously hidden rows become visible to the older code. Pair the rollback with a data decision (restore the rows, hard-delete them, or also downgrade the migration) rather than assuming the old hard-delete semantics still hold. **Downgrading the migration destroys the deletion markers**: `downgrade()` drops the `deleted_at` column, so any not-yet-restored soft-deleted datasets silently become live, active datasets with no record they were ever deleted. Reconcile the trash (restore or hard-delete each row) *before* downgrading, and disable the `SOFT_DELETE` flag first so no new soft deletes land mid-rollback.
|
||||
|
||||
**SQL Lab / dataset-creation flows:** creating a dataset over a table whose dataset sits in the trash is refused. The SQL Lab "save as dataset" flow (`get_or_create_dataset`) and file uploads return a **422 naming the hidden twin and the restore endpoint**; the plain create, update, and duplicate paths currently fail with the generic "already exists" 422. In all cases the remediation is the same: restore the hidden dataset (or use a different table name). Perm-string maintenance also covers hidden rows: renaming a database rewrites `perm`/`schema_perm`/`catalog_perm` on soft-deleted datasets and their charts, so a later restore does not resurrect stale permission strings.
|
||||
|
||||
**Importer behavior:** importing a dataset YAML whose UUID matches an existing **soft-deleted** dataset is treated as an implicit restore-with-update — **and this happens even when `overwrite` is not set**. This is a deliberate asymmetry with active rows: an active dataset imported without `overwrite=true` is returned unchanged, but a soft-deleted UUID match is restored _and_ has the upload's contents applied regardless of the `overwrite` argument, on the reasoning that re-importing a deleted dataset's exact UUID is an explicit request to bring it back. The restore preserves the original PK, the chart back-reference, `table_columns`, and `sql_metrics`. Non-editors get `ImportFailedError`. Callers without `can_write` get `ImportFailedError` instead of silently receiving the soft-deleted row.
|
||||
**Importer behavior:** importing a dataset YAML whose UUID matches an existing **soft-deleted** dataset is treated as an implicit restore-with-update — **and this happens even when `overwrite` is not set**. This is a deliberate asymmetry with active rows: an active dataset imported without `overwrite=true` is returned unchanged, but a soft-deleted UUID match is restored *and* has the upload's contents applied regardless of the `overwrite` argument, on the reasoning that re-importing a deleted dataset's exact UUID is an explicit request to bring it back. The restore preserves the original PK, the chart back-reference, `table_columns`, and `sql_metrics`. Non-editors get `ImportFailedError`. Callers without `can_write` get `ImportFailedError` instead of silently receiving the soft-deleted row.
|
||||
|
||||
**Uniqueness-validation changes that apply regardless of the feature flag:** two dataset uniqueness checks were tightened alongside this work and are active even with `SOFT_DELETE` off. (1) Create/update uniqueness treats a dataset whose `catalog` is `NULL` as belonging to the database's default catalog, so a legacy twin pair (`catalog=NULL` vs. `catalog=<default>`, same database/schema/name) that older versions allowed now fails validation with "already exists" when either row is edited — resolve by renaming or removing one of the twins. (2) Duplicating a dataset now checks name collisions scoped to the target (database, catalog, schema) instead of globally by name alone: duplicates into other databases that were previously blocked are now allowed.
|
||||
|
||||
@@ -598,9 +592,9 @@ With the flag enabled: `DELETE /api/v1/chart/<id>` no longer hard-deletes the ch
|
||||
|
||||
**Schema migration:** the migration adds a nullable `deleted_at` column and an index on it (`ix_slices_deleted_at`) to the `slices` table. The column add is instant; the index build runs inline (no `CONCURRENTLY`) and may briefly block writes on the `slices` table (INSERT/UPDATE/DELETE are queued while the index builds; reads are unaffected) on large Postgres deployments. MySQL InnoDB builds the index online (no blocking).
|
||||
|
||||
**Rollback note:** if the application code is rolled back after charts have been soft-deleted, the older code path's visibility filter no longer applies and previously hidden rows become visible to the older code. Pair the rollback with a data decision (restore the rows, hard-delete them, or also downgrade the migration) rather than assuming the old hard-delete semantics still hold. **Downgrading the migration destroys the deletion markers**: `downgrade()` drops the `deleted_at` column, so any not-yet-restored soft-deleted charts silently become live, active charts with no record they were ever deleted. Reconcile the trash (restore or hard-delete each row) _before_ downgrading, and disable the `SOFT_DELETE` flag first so no new soft deletes land mid-rollback.
|
||||
**Rollback note:** if the application code is rolled back after charts have been soft-deleted, the older code path's visibility filter no longer applies and previously hidden rows become visible to the older code. Pair the rollback with a data decision (restore the rows, hard-delete them, or also downgrade the migration) rather than assuming the old hard-delete semantics still hold. **Downgrading the migration destroys the deletion markers**: `downgrade()` drops the `deleted_at` column, so any not-yet-restored soft-deleted charts silently become live, active charts with no record they were ever deleted. Reconcile the trash (restore or hard-delete each row) *before* downgrading, and disable the `SOFT_DELETE` flag first so no new soft deletes land mid-rollback.
|
||||
|
||||
**Importer behavior:** importing a chart YAML whose UUID matches an existing **soft-deleted** chart is treated as an implicit restore-with-update — **and this happens even when `overwrite` is not set**. This is a deliberate asymmetry with active rows: an active chart imported without `overwrite=true` is returned unchanged, but a soft-deleted UUID match is restored _and_ has the upload's contents applied regardless of the `overwrite` argument, on the reasoning that re-importing a deleted chart's exact UUID is an explicit request to bring it back. The restore preserves the original PK and all out-of-archive references (`dashboard_slices` junctions, `report.chart_id`, tag rows). The operation is permission-gated: non-editors get `ImportFailedError`, and callers without `can_write` get `ImportFailedError` instead of silently receiving the soft-deleted row.
|
||||
**Importer behavior:** importing a chart YAML whose UUID matches an existing **soft-deleted** chart is treated as an implicit restore-with-update — **and this happens even when `overwrite` is not set**. This is a deliberate asymmetry with active rows: an active chart imported without `overwrite=true` is returned unchanged, but a soft-deleted UUID match is restored *and* has the upload's contents applied regardless of the `overwrite` argument, on the reasoning that re-importing a deleted chart's exact UUID is an explicit request to bring it back. The restore preserves the original PK and all out-of-archive references (`dashboard_slices` junctions, `report.chart_id`, tag rows). The operation is permission-gated: non-editors get `ImportFailedError`, and callers without `can_write` get `ImportFailedError` instead of silently receiving the soft-deleted row.
|
||||
|
||||
- [39914](https://github.com/apache/superset/pull/39914) `ALERT_REPORT_SLACK_V2` now defaults to `True` and the legacy Slack v1 integration (`Slack` recipient type, `files.upload` API) is deprecated for removal in the next major. Slack blocked new apps from `files.upload` in May 2024 and fully retired the method for all apps on November 12, 2025; because the v1 path sends files through `files.upload`, v1 file-bearing sends now fail at the API level — only text-only `chat_postMessage` still works via the legacy path. Grant your Slack bot the `channels:read` and `groups:read` scopes so existing `Slack` recipients can be auto-upgraded to `SlackV2` on next send. Operators who explicitly override the flag to `False`, or whose Slack bot is missing those scopes, will see deprecation warnings while text-only sends continue through the legacy path.
|
||||
|
||||
@@ -624,7 +618,7 @@ The partial-index replacement is dialect-dependent: PostgreSQL uses a native `WH
|
||||
|
||||
**Slug semantics:** on PostgreSQL and MySQL 8.0.13+, the slug of a soft-deleted dashboard is **free for reuse**. A new active dashboard can claim it immediately. Restoring a soft-deleted dashboard whose slug has since been claimed returns **422 with a clean error** (`DashboardSlugConflictError`) — rename one of the dashboards and retry; the restore is not silently rejected by a database-level constraint violation.
|
||||
|
||||
**Importer behavior:** importing a dashboard YAML whose UUID matches an existing **soft-deleted** dashboard is treated as an implicit restore-with-update — **and this happens even when `overwrite` is not set**. This is a deliberate asymmetry with active rows: an active dashboard imported without `overwrite=true` is returned unchanged (the import never mutates it), but a soft-deleted UUID match is restored _and_ has the upload's contents applied regardless of the `overwrite` argument, on the reasoning that re-importing a deleted dashboard's exact UUID is an explicit request to bring it back. The restore preserves the original PK and all pre-deletion relationship rows (`dashboard_slices` junctions, editor/viewer subjects, tags). Callers whose imports must never mutate existing state should treat bundles that may contain previously deleted UUIDs accordingly. The operation is permission-gated: it requires `can_write` and editorship of the deleted row (or admin) — non-editors get `ImportFailedError`, and callers without `can_write` get `ImportFailedError` instead of silently receiving the soft-deleted row.
|
||||
**Importer behavior:** importing a dashboard YAML whose UUID matches an existing **soft-deleted** dashboard is treated as an implicit restore-with-update — **and this happens even when `overwrite` is not set**. This is a deliberate asymmetry with active rows: an active dashboard imported without `overwrite=true` is returned unchanged (the import never mutates it), but a soft-deleted UUID match is restored *and* has the upload's contents applied regardless of the `overwrite` argument, on the reasoning that re-importing a deleted dashboard's exact UUID is an explicit request to bring it back. The restore preserves the original PK and all pre-deletion relationship rows (`dashboard_slices` junctions, editor/viewer subjects, tags). Callers whose imports must never mutate existing state should treat bundles that may contain previously deleted UUIDs accordingly. The operation is permission-gated: it requires `can_write` and editorship of the deleted row (or admin) — non-editors get `ImportFailedError`, and callers without `can_write` get `ImportFailedError` instead of silently receiving the soft-deleted row.
|
||||
|
||||
### Engine spec capability flag: `supports_offset`
|
||||
|
||||
@@ -636,10 +630,10 @@ A new `BaseEngineSpec.supports_offset` attribute (default `True`) indicates whet
|
||||
|
||||
A new feature flag `GRANULAR_EXPORT_CONTROLS` introduces three fine-grained permissions that replace the legacy `can_csv` permission:
|
||||
|
||||
| Permission | Controls |
|
||||
| -------------------- | ---------------------------- |
|
||||
| `can_export_data` | CSV, Excel, JSON exports |
|
||||
| `can_export_image` | Screenshot/PDF exports |
|
||||
| Permission | Controls |
|
||||
|---|---|
|
||||
| `can_export_data` | CSV, Excel, JSON exports |
|
||||
| `can_export_image` | Screenshot/PDF exports |
|
||||
| `can_copy_clipboard` | Copy-to-clipboard operations |
|
||||
|
||||
When the feature flag is enabled, these permissions are enforced on both the frontend (disabled buttons with tooltips) and backend (403 responses from API endpoints). When disabled, legacy `can_csv` behavior is preserved.
|
||||
@@ -677,17 +671,14 @@ The Kenya country map has been updated to reflect the 47 counties established un
|
||||
MCP (Model Context Protocol) tools now include enhanced observability instrumentation for monitoring and debugging:
|
||||
|
||||
**Two-layer instrumentation:**
|
||||
|
||||
1. **Middleware layer** (`LoggingMiddleware`): Automatically logs all MCP tool calls with `duration_ms` and `success` status in the audit log (Action Log UI, logs table)
|
||||
2. **Sub-operation tracking**: All 19 MCP tools include granular `event_logger.log_context()` blocks for tracking individual operations like validation, database writes, and query execution
|
||||
|
||||
**Action naming convention:**
|
||||
|
||||
- Tool-level logs: `mcp_tool_call` (via middleware)
|
||||
- Sub-operation logs: `mcp.{tool_name}.{operation}` (e.g., `mcp.generate_chart.validation`, `mcp.execute_sql.query_execution`)
|
||||
|
||||
**Querying MCP logs:**
|
||||
|
||||
```sql
|
||||
-- Top slowest MCP operations
|
||||
SELECT action, COUNT(*) as calls, AVG(duration_ms) as avg_ms
|
||||
@@ -722,7 +713,6 @@ A new `DISTRIBUTED_COORDINATION_CONFIG` configuration provides a unified Redis-b
|
||||
The distributed coordination is used by the Global Task Framework (GTF) for abort notifications and task completion signaling, and will eventually replace `GLOBAL_ASYNC_QUERIES_CACHE_BACKEND` as the standard signaling backend. Configuring this is recommended for Redis enabled production deployments.
|
||||
|
||||
Example configuration in `superset_config.py`:
|
||||
|
||||
```python
|
||||
DISTRIBUTED_COORDINATION_CONFIG = {
|
||||
"CACHE_TYPE": "RedisCache",
|
||||
@@ -737,11 +727,9 @@ See `superset/config.py` for complete configuration options.
|
||||
### WebSocket config for GAQ with Docker
|
||||
|
||||
[35896](https://github.com/apache/superset/pull/35896) and [37624](https://github.com/apache/superset/pull/37624) updated documentation on how to run and configure Superset with Docker. Specifically for the WebSocket configuration, a new `docker/superset-websocket/config.example.json` was added to the repo, so that users could copy it to create a `docker/superset-websocket/config.json` file. The existing `docker/superset-websocket/config.json` was removed and git-ignored, so if you're using GAQ / WebSocket make sure to:
|
||||
|
||||
- Stash/backup your existing `config.json` file, to re-apply it after (will get git-ignored going forward)
|
||||
- Update the `volumes` configuration for the `superset-websocket` service in your `docker-compose.override.yml` file, to include the `docker/superset-websocket/config.json` file. For example:
|
||||
|
||||
```yaml
|
||||
``` yaml
|
||||
services:
|
||||
superset-websocket:
|
||||
volumes:
|
||||
@@ -754,9 +742,7 @@ services:
|
||||
### Example Data Loading Improvements
|
||||
|
||||
#### New Directory Structure
|
||||
|
||||
Examples are now organized by name with data and configs co-located:
|
||||
|
||||
```
|
||||
superset/examples/
|
||||
├── _shared/ # Shared database & metadata configs
|
||||
@@ -769,14 +755,12 @@ superset/examples/
|
||||
```
|
||||
|
||||
#### Simplified Parquet-based Loading
|
||||
|
||||
- Auto-discovery: create `superset/examples/my_dataset/data.parquet` to add a new example
|
||||
- Parquet is an Apache project format: compressed (~27% smaller), self-describing schema
|
||||
- YAML configs define datasets, charts, and dashboards declaratively
|
||||
- Removed Python-based data generation from individual example files
|
||||
|
||||
#### Test Data Reorganization
|
||||
|
||||
- Moved `big_data.py` to `superset/cli/test_loaders.py` - better reflects its purpose as a test utility
|
||||
- Fixed inverted logic for `--load-test-data` flag (now correctly includes .test.yaml files when flag is set)
|
||||
- Clarified CLI flags:
|
||||
@@ -786,7 +770,6 @@ superset/examples/
|
||||
- `--load-big-data` / `-b`: Generate synthetic stress-test data
|
||||
|
||||
#### Bug Fixes
|
||||
|
||||
- Fixed numpy array serialization for PostgreSQL (converts complex types to JSON strings)
|
||||
- Fixed KeyError for `allow_csv_upload` field in database configs (now optional with default)
|
||||
- Fixed test data loading logic that was incorrectly filtering files
|
||||
@@ -796,7 +779,6 @@ superset/examples/
|
||||
The MCP (Model Context Protocol) service enables AI assistants and automation tools to interact programmatically with Superset.
|
||||
|
||||
#### New Features
|
||||
|
||||
- MCP service infrastructure with FastMCP framework
|
||||
- Tools for dashboards, charts, datasets, SQL Lab, and instance metadata
|
||||
- Optional dependency: install with `pip install apache-superset[fastmcp]`
|
||||
@@ -806,7 +788,6 @@ The MCP (Model Context Protocol) service enables AI assistants and automation to
|
||||
#### New Configuration Options
|
||||
|
||||
**Development** (single-user, local testing):
|
||||
|
||||
```python
|
||||
# superset_config.py
|
||||
MCP_DEV_USERNAME = "admin" # User for MCP authentication
|
||||
@@ -815,7 +796,6 @@ MCP_SERVICE_PORT = 5008
|
||||
```
|
||||
|
||||
**Production** (JWT-based, multi-user):
|
||||
|
||||
```python
|
||||
# superset_config.py
|
||||
MCP_AUTH_ENABLED = True
|
||||
@@ -861,14 +841,12 @@ superset mcp run --port 5008 --use-factory-config
|
||||
The MCP service runs as a **separate process** from the Superset web server.
|
||||
|
||||
**Important**:
|
||||
|
||||
- Requires same Python environment and configuration as Superset
|
||||
- Shares database connections with main Superset app
|
||||
- Can be scaled independently from web server
|
||||
- Requires `fastmcp` package (optional dependency)
|
||||
|
||||
**Installation**:
|
||||
|
||||
```bash
|
||||
# Install with MCP support
|
||||
pip install apache-superset[fastmcp]
|
||||
@@ -882,7 +860,6 @@ Use systemd, supervisord, or Kubernetes to manage the MCP service process.
|
||||
See `superset/mcp_service/PRODUCTION.md` for deployment guides.
|
||||
|
||||
**Security**:
|
||||
|
||||
- Development: Uses `MCP_DEV_USERNAME` for single-user access
|
||||
- Production: **MUST** configure JWT authentication
|
||||
- See `superset/mcp_service/SECURITY.md` for details
|
||||
@@ -902,10 +879,8 @@ See `superset/mcp_service/PRODUCTION.md` for deployment guides.
|
||||
- [35062](https://github.com/apache/superset/pull/35062): Changed the function signature of `setupExtensions` to `setupCodeOverrides` with options as arguments.
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- [37370](https://github.com/apache/superset/pull/37370): The `APP_NAME` configuration variable no longer controls the browser window/tab title or other frontend branding. Application names should now be configured using the theme system with the `brandAppName` token. The `APP_NAME` config is still used for backend contexts (MCP service, logs, etc.) and serves as a fallback if `brandAppName` is not set.
|
||||
- **Migration:**
|
||||
|
||||
```python
|
||||
# Before (Superset 5.x)
|
||||
APP_NAME = "My Custom App"
|
||||
@@ -949,16 +924,16 @@ See `superset/mcp_service/PRODUCTION.md` for deployment guides.
|
||||
|
||||
Eight M:N association tables move from a synthetic `id INTEGER PRIMARY KEY` to a composite `PRIMARY KEY (fk1, fk2)` on their two foreign-key columns. The surrogate `id` is dropped, and the redundant `UNIQUE (fk1, fk2)` on the two tables that carried one is removed (now subsumed by the PK).
|
||||
|
||||
| Table | Composite PK |
|
||||
| ---------------------- | ------------------------------- |
|
||||
| `dashboard_roles` | `(dashboard_id, role_id)` |
|
||||
| `dashboard_slices` | `(dashboard_id, slice_id)` |
|
||||
| `dashboard_user` | `(user_id, dashboard_id)` |
|
||||
| Table | Composite PK |
|
||||
|---|---|
|
||||
| `dashboard_roles` | `(dashboard_id, role_id)` |
|
||||
| `dashboard_slices` | `(dashboard_id, slice_id)` |
|
||||
| `dashboard_user` | `(user_id, dashboard_id)` |
|
||||
| `report_schedule_user` | `(user_id, report_schedule_id)` |
|
||||
| `rls_filter_roles` | `(role_id, rls_filter_id)` |
|
||||
| `rls_filter_tables` | `(table_id, rls_filter_id)` |
|
||||
| `slice_user` | `(user_id, slice_id)` |
|
||||
| `sqlatable_user` | `(user_id, table_id)` |
|
||||
| `rls_filter_roles` | `(role_id, rls_filter_id)` |
|
||||
| `rls_filter_tables` | `(table_id, rls_filter_id)` |
|
||||
| `slice_user` | `(user_id, slice_id)` |
|
||||
| `sqlatable_user` | `(user_id, table_id)` |
|
||||
|
||||
**Before upgrading:**
|
||||
|
||||
@@ -969,7 +944,6 @@ Eight M:N association tables move from a synthetic `id INTEGER PRIMARY KEY` to a
|
||||
For large `dashboard_slices` / `report_schedule_user` tables, see the operator runbook in [#39859](https://github.com/apache/superset/pull/39859) — pre-flight inventory queries, per-dialect lock-window sizing, and the duplicate / NULL-FK roll-up — to plan the maintenance window.
|
||||
|
||||
## 6.0.0
|
||||
|
||||
- [33055](https://github.com/apache/superset/pull/33055): Upgrades Flask-AppBuilder to 5.0.0. The AUTH_OID authentication type has been deprecated and is no longer available as an option in Flask-AppBuilder. OpenID (OID) is considered a deprecated authentication protocol - if you are using AUTH_OID, you will need to migrate to an alternative authentication method such as OAuth, LDAP, or database authentication before upgrading.
|
||||
- [34871](https://github.com/apache/superset/pull/34871): Fixed Jest test hanging issue from Ant Design v5 upgrade. MessageChannel is now mocked in test environment to prevent rc-overflow from causing Jest to hang. Test environment only - no production impact.
|
||||
- [34782](https://github.com/apache/superset/pull/34782): Dataset exports now include the dataset ID in their file name (similar to charts and dashboards). If managing assets as code, make sure to rename existing dataset YAMLs to include the ID (and avoid duplicated files).
|
||||
@@ -978,8 +952,8 @@ For large `dashboard_slices` / `report_schedule_user` tables, see the operator r
|
||||
- Change any hex color values to one of: `"success"`, `"processing"`, `"error"`, `"warning"`, `"default"`
|
||||
- Custom colors are no longer supported to maintain consistency with Ant Design components
|
||||
- [34561](https://github.com/apache/superset/pull/34561) Added tiled screenshot functionality for Playwright-based reports to handle large dashboards more efficiently. When enabled (default: `SCREENSHOT_TILED_ENABLED = True`), dashboards with 20+ charts or height exceeding 5000px will be captured using multiple viewport-sized tiles and combined into a single image. This improves report generation performance and reliability for large dashboards.
|
||||
Note: Pillow is now a required dependency (previously optional) to support image processing for tiled screenshots.
|
||||
`thumbnails` optional dependency is now deprecated and will be removed in the next major release (7.0).
|
||||
Note: Pillow is now a required dependency (previously optional) to support image processing for tiled screenshots.
|
||||
`thumbnails` optional dependency is now deprecated and will be removed in the next major release (7.0).
|
||||
- [33084](https://github.com/apache/superset/pull/33084) The DISALLOWED_SQL_FUNCTIONS configuration now includes additional potentially sensitive database functions across PostgreSQL, MySQL, SQLite, MS SQL Server, and ClickHouse. Existing queries using these functions may now be blocked. Review your SQL Lab queries and dashboards if you encounter "disallowed function" errors after upgrading
|
||||
- [34235](https://github.com/apache/superset/pull/34235) CSV exports now use `utf-8-sig` encoding by default to include a UTF-8 BOM, improving compatibility with Excel.
|
||||
- [34258](https://github.com/apache/superset/pull/34258) changing the default in Dockerfile to INCLUDE_CHROMIUM="false" (from "true") in the past. This ensures the `lean` layer is lean by default, and people can opt-in to the `chromium` layer by setting the build arg `INCLUDE_CHROMIUM=true`. This is a breaking change for anyone using the `lean` layer, as it will no longer include Chromium by default.
|
||||
|
||||
@@ -18,7 +18,7 @@ code is less ambiguous and is unique to all regions in the world.
|
||||
## Included Maps
|
||||
|
||||
The current list of countries can be found in the src
|
||||
[plugin-chart-country-map/src/countries.ts](https://github.com/apache/superset/blob/master/superset-frontend/plugins/plugin-chart-country-map/src/countries.ts)
|
||||
[legacy-plugin-chart-country-map/src/countries.ts](https://github.com/apache/superset/blob/master/superset-frontend/plugins/legacy-plugin-chart-country-map/src/countries.ts)
|
||||
|
||||
The Country Maps visualization already ships with the maps for the following countries:
|
||||
|
||||
@@ -31,10 +31,10 @@ The Country Maps visualization already ships with the maps for the following cou
|
||||
## Adding a New Country
|
||||
|
||||
To add a new country to the list, you'd have to edit files in
|
||||
[@superset-ui/plugin-chart-country-map](https://github.com/apache/superset/tree/master/superset-frontend/plugins/plugin-chart-country-map).
|
||||
[@superset-ui/legacy-plugin-chart-country-map](https://github.com/apache/superset/tree/master/superset-frontend/plugins/legacy-plugin-chart-country-map).
|
||||
|
||||
1. Generate a new GeoJSON file for your country following the guide in [this Jupyter notebook](https://github.com/apache/superset/blob/master/superset-frontend/plugins/plugin-chart-country-map/scripts/Country%20Map%20GeoJSON%20Generator.ipynb).
|
||||
2. Edit the countries list in [plugin-chart-country-map/src/countries.ts](https://github.com/apache/superset/blob/master/superset-frontend/plugins/plugin-chart-country-map/src/countries.ts).
|
||||
1. Generate a new GeoJSON file for your country following the guide in [this Jupyter notebook](https://github.com/apache/superset/blob/master/superset-frontend/plugins/legacy-plugin-chart-country-map/scripts/Country%20Map%20GeoJSON%20Generator.ipynb).
|
||||
2. Edit the countries list in [legacy-plugin-chart-country-map/src/countries.ts](https://github.com/apache/superset/blob/master/superset-frontend/plugins/legacy-plugin-chart-country-map/src/countries.ts).
|
||||
3. Install superset-frontend dependencies: `cd superset-frontend && npm install`
|
||||
4. Verify your countries in Superset plugins storybook: `npm run plugins:storybook`.
|
||||
5. Build and install Superset from source code.
|
||||
|
||||
562
superset-frontend/package-lock.json
generated
@@ -51,22 +51,24 @@
|
||||
"@scarf/scarf": "^1.4.0",
|
||||
"@superset-ui/chart-controls": "file:./packages/superset-ui-chart-controls",
|
||||
"@superset-ui/core": "file:./packages/superset-ui-core",
|
||||
"@superset-ui/legacy-plugin-chart-calendar": "file:./plugins/legacy-plugin-chart-calendar",
|
||||
"@superset-ui/legacy-plugin-chart-chord": "file:./plugins/legacy-plugin-chart-chord",
|
||||
"@superset-ui/legacy-plugin-chart-country-map": "file:./plugins/legacy-plugin-chart-country-map",
|
||||
"@superset-ui/legacy-plugin-chart-horizon": "file:./plugins/legacy-plugin-chart-horizon",
|
||||
"@superset-ui/legacy-plugin-chart-paired-t-test": "file:./plugins/legacy-plugin-chart-paired-t-test",
|
||||
"@superset-ui/legacy-plugin-chart-parallel-coordinates": "file:./plugins/legacy-plugin-chart-parallel-coordinates",
|
||||
"@superset-ui/legacy-plugin-chart-partition": "file:./plugins/legacy-plugin-chart-partition",
|
||||
"@superset-ui/legacy-plugin-chart-rose": "file:./plugins/legacy-plugin-chart-rose",
|
||||
"@superset-ui/legacy-plugin-chart-world-map": "file:./plugins/legacy-plugin-chart-world-map",
|
||||
"@superset-ui/legacy-preset-chart-nvd3": "file:./plugins/legacy-preset-chart-nvd3",
|
||||
"@superset-ui/plugin-chart-ag-grid-table": "file:./plugins/plugin-chart-ag-grid-table",
|
||||
"@superset-ui/plugin-chart-calendar": "file:./plugins/plugin-chart-calendar",
|
||||
"@superset-ui/plugin-chart-cartodiagram": "file:./plugins/plugin-chart-cartodiagram",
|
||||
"@superset-ui/plugin-chart-chord": "file:./plugins/plugin-chart-chord",
|
||||
"@superset-ui/plugin-chart-country-map": "file:./plugins/plugin-chart-country-map",
|
||||
"@superset-ui/plugin-chart-echarts": "file:./plugins/plugin-chart-echarts",
|
||||
"@superset-ui/plugin-chart-handlebars": "file:./plugins/plugin-chart-handlebars",
|
||||
"@superset-ui/plugin-chart-horizon": "file:./plugins/plugin-chart-horizon",
|
||||
"@superset-ui/plugin-chart-paired-t-test": "file:./plugins/plugin-chart-paired-t-test",
|
||||
"@superset-ui/plugin-chart-parallel-coordinates": "file:./plugins/plugin-chart-parallel-coordinates",
|
||||
"@superset-ui/plugin-chart-partition": "file:./plugins/plugin-chart-partition",
|
||||
"@superset-ui/plugin-chart-pivot-table": "file:./plugins/plugin-chart-pivot-table",
|
||||
"@superset-ui/plugin-chart-point-cluster-map": "file:./plugins/plugin-chart-point-cluster-map",
|
||||
"@superset-ui/plugin-chart-table": "file:./plugins/plugin-chart-table",
|
||||
"@superset-ui/plugin-chart-word-cloud": "file:./plugins/plugin-chart-word-cloud",
|
||||
"@superset-ui/plugin-chart-world-map": "file:./plugins/plugin-chart-world-map",
|
||||
"@superset-ui/preset-chart-deckgl": "file:./plugins/preset-chart-deckgl",
|
||||
"@superset-ui/switchboard": "file:./packages/superset-ui-switchboard",
|
||||
"@types/d3-format": "^3.0.1",
|
||||
@@ -10768,26 +10770,54 @@
|
||||
"resolved": "packages/generator-superset",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@superset-ui/legacy-plugin-chart-calendar": {
|
||||
"resolved": "plugins/legacy-plugin-chart-calendar",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@superset-ui/legacy-plugin-chart-chord": {
|
||||
"resolved": "plugins/legacy-plugin-chart-chord",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@superset-ui/legacy-plugin-chart-country-map": {
|
||||
"resolved": "plugins/legacy-plugin-chart-country-map",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@superset-ui/legacy-plugin-chart-horizon": {
|
||||
"resolved": "plugins/legacy-plugin-chart-horizon",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@superset-ui/legacy-plugin-chart-paired-t-test": {
|
||||
"resolved": "plugins/legacy-plugin-chart-paired-t-test",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@superset-ui/legacy-plugin-chart-parallel-coordinates": {
|
||||
"resolved": "plugins/legacy-plugin-chart-parallel-coordinates",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@superset-ui/legacy-plugin-chart-partition": {
|
||||
"resolved": "plugins/legacy-plugin-chart-partition",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@superset-ui/legacy-plugin-chart-rose": {
|
||||
"resolved": "plugins/legacy-plugin-chart-rose",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@superset-ui/legacy-plugin-chart-world-map": {
|
||||
"resolved": "plugins/legacy-plugin-chart-world-map",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@superset-ui/legacy-preset-chart-nvd3": {
|
||||
"resolved": "plugins/legacy-preset-chart-nvd3",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@superset-ui/plugin-chart-ag-grid-table": {
|
||||
"resolved": "plugins/plugin-chart-ag-grid-table",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@superset-ui/plugin-chart-calendar": {
|
||||
"resolved": "plugins/plugin-chart-calendar",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@superset-ui/plugin-chart-cartodiagram": {
|
||||
"resolved": "plugins/plugin-chart-cartodiagram",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@superset-ui/plugin-chart-chord": {
|
||||
"resolved": "plugins/plugin-chart-chord",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@superset-ui/plugin-chart-country-map": {
|
||||
"resolved": "plugins/plugin-chart-country-map",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@superset-ui/plugin-chart-echarts": {
|
||||
"resolved": "plugins/plugin-chart-echarts",
|
||||
"link": true
|
||||
@@ -10796,22 +10826,6 @@
|
||||
"resolved": "plugins/plugin-chart-handlebars",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@superset-ui/plugin-chart-horizon": {
|
||||
"resolved": "plugins/plugin-chart-horizon",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@superset-ui/plugin-chart-paired-t-test": {
|
||||
"resolved": "plugins/plugin-chart-paired-t-test",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@superset-ui/plugin-chart-parallel-coordinates": {
|
||||
"resolved": "plugins/plugin-chart-parallel-coordinates",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@superset-ui/plugin-chart-partition": {
|
||||
"resolved": "plugins/plugin-chart-partition",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@superset-ui/plugin-chart-pivot-table": {
|
||||
"resolved": "plugins/plugin-chart-pivot-table",
|
||||
"link": true
|
||||
@@ -10828,10 +10842,6 @@
|
||||
"resolved": "plugins/plugin-chart-word-cloud",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@superset-ui/plugin-chart-world-map": {
|
||||
"resolved": "plugins/plugin-chart-world-map",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@superset-ui/preset-chart-deckgl": {
|
||||
"resolved": "plugins/preset-chart-deckgl",
|
||||
"link": true
|
||||
@@ -16523,6 +16533,12 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/cephes": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cephes/-/cephes-2.0.0.tgz",
|
||||
"integrity": "sha512-4GMUzkcXHZ0HMZ3gZdBrv8pQs1/zkJh2Q9rQOF8NJZHanM359y3XOSdeqmDBPfxQKYQpJt58R3dUpofrIXJ2mg==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/chai": {
|
||||
"version": "5.3.3",
|
||||
"resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz",
|
||||
@@ -18940,6 +18956,15 @@
|
||||
"readable-stream": "^3.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/distributions": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/distributions/-/distributions-2.2.0.tgz",
|
||||
"integrity": "sha512-n7ybud+CRAOZlpg+ETuA0PTiSBfyVNt8Okns5gSK4NvHwj7RamQoufptOucvVcTn9CV4DZ38p1k6TgwMexUNkQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cephes": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dnd-core": {
|
||||
"version": "11.1.3",
|
||||
"resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-11.1.3.tgz",
|
||||
@@ -20794,6 +20819,12 @@
|
||||
"integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==",
|
||||
"license": "(MIT AND Zlib)"
|
||||
},
|
||||
"node_modules/fast-safe-stringify": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
|
||||
"integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-uri": {
|
||||
"version": "3.1.4",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz",
|
||||
@@ -31474,6 +31505,15 @@
|
||||
"fflate": "^0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nvd3-fork": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/nvd3-fork/-/nvd3-fork-2.0.5.tgz",
|
||||
"integrity": "sha512-Sq3q2rvR/9FJ35LVmqdQJAnfmD15BaIHSBg5wZZL/WLcq/nthff8ukabwFdbW0zeE1c/yPq+DKl6MxnUTR45DA==",
|
||||
"license": "Apache-2.0",
|
||||
"peerDependencies": {
|
||||
"d3": "^3.4.4"
|
||||
}
|
||||
},
|
||||
"node_modules/nwsapi": {
|
||||
"version": "2.2.23",
|
||||
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.23.tgz",
|
||||
@@ -36010,6 +36050,15 @@
|
||||
"integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/reactable": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/reactable/-/reactable-1.1.0.tgz",
|
||||
"integrity": "sha512-SnvZ3CXyFFxGotw9cqNiVUGb2oW16UlIypGQZRJGgPiJuFqW22jO7A+Y/Tvv8no8F/bZoLdZ+QJP7eZfcc9kCw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "* || ^0.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/read": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/read/-/read-4.1.0.tgz",
|
||||
@@ -44135,6 +44184,245 @@
|
||||
"version": "0.20.3",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"plugins/legacy-plugin-chart-calendar": {
|
||||
"name": "@superset-ui/legacy-plugin-chart-calendar",
|
||||
"version": "0.20.3",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"d3-array": "^3.2.4",
|
||||
"d3-selection": "^3.0.0",
|
||||
"d3-tip": "^0.9.1",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@apache-superset/core": "*",
|
||||
"@emotion/react": "^11.4.1",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"react": "^18.3.0"
|
||||
}
|
||||
},
|
||||
"plugins/legacy-plugin-chart-calendar/node_modules/d3-array": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
|
||||
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"internmap": "1 - 2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"plugins/legacy-plugin-chart-calendar/node_modules/d3-selection": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
|
||||
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"plugins/legacy-plugin-chart-chord": {
|
||||
"name": "@superset-ui/legacy-plugin-chart-chord",
|
||||
"version": "0.20.3",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"d3": "^3.5.17",
|
||||
"prop-types": "^15.8.1",
|
||||
"react": "^19.2.7"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*"
|
||||
}
|
||||
},
|
||||
"plugins/legacy-plugin-chart-chord/node_modules/react": {
|
||||
"version": "19.2.7",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz",
|
||||
"integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"plugins/legacy-plugin-chart-country-map": {
|
||||
"name": "@superset-ui/legacy-plugin-chart-country-map",
|
||||
"version": "0.20.3",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"d3": "^3.5.17",
|
||||
"d3-array": "^3.2.4",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"react": "^18.3.0"
|
||||
}
|
||||
},
|
||||
"plugins/legacy-plugin-chart-country-map/node_modules/d3-array": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
|
||||
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"internmap": "1 - 2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"plugins/legacy-plugin-chart-horizon": {
|
||||
"name": "@superset-ui/legacy-plugin-chart-horizon",
|
||||
"version": "0.20.3",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"d3-array": "^3.2.4",
|
||||
"d3-scale": "^4.0.2",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"react": "^18.3.0"
|
||||
}
|
||||
},
|
||||
"plugins/legacy-plugin-chart-horizon/node_modules/d3-array": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
|
||||
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"internmap": "1 - 2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"plugins/legacy-plugin-chart-paired-t-test": {
|
||||
"name": "@superset-ui/legacy-plugin-chart-paired-t-test",
|
||||
"version": "0.20.3",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"distributions": "^2.2.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"reactable": "^1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"react": "^18.3.0"
|
||||
}
|
||||
},
|
||||
"plugins/legacy-plugin-chart-parallel-coordinates": {
|
||||
"name": "@superset-ui/legacy-plugin-chart-parallel-coordinates",
|
||||
"version": "0.20.3",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"d3v3": "npm:d3@3.5.17",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"react": "^18.3.0"
|
||||
}
|
||||
},
|
||||
"plugins/legacy-plugin-chart-partition": {
|
||||
"name": "@superset-ui/legacy-plugin-chart-partition",
|
||||
"version": "0.20.3",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"d3": "^3.5.17",
|
||||
"d3-hierarchy": "^3.1.2",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"@testing-library/jest-dom": "*",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"react": "^18.3.0",
|
||||
"react-dom": "^18.3.0"
|
||||
}
|
||||
},
|
||||
"plugins/legacy-plugin-chart-rose": {
|
||||
"name": "@superset-ui/legacy-plugin-chart-rose",
|
||||
"version": "0.20.3",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"d3": "^3.5.17",
|
||||
"nvd3-fork": "^2.0.5",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@apache-superset/core": "*",
|
||||
"@emotion/react": "^11.4.1",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"react": "^18.3.0"
|
||||
}
|
||||
},
|
||||
"plugins/legacy-plugin-chart-world-map": {
|
||||
"name": "@superset-ui/legacy-plugin-chart-world-map",
|
||||
"version": "0.20.3",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"d3": "^3.5.17",
|
||||
"d3-array": "^3.2.4",
|
||||
"datamaps": "^0.5.10",
|
||||
"prop-types": "^15.8.1",
|
||||
"tinycolor2": "^1.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"react": "^18.3.0"
|
||||
}
|
||||
},
|
||||
"plugins/legacy-plugin-chart-world-map/node_modules/d3-array": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
|
||||
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"internmap": "1 - 2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"plugins/legacy-preset-chart-nvd3": {
|
||||
"name": "@superset-ui/legacy-preset-chart-nvd3",
|
||||
"version": "0.20.3",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"d3": "^3.5.17",
|
||||
"d3-tip": "^0.9.1",
|
||||
"dompurify": "^3.4.12",
|
||||
"fast-safe-stringify": "^2.1.1",
|
||||
"lodash": "^4.18.1",
|
||||
"lodash-es": "^4.18.1",
|
||||
"nvd3-fork": "^2.0.5",
|
||||
"prop-types": "^15.8.1",
|
||||
"urijs": "^1.19.11"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"dayjs": "^1.11.21",
|
||||
"react": "^18.3.0"
|
||||
}
|
||||
},
|
||||
"plugins/plugin-chart-ag-grid-table": {
|
||||
"name": "@superset-ui/plugin-chart-ag-grid-table",
|
||||
"version": "0.20.3",
|
||||
@@ -44177,45 +44465,6 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"plugins/plugin-chart-calendar": {
|
||||
"name": "@superset-ui/plugin-chart-calendar",
|
||||
"version": "0.20.3",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"d3-array": "^3.2.4",
|
||||
"d3-selection": "^3.0.0",
|
||||
"d3-tip": "^0.9.1",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@apache-superset/core": "*",
|
||||
"@emotion/react": "^11.4.1",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"react": "^18.3.0"
|
||||
}
|
||||
},
|
||||
"plugins/plugin-chart-calendar/node_modules/d3-array": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
|
||||
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"internmap": "1 - 2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"plugins/plugin-chart-calendar/node_modules/d3-selection": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
|
||||
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"plugins/plugin-chart-cartodiagram": {
|
||||
"name": "@superset-ui/plugin-chart-cartodiagram",
|
||||
"version": "0.0.1",
|
||||
@@ -44244,58 +44493,6 @@
|
||||
"react-dom": "^18.3.0"
|
||||
}
|
||||
},
|
||||
"plugins/plugin-chart-chord": {
|
||||
"name": "@superset-ui/plugin-chart-chord",
|
||||
"version": "0.20.3",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"d3": "^3.5.17",
|
||||
"prop-types": "^15.8.1",
|
||||
"react": "^19.2.7"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*"
|
||||
}
|
||||
},
|
||||
"plugins/plugin-chart-chord/node_modules/react": {
|
||||
"version": "19.2.7",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz",
|
||||
"integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"plugins/plugin-chart-country-map": {
|
||||
"name": "@superset-ui/plugin-chart-country-map",
|
||||
"version": "0.20.3",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"d3": "^3.5.17",
|
||||
"d3-array": "^3.2.4",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"react": "^18.3.0"
|
||||
}
|
||||
},
|
||||
"plugins/plugin-chart-country-map/node_modules/d3-array": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
|
||||
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"internmap": "1 - 2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"plugins/plugin-chart-echarts": {
|
||||
"name": "@superset-ui/plugin-chart-echarts",
|
||||
"version": "0.20.3",
|
||||
@@ -44383,79 +44580,6 @@
|
||||
"sprintf-js": ">= 1.1.1 < 2"
|
||||
}
|
||||
},
|
||||
"plugins/plugin-chart-horizon": {
|
||||
"name": "@superset-ui/plugin-chart-horizon",
|
||||
"version": "0.20.3",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"d3-array": "^3.2.4",
|
||||
"d3-scale": "^4.0.2",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"react": "^18.3.0"
|
||||
}
|
||||
},
|
||||
"plugins/plugin-chart-horizon/node_modules/d3-array": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
|
||||
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"internmap": "1 - 2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"plugins/plugin-chart-paired-t-test": {
|
||||
"name": "@superset-ui/plugin-chart-paired-t-test",
|
||||
"version": "0.20.3",
|
||||
"license": "Apache-2.0",
|
||||
"peerDependencies": {
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"react": "^18.3.0"
|
||||
}
|
||||
},
|
||||
"plugins/plugin-chart-parallel-coordinates": {
|
||||
"name": "@superset-ui/plugin-chart-parallel-coordinates",
|
||||
"version": "0.20.3",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"d3v3": "npm:d3@3.5.17",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"react": "^18.3.0"
|
||||
}
|
||||
},
|
||||
"plugins/plugin-chart-partition": {
|
||||
"name": "@superset-ui/plugin-chart-partition",
|
||||
"version": "0.20.3",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"d3": "^3.5.17",
|
||||
"d3-hierarchy": "^3.1.2",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"@testing-library/jest-dom": "*",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"react": "^18.3.0",
|
||||
"react-dom": "^18.3.0"
|
||||
}
|
||||
},
|
||||
"plugins/plugin-chart-pivot-table": {
|
||||
"name": "@superset-ui/plugin-chart-pivot-table",
|
||||
"version": "0.20.3",
|
||||
@@ -44564,36 +44688,6 @@
|
||||
"react": "^18.3.0"
|
||||
}
|
||||
},
|
||||
"plugins/plugin-chart-world-map": {
|
||||
"name": "@superset-ui/plugin-chart-world-map",
|
||||
"version": "0.20.3",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"d3": "^3.5.17",
|
||||
"d3-array": "^3.2.4",
|
||||
"datamaps": "^0.5.10",
|
||||
"prop-types": "^15.8.1",
|
||||
"tinycolor2": "^1.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@apache-superset/core": "*",
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"react": "^18.3.0"
|
||||
}
|
||||
},
|
||||
"plugins/plugin-chart-world-map/node_modules/d3-array": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
|
||||
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"internmap": "1 - 2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"plugins/preset-chart-deckgl": {
|
||||
"name": "@superset-ui/preset-chart-deckgl",
|
||||
"version": "1.0.0",
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
"test": "cross-env NODE_ENV=test NODE_OPTIONS=\"--max-old-space-size=8192\" jest --max-workers=80% --silent",
|
||||
"test-loud": "cross-env NODE_ENV=test NODE_OPTIONS=\"--max-old-space-size=8192\" jest --max-workers=80%",
|
||||
"type": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" tsc --noEmit",
|
||||
"update-maps": "cd plugins/plugin-chart-country-map/scripts && jupyter nbconvert --to notebook --execute --inplace --allow-errors --ExecutePreprocessor.timeout=1200 'Country Map GeoJSON Generator.ipynb'",
|
||||
"update-maps": "cd plugins/legacy-plugin-chart-country-map/scripts && jupyter nbconvert --to notebook --execute --inplace --allow-errors --ExecutePreprocessor.timeout=1200 'Country Map GeoJSON Generator.ipynb'",
|
||||
"validate-release": "../RELEASING/validate_this_release.sh"
|
||||
},
|
||||
"browserslist": [
|
||||
@@ -136,14 +136,16 @@
|
||||
"@scarf/scarf": "^1.4.0",
|
||||
"@superset-ui/chart-controls": "file:./packages/superset-ui-chart-controls",
|
||||
"@superset-ui/core": "file:./packages/superset-ui-core",
|
||||
"@superset-ui/plugin-chart-calendar": "file:./plugins/plugin-chart-calendar",
|
||||
"@superset-ui/plugin-chart-chord": "file:./plugins/plugin-chart-chord",
|
||||
"@superset-ui/plugin-chart-country-map": "file:./plugins/plugin-chart-country-map",
|
||||
"@superset-ui/plugin-chart-horizon": "file:./plugins/plugin-chart-horizon",
|
||||
"@superset-ui/plugin-chart-paired-t-test": "file:./plugins/plugin-chart-paired-t-test",
|
||||
"@superset-ui/plugin-chart-parallel-coordinates": "file:./plugins/plugin-chart-parallel-coordinates",
|
||||
"@superset-ui/plugin-chart-partition": "file:./plugins/plugin-chart-partition",
|
||||
"@superset-ui/plugin-chart-world-map": "file:./plugins/plugin-chart-world-map",
|
||||
"@superset-ui/legacy-plugin-chart-calendar": "file:./plugins/legacy-plugin-chart-calendar",
|
||||
"@superset-ui/legacy-plugin-chart-chord": "file:./plugins/legacy-plugin-chart-chord",
|
||||
"@superset-ui/legacy-plugin-chart-country-map": "file:./plugins/legacy-plugin-chart-country-map",
|
||||
"@superset-ui/legacy-plugin-chart-horizon": "file:./plugins/legacy-plugin-chart-horizon",
|
||||
"@superset-ui/legacy-plugin-chart-paired-t-test": "file:./plugins/legacy-plugin-chart-paired-t-test",
|
||||
"@superset-ui/legacy-plugin-chart-parallel-coordinates": "file:./plugins/legacy-plugin-chart-parallel-coordinates",
|
||||
"@superset-ui/legacy-plugin-chart-partition": "file:./plugins/legacy-plugin-chart-partition",
|
||||
"@superset-ui/legacy-plugin-chart-rose": "file:./plugins/legacy-plugin-chart-rose",
|
||||
"@superset-ui/legacy-plugin-chart-world-map": "file:./plugins/legacy-plugin-chart-world-map",
|
||||
"@superset-ui/legacy-preset-chart-nvd3": "file:./plugins/legacy-preset-chart-nvd3",
|
||||
"@superset-ui/plugin-chart-ag-grid-table": "file:./plugins/plugin-chart-ag-grid-table",
|
||||
"@superset-ui/plugin-chart-cartodiagram": "file:./plugins/plugin-chart-cartodiagram",
|
||||
"@superset-ui/plugin-chart-echarts": "file:./plugins/plugin-chart-echarts",
|
||||
|
||||
@@ -104,15 +104,24 @@ export default class ChartClient {
|
||||
const buildQueryRegistry = getChartBuildQueryRegistry();
|
||||
|
||||
if (metaDataRegistry.has(visType)) {
|
||||
const { useLegacyApi } = metaDataRegistry.get(visType)!;
|
||||
const buildQuery =
|
||||
(await buildQueryRegistry.get(visType)) ?? (() => formData);
|
||||
const requestConfig: RequestConfig = {
|
||||
endpoint: '/api/v1/chart/data',
|
||||
jsonPayload: {
|
||||
query_context: buildQuery(formData),
|
||||
},
|
||||
...options,
|
||||
};
|
||||
const requestConfig: RequestConfig = useLegacyApi
|
||||
? {
|
||||
endpoint: '/explore_json/',
|
||||
postPayload: {
|
||||
form_data: buildQuery(formData),
|
||||
},
|
||||
...options,
|
||||
}
|
||||
: {
|
||||
endpoint: '/api/v1/chart/data',
|
||||
jsonPayload: {
|
||||
query_context: buildQuery(formData),
|
||||
},
|
||||
...options,
|
||||
};
|
||||
|
||||
return this.client
|
||||
.post(requestConfig)
|
||||
|
||||
@@ -21,6 +21,7 @@ import { render, waitFor, configure, act } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
import StatefulChart from './StatefulChart';
|
||||
import getChartControlPanelRegistry from '../registries/ChartControlPanelRegistrySingleton';
|
||||
import getChartMetadataRegistry from '../registries/ChartMetadataRegistrySingleton';
|
||||
import getChartBuildQueryRegistry from '../registries/ChartBuildQueryRegistrySingleton';
|
||||
|
||||
// Configure testing library to use data-test attribute
|
||||
@@ -28,6 +29,7 @@ configure({ testIdAttribute: 'data-test' });
|
||||
|
||||
// Mock the registries
|
||||
jest.mock('../registries/ChartControlPanelRegistrySingleton');
|
||||
jest.mock('../registries/ChartMetadataRegistrySingleton');
|
||||
jest.mock('../registries/ChartBuildQueryRegistrySingleton');
|
||||
jest.mock('../clients/ChartClient');
|
||||
|
||||
@@ -65,6 +67,12 @@ beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
// Setup default registry mocks
|
||||
jest.mocked(getChartMetadataRegistry).mockReturnValue({
|
||||
get: jest.fn().mockReturnValue({
|
||||
useLegacyApi: false,
|
||||
}),
|
||||
} as unknown as ReturnType<typeof getChartMetadataRegistry>);
|
||||
|
||||
jest.mocked(getChartBuildQueryRegistry).mockReturnValue({
|
||||
get: jest.fn().mockResolvedValue(null),
|
||||
} as unknown as ReturnType<typeof getChartBuildQueryRegistry>);
|
||||
@@ -738,10 +746,11 @@ test('resolves async (202) responses via the injected handleAsyncChartData hook'
|
||||
await waitFor(() => {
|
||||
expect(handleAsyncChartData).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
// Delegates the raw response + job metadata (and abort signal)
|
||||
// Delegates the raw response + job metadata (and useLegacyApi + abort signal)
|
||||
expect(handleAsyncChartData).toHaveBeenCalledWith(
|
||||
{ status: 202 },
|
||||
asyncJob,
|
||||
false,
|
||||
expect.any(AbortSignal),
|
||||
);
|
||||
// Chart renders once the async data resolves
|
||||
@@ -789,6 +798,43 @@ test('renders synchronous (200) responses that include a response object', async
|
||||
expect(mockChartClient.client.post).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('wraps the legacy async body as { result: [body] } for the async handler', async () => {
|
||||
const legacyBody = { job_id: 'j1', channel_id: 'c1', status: 'running' };
|
||||
mockChartClient.client.post.mockResolvedValue({
|
||||
response: { status: 202 } as Response,
|
||||
json: legacyBody,
|
||||
});
|
||||
// Force the legacy API path for this viz type
|
||||
jest.mocked(getChartMetadataRegistry).mockReturnValue({
|
||||
get: jest.fn().mockReturnValue({ useLegacyApi: true }),
|
||||
} as unknown as ReturnType<typeof getChartMetadataRegistry>);
|
||||
const handleAsyncChartData = jest
|
||||
.fn()
|
||||
.mockResolvedValue([{ data: 'legacy result' }]);
|
||||
|
||||
const { getByTestId } = render(
|
||||
<StatefulChart
|
||||
formData={mockFormData}
|
||||
chartType="test_chart"
|
||||
hooks={{ handleAsyncChartData }}
|
||||
/>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(handleAsyncChartData).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
// Legacy body must be wrapped to match the V1 response signature
|
||||
expect(handleAsyncChartData).toHaveBeenCalledWith(
|
||||
{ status: 202 },
|
||||
{ result: [legacyBody] },
|
||||
true,
|
||||
expect.any(AbortSignal),
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(getByTestId('super-chart')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
test('does not apply a superseded async response over a newer one', async () => {
|
||||
mockChartClient.client.post.mockResolvedValue({
|
||||
response: { status: 202 } as Response,
|
||||
@@ -978,7 +1024,7 @@ test('passes an abort signal to the async handler and aborts it on unmount', asy
|
||||
response: { status: 202 } as Response,
|
||||
json: { job_id: 'j', channel_id: 'c' },
|
||||
});
|
||||
// Typed with a rest param so mock.calls is indexable (the 3rd arg is the signal)
|
||||
// Typed with a rest param so mock.calls is indexable (the 4th arg is the signal)
|
||||
const handleAsyncChartData = jest.fn(
|
||||
(..._args: unknown[]) => new Promise<never>(() => {}), // never resolves
|
||||
);
|
||||
@@ -994,7 +1040,7 @@ test('passes an abort signal to the async handler and aborts it on unmount', asy
|
||||
await waitFor(() => {
|
||||
expect(handleAsyncChartData).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
const signal = handleAsyncChartData.mock.calls[0][2] as AbortSignal;
|
||||
const signal = handleAsyncChartData.mock.calls[0][3] as AbortSignal;
|
||||
expect(signal).toBeInstanceOf(AbortSignal);
|
||||
expect(signal.aborted).toBe(false);
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
import { Loading } from '../../components/Loading';
|
||||
import ChartClient from '../clients/ChartClient';
|
||||
import getChartBuildQueryRegistry from '../registries/ChartBuildQueryRegistrySingleton';
|
||||
import getChartMetadataRegistry from '../registries/ChartMetadataRegistrySingleton';
|
||||
import getChartControlPanelRegistry from '../registries/ChartControlPanelRegistrySingleton';
|
||||
import SuperChart from './SuperChart';
|
||||
|
||||
@@ -280,6 +281,9 @@ export default function StatefulChart(props: StatefulChartProps) {
|
||||
}
|
||||
finalFormData.viz_type = vizType;
|
||||
|
||||
// Get chart metadata
|
||||
const { useLegacyApi } = getChartMetadataRegistry().get(vizType) || {};
|
||||
|
||||
// Build query using the chart's buildQuery function
|
||||
const buildQuery = await getChartBuildQueryRegistry().get(vizType);
|
||||
let queryContext;
|
||||
@@ -291,20 +295,31 @@ export default function StatefulChart(props: StatefulChartProps) {
|
||||
queryContext = buildQueryContext(finalFormData);
|
||||
}
|
||||
|
||||
// Ensure query_context is properly formatted for the API
|
||||
if (!queryContext.queries) {
|
||||
// Ensure query_context is properly formatted for new API
|
||||
if (!useLegacyApi && !queryContext.queries) {
|
||||
queryContext = { queries: [queryContext] };
|
||||
}
|
||||
const endpoint = useLegacyApi ? '/explore_json/' : '/api/v1/chart/data';
|
||||
|
||||
const requestConfig: RequestConfig = {
|
||||
endpoint: '/api/v1/chart/data',
|
||||
endpoint,
|
||||
signal: controller.signal,
|
||||
...(timeout && { timeout: timeout * 1000 }),
|
||||
jsonPayload: {
|
||||
};
|
||||
|
||||
if (useLegacyApi) {
|
||||
requestConfig.postPayload = {
|
||||
form_data: {
|
||||
...finalFormData,
|
||||
...(force && { force: true }),
|
||||
},
|
||||
};
|
||||
} else {
|
||||
requestConfig.jsonPayload = {
|
||||
...queryContext,
|
||||
...(force && { force: true }),
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
const clientResponse =
|
||||
await chartClientRef.current!.client.post(requestConfig);
|
||||
@@ -332,10 +347,18 @@ export default function StatefulChart(props: StatefulChartProps) {
|
||||
'the async handler or disable GLOBAL_ASYNC_QUERIES for this chart.',
|
||||
);
|
||||
}
|
||||
// The async handler (handleChartDataResponse) expects the V1 chart data
|
||||
// response signature. The legacy endpoint returns a flat body, so wrap
|
||||
// it as { result: [body] } exactly like legacyChartDataRequest does for
|
||||
// the standard chart path; the V1 body is already correctly shaped.
|
||||
const asyncPayload = useLegacyApi
|
||||
? ({ result: [clientResponse.json] } as JsonObject)
|
||||
: (clientResponse.json as JsonObject);
|
||||
responseData = ensureIsArray(
|
||||
await hooks.handleAsyncChartData(
|
||||
rawResponse,
|
||||
clientResponse.json as JsonObject,
|
||||
asyncPayload,
|
||||
useLegacyApi,
|
||||
controller.signal,
|
||||
),
|
||||
);
|
||||
@@ -351,8 +374,10 @@ export default function StatefulChart(props: StatefulChartProps) {
|
||||
: [clientResponse.json]
|
||||
) as JsonObject[];
|
||||
|
||||
// Handle the nested result structure from the API
|
||||
responseData = (rows[0]?.result ? rows[0].result : rows) as QueryData[];
|
||||
// Handle the nested result structure from the new API
|
||||
responseData = (
|
||||
!useLegacyApi && rows[0]?.result ? rows[0].result : rows
|
||||
) as QueryData[];
|
||||
}
|
||||
|
||||
// Don't pair this request's data with newer props or fire a stale onLoad
|
||||
|
||||
@@ -40,6 +40,7 @@ export interface ChartMetadataConfig {
|
||||
supportedAnnotationTypes?: string[];
|
||||
thumbnail: string;
|
||||
thumbnailDark?: string;
|
||||
useLegacyApi?: boolean;
|
||||
behaviors?: Behavior[];
|
||||
exampleGallery?: ExampleImage[];
|
||||
tags?: string[];
|
||||
@@ -74,6 +75,8 @@ export default class ChartMetadata {
|
||||
|
||||
thumbnailDark?: string;
|
||||
|
||||
useLegacyApi: boolean;
|
||||
|
||||
behaviors: Behavior[];
|
||||
|
||||
datasourceCount: number;
|
||||
@@ -109,6 +112,7 @@ export default class ChartMetadata {
|
||||
supportedAnnotationTypes = [],
|
||||
thumbnail,
|
||||
thumbnailDark,
|
||||
useLegacyApi = false,
|
||||
behaviors = [],
|
||||
datasourceCount = 1,
|
||||
enableNoResults = true,
|
||||
@@ -140,6 +144,7 @@ export default class ChartMetadata {
|
||||
this.supportedAnnotationTypes = supportedAnnotationTypes;
|
||||
this.thumbnail = thumbnail;
|
||||
this.thumbnailDark = thumbnailDark;
|
||||
this.useLegacyApi = useLegacyApi;
|
||||
this.behaviors = behaviors;
|
||||
this.datasourceCount = datasourceCount;
|
||||
this.enableNoResults = enableNoResults;
|
||||
|
||||
@@ -75,6 +75,7 @@ type Hooks = {
|
||||
handleAsyncChartData?: (
|
||||
response: Response,
|
||||
json: JsonObject,
|
||||
useLegacyApi?: boolean,
|
||||
signal?: AbortSignal,
|
||||
) => Promise<QueryData[]> | QueryData[];
|
||||
} & PlainObject;
|
||||
|
||||
@@ -89,6 +89,7 @@ const StyledItem = styled.div<{
|
||||
& .metadata-text {
|
||||
color: ${theme.colorTextSecondary};
|
||||
min-width: ${TEXT_MIN_WIDTH}px;
|
||||
max-width: ${TEXT_MAX_WIDTH}px;
|
||||
overflow: hidden;
|
||||
text-overflow: ${collapsed ? 'unset' : 'ellipsis'};
|
||||
white-space: nowrap;
|
||||
|
||||
@@ -1032,6 +1032,35 @@ test('do not count unselected disabled options in "Select all"', async () => {
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('"Select all" does not count null-valued options', async () => {
|
||||
// A falsy-valued option (e.g. <NULL>, value: null) is skipped by
|
||||
// handleSelectAll, so it must not be counted in the "Select all" badge or
|
||||
// the count overstates the selection. Regression test for #40228. Uses a
|
||||
// local options array to stay isolated from tests that mutate OPTIONS.
|
||||
const localOptions = [
|
||||
{ label: 'Alpha', value: 1 },
|
||||
{ label: 'Bravo', value: 2 },
|
||||
];
|
||||
render(
|
||||
<Select
|
||||
{...defaultProps}
|
||||
options={[...localOptions, NULL_OPTION]}
|
||||
mode="multiple"
|
||||
maxTagCount={0}
|
||||
/>,
|
||||
);
|
||||
await open();
|
||||
// Three options are visible, but the <NULL> option is not bulk-selectable,
|
||||
// so the badge must count only the two real options (would be 3 before fix).
|
||||
await userEvent.click(
|
||||
await screen.findByText(selectAllButtonText(localOptions.length)),
|
||||
);
|
||||
// And Select all selects exactly those two — the null option is skipped.
|
||||
const values = await findAllSelectValues();
|
||||
expect(values.length).toBe(1);
|
||||
expect(values[0]).toHaveTextContent(`+ ${localOptions.length} ...`);
|
||||
});
|
||||
|
||||
test('"Deselect all" counts all selected options', async () => {
|
||||
render(<Select {...defaultProps} allowNewOptions mode="multiple" />);
|
||||
await open();
|
||||
|
||||
@@ -332,7 +332,12 @@ const Select = forwardRef(
|
||||
const isDisabled = option.disabled;
|
||||
const isNew = option.isNewOption;
|
||||
|
||||
// Mirror handleSelectAll, which skips falsy-valued options (e.g. the
|
||||
// <NULL> option whose value is null): they are not bulk-selectable,
|
||||
// so counting them here makes the "Select all" badge overstate what
|
||||
// gets selected.
|
||||
if (
|
||||
option.value &&
|
||||
(!isDisabled || isSelected) &&
|
||||
((isNew && isSelected) || !isNew)
|
||||
) {
|
||||
|
||||
@@ -160,6 +160,42 @@ describe('ChartClient', () => {
|
||||
datasource: '1__table',
|
||||
}),
|
||||
).rejects.toEqual(new Error('Unknown chart type: rainbow_3d_pie')));
|
||||
|
||||
test('fetches data from the legacy API if ChartMetadata has useLegacyApi=true,', () => {
|
||||
// note legacy charts do not register a buildQuery function in the registry
|
||||
getChartMetadataRegistry().registerValue(
|
||||
'word_cloud_legacy',
|
||||
new ChartMetadata({
|
||||
name: 'Legacy Word Cloud',
|
||||
thumbnail: '.png',
|
||||
useLegacyApi: true,
|
||||
}),
|
||||
);
|
||||
|
||||
fetchMock.post('glob:*/api/v1/chart/data', () =>
|
||||
Promise.reject(new Error('Unexpected all to v1 API')),
|
||||
);
|
||||
|
||||
// post `Superset.route_base = ""`, the legacy endpoint
|
||||
// collapsed from `/superset/explore_json/` to `/explore_json/`.
|
||||
fetchMock.post('glob:*/explore_json/', {
|
||||
field1: 'abc',
|
||||
field2: 'def',
|
||||
});
|
||||
|
||||
return expect(
|
||||
chartClient.loadQueryData({
|
||||
granularity: 'minute',
|
||||
viz_type: 'word_cloud_legacy',
|
||||
datasource: '1__table',
|
||||
}),
|
||||
).resolves.toEqual([
|
||||
{
|
||||
field1: 'abc',
|
||||
field2: 'def',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('.loadDatasource(datasourceKey, options)', () => {
|
||||
|
||||
@@ -38,14 +38,6 @@
|
||||
*
|
||||
* NOTE: the embedded suite only runs when the embedded SDK bundle is built and
|
||||
* INCLUDE_EMBEDDED=true (CI sets both). It is skipped otherwise.
|
||||
*
|
||||
* NOTE: the embedded project runs with admin storageState, and the session
|
||||
* cookie the iframe ends up with is racy: the /embedded/<uuid> response
|
||||
* rotates it to an anonymous session while the SDK's parallel csrf and
|
||||
* guest-token fetches rewrite the admin one, so chart data requests are
|
||||
* evaluated as either admin or guest depending on which response lands last.
|
||||
* Fixture charts must therefore hold up under GUEST evaluation — see the
|
||||
* query_context note below — or the suite only passes when admin wins.
|
||||
*/
|
||||
import { test, expect, Browser, BrowserContext, Page } from '@playwright/test';
|
||||
import { createServer, IncomingMessage, ServerResponse, Server } from 'http';
|
||||
@@ -190,43 +182,12 @@ test.describe('Embedded Pivot Table collapse state (#33406)', () => {
|
||||
row_limit: 1000,
|
||||
order_desc: true,
|
||||
};
|
||||
// Charts saved through Explore always persist a query_context alongside
|
||||
// params. Store one here too: guest (embedded) requests are validated
|
||||
// against the stored chart, and a params-only chart makes the guest
|
||||
// payload look tampered (its query `columns` aren't found on the chart),
|
||||
// failing every chart data request with a 403.
|
||||
const queryContext = {
|
||||
datasource: { id: datasetId, type: 'table' },
|
||||
force: false,
|
||||
queries: [
|
||||
{
|
||||
filters: [],
|
||||
extras: { having: '', where: '' },
|
||||
applied_time_extras: {},
|
||||
columns: ['state', 'name'],
|
||||
metrics: ['count'],
|
||||
orderby: [['count', false]],
|
||||
annotation_layers: [],
|
||||
row_limit: 1000,
|
||||
series_limit: 0,
|
||||
order_desc: true,
|
||||
url_params: {},
|
||||
custom_params: {},
|
||||
custom_form_data: {},
|
||||
},
|
||||
],
|
||||
form_data: params,
|
||||
result_format: 'json',
|
||||
result_type: 'full',
|
||||
};
|
||||
const chartResp = await apiPost(setupPage, 'api/v1/chart/', {
|
||||
slice_name: `pivot_collapse_repro_${Date.now()}`,
|
||||
viz_type: 'pivot_table_v2',
|
||||
datasource_id: datasetId,
|
||||
datasource_type: 'table',
|
||||
params: JSON.stringify(params),
|
||||
query_context: JSON.stringify(queryContext),
|
||||
query_context_generation: true,
|
||||
});
|
||||
chartId = (await chartResp.json()).id;
|
||||
|
||||
|
||||
@@ -17,10 +17,10 @@ specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
|
||||
## @superset-ui/plugin-chart-calendar
|
||||
## @superset-ui/legacy-plugin-chart-calendar
|
||||
|
||||
[](https://www.npmjs.com/package/@superset-ui/plugin-chart-calendar)
|
||||
[](https://libraries.io/npm/@superset-ui%2Fplugin-chart-calendar)
|
||||
[](https://www.npmjs.com/package/@superset-ui/legacy-plugin-chart-calendar)
|
||||
[](https://libraries.io/npm/@superset-ui%2Flegacy-plugin-chart-calendar)
|
||||
|
||||
This plugin provides Calendar Heatmap for Superset.
|
||||
|
||||
@@ -30,7 +30,7 @@ Configure `key`, which can be any `string`, and register the plugin. This `key`
|
||||
lookup this chart throughout the app.
|
||||
|
||||
```js
|
||||
import CalendarChartPlugin from '@superset-ui/plugin-chart-calendar';
|
||||
import CalendarChartPlugin from '@superset-ui/legacy-plugin-chart-calendar';
|
||||
|
||||
new CalendarChartPlugin().configure({ key: 'calendar' }).register();
|
||||
```
|
||||
@@ -1,18 +1,18 @@
|
||||
{
|
||||
"name": "@superset-ui/plugin-chart-calendar",
|
||||
"name": "@superset-ui/legacy-plugin-chart-calendar",
|
||||
"version": "0.20.3",
|
||||
"description": "Superset Chart - Calendar Heatmap",
|
||||
"description": "Superset Legacy Chart - Calendar Heatmap",
|
||||
"keywords": [
|
||||
"superset"
|
||||
],
|
||||
"homepage": "https://github.com/apache/superset/tree/master/superset-frontend/plugins/plugin-chart-calendar#readme",
|
||||
"homepage": "https://github.com/apache/superset/tree/master/superset-frontend/plugins/legacy-plugin-chart-calendar#readme",
|
||||
"bugs": {
|
||||
"url": "https://github.com/apache/superset/issues"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/apache/superset.git",
|
||||
"directory": "superset-frontend/plugins/plugin-chart-calendar"
|
||||
"directory": "superset-frontend/plugins/legacy-plugin-chart-calendar"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"author": "Superset",
|
||||
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
@@ -43,13 +43,13 @@ const metadata = new ChartMetadata({
|
||||
],
|
||||
thumbnail,
|
||||
thumbnailDark,
|
||||
useLegacyApi: true,
|
||||
});
|
||||
|
||||
export default class CalendarChartPlugin extends ChartPlugin {
|
||||
constructor() {
|
||||
super({
|
||||
loadChart: () => import('./ReactCalendar'),
|
||||
loadBuildQuery: () => import('./buildQuery'),
|
||||
metadata,
|
||||
transformProps,
|
||||
controlPanel,
|
||||
@@ -18,14 +18,14 @@
|
||||
*/
|
||||
|
||||
import { SuperChart } from '@superset-ui/core';
|
||||
import CalendarChartPlugin from '@superset-ui/plugin-chart-calendar';
|
||||
import CalendarChartPlugin from '@superset-ui/legacy-plugin-chart-calendar';
|
||||
import data from './data';
|
||||
import { dummyDatasource, withResizableChartDemo } from '@storybook-shared';
|
||||
|
||||
new CalendarChartPlugin().configure({ key: 'calendar' }).register();
|
||||
|
||||
export default {
|
||||
title: 'Chart Plugins/plugin-chart-calendar',
|
||||
title: 'Legacy Chart Plugins/legacy-plugin-chart-calendar',
|
||||
decorators: [withResizableChartDemo],
|
||||
args: {
|
||||
cellSize: 10,
|
||||
@@ -17,15 +17,8 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
ChartProps,
|
||||
ensureIsArray,
|
||||
getMetricLabel,
|
||||
getNumberFormatter,
|
||||
QueryFormMetric,
|
||||
} from '@superset-ui/core';
|
||||
import { ChartProps, getNumberFormatter } from '@superset-ui/core';
|
||||
import { getFormattedUTCTime } from './utils';
|
||||
import transformData from './transformData';
|
||||
|
||||
export default function transformProps(chartProps: ChartProps) {
|
||||
const { height, formData, queriesData, datasource } = chartProps;
|
||||
@@ -51,29 +44,9 @@ export default function transformProps(chartProps: ChartProps) {
|
||||
getFormattedUTCTime(ts, xAxisTimeFormat);
|
||||
const valueFormatter = getNumberFormatter(yAxisFormat);
|
||||
|
||||
// The legacy explore_json endpoint computed the per-metric value maps
|
||||
// and domain range server-side; v1 responses arrive as flat records.
|
||||
const {
|
||||
data: rawData,
|
||||
from_dttm: fromDttm,
|
||||
to_dttm: toDttm,
|
||||
} = queriesData[0];
|
||||
const data = Array.isArray(rawData)
|
||||
? transformData(
|
||||
rawData,
|
||||
ensureIsArray(formData.metrics as QueryFormMetric[]).map(
|
||||
getMetricLabel,
|
||||
),
|
||||
fromDttm,
|
||||
toDttm,
|
||||
domainGranularity,
|
||||
subdomainGranularity,
|
||||
)
|
||||
: rawData;
|
||||
|
||||
return {
|
||||
height,
|
||||
data,
|
||||
data: queriesData[0].data,
|
||||
cellPadding,
|
||||
cellRadius,
|
||||
cellSize,
|
||||
@@ -17,10 +17,10 @@ specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
|
||||
## @superset-ui/plugin-chart-chord
|
||||
## @superset-ui/legacy-plugin-chart-chord
|
||||
|
||||
[](https://www.npmjs.com/package/@superset-ui/plugin-chart-chord)
|
||||
[](https://libraries.io/npm/@superset-ui%2Fplugin-chart-chord)
|
||||
[](https://www.npmjs.com/package/@superset-ui/legacy-plugin-chart-chord)
|
||||
[](https://libraries.io/npm/@superset-ui%2Flegacy-plugin-chart-chord)
|
||||
|
||||
This plugin provides Chord Diagram for Superset.
|
||||
|
||||
@@ -30,7 +30,7 @@ Configure `key`, which can be any `string`, and register the plugin. This `key`
|
||||
lookup this chart throughout the app.
|
||||
|
||||
```js
|
||||
import ChordChartPlugin from '@superset-ui/plugin-chart-chord';
|
||||
import ChordChartPlugin from '@superset-ui/legacy-plugin-chart-chord';
|
||||
|
||||
new ChordChartPlugin().configure({ key: 'chord' }).register();
|
||||
```
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@superset-ui/plugin-chart-chord",
|
||||
"name": "@superset-ui/legacy-plugin-chart-chord",
|
||||
"version": "0.20.3",
|
||||
"description": "Superset Chart - Chord Diagram",
|
||||
"description": "Superset Legacy Chart - Chord Diagram",
|
||||
"sideEffects": [
|
||||
"*.css"
|
||||
],
|
||||
@@ -14,7 +14,7 @@
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/apache/superset.git",
|
||||
"directory": "superset-frontend/plugins/plugin-chart-chord"
|
||||
"directory": "superset-frontend/plugins/legacy-plugin-chart-chord"
|
||||
},
|
||||
"keywords": [
|
||||
"superset"
|
||||
@@ -24,7 +24,7 @@
|
||||
"bugs": {
|
||||
"url": "https://github.com/apache/superset/issues"
|
||||
},
|
||||
"homepage": "https://github.com/apache/superset/tree/master/superset-frontend/plugins/plugin-chart-chord#readme",
|
||||
"homepage": "https://github.com/apache/superset/tree/master/superset-frontend/plugins/legacy-plugin-chart-chord#readme",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
|
Before Width: | Height: | Size: 132 KiB After Width: | Height: | Size: 132 KiB |
|
Before Width: | Height: | Size: 119 KiB After Width: | Height: | Size: 119 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 103 KiB |
@@ -39,16 +39,16 @@ const metadata = new ChartMetadata({
|
||||
},
|
||||
],
|
||||
name: t('Chord Diagram'),
|
||||
tags: [t('Circular'), t('Proportional'), t('Relational')],
|
||||
tags: [t('Circular'), t('Legacy'), t('Proportional'), t('Relational')],
|
||||
thumbnail,
|
||||
thumbnailDark,
|
||||
useLegacyApi: true,
|
||||
});
|
||||
|
||||
export default class ChordChartPlugin extends ChartPlugin {
|
||||
constructor() {
|
||||
super({
|
||||
loadChart: () => import('./ReactChord'),
|
||||
loadBuildQuery: () => import('./buildQuery'),
|
||||
metadata,
|
||||
transformProps,
|
||||
controlPanel,
|
||||
@@ -18,14 +18,14 @@
|
||||
*/
|
||||
|
||||
import { SuperChart, VizType } from '@superset-ui/core';
|
||||
import ChordChartPlugin from '@superset-ui/plugin-chart-chord';
|
||||
import ChordChartPlugin from '@superset-ui/legacy-plugin-chart-chord';
|
||||
import data from './data';
|
||||
import { withResizableChartDemo } from '@storybook-shared';
|
||||
|
||||
new ChordChartPlugin().configure({ key: VizType.Chord }).register();
|
||||
|
||||
export default {
|
||||
title: 'Chart Plugins/plugin-chart-chord',
|
||||
title: 'Legacy Chart Plugins/legacy-plugin-chart-chord',
|
||||
decorators: [withResizableChartDemo],
|
||||
args: {
|
||||
colorScheme: 'd3Category10',
|
||||
@@ -16,10 +16,18 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import CalendarChartPlugin from '../src/index';
|
||||
import { ChartProps } from '@superset-ui/core';
|
||||
|
||||
test('registers the plugin with its v1 metadata and buildQuery loader', () => {
|
||||
const plugin = new CalendarChartPlugin();
|
||||
expect(plugin.metadata.name).toEqual('Calendar Heatmap');
|
||||
expect(plugin.loadBuildQuery).toBeDefined();
|
||||
});
|
||||
export default function transformProps(chartProps: ChartProps) {
|
||||
const { width, height, formData, queriesData } = chartProps;
|
||||
const { yAxisFormat, colorScheme, sliceId } = formData;
|
||||
|
||||
return {
|
||||
colorScheme,
|
||||
data: queriesData[0].data,
|
||||
height,
|
||||
numberFormat: yAxisFormat,
|
||||
width,
|
||||
sliceId,
|
||||
};
|
||||
}
|
||||
@@ -17,10 +17,10 @@ specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
|
||||
## @superset-ui/plugin-chart-country-map
|
||||
## @superset-ui/legacy-plugin-chart-country-map
|
||||
|
||||
[](https://www.npmjs.com/package/@superset-ui/plugin-chart-country-map)
|
||||
[](https://libraries.io/npm/@superset-ui%2Fplugin-chart-country-map)
|
||||
[](https://www.npmjs.com/package/@superset-ui/legacy-plugin-chart-country-map)
|
||||
[](https://libraries.io/npm/@superset-ui%2Flegacy-plugin-chart-country-map)
|
||||
|
||||
This plugin provides Country Map for Superset.
|
||||
|
||||
@@ -30,7 +30,7 @@ Configure `key`, which can be any `string`, and register the plugin. This `key`
|
||||
lookup this chart throughout the app.
|
||||
|
||||
```js
|
||||
import CountryMapChartPlugin from '@superset-ui/plugin-chart-country-map';
|
||||
import CountryMapChartPlugin from '@superset-ui/legacy-plugin-chart-country-map';
|
||||
|
||||
new CountryMapChartPlugin().configure({ key: 'country-map' }).register();
|
||||
```
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@superset-ui/plugin-chart-country-map",
|
||||
"name": "@superset-ui/legacy-plugin-chart-country-map",
|
||||
"version": "0.20.3",
|
||||
"description": "Superset Chart - Country Map",
|
||||
"description": "Superset Legacy Chart - Country Map",
|
||||
"main": "lib/index.js",
|
||||
"module": "esm/index.js",
|
||||
"files": [
|
||||
@@ -11,7 +11,7 @@
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/apache/superset.git",
|
||||
"directory": "superset-frontend/plugins/plugin-chart-country-map"
|
||||
"directory": "superset-frontend/plugins/legacy-plugin-chart-country-map"
|
||||
},
|
||||
"keywords": [
|
||||
"superset"
|
||||
@@ -21,7 +21,7 @@
|
||||
"bugs": {
|
||||
"url": "https://github.com/apache/superset/issues"
|
||||
},
|
||||
"homepage": "https://github.com/apache/superset/tree/master/superset-frontend/plugins/plugin-chart-country-map#readme",
|
||||
"homepage": "https://github.com/apache/superset/tree/master/superset-frontend/plugins/legacy-plugin-chart-country-map#readme",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||