# Updating Superset This file documents any backwards-incompatible changes in Superset and assists people when migrating to a new version. ## Next - Refreshing a dataset's columns from its source (the "Sync columns from source" button, `PUT /api/v1/dataset/?override_columns=true`, or `PUT /api/v1/dataset//refresh`) no longer resets **Is dimension** and **Is filterable** on existing columns. Newly discovered columns still arrive with both enabled; a deployment that relied on a resync to restore those defaults must set them explicitly. - Webhook alerts/reports and the Impala `cancel_query` call now share `get_ssrf_safe_requester`, whose session sets `trust_env=False`. Unless `ALERT_REPORTS_WEBHOOK_ALLOW_INTERNAL_HOSTS` / `IMPALA_CANCEL_QUERY_ALLOW_INTERNAL_HOSTS` is `True`, these requests no longer honor `HTTP(S)_PROXY`/`NO_PROXY`, `REQUESTS_CA_BUNDLE`/`CURL_CA_BUNDLE`, or `.netrc`, because proxied connections would bypass the connected-peer validation. Deployments that must reach webhooks through a proxy or custom CA bundle should set the matching allow-internal flag, which restores the plain `requests` behavior (and skips peer validation). - MCP `update_dashboard` rejects blank or whitespace-only `dashboard_title` values with "Text must not be empty"; omit the field to keep the stored title. MCP chart names and theme names are capped at 250 characters, matching their model columns (the published chart-name limit was 255). - **MCP authentication hooks and disabled API keys:** Custom `MCP_USER_RESOLVER` functions that read `access_token.client_id` must use `get_verifier_client_id(access_token)` from `superset.mcp_service.composite_token_verifier` instead. FastMCP 4's `MultiAuth` qualifies `client_id`; the helper returns the originating verifier's ID on both FastMCP 3 and 4. This ID routes authentication; it is not proof that the bearer is valid. When API keys are disabled (`MCP_API_KEY_ENABLED=False`, or `FAB_API_KEY_ENABLED=False` without an MCP override), an explicit API-key pass-through token raises `PermissionError` instead of falling through to another identity. `tools/list` returns an empty list for that client, not public tools. Remove the API-key credential to use another authentication method, or explicitly enable API-key authentication. - **Large nullable-integer dimensions stay exact in chart data.** In a plain chart query (no post-processing or time offsets, and not a report's client-processed result), a dimension column of nullable integers that holds a value beyond `2**53 - 1` is no longer rounded to a float. JSON responses carry such values as strings (the existing contract for large integers), and CSV, Excel and Arrow exports carry the exact integer. Consumers that parsed these values as JSON numbers receive a string for them. Metric columns are unchanged, and results cached before the upgrade keep the old values until they expire. - **Bulk semantic view creation is capped and time-bounded.** With `SEMANTIC_LAYERS` enabled, `POST /api/v1/semantic_view/` refuses requests with more than `SEMANTIC_VIEW_BULK_CREATE_MAX_VIEWS` views (default 100) with a 400, and views not checked within `SEMANTIC_VIEW_BULK_CREATE_TIMEOUT` seconds (default 30) are reported with `error_type: "TIMEOUT"` instead of being looked up. Clients adding more views should split them across requests and retry timed-out views. - `superset-frontend` type-checks with TypeScript 7, whose native compiler no longer ships the JavaScript compiler API. The webpack dev server therefore no longer runs `fork-ts-checker-webpack-plugin`, and the `DISABLE_TS_CHECKER` environment variable is gone; run `npm run type` (or `npm run type -- --watch`) for type errors and `npm run plugins:build` to refresh package `.d.ts` output. Storybook's prop tables now come from `react-docgen` instead of `react-docgen-typescript`. - MCP `get_dashboard_data` reports `total_rows=null` and `truncated=true` when row-collapsing post-processing leaves input coverage unverified, not only when truncation is observed. Output row counts do not prove complete input coverage. `fetch_row_limit` reports the executed query limit, including server caps. - MCP `duplicate_dashboard` deep-copies charts by default. Callers that intentionally share charts with the source dashboard must pass `duplicate_slices=false`. MCP `list_dashboards`, `list_charts`, `list_datasets`, `list_reports` and `list_databases` reject unknown request fields instead of silently ignoring them, with suggestions for supported fields. Use `created_by_me` for authorship or `edited_by_me` for direct user ownership where supported, not `owned_by_me`. - The extension storage write routes (`PUT`/`DELETE` under `/api/v1/extensions///storage/`) are no longer exempt from CSRF protection. They inherited the exemption from Flask-AppBuilder's `BaseApi`; they now behave like every other Superset REST API that is not in `WTF_CSRF_EXEMPT_LIST`. The browser already sends the token. Any other client that writes extension storage, whether it authenticates with a session cookie, a JWT bearer token or an API key, must first call `GET /api/v1/security/csrf_token/` with its credential, then send the returned token in the `X-CSRFToken` header together with the session cookie set by that call (the token is bound to the session). The client's role must also have `can_read` on `SecurityRestApi` (shown as `can read on SecurityRestApi` in the role editor) to fetch the token. Over HTTPS, Flask-WTF also requires a same-origin `Referer` header on the write (`WTF_CSRF_SSL_STRICT`, on by default); browsers send one, scripted clients must add it. - **Pivot Table mixed-metric export totals.** CSV/Excel exports and scheduled CSV/text reports leave Total and Subtotal cells blank when they combine different metrics, including two SUM metrics. Metric-specific values and totals remain available, including with Combine metrics and Transpose pivot enabled. Previously these cells could show an arbitrary metric's value or a sum across unrelated metrics. Downstream consumers must handle empty cells or use a single metric that explicitly computes the desired combined value. This change does not restore result-aggregation parity between exports and the browser. - **Impala SQL Lab stops no longer call the coordinator's HTTP cancel.** A SQL Lab stop on Impala is delivered to the worker that is polling the query, which cancels it with `cancel_operation()`, as Hive and Presto already do. The HTTP cancel invalidated the handle the worker was still polling, so the run failed instead of ending as stopped. When no worker is polling, the query is marked stopped but Impala keeps running it and holds its admission slot until it finishes or Impala closes it: for example when the worker has died or restarted, or when the stop arrives while results are being fetched. Operators who relied on the HTTP cancel to free admission slots in those cases should rely on Impala's own query and session timeouts. Chart and MCP cancellation still use the HTTP cancel. - `POST /api/v1/query/stop` returns HTTP 422 instead of 200 when the query has already finished or its worker has committed the target transaction. The response contains `message: "Could not cancel query"` and the recorded `query`; an async handle's `cancel()` returns `False`. After a target commit, that query can still be running while SQL Lab prepares its results. PostgreSQL/MySQL metadata databases serialize Stop against the worker's final stop check and target commit. If Stop wins, transactional work is discarded; if the worker wins, Stop is rejected even before results are published. An accepted Stop returns HTTP 200 (`cancel()` returns `True`) even if engine cancellation or subsequent cancellation-metadata persistence fails. Its stopped state and original end time survive repeated Stop requests and worker timeouts. Repeated Stop requests do not retry engine cancellation. Engine cancellation is best-effort: its statement may continue, but SQL Lab discards the results. Stop cannot undo autocommit, implicit-commit DDL, or SQL commits performed during execution. With SQLite metadata (which does not support row locks), a Stop accepted between the worker's final check and target commit can also leave transactional changes committed. Target and metadata commits are not atomic: a metadata commit failure after the target commit can likewise leave database changes without the committed marker. - **Semantic dimension variant resolution.** With `SEMANTIC_LAYERS` enabled, same-name dimension variants use one default preference for metadata, filters and grouping fallbacks: raw (no grain), then second/minute/hour/day/week/month/quarter/year, then other grain representations in lexical order. Explicit supported grouping grains remain honored; filters and time bounds use the default independently of grouping. Sorting and series limits use the selected grouping grain. This can change results that depended on arbitrary catalog ordering. Providers exposing different IDs or conflicting types, definitions or descriptions for the same name and grain must disambiguate their catalog; such catalogs are rejected instead of silently selecting a variant. On dashboards, only the affected dataset reports the ambiguity; other datasets still load. Independent temporal predicates are retained unless replaced by the query's time-axis bounds; time-series offsets and separate inner-bound queries replace ranges on their selected axis. Retained predicates can narrow results where earlier releases discarded them. - **Semantic NULL filters and provider SDK upgrade.** Semantic filters normalize UI NULL/empty-string selections. Mixed positive NULL membership uses `OrFilter`, which every semantic provider must render; previously NULL rows could be omitted. Update provider adapters before, or together with, the host/SDK upgrade. Adapters must narrow `SemanticQuery.filters` and `GroupLimit.filters` members (`Filter | OrFilter`) before reading leaf attributes; `get_values` remains `set[Filter] | None`. Empty membership and invalid NULL comparison operands return query validation errors. Semantic result keys include a filter-protocol version, and chart-backed annotation results also receive a new cache discriminator: expect a one-time cold cache after upgrading. See the [semantic filter contract](docs/developer_docs/extensions/semantic-filters.md) for NULL value typing, mandatory `OrFilter` rendering and guarded loading on older SDKs. - `SEMANTIC_LAYER_CONTAINMENT_CACHE` is off by default; opted-in providers can reuse cached semantic-view results for narrower queries. Containment keys include the view's `metadata_cache_token`, so metadata refresh makes old entries miss. Enable `SEMANTIC_LAYERS`; configure shared persistent `DATA_CACHE_CONFIG` and Redis-backed `DISTRIBUTED_COORDINATION_CONFIG` on every web/worker process. - `!=`, `NOT LIKE` and `NOT ILIKE` filters on SQLAlchemy-backed datasets now also return rows where the column is NULL, as do whole-array `!=` / `NOT IN` filters and `!=` / `NOT IN` filters on advanced data type columns (internet address, port). Previously these rows were dropped by SQL three-valued logic (`NULL != 'CA'` is not true), so a filter such as `state != 'CA'` removed NULL rows in addition to `CA`. Dashboards, charts and reports that use these operators may therefore show additional NULL rows. An explicit NULL operand behaves as before (`!= NULL` still means `IS NOT NULL`). - Address validation refuses additional non-global address forms. Addresses in the well-known NAT64 prefix `64:ff9b::/96` are checked using their embedded IPv4 address and refused when that address is unsafe. Local-use translation (`64:ff9b:1::/48`), IPv4-compatible (`::/96`), 6to4 and Teredo forms are refused outright. Operator-specific NAT64 prefixes are not detected: recognizing them requires knowledge of the operator's prefix configuration. Existing per-feature internal-host settings continue to control OAuth2, webhook, URL dataset import and Impala checks. - Semantic-layer APIs are registered regardless of the `SEMANTIC_LAYERS` flag. The flag is evaluated at runtime for API requests and background queries. Disabling it refuses semantic-view data, chart saves and cached chart/dashboard images containing semantic charts, while retaining stored assets. Both fresh and cached dashboard screenshots/thumbnails return 404 when any chart uses a semantic view. Interactive dashboards retain unavailable semantic charts and continue rendering ordinary datasets. Dual-source MCP tools retain dataset access; unscoped metric listings omit semantic views. Legacy batch warm-up reports a per-chart error for a disabled semantic chart and continues. Existing permissions remain required when enabled. Run `superset init` during upgrade to register the existing semantic permissions even when the feature is off. Runtime flag hooks can enable the feature without another restart or permission sync; provider extensions must already be loaded. Do not target runtime enablement until all web and worker processes run this version. - Semantic-view chart-defined metric filters return a client error identifying the metric; select a dimension or remove the unsupported predicate. Dashboard filters on incompatible columns (including metrics) are skipped and reported as rejected, so the chart renders with an **Incompatible** filter indicator. A chart-defined filter on a column the semantic view no longer has also returns a client error naming the column, instead of being skipped (which widened the result); edit the chart's filters and select a dimension. This differs from SQL datasets, which skip and report such filters. Scheduled reports, alerts and thumbnails for such charts fail until the chart is edited. A "No filter" time range on a missing column is not refused. Dashboard time overrides mark only previously unbounded temporal predicates as dashboard filters; bounded chart-defined filters on missing columns or metrics remain refused. This change affects only opt-in, pre-GA deployments with `SEMANTIC_LAYERS` enabled. On upgrade, dashboard tabs already open across the deploy send filters without `isExtra`; their semantic charts return HTTP 400 for incompatible dashboard filters until the user reloads the page. This accepted pre-GA compatibility window will be revisited before GA. A dashboard filter carried into a new chart (such as drill-by "Edit chart") becomes a chart filter. Valid dimension filters are reported as applied. - Dashboard-applied filters now carry `isExtra` in chart-data requests, including MCP chart tools that accept `extra_form_data` (chart data, SQL and previews, for saved and unsaved charts). This changes chart cache keys for dashboard-filtered queries once, so expect a cold chart cache right after deploying. - Semantic-layer configuration Save and Reload metadata increment a database-backed cache generation. Apply the `c179af013f48` migration before deploying the updated application; existing semantic chart caches become cold once on upgrade. Providers must accept required keyword-only `cache_token` and `context` on `from_configuration`; the optional `from_configuration_with_cache_token` hook is removed. Local metadata caches must include `cache_token` in their keys before lookup or eager discovery for complete invalidation. The default namespace uses the active metadata database's durable installation identity rather than its address, so web, Celery and MCP agree across host/port/database aliases. This causes a one-time cache miss for semantic-layer results after deployment when using the default namespace; update all roles together. Existing explicit namespaces retain their key scheme. When tenants share a metadata database or a full database clone must be isolated from its source, configure `SEMANTIC_LAYER_CACHE_NAMESPACE` as a globally unique workspace identity that resolves identically in all three contexts. A metadata session must not span workspaces. Saves handled by old instances during a rolling deployment do not bump the generation; repeat Reload metadata after rollout for those changes. After restoring a metadata database backup, clear surviving data/provider caches or rotate the workspace namespace to avoid reusing generations from the backup. See the [cache invalidation contract](docs/developer_docs/semantic-layer-cache-clear.md). - Malformed explicit `time_range` values are rejected with a validation error (HTTP 400 on chart-data requests) instead of silently producing an upper-bound-only scan. Update saved charts, dashboard filters, imports, and API callers to use ` : ` (including spaces around the colon) or a supported shorthand, such as `Last week`. An empty string is invalid; use `No filter` for no time filter. Omitted `time_range` values still support legacy `since`/`until` bounds. - Semantic-view providers may declare `SemanticView.preferred_temporal_dimension` to choose the default exposed temporal dimension for new charts. The declaration is optional; absent, unknown or non-temporal names retain the existing fallback. Saved chart selections are preserved. ### Dashboard copy layout validation Copying a dashboard with its charts returns HTTP 422 if a layout slot references an existing chart outside the source dashboard's membership. No partial copy is saved. Open and re-save the source dashboard to rebuild its chart membership before retrying. Non-null malformed chart references also return HTTP 422. Empty slots left by older copies (null or missing chart IDs in object metadata) are converted to placeholders, preserving their size. Detached slots are reattached to a reachable container by layout repair. - MCP data-bearing tools enforce mandatory chart-query result limits before response serialization or CSV/XLSX export, independently of the configurable response size guard (`MCP_RESPONSE_SIZE_CONFIG`). Results are limited to 32 queries, 50,000 rows per query, 100,000 rows in total, 4,096 columns per row, 2,500,000 values, 16 MiB of JSON, and 1 MiB of metadata. Nested cells are limited to 4,096 items per container and 32 levels of nesting; text cells are limited to 65,536 UTF-8 bytes. Increasing `SQL_MAX_ROW` or raising/disabling the response size guard does not raise these fixed limits. Oversized results, including saved Table exports, return `InvalidQueryResult`; lower row limits, filter, select fewer/narrower columns, or aggregate before exporting. Bullet preview numeric format precision is limited to 20 digits; raw data reads do not validate presentation formats. - MCP `update_chart` requires a complete `config` when changing `dataset_id` to a different dataset, for both preview and immediate-save requests. Re-sending the existing dataset ID remains an idempotent update. - Semantic-layer providers may opt into `SemanticLayer.result_cache_version` to isolate chart, filter-value and chart-backed annotation results from older producer guarantees. The default `None` preserves existing cache keys. Providers enforcing completeness should raise the public `superset_core.semantic_layers.errors.SemanticResultCompletenessError` when results are incomplete or cannot be verified (an additive `apache-superset-core` API, available from 0.2.0; import it from `superset_core.semantic_layers.errors`, not the same-named host class in `superset.exceptions`); the host converts it to its client error, and these failures do not publish a successful async result cache key. A provider raising `superset.exceptions.SemanticResultCompletenessError` directly is still accepted for one release. Deploy compatible host/provider versions to all web and worker processes and drain old deliveries before activating a new guarantee. Mixed fleets and an old host with an opted-in provider are unsupported. - A chart whose datasource no longer exists (hard-deleted, or no datasource ID) has its denormalized `perm`, `schema_perm` and `catalog_perm` cleared when it is saved, including during the dataset's deletion. Purging a soft-deleted dataset (the retention task or force purge) keeps its charts but clears their datasource ID and these permission fields, as an ordinary hard delete already does, so a later dataset cannot take over those charts. Such orphaned charts no longer appear in the chart list through `schema_access` or `catalog_access` grants, matching the object-level check that already denies them. Charts of a soft-deleted dataset keep their permissions, so restoring the dataset restores access. Purge does not advance the detached charts' history or ETags; the purge audit's `affected_referrers` records which charts were detached. Charts left by purges that ran before this change are not migrated: they keep the purged dataset's ID and permission fields. Saving such a chart clears its permission fields only while no dataset has that ID; if a new dataset has since taken the ID, the save adopts that dataset instead. Operators should find charts whose `table` datasource no longer exists and repoint them to the intended dataset or detach them. - MCP dashboard mutation tools refuse externally managed dashboards, including owner and role changes. Update the dashboard in its external source of truth instead; the response sets `managed_externally: true`. Read-only tools and certification inspection are unaffected. - `DISALLOWED_SQL_FUNCTIONS` now names the whole PostgreSQL XML family, plus the two full-text search functions that share its blind spot. Seven shapes were missing -- `schema_to_xml`, `schema_to_xmlschema`, `schema_to_xml_and_xmlschema`, `database_to_xml_and_xmlschema`, `table_to_xmlschema`, `ts_rewrite` and `ts_stat` -- which, being scalar calls with no FROM clause and no sub-query, cleared every gate that reasons about table references. `ts_rewrite` and `ts_stat` run their text argument as a query, so the statement they execute is a string with no clause of its own for such a gate to see. The same reasoning adds a new `oracle` entry naming `getxml`, `getxmltype`, `newcontext` and `newcontextfromhierarchy`, the `DBMS_XMLGEN` / `DBMS_XMLQUERY` calls that run a query handed to them as text, and the URI readers that name a table by path string -- `dburitype`, `xdburitype`, `httpuritype` and `geturi`, the last reached through `URIFACTORY.GETURI(...)`. They are listed by bare name because that is what the parser sees for a package-qualified call. The denylist is shared with SQL Lab and Charts, so a deployment relying on any of these functions there will now see it refused -- including a deployment that never enables `PARTITION_FILTER_MAPPING`, since the denylist is not gated on the feature flag. Override `DISALLOWED_SQL_FUNCTIONS` in `superset_config.py` if that is intended. - A partition value transform is now refused if it changes data -- `nextval(:value)`, a stored-procedure call, a PostgreSQL large-object writer. The transform is evaluated against the engine to check that it mirrors, and that evaluation is cached and repeated, so it has to be a read. The check is the same `is_mutating` gate SQL Lab and chart queries already apply, and it covers transforms already in storage: one stored by an earlier version simply stops mirroring rather than failing a query. - Only the mapped column may carry a partition value transform, and that is now enforced on every write rather than only inside `UpdateDatasetCommand`. A transform arriving on any other column is dropped -- by the deprecated `POST /datasource/save/`, and by `PUT /api/v1/dataset/` and dataset import even when `PARTITION_FILTER_MAPPING` is off. An API client that was storing one there will find the field `null` on the next read. The value was never readable: a transform on a column the mapping does not mirror is invisible in the dataset editor, and it went live the moment the mapped column resolved back to it -- silently dropping rows from every chart on the dataset, with the pruning indicator still reporting the mapping healthy. Transforms already in storage on a column a request does not carry are left alone unless the feature flag is on. - For MySQL/MariaDB metadata databases, use `READ COMMITTED` isolation. Superset defaults the `mysql` and `postgresql` URI backends to `READ COMMITTED` when no `isolation_level` is configured. For the `mariadb` URI backend (including `mariadb://` and `mariadb+pymysql://`), set `SQLALCHEMY_ENGINE_OPTIONS = {"isolation_level": "READ COMMITTED"}` explicitly. With any other isolation level, dataset and semantic-view deletion and purge retain `datasource_access` permission records by design, rather than risk revoking a shared grant based on a stale snapshot. Records with no remaining owner become orphans and can accumulate. Cleanup is also skipped if the isolation level cannot be verified. - Example export (`/export_as_example/`) rejects dashboards whose charts or native-filter targets use semantic views; use the ordinary chart/dashboard bundle export instead. - Semantic-view chart and dashboard bundles use typed UUID references and require a destination with support for this format and an already provisioned, accessible view with the same UUID. They do not provision semantic layers/views or export their configuration or credentials. Older readers cannot import these semantic bundles. Older dashboard bundles with semantic filter targets but no typed `datasourceRef` are also rejected; re-export them with this version rather than binding their local IDs to unrelated destination tables. Imports fail explicitly if a semantic dependency is missing, inaccessible, feature-disabled or provider-unregistered. Chart and dashboard exports omit references for missing views so unrelated assets can still be archived; orphaned charts and dashboard targets cannot be imported without repairing their datasources. Existing-view access, feature and provider checks remain enforced, including in full asset exports. Ordinary table bundles retain their existing format. The examples loader rejects semantic bundles; use the chart, dashboard or assets importer instead. ### Table and AG Grid server pagination for semantic views Table and AG Grid Table charts backed by semantic views require the optional `ROW_OFFSET` capability to enable Server pagination. Missing capability metadata disables the control with an explanation; SQL datasets are unchanged. Saved pagination settings are not rewritten. Users can explicitly turn off an unsupported saved setting; provider errors remain authoritative until that edit. Extension providers must use a compatible core version before declaring the new capability. ### Graph null category colors Graph charts label SQL `NULL` categories as `` and share their colors with other charts using that label. Move any `label_colors["N/A"]` setting intended for SQL NULL to `label_colors[""]`; `N/A` continues to identify the literal value. The literal string `` is quoted as `""` to keep it separate from SQL NULL. Quoted forms of that literal are also escaped to avoid collisions; other quoted labels keep their existing display and color keys. Use the displayed label as the custom color key, for example: ```json {"": "#e53935", "\"\"": "#123456"} ``` ### Alerts & Reports: runtime configuration and per-schedule "Run As" executor (SIP-209) - The migration creates an empty versioned document in the existing `key_value` table for the global Alerts & Reports settings that admins can now manage from the **Configuration** button on the Alerts & Reports list page (or `GET`/`PUT /api/v1/report/configuration/`). `ALERTS_ATTACH_REPORTS`, `ALERT_MINIMUM_INTERVAL` and `REPORT_MINIMUM_INTERVAL` are deprecated: they keep working as fallbacks until the corresponding setting is saved in the UI, at which point the saved value wins. Two new settings, **Limit recipients to users** and **Allowed e-mail domains**, restrict e-mail recipients; they are enforced when saving a schedule and at execution time. - Alerts have an **Include attachment** toggle, represented by `report_format: "NONE"` when off. Attachment-free alerts need no chart/dashboard; asset-less notifications omit the asset link. An alert with an attachment format must have a chart/dashboard even when global alert attachments are disabled. Saved attachment settings are retained. Reports always require content. Downgrading past this migration changes attachment-free alerts with a saved chart or dashboard to PNG and deletes attachment-free alerts with no saved asset. Back up the metadata database before downgrading if those alerts must be retained (or update them to set a valid attachment). - **Behavior change for existing Text alerts:** Before this upgrade, a chart alert with `report_format: "TEXT"` embedded its data table in the notification even when `ALERTS_ATTACH_REPORTS` was off. After this upgrade, when the global **Enable attachments for alerts** setting is off, the alert notification is still sent but **the embedded table is omitted**. This applies even if `ALERT_REPORT_DYNAMIC_EXECUTOR` remains off. Disabling attachments now fully bypass data collection, to ensure notification will be sent right away. - Behind the new `ALERT_REPORT_DYNAMIC_EXECUTOR` feature flag (off by default), alerts and reports record the user they execute as (`run_as`, plus `run_alert_query_as` for the alert condition query). Non-admins can only set themselves. `ALERT_REPORTS_EXECUTORS` is deprecated in favor of these fields; schedules without a value keep using it. The migration adds nullable `run_as_fk` / `run_alert_query_as_fk` columns to `report_schedule` and does not change untouched legacy schedules. Executor types are persisted separately: deleting a selected user does not restore legacy execution. - Admins can select a specific user or Application default (`ALERT_REPORTS_EXECUTORS`). An unset content executor remains on the application default when edited, and admins can clear an explicit choice back to it. A blank alert-query executor inherits the content executor dynamically. - Saving a schedule while `ALERT_REPORT_DYNAMIC_EXECUTOR` is off clears any stored per-schedule content and alert-query executor selections. The schedule continues using `ALERT_REPORTS_EXECUTORS` if the flag is enabled again. Untouched schedules retain their selections. - Non-admins must select **Execute using my permissions** before changing content or recipients on a schedule using another user, a typed executor, or a legacy content executor. For alerts, this action switches both executors to the current user on Save. Metadata-only edits preserve existing executor settings. - Saved `null` configuration values clear a setting without restoring application-config defaults (only keys never saved inherit defaults/fallbacks). Configuration read failures propagate instead of treating delivery as unrestricted. The UI updates only changed keys. - Recipient restrictions also cover retry/final-failure notices to configured recipients; operational notices to owners/editors remain exempt. - Missing executors and recipient-policy violations terminate an execution without retry. - Domain allow-lists accept wildcards. Matching is case-insensitive. - The SIP migration follows the execution-ownership migration. Run `superset db upgrade` before starting this code, with scheduling paused and active work/queued retries drained as described below. Keep web and worker versions aligned. Before rollback, replace `NONE` formats with a supported format and account for losing explicit executor/policy settings. ### Semantic provider error handling Semantic chart-query execution and value suggestions return HTTP 400 with host-owned guidance for typed provider rejections, and generic HTTP 500 responses for provider faults. In chart execution this includes provider-raised `SupersetTimeoutException` (formerly 408), `SupersetGenericDBErrorException` and `SupersetTemplateParamsErrorException` (formerly 400). Host input validation and worker control exceptions retain their existing handling. Failed server-side value searches no longer retry without their narrowing filter; manual filter-value entry remains available. Providers should raise `SemanticQueryRejectedError` only for positively identified input-validation failures. See the [provider error contract](docs/developer_docs/semantic-query-errors.md). ### Semantic-view Table charts without a temporal axis Semantic-view Table charts omit recognized dormant time grains from frontend-generated aggregate queries when no temporal axis is present. The saved grain and Time Grain control visibility are unchanged. Direct API payloads and saved chart-data GET requests that bypass frontend rebuilding retain strict validation; some old stored query contexts can therefore still fail. Deploy updated frontend assets with this change. ### SQLite time filters on `DATE` columns On SQLite, Shillelagh and the Superset meta database, a time filter on a `DATE` column writes its bounds as a date, such as `'2026-09-20'`, and no longer uses the column's **Datetime format** (`python_date_format`) or the database's `python_date_format_by_column_name`. This fixes ranges that started and ended one day late on `DATE` columns holding `YYYY-MM-DD` text. A `DATE` column that holds values in another format, such as `20260920`, `09/20/2026` or epoch seconds, now matches no rows, even with a Datetime format set. Columns declared as `INTEGER` are not affected. On Shillelagh and the meta database, a bound with a time of day is cut to its date. ### MCP chart target aliases Chart tools interpret legacy `datasource_id` with `datasource_type`: `table` (the default when omitted) selects a dataset; `semantic_view` selects a view. For table-only requests, an explicit `dataset_id` takes precedence over `datasource_id`. Unsupported types or conflicting semantic selectors return validation errors instead of silently selecting a table. Prefer `dataset_id` or `view_id`. ### Apache Doris connection form and `DBS_AVAILABLE_DENYLIST` `DBS_AVAILABLE_DENYLIST` is matched against an engine spec's `default_driver`. The Doris engine spec's `default_driver` is `mysqldb` (the driver pydoris registers), so a deployment that hides Doris with `{"pydoris": {"pydoris"}}` sees it listed again; change the entry to `{"pydoris": {"mysqldb"}}`. The Doris connection form's SSL switch sets `ssl_mode=VERIFY_CA`. With MariaDB Connector/C this also verifies the server hostname, so the switch needs `ssl_ca=` in Additional parameters and a Doris FE certificate matching the host. A Doris FE on its default self-signed certificate fails to connect once the switch is on. ### MCP get_chart_data error categories `get_chart_data` returns `AccessDenied` when a query is refused for access, and `ValidationError` when a semantic layer provider rejects the query or Superset's query validation rejects it while it runs (for example, a semantic view refusing an empty `IN` filter), matching the chart-data API's 403 and 400. These were previously reported as `DataError`. Clients that matched on `DataError` for these cases should handle the new types. On the saved-chart path these errors also no longer emit an error-level MCP log notification; the category is carried by the response envelope alone. ### Empty MCP chart previews Saved Bubble and Histogram Vega-Lite previews with zero rows return an empty specification instead of `NoDataError`, matching their unsaved previews. Clients should handle empty specifications rather than relying on that error. ### MCP chart tools advertise a compact chart config schema `generate_chart`, `update_chart`, `update_chart_preview` and `generate_explore_link` no longer inline every chart type's JSON Schema in their tool input schemas. Their `config` parameter is advertised as an object whose `chart_type` is one of the supported chart types. The fields and examples for each chart type come from `get_chart_type_schema(chart_type)`, which returns the same per-type schema as before. Server-side validation is unchanged: requests are still validated against the complete chart configuration model and invalid fields are rejected with the same errors. MCP clients that built chart configs only from the tool input schema should call `get_chart_type_schema` first. ### DynamoDB timestamp string format DynamoDB time-filter bounds use ISO 8601 with a `T` separator, preserving fractional seconds (for example, `2019-01-02T04:00:00.500000`). Tables storing space-separated timestamps such as `str(datetime)` must normalize their stored strings to the same ISO format before using these time filters. Otherwise both sub-day and whole-day ranges can return incorrect rows: a January 2–3 range can exclude January 2 and include January 3 instead. boto3 does not serialize Python datetime objects; applications choose the string format. Use a consistent timezone and precision for stored strings and bounds. - The `/register/` self-registration page and the login page's "Register" button are only served for the auth types that support self-registration (`AUTH_DB`, and `AUTH_OAUTH` for the page), and only when `AUTH_USER_REGISTRATION` is enabled. LDAP, SAML and `AUTH_REMOTE_USER` deployments have to set `AUTH_USER_REGISTRATION = True` so Flask-AppBuilder provisions users on first login, which used to publish a public registration form that Flask-AppBuilder has no handler for (submitting it returned a 404). First-login provisioning is unchanged. This also closes `GET /register/activation/`, which previously stayed reachable and able to provision a user regardless of `AUTH_USER_REGISTRATION`; it now requires the same gate as `/register/`. ### MCP chart tools advertise a compact chart config schema `generate_chart`, `update_chart`, `update_chart_preview` and `generate_explore_link` no longer inline every chart type's JSON Schema in their tool input schemas. Their `config` parameter is advertised as an object whose `chart_type` is one of the supported chart types. The fields and examples for each chart type come from `get_chart_type_schema(chart_type)`, which returns the same per-type schema as before. Server-side validation is unchanged: requests are still validated against the complete chart configuration model and invalid fields are rejected with the same errors. MCP clients that built chart configs only from the tool input schema should call `get_chart_type_schema` first. ### SQL execution request limits are safety caps The MCP `execute_sql` request's `limit` no longer overrides a smaller outer SQL LIMIT. It caps the last statement at the smaller of the SQL LIMIT and the request limit, subject to `SQL_MAX_ROW`. For example, SQL `LIMIT 5` with request `limit: 10` keeps `LIMIT 5`. To return more rows, increase or remove the SQL LIMIT explicitly. Omitting the request limit still leaves SQL limits unchanged. Inner query and CTE limits are not changed. This contract change also applies to the public `Database.execute()` and `Database.execute_async()` APIs through `QueryOptions.limit`, including dry runs. SQL Lab also recognizes literal `FETCH FIRST` counts and parenthesized literal limits. SQL Server `TOP ... PERCENT` and `TOP ... WITH TIES` are no longer interpreted as fixed row counts: SQL Lab uses the row-limit dropdown (subject to server limits) and rewrites these clauses to a fixed `TOP` cap. For example, `TOP 5 PERCENT` with a 1000-row dropdown can return up to 1000 rows rather than the previous five. `BigQueryEngineSpec.fetch_data` no longer falls back to a plain `fetchall()` once it has read its initial sample, since a forward-only cursor cannot replay those rows. A BigQuery driver error after the sample now fails the query instead of returning the remaining rows as a success. This applies to every caller, including legacy SQL Lab execution and dataset column discovery. An error on the initial read still falls back as before. ### Doris SSL requests require TLS The Doris SSL toggle uses `ssl_mode=VERIFY_CA`. Saved `ssl_mode=REQUIRED` and `ssl=1` URLs also require TLS and follow the mysqlclient rules below: `REQUIRED` is kept with Oracle libmysqlclient 5.7/8.x/9.x and upgraded to `VERIFY_CA` with MariaDB Connector/C or an unrecognized client, which can otherwise fall back to cleartext. Supply the trusted `ssl_ca` (or `connect_args.ssl.ca`) and a certificate valid for the connection hostname. Conflicting advanced settings fail closed. This shares MySQL's TLS normalization without changing Doris catalog/schema handling. ### MySQL SSL requests require TLS The SSL toggle (or ssl=1 in the URI) requires TLS, and so does a saved ssl_mode of REQUIRED, VERIFY_CA or VERIFY_IDENTITY without ssl=1. With mysqlclient, Oracle libmysqlclient 5.7/8.x/9.x uses ssl_mode=REQUIRED; MariaDB Connector/C and unrecognized client versions use VERIFY_CA to prevent cleartext fallback. Explicit VERIFY_CA and VERIFY_IDENTITY are retained. Contradictory options such as ssl_mode=DISABLED or ssl_disabled=True fail rather than cancelling the SSL request. Existing saved connections with the toggle on are affected at upgrade, without a feature flag. Verification can fail for self-signed/default server certificates or missing trust roots. MySQL does not use the connection form's Root certificate field (server_cert). Set ssl_ca to a trusted CA file path available on every web and worker node, for example in the URI (?ssl=1&ssl_ca=/path/to/ca.pem). Connector/Python and PyMySQL enable ssl_verify_cert=True. PyMySQL requires version 1.2 or newer; use individual ssl_ca, ssl_cert and ssl_key options instead of a nested ssl dictionary with the toggle. Options that disable TLS or required verification are rejected. Standard Aurora MySQL connections intentionally follow the same rules, including IAM connections. For certificate verification, install the Amazon RDS CA bundle on every web and worker node and set ssl_ca to that file. IAM authentication does not supply a CA. The Aurora Data API uses HTTPS and needs no MySQL TLS arguments. SSH tunnels rewrite the connection host to the local bind address (typically 127.0.0.1). MariaDB Connector/C also checks hostname identity with VERIFY_CA, so a certificate for the remote database hostname will fail. For SSH-only transport, turn off the SSL toggle and remove ssl=1; this removes the TLS guarantee on the SSH endpoint-to-database leg. If end-to-end TLS is required, use a driver/native TLS configuration compatible with the tunnel and validate it separately. Operators using native TLS settings can turn off the toggle, remove ssl=1 and configure extra.engine_params.connect_args (for example a driver-supported native ssl dictionary). Superset passes those settings through without enforcing TLS; ensure the chosen driver configuration does not silently fall back to cleartext. Connections using the separate MariaDB engine (mariadb:// URIs and its drivers) get the same handling. With MariaDB Connector/Python (mariadb+mariadbconnector://), the toggle keeps ssl=True and enables ssl_verify_cert=True. Other MySQL-compatible engines such as OceanBase and StarRocks keep their existing SSL handling. ### SQL Lab decimal results use exact strings SQL Lab represents database `DECIMAL`/`NUMERIC` values as JSON strings instead of JSON numbers, preserving precision and trailing zeros in the results grid and exports. The strings use fixed-point notation, and non-finite `NaN` and `Infinity` decimals are returned as `null`. Numeric sorting still compares their exact values. API consumers performing arithmetic should parse these strings with a decimal library, not JavaScript `Number`. This includes SQL Lab extensions: the `data` rows passed to `sqlLab.onDidQuerySuccess` listeners carry strings for `numeric` columns (for example PostgreSQL `SUM(bigint)`, `AVG` or `ROUND` results), so adding them with `+` concatenates instead of summing. The `sqleditor.extension.resultTable` override, which replaces `FilterableTable` in both SQL Lab results and table previews, also receives string rows but not the built-in exact-decimal comparator; extensions must implement their own decimal sorting. Chart and dataset queries also use fixed-point strings for Decimal columns that fall back to string conversion (for example, Decimals mixed with floats), and non-finite Decimals in those columns become null. Other chart result serialization and chart number formatting are unchanged. Re-run queries whose cached JSON results were produced before upgrading all workers. ### Version history retention setting Use `VERSION_HISTORY_RETENTION_DAYS` for both the application setting and environment variable. `SUPERSET_VERSION_HISTORY_RETENTION_DAYS` shipped in 7.0 and remains a deprecated compatibility alias when the new setting is absent. Existing positive windows are preserved; existing zero or negative values still disable pruning. Migrate those deployments to `VERSION_HISTORY_RETENTION_DAYS=0`. Set the new environment variable to take precedence over a legacy value. In a custom config that star-imports defaults, an inherited new 30-day value cannot be distinguished from an explicit 30-day override; when the old key is also present, the safer disable or longer window is retained. Remove the old key when setting the new value in such a config. **Do not copy an old `-1` value to the new key:** the new `-1` makes history immediately eligible on the next scheduled run instead of disabling pruning. Startup logs a warning when the new `-1` is active. The default remains 30 days when neither key is set; zero disables pruning. For both this setting and `SOFT_DELETE_RETENTION_DAYS`, `-1` means immediate eligibility on the next scheduled cleanup run, with its clock as cutoff (not a future cutoff). Live/current data and normal purge guards remain protected. An absent environment value retains the 30-day default. Invalid or oversized supplied environment values defer scheduled cleanup with 0 for both settings. Host policy failures also defer. Malformed or oversized standalone soft-delete runtime config and stored CLI windows likewise defer purge with 0 instead of falling back to a shorter retention window, and a malformed version-history value in the live `app.config` defers the scheduled prune with 0 and a warning rather than failing the run. Only absent values use the fallback. ### Version history API access follows `VERSION_HISTORY` `VERSION_HISTORY` controls the UI and all chart, dashboard, and dataset version-list, version-snapshot, activity, and version-restore endpoints. With the flag disabled, callers who pass the existing route permissions receive 404 instead of being able to use those APIs directly. When enabled, existing route permissions and object-level editorship checks still apply. The default remains enabled. API consumers that disabled the flag to hide only the panel must enable it to retain history API access. Capture and retention remain independently configured; ordinary entity CRUD and soft-delete recovery are unaffected. With only `ENABLE_VERSIONING_CAPTURE` disabled, existing history is readable if `VERSION_HISTORY` is enabled, but version restore returns 404. An optional `VERSIONING_CAPTURE_PREDICATE(session)` lets hosts restrict capture at save time without changing process-global listeners. `None` preserves existing behavior. A false result skips history capture but not the live ORM save, and refuses version restore with 404. Hosts must supply tenant context for request, import, and background writes and keep the decision stable within a transaction; version reads (ETag and version info on the chart, dashboard and dataset APIs) consult the predicate with the same request session as the save they accompany. Existing history and independent retention are unchanged; skipped edits are not reconstructed. The first enabled edit of an entity without history may create the existing baseline of its then-current state. Expected service failures must be handled by the host predicate; programming/database errors are not suppressed. Parent and child snapshots are rebuilt in the save transaction after a captured edit. If that rebuild fails, the save fails and must be rolled back, so an incomplete snapshot is not exposed as restorable history. ### Guest token RLS rules without a dataset apply inside sub-queries A guest token RLS rule with no `dataset` key applies to every dataset. Such rules are now also injected into sub-queries of custom SQL expressions (with `ALLOW_ADHOC_SUBQUERY` enabled) and into SQL Lab queries, for every table that resolves to a dataset, instead of only into the chart's outer query. In a virtual dataset's SQL they are injected into the tables read inside an uncorrelated sub-query (scalar, `IN` or `EXISTS`, including CTEs such a sub-query reads). Tables whose rows reach the virtual dataset's output (`FROM`, joins, derived tables, `LATERAL`) or are keyed to them by a correlated sub-query (one that itself references an outer table or CTE by name or alias, such as `lookup.id = a.lid`; a sub-query nested in it doesn't count) are still left to the outer query, which already applies the rules. An unqualified outer reference (`WHERE id = lid`) can't be told apart from a local column, so that sub-query gets the rules; qualify it to keep a lookup without the column working. Like a join, a correlated sub-query over another multi-tenant table is not scoped by these rules; give such rules a `dataset` if needed. The virtual dataset's own RLS rules, which its outer query applies too, are injected into those uncorrelated sub-queries the same way, for every user, when a sub-query reads the table the virtual dataset is named after. If a sub-query reads a dataset that lacks a column the rule references, the query now fails with a column-not-found error instead of reading rows the rule was meant to exclude. To keep such charts working, set `dataset` on the rule so it only targets the datasets that have the column. ### MCP response size guard: byte limit instead of estimated token count The MCP response-size guard no longer estimates LLM token counts (it previously used `tiktoken`'s `cl100k_base` encoding, with a character-based fallback). An MCP server has no way to know which client or tokenizer is actually consuming a response, so token estimation was replaced with the exact serialized UTF-8 byte length of the response, which is deterministic and tokenizer-agnostic. This also removes the `tiktoken` dependency entirely, including the unannounced network request it could make to download its vocabulary on a cold cache. `MCP_RESPONSE_SIZE_CONFIG["token_limit"]` is renamed to `MCP_RESPONSE_SIZE_CONFIG["max_bytes"]`, and its default changes from 25,000 (estimated tokens) to 50,000 (exact bytes). Any deployment that has set `token_limit` in `superset_config.py` must rename the key to `max_bytes` and adjust the value for byte semantics. ### Scheduled report and alert retry admission Run `superset db upgrade` before starting workers with this version. The migration adds nullable `execution_owner` and `execution_window` columns to `report_schedule`. Pause scheduling and drain in-flight executions and queued retry tasks before upgrading workers together: older workers do not participate in execution fencing. Two-argument retries without ownership evidence are discarded. Older one-argument retry messages cannot be distinguished from fresh cron tasks and must be drained; they are not reliably discarded by the new workers. Restart scheduling after migration and worker replacement. Mixed-version workers are not supported for this transition: old workers cannot consume the new retry task signature. Drain queued retries as well as active jobs before replacement. New workers log `report_retry_discarded` with `reason=missing_execution_owner` and increment `reports.execute.legacy_retry_discarded` for legacy retry tasks. Rerun any affected schedule after the upgrade; discarded retries are not automatically replayed. In the other direction, a new worker can enqueue a three-argument retry that an old worker cannot consume: the old worker raises `TypeError`, loses that retry, and can leave the schedule in `RETRYING` until stale-retry recovery. Do not perform a mixed-version rolling deployment; stop scheduling, drain active jobs and queued retries, then replace all consumers before restarting scheduling. With `ALERT_REPORTS_RETRY` enabled, alerts can opt into retries as reports do. Retries re-evaluate alert conditions. Whole-execution retries stop once delivery has started because a failed send may already have reached a recipient. Retry notifications no longer include raw provider diagnostics; consult execution logs. Ordinary editor-only error emails retain HTML-sanitized diagnostics. Notifications to configured recipients, including retry/final-failure notices, remain redacted. Ownership is rechecked in a short committed transaction before each recipient. No schedule-row lock is held during SMTP/Slack I/O. Recovery can fence subsequent sends, but cannot recall a notification already in flight; delivery is not atomic with database ownership and exactly-once delivery is not guaranteed. If the fallback ERROR-log transaction cannot commit, the execution still depends on working-timeout recovery. Error-notification delivery and its database marker are not atomic: a send followed by a marker-write failure can result in a duplicate error notice. This does not authorize replay of data-bearing notifications. - With `SEMANTIC_LAYERS` enabled, combined connection discovery honors `Database.can_read` and `SemanticLayer.can_read` independently. Each permitted source retains its normal row filters, including dynamic database filters for Admin. A source filter never includes rows or counts from a denied source; callers with neither read permission are denied. Feature-off database browsing is unchanged. - The combined datasource list (`GET /api/v1/datasource/`) accepts Dataset read without an additional Datasource read grant, regardless of `SEMANTIC_LAYERS`. With the flag enabled, SemanticView read independently permits semantic-view discovery. Existing row-level dataset/chart access remains enforced. - The `presto` extra requires PyHive 0.7.0 or later. PyHive 0.6.5 cannot load its Presto dialect under SQLAlchemy 2 because it imports `sqlalchemy.databases`. Upgrade existing installations with `pip install "pyhive[presto]>=0.7.0"`. - The Pinot extra requires pinotdb[sqlalchemy]>=8.0.0,<10.0.0. Earlier releases declare SQLAlchemy below 2 in their SQLAlchemy extra. - The Databricks extra requires databricks-sqlalchemy 2.x (at least 2.0.1). The 1.x dialect requires SQLAlchemy below 2. - `superset deletion-retention force-purge` now exits **1** when the target is blocked by a deletion rule or is not found (the messages are unchanged), so a scripted compliance erasure cannot mistake a refusal for a completed purge. Only a completed purge exits 0; a usage error still exits 2. ### Deprecated permission cleanup may change custom role grants Two migrations now clean up permissions deprecated in past releases that previously stuck around forever after an upgrade (#33272). If a custom role holds one of these permissions, upgrading will either: - **Delete it outright**, for permissions whose underlying feature has no live equivalent (e.g. the access-request workflow), or whose only live successor would grant a role a materially broader capability than it ever had -- for example `can_testconn` is deleted rather than resurrected as `Database.can_write`, which would let the role create, edit, or delete any database connection, not just test one; or - **Migrate it to a verified live successor** (e.g. `can_explore_json` -> `can_read` on Chart), preserving the role's effective access. `can_copy_dash` is the exception to this rule. Although its live successor, `Dashboard.can_write`, is broader than the historical permission, the current dashboard-copy endpoint is itself authorized by `Dashboard.can_write`. It is therefore migrated rather than deleted so existing access to dashboard copying is preserved. If a custom role in your deployment relies on one of the deleted permissions, re-grant the appropriate live permission to it manually after upgrading. See the two migrations' docstrings (`superset/migrations/versions/ 2026-09-10_00-00_1f5f4fb8bfc1_delete_deprecated_permissions_33272.py` and `..._00-01_3ce9a4572f8a_rename_deprecated_permissions_33272.py`) for the full per-permission mapping and reasoning. - Snowflake stage file-management statements (`PUT`, `GET`, `REMOVE` and its `RM` alias) are rejected in user-submitted SQL (SQL Lab, the cost-estimate path and alert queries), regardless of the database's `allow_dml` setting. `PUT`/`GET` perform file I/O on the host running the query, and `REMOVE`/`RM` delete files within a stage; none of them read or write table data, so `allow_dml` does not govern them. Deployments that ran these through SQL Lab should manage stage files with Snowflake's own clients instead. `LIST`/`LS`, which only enumerate staged files, are unaffected. ### MySQL metadata database now actually defaults to READ COMMITTED Superset has always *intended* to default the metadata-database isolation level to READ COMMITTED on MySQL (and logged that it did), but the code discarded the result of SQLAlchemy's generative `execution_options()` call, so every MySQL deployment without an explicit `isolation_level` in `SQLALCHEMY_ENGINE_OPTIONS` has in fact been running at InnoDB's default REPEATABLE READ. The default is now applied for real. PostgreSQL is unaffected (its server default is already READ COMMITTED), and an explicit `SQLALCHEMY_ENGINE_OPTIONS["isolation_level"]` was and remains respected. If your deployment relies on REPEATABLE READ semantics (snapshot-stable long transactions, RR gap-locking behavior), pin the previous effective behavior explicitly: ```python SQLALCHEMY_ENGINE_OPTIONS = {"isolation_level": "REPEATABLE READ"} ``` ### MCP metric discovery defaults and embedded dimensions `list_metrics` defaults to 25 metrics per page and does not embed compatible dimensions. Clients needing those dimensions should call `get_compatible_dimensions` for the chosen metrics, or explicitly request `include_compatible_dimensions=true` with `page_size` at most 8 for every scope, including built-in `dataset_id` requests. Non-embedded requests retain the 500-metric ceiling. This fixed embedding cap is independent of the operator's `MCP_RESPONSE_SIZE_CONFIG['max_bytes']` (50,000 by default); it does not guarantee that every payload fits a configured response limit. ### Legacy FAB password reset pages are removed The Flask-AppBuilder server-rendered password reset pages at `/resetpassword/form` (admin reset of another account) and `/resetmypassword/form` (self-service reset) are no longer registered; both routes answer 404, and the "Reset Password" and "Reset my password" buttons on the legacy FAB user pages that led to them are gone. `superset init` no longer assigns their permissions (`can this form get/post on ResetPasswordView` and `ResetMyPasswordView`, plus the `resetpasswords` and `resetmypassword` actions on `UserDBModelView`) to the built-in Admin/Alpha/Gamma/sql_lab roles, and a data migration removes those same permissions from every other (operator-defined) role that already held them. The one exception is a custom-named `AUTH_ROLE_ADMIN`: FAB re-registers the two `UserDBModelView` action permissions on app creation and grants new ones to that role, and `superset init` only resyncs the built-in roles, so such a role re-acquires those two (still hidden, still 404) entries. Every flow they served lives in the SPA: administrators reset a user's password from the "New password" fields in the Users list edit modal (`PUT /api/v1/security/users/`), users change their own from the "Reset my password" modal on their profile page (`PUT /api/v1/me/`, which requires `current_password` when the account already has one), and a pending forced password change (`ENABLE_FORCE_PASSWORD_CHANGE`) now redirects to that profile page instead of the removed form. Deployments that link to either legacy route should point at `/user_info/` or the Users list instead. Reaching the profile page requires `can_read` on `user`, which the built-in Admin/Alpha/Gamma roles hold by default but a custom role is not guaranteed to. A user whose only role lacks it and who gets redirected there by a forced password change is now redirected to logout with an explanatory message instead, so they are never trapped in a redirect loop. Under `AUTH_REMOTE_USER` a logout cannot help (the header re-authenticates the user immediately), so such a user gets a terminal 403 with the same explanation and no logout. Clearing the flag also needs `can_read` and `can_write` on `CurrentUserRestApi` (for `GET` and `PUT` on `/api/v1/me/`), so grant all three permissions to any custom role whose members may have `password_must_change` set on their account. `ENABLE_FORCE_PASSWORD_CHANGE` is the deployment-wide setting that turns the check on; `password_must_change` is the per-user flag it acts on. ### Unused `INCLUDE_FIREFOX` build arg and dead config removed Screenshots use only Playwright with Chromium, so the `INCLUDE_FIREFOX` Docker build arg, which installed a Firefox browser that nothing used, has been removed from the `Dockerfile` and the `docker-compose` files. Builds that still pass `INCLUDE_FIREFOX` keep working, though Docker may warn that the build argument is unused. Use `INCLUDE_CHROMIUM=true` to add the browser that screenshots need. The `EMAIL_PAGE_RENDER_WAIT` config key and the `ENABLE_PLAYWRIGHT` variable in `docker/.env` are also removed. Nothing in Superset read either of them, so setting them had no effect and they can be deleted from custom configs. ### Default Docker image is now batteries-included; the minimal image moves to `-lean` The default `apache/superset` Docker image (the plain tags: `latest`, `master`, `7.0.0`, per-SHA) is now a batteries-included, production-grade image. It bundles the common metadata/analytics drivers (`psycopg2-binary` for PostgreSQL, `mysqlclient` for MySQL), the MCP server dependencies (`fastmcp`), and a headless Chromium (via Playwright) for Alerts & Reports and thumbnail generation. It still runs as the non-root `superset` user and is byte-compiled like before. The previous minimal image — with **no** database drivers — is still published, but under `-lean` tags: `latest-lean`, `master-lean`, `7.0.0-lean`, `-lean`. This naming starts with 7.0.0. For earlier releases (6.1.0 and before), the plain tag (e.g. `6.1.0`) is the minimal image; where a `-lean` tag exists for one of those releases, it is the same minimal image. **What operators should expect:** - **The default image is larger** (several hundred MB more) because it ships a headless Chromium and extra drivers. If you were relying on the minimal default and layering your own drivers, switch your base image to the corresponding `-lean` tag (for example `FROM apache/superset:master` becomes `FROM apache/superset:master-lean`) to keep the previous footprint. - **No config change is required** for most deployments; the metadata-database drivers most installations need are now present out of the box. - The `-dev` images (`latest-dev`, `master-dev`, …) are unchanged. ### Docker image publishing now excludes standalone `websocket` and `dockerize` images The Apache Superset Docker Hub repository no longer publishes standalone `apache/superset:*websocket` or `apache/superset:*dockerize` image tags. The realtime WebSocket server is bundled in the `superset`, `lean`, and `dev` images and can be launched with `/app/docker/entrypoints/run-websocket.sh`. Helm init containers use the main Superset image for dependency checks. After this policy is cherry-picked into each active release branch, its pushes will validate the Docker build locally instead of publishing Docker Hub images or cache layers. Until then, those branches retain their previous publishing behavior. Official release tags (`X.Y.Z`, `latest`, and their preset variants) are published only by the release workflow after release manager sign-off. The scheduled release-image refresh workflow was removed, so official release tag digests are not overwritten outside release publishing. ### Report capture readiness is rechecked immediately before screenshots Scheduled report and alert captures require chart readiness to remain stable immediately before Chromium captures the image. A capture that re-enters a loading state during that window fails instead of delivering a screenshot with spinners. ### Improve Db2 Time Grain Expressions The Db2 engine spec has been streamlined by using the DATE_TRUNC scalar function, which requires Db2 11.1.0 or higher. Per the ISO 8601 standards, the `WEEK` time grain now shifts the first day of the week to Monday as part of this change. ### Update IBM Db2 for i Time Grain Expressions IBM Db2 for i inherits its engine spec from Db2 but does not support the DATE_TRUNC scalar function, so it will use the previous arithmetic expressions defined for Db2. Its `WEEK` time grain now uses `DAYOFWEEK_ISO` to align with the Db2 change. ### Scheduled rendered reports fail closed after capture rejection Scheduled PDF and PNG delivery requires an accepted report capture context. A terminal rejection from standard, tiled, or combined-image validation is sticky for that execution and is checked again at the notification boundary. Reports and alerts cannot deliver a rendered artifact after rejection, even if an intermediate capture layer accidentally catches the original error. Text-only failure notifications and thumbnail capture behavior are unchanged. ### Resample "Fill the entire time range" Charts with Resample can enable **Fill the entire time range** so gap-filling covers the full queried window (`from_dttm` / `to_dttm`), not only between the first and last returned data points. Existing charts are unchanged until the control is turned on. Resample projections remain capped by `MAX_RESAMPLE_ROWS` (default `1_000_000`). That cap now also covers calendar frequencies (month, quarter, year, …) that previously skipped the check because they have no fixed `Timedelta`. ### Dashboard read fallback requires a published dashboard The object-read gate's datasource-based fallback — including the admit for dashboards with no charts — now applies to **published** dashboards only. For the datasource branch this matches the list filter's fallback, which was already published-only; for the no-charts admit the list filter still never yields chart-less dashboards to ordinary users, a deliberate pre-existing asymmetry that this change narrows but does not remove (the object gate admits opening a published chart-less dashboard; the list filter does not surface it). Previously an *unpublished* dashboard with an empty viewers list was readable by any authenticated user who could access one member datasource (or by every authenticated user, when it had no charts — including markdown-only dashboards), even though it appeared in no default list; and removing the last viewer subject from a dashboard silently widened access, because the viewer branch is published-gated while the fallback was not. Owners (folded into editors by the subjects model), editors — including resolver-granted editors — and admins are unaffected: they are admitted before the fallback regardless of published state. Everything consuming the gate inherits the tightening — including **alert and report execution**, not only schedule creation/validation. An already-scheduled report against an *unpublished, no-viewers* dashboard whose execution principal is a datasource-entitled non-editor will fail on its next run after upgrade. Before upgrading, audit report schedules targeting unpublished dashboards. For each one, first identify the principal the report actually runs as: that is decided by `ALERT_REPORTS_EXECUTORS`, not by who owns the schedule. Then apply one of: - **Publish the dashboard** (and confirm the execution principal keeps the read access the gate still requires — viewer membership when the dashboard has viewers, or access to a member datasource when it does not). For the unpublished, no-viewers, datasource-entitled case above, publishing alone supplies the missing prerequisite. - **Grant the execution principal dashboard editorship**, where that privilege is appropriate — editors are admitted ahead of the fallback regardless of published state. Adding the principal to the dashboard's **viewers is not a remedy on its own**: the viewer branch is itself published-gated, so a viewer of an unpublished dashboard is still refused. Re-owning the schedule is not a reliable substitute either — with a `FixedExecutor` (a service or selenium account) the resolved user does not follow schedule ownership at all, and the ownership-sensitive executor types have their own creator/modifier/editor selection rules. ### Tagging is on by default `TAGGING_SYSTEM` now ships **on**. The Tags menu entry, the tag columns and filters on the chart, dashboard and saved-query lists, and the Tags field in the chart and dashboard property modals are all visible without configuration, and tags are included in asset export and import. **What operators should expect:** - **Implicit tags no longer accrue.** Saving a chart, dashboard, dataset or saved query, and favoriting an asset, used to write rows to `tag` and `tagged_object` (`type:chart`, `editor:`, `favorited_by:`). That generation has since been removed; see "Superset no longer auto-generates `type:`/`editor:`/`favorited_by:` tags" below for what happens to rows created before upgrading. - **Exports gain a `tags` key and a `tags.yaml` file.** Chart and dashboard export bundles carry custom tags. Importers on 6.0 and later understand both; older importers skip the unrecognized `tags.yaml` file but reject chart and dashboard YAML that contains a `tags` key, so strip that key before importing a bundle into Superset 5.x or earlier. - **Only the cleanup listeners remain, and they run unconditionally.** The creation listeners from the bullet above are gone entirely; the tagging SQLA event listeners still attached at startup are deletion-only, and they run regardless of `TAGGING_SYSTEM`, so deleting an asset never leaves orphaned `tagged_object` rows behind. Set `FEATURE_FLAGS = {"TAGGING_SYSTEM": False}` to restore the previous behavior. Existing tag rows are left untouched. ### MCP structured tool outputs are opt-in Native MCP tools define concrete output schemas, but Superset preserves the text-only wire contract by default for compatibility with clients and transport bridges that cannot encode structured results. Set the following only after validating every MCP client and bridge used by the deployment: ```python MCP_STRUCTURED_OUTPUT_ENABLED = True ``` When enabled, tool discovery includes `outputSchema` and successful tool calls include matching `structuredContent` alongside the existing text representation. When disabled, the outer compatibility middleware removes both fields as a pair; server-side output validation still applies to native tools. `StructuredContentStripperMiddleware` is deprecated for custom startup paths but retains its original stripping behavior. Replace it with `ToolResultCompatibilityMiddleware(structured_output_enabled=False)`. ### Version-history and activity endpoints are edit-gated Version-history and activity endpoints (`GET /api/v1/{chart,dashboard,dataset}//versions/…` and `…/activity/`) are now edit-gated: they require object-level editorship (owner/editor/admin) of the entity, matching the UI's edit-gated Version history menu and the restore endpoint's gate. Read-only users who could previously retrieve the full change log (author identities, field-level before/after diffs) via the API now receive 403. Embedded guest-token principals are always refused on these endpoints, even when a role subject they hold has been granted editorship. Related-entity visibility filtering inside the activity stream is unchanged. ### Updates of externally managed entities are refused server-side `PUT /api/v1/{chart,dashboard,dataset}/` — including the chart query-context-only save, `PUT /api/v1/dataset//refresh`, and the legacy Explore chart overwrite (`/superset/explore/`, `action=overwrite`) — now refuses an **externally managed** entity (`is_managed_externally = True`) with HTTP 403, enforcing server-side what the UI already does by hiding the edit affordances. Previously the refusal existed only in the browser, so an otherwise-authorized editor could mutate such an entity by calling the endpoint directly and have the change overwritten on the next external sync (the stored chart query context is executable state — report execution runs it — so it is gated too; Explore's background query-context save receives a 403 it ignores for such charts). The dashboard colors-sync path (`PUT /api/v1/dashboard//colors`, fired in the background while a dashboard is viewed) keeps working for the **derived** color values (`color_scheme_domain`, `shared_label_colors`, `map_label_colors`) but refuses a payload that would change the authoritative `color_scheme`/`label_colors`. The `is_managed_externally` flag itself is now ignored by the ordinary PUT schemas (accepted for wire compatibility, then discarded): it was previously client-writable there, and with the new gate a client-set `true` would have been irreversible via the API. A matching gate for version restore is added separately in #44013. ### Global Async Queries re-platformed onto the Global Task Framework (breaking) Global Async Queries (GAQ) no longer runs on its own bespoke async-events plumbing. Async chart data is now executed as Global Task Framework (GTF) tasks (one task per `QueryObject`), the browser learns of completion by polling `GET /api/v1/task/status_changes` (optionally accelerated by the WebSocket transport below) and re-issuing the original `/chart/data` request against the now-warm per-query cache, and the realtime WebSocket server is a generic, feature-agnostic task push transport rather than a GAQ-specific event tail. Breaking removals (no deprecation window): - The `/api/v1/async_event/` REST API, `AsyncQueryManager`, and the `qc-` query-context descriptor replay endpoint (`GET /api/v1/chart/data/`) are removed. Any client that consumed a `result_url` from a `202` response must move to the re-request model (the built-in frontend already does). - The following config keys are removed: `GLOBAL_ASYNC_QUERIES_CACHE_BACKEND`, `GLOBAL_ASYNC_QUERIES_TRANSPORT`, `GLOBAL_ASYNC_QUERIES_WEBSOCKET_URL`, `GLOBAL_ASYNC_QUERIES_REDIS_STREAM_PREFIX`, `GLOBAL_ASYNC_QUERIES_REDIS_STREAM_LIMIT`, `GLOBAL_ASYNC_QUERIES_REDIS_STREAM_LIMIT_FIREHOSE`, `GLOBAL_ASYNC_QUERIES_REGISTER_REQUEST_HANDLERS`, `GLOBAL_ASYNC_QUERIES_JWT_*`, and `GLOBAL_ASYNC_QUERY_MANAGER_CLASS`. The coordinator (locks, GTF, and now GAQ) uses `DISTRIBUTED_COORDINATION_CONFIG` exclusively. Enabling async chart data in the new flow: ```python # feature flag: makes async chart data available (auto-enables GLOBAL_TASK_FRAMEWORK) FEATURE_FLAGS = {"GLOBAL_ASYNC_QUERIES": True} # a Redis connection for distributed coordination (locks, GTF signalling, # and the realtime pub/sub); required for async execution in production DISTRIBUTED_COORDINATION_CONFIG = { "CACHE_TYPE": "RedisCache", "CACHE_REDIS_HOST": "localhost", "CACHE_REDIS_PORT": 6379, "CACHE_REDIS_DB": 0, } ``` Async is now **opt-in per request**: `GLOBAL_ASYNC_QUERIES` only makes async *available*; whether a given `/chart/data` request runs async is decided by an `async_mode` request flag (endpoint default `false`, so programmatic API clients keep the synchronous `200` flow unless they opt in). The built-in frontend resolves the `async_mode` it sends from a policy chain — per-dashboard override → deployment default `GLOBAL_ASYNC_QUERIES_DEFAULT` (default `true`) → the feature flag — so the UI keeps its existing async behavior by default. **Embedded (guest token) async requires explicit role grants.** Async chart-data completion is observed through `GET /api/v1/task/status_changes` (gated by `can_read Task`) and, when the WebSocket transport is enabled, over the socket (gated by `can_read Realtime`). An authenticated Gamma user has `can_read Task` by default; the default guest role (`Public`) does **not**. So an embedded guest only runs async when the operator grants its role `can_read Task` (and `can_read Realtime` for the socket) — otherwise the request transparently falls back to the synchronous `200` flow rather than returning a `202` the guest could never resolve. Enabling the realtime WebSocket transport (optional; when enabled it becomes the completion transport for async chart-data — see the note on the interval poll): > **Note:** the realtime WebSocket transport is opt-in (`WEBSOCKET_ENABLE` > defaults to `False`). When it is **disabled**, async chart-data completion is > driven entirely by the `status_changes` interval poll (the source of truth). > When it is **enabled**, completion is delivered over the socket and the > recurring interval poll does not run; a one-shot `status_changes` catch-up on > waiter registration and on socket reconnect reconciles anything missed while > disconnected. The socket accelerates delivery over the authoritative > `status_changes` API rather than replacing it: Redis Pub/Sub is best-effort > (at-most-once, no replay), so a disconnect is reconciled by the catch-up on > reconnect/registration. In the rare case a `task.status` is missed while the > socket stays open, the request's give-up runs one final `status_changes` read > before timing out — so a chart whose query actually finished still resolves; only > if that read can't confirm completion does the request end in a bounded error (a > page reload re-establishes state). ```python WEBSOCKET_ENABLE = True WEBSOCKET_URL = "ws://:8080/" WEBSOCKET_JWT_SECRET = "" ``` The built-in Gamma role receives `can_read Realtime`; grant that permission to custom roles that should receive websocket notifications. Run the `superset-websocket` Node server on the **same browser-visible host** (so its JWT channel cookie is shared) and point its `redis` config at the same instance as `DISTRIBUTED_COORDINATION_CONFIG`, plus `jwtSecret` / `jwtCookieName` matching the Flask config (`WEBSOCKET_JWT_SECRET` / `WEBSOCKET_JWT_COOKIE_NAME`, default `superset-ws-token`). During websocket JWT secret rotation, set the websocket server's `previousJwtSecret` / `PREVIOUS_JWT_SECRET` to the old key while Flask continues minting cookies with `WEBSOCKET_JWT_SECRET`. The server is bundled in the official Superset image and launched via an alternate entrypoint — no separate image is required: `docker run /app/docker/entrypoints/run-websocket.sh` (or the opt-in `websocket` profile in `docker compose`). It **subscribes** to a single Redis Pub/Sub channel, `realtime`, which carries a self-describing `{topic, scope, routes, payload}` envelope (both the broadcast `entity.changed` nudges and the targeted `task.status` messages), and forwards `{topic, payload}` to browsers after routing — so a Redis ACL for the websocket server must allow subscribing to `realtime` (this replaces the earlier `entity-changes:*` / `task-status` channels); see `superset-websocket/README.md`. Orphaned GTF tasks (a worker killed mid-execution) are now detected and cleaned up server-side. While a worker holds a task it writes a liveness heartbeat (`tasks.last_heartbeat`, every `GTF_TASK_HEARTBEAT_INTERVAL` seconds, default `15`); a dedicated `reap_orphaned_tasks` Celery beat job reaps any active task whose heartbeat is older than `GTF_ORPHAN_TASK_TIMEOUT` (default `60`) — revoking its Celery job, marking it `FAILURE` so waiters unblock, and (on engines that support query cancellation) cancelling the abandoned warehouse query out-of-band. Enable the `reap_orphaned_tasks` beat schedule on a short interval (e.g. every minute); it is separate from `prune_tasks` (a heavier retention delete run infrequently). The heartbeat write is issued out-of-band and deliberately does not advance `changed_on`. Async chart-data query tasks are now cancellable: a per-query timeout (`GLOBAL_ASYNC_QUERIES_QUERY_TIMEOUT`, default `None` = unbounded) or a user cancel aborts the task, and on database engines that support query cancellation (e.g. PostgreSQL, MySQL, Snowflake, Redshift) the abort also cancels the running warehouse query over a fresh connection — including when the worker died (the reaper cancels it). Engines without cancel support are unaffected — the task is still freed, but the query runs to completion. - Calculated (expression) dataset columns are now wrapped in parentheses when compiled to SQL (`()`), in `SELECT`, `GROUP BY`, `ORDER BY`, `COUNT(DISTINCT ...)`, and the series-limit (top-N) prequery/JOIN paths. This fixes a correctness bug where a bare boolean operator (e.g. `OR`) inside a calculated column used as a series dimension leaked into the surrounding operator precedence (`state = 'CA' OR state = 'NY' = 1` mis-parsing as `state = 'CA' OR (state = 'NY' = 1)`). Query results are otherwise unchanged, but the generated SQL text for calculated-column queries differs; deployments that key on the exact compiled SQL (custom result-cache keys, logging, or SQL diffing) may observe the added parentheses. Physical columns are unaffected, as are calculated columns used as a temporal (time/x-axis) dimension, which resolve through a separate time-grain path (`get_timestamp_expression`). - **[BREAKING] `SemanticLayer` and `SemanticView` are now classified in the Flask-AppBuilder role sets**, so `sync_role_definitions` (run on `superset init` and on startup) stops granting the built-in **Gamma** role write access to them. `SemanticLayer` is treated like `Database` (`READ_ONLY_MODEL_VIEWS`): create/edit/delete become **admin-only**, while read stays broadly available (its configuration is returned masked). `SemanticView` is treated like `Dataset` (`GAMMA_READ_ONLY_MODEL_VIEWS`): writes are Alpha-tier, reads Gamma-tier. Its custom read endpoints (`views`, `connections`) are mapped to `can_read` so they remain accessible under the read-only classification. A deployment relying on Gamma users creating or editing semantic layers/views must grant those permissions through a custom role. A migration retires the now-unused `can_views` / `can_connections` permissions left on the `SemanticLayer` view menu by earlier builds. Two upgrade-time notes on that migration: it seeds the `SemanticLayer` view menu and its `can_read` PVM if absent, so even a fresh or flag-off install gains that permission (harmless — the endpoints 404 while `SEMANTIC_LAYERS` is off); and retiring the stale permissions remaps any role that held them onto `can_read`, a small widening — a custom role granted only `can_views` or `can_connections` gains `can_read` (the semantic-layer list and its masked-configuration detail), which it could not previously reach. Operators who hand-rolled semantic-layer roles should re-audit them after upgrading. The feature remains gated behind the default-off `SEMANTIC_LAYERS` flag. ### Archived dataset purge requires impact confirmation `GET /api/v1/dataset//purge-impact` returns the charts and distinct dashboards affected by permanently deleting an archived dataset, together with an opaque `impact_token`. The dataset purge endpoint now requires that token in the JSON body as `confirmed_impact_token`. API clients that call `POST /api/v1/dataset//purge` must fetch and display the impact first; requests with a missing or malformed token are rejected with 400. The server rechecks the dependency identities immediately before mutation. If they changed, purge performs no deletion and returns 409 with a refreshed impact payload. Clients must display the new impact and obtain renewed confirmation before retrying. Preview or recheck failures fail closed rather than treating unknown impact as zero. Chart and dashboard purge endpoints are unchanged. - The dashboard datasource-based visibility fallback now fails closed: a dashboard whose member charts’ datasources cannot be resolved (deleted datasource rows, missing `datasource_id`, or unsupported datasource types) is no longer accessible to users without explicit editor/viewer rights, and a dashboard composed of semantic-view charts now requires `datasource_access` on (at least one of) its semantic views or their parent semantic layer — previously any authenticated user could open such a dashboard’s shell. Because the fallback now considers every member chart rather than only table-backed ones, a user holding `datasource_access` on any single member datasource — including a semantic view or its parent layer — can open a mixed dashboard that previously denied them. Dashboards with no charts remain accessible, and dashboards with explicit viewers are unaffected. Conversely, holders of `all_datasource_access` now see every published no-viewer dashboard in the dashboard list — including chart-less ones previously hidden by the inner joins — matching what the object-level gate already allowed them to open. - Version restore (`POST /api/v1/{chart,dashboard,dataset}//versions//restore`) now refuses an **externally managed** entity (`is_managed_externally = True`) with HTTP 403, enforcing server-side what the docs already promised. Previously the refusal existed only in the browser, so an otherwise-authorized editor could restore such an entity by calling the endpoint directly and have the restore overwritten on the next external sync. Soft-delete recovery is deliberately unaffected — it changes visibility, not content. ### Themes support per-theme editors Themes now carry a list of **editors** (users, roles, or groups). A new `theme_editors` junction table is created by the migration `f7e8d9c0b1a2_add_theme_editors_table`. On upgrade, each existing non-system theme's creator is backfilled as an editor so authors keep edit access (an empty editors list means admin-only). System themes are left with no editors and remain admin-only to edit. Behavioral changes: - **[BREAKING] Editing and deleting a theme is tightened to editors or Admins.** Previously any principal with `can_write` on `Theme` (which the built-in **Alpha** role holds, since `Theme` is in `GAMMA_READ_ONLY_MODEL_VIEWS`) could edit or delete any non-system theme. Editing and deleting now require the caller to be an editor of that theme; non-editors receive a `403`. Admins bypass the check and remain able to edit or delete any theme. A non-editor cannot add themselves to a theme's editors via `PUT`. - **Theme creation is unchanged** and still requires `can_write` on `Theme`. The creator is automatically added as an editor, and creation now flows through a new `CreateThemeCommand`. - **System themes remain protected** and the system-default/dark theme administration endpoints continue to require an admin plus `ENABLE_UI_THEME_ADMINISTRATION`. - **Importing over an existing theme requires editorship** of that theme (admins bypass); importing new themes still only requires `can_write`. - Editor subject IDs are intentionally not part of a theme's export, so they are not portable across deployments. New config key `SUBJECTS_RELATED_TYPES_THEMES` (default `None`, inheriting the global `SUBJECTS_RELATED_TYPES`) controls which subject types appear in the theme editor picker. - With version history enabled, the first save through the chart editor of a chart created by an older Superset version, an import, or the API may record a one-time settings-migration entry alongside the user's change. On a chart opened normally in Explore nearly all of it is suppressed from the readable history (apache/superset#43350) — the legacy-time rewrite into `adhoc_filters` happens during control initialization and is suppressed with the rest — so what can still record is what the save itself adds (`dashboards`, `query_context`) plus one narrow edge: a legacy key the rewrite removes (such as `granularity_sqla`) can record its removal while its modern replacement stays suppressed. When Explore is opened from a dashboard, via a shared `form_data_key` link, or with a `viz_type` URL parameter, that suppression evidence is deliberately not collected (fail-open), so a first save from those entry points can record the broader set of automatic rewrites. Subsequent saves of the same chart are unaffected. This can recur once per pre-existing chart after an upgrade. - The purge audit log can now be pruned automatically. The new `deletion_retention.prune_purge_audit` Celery beat task (daily, 03:30, in the default `CeleryConfig.beat_schedule`) removes duplicate `blocked` records within an entity's current blockage streak (the earliest — "blocked since" — record and the first record after each change of block `reason` always survive, mirroring the audit writer's own suppression rule) and ages out operational records (`blocked` from resolved streaks, `failed`) older than `PURGE_AUDIT_OPERATIONAL_RETENTION_DAYS` (default 90). A streak is ended only by proof the object is gone (`confirmed`/`target_absent`); a `failed` attempt does not reset the "blocked since" record. Force-purge (`force`-triggered) `blocked` records are exempt from both the duplicate collapse and the operational age-out and are retained permanently — in resolved streaks too — so repeated `force-purge` attempts against a persistently blocked entity still add a record each, regardless of this task. Completed-destruction evidence (`confirmed`, `target_absent`) is **never touched** unless the separate `PURGE_AUDIT_EVIDENCE_RETENTION_DAYS` opt-in is explicitly set, which is the operator's assertion that an approved compliance policy permits expiring destruction evidence. Automatic deletion is disabled by default; set `PURGE_AUDIT_PRUNING_ENABLED = True` after reviewing these policies to enable it — and only as the **second phase of a two-phase rollout**. First deploy the migrations and this release's coordinated audit-writer code to **every** process that writes audit rows — web/API servers, the CLI (`superset deletion-retention …`), and Celery workers — and let in-flight writes from older processes drain; only then enable the flag. A writer on older code stamps and commits its audit row without the coordination lock, so during a mixed-version rollout it can publish an earlier `pending` row after a pruning batch's locked re-check and delete, later turning the deleted block into the required post-boundary survivor. Conversely, before rolling any audit writer back to the older protocol, disable the flag AND wait for every active pruning run to exit — the flag is checked only when a task starts, so a run already underway continues through its remaining batches and categories (including a task still in discovery or between batches) — since a writer rolled back mid-run would admit exactly the uncoordinated insert the forward rollout prohibits. Each pruning batch holds the singleton audit coordination lock — the lock audit creation/recovery also takes — for its locked re-check. Cost depends on batch size, entity history, backend and query plan, not a fixed time bound. `PURGE_AUDIT_PRUNING_BATCH_SIZE` accepts non-boolean integers in **[1, 100]**, default 50. Deployments using an earlier build that accepted 101–500 must lower that setting before upgrading; invalid values skip the entire run and report the key. Monitor the `invalid_config` counter as well as backlog: a skipped run's `carried_over=0` does not establish that the backlog is empty. The ceiling keeps repeated window-scope binds below SQLite's historical 999-variable limit; custom limits below 750 require a smaller batch. MySQL before 8.0, MariaDB before 10.2, SQLite before 3.25 and unknown MySQL-family/SQLite versions use equivalent correlated predecessor probes instead of window tables. That legacy plan may be slower on deep histories; measure the selected path and writer wait on the target backend rather than reusing timings from another plan. MySQL-family dispatch trusts the dialect's `SELECT VERSION()` banner: a proxy advertising MySQL 8.x in front of MySQL 5.7 selects `LAG` and fails at runtime. Ten batches are shared across all categories per run, so the removal upper bound is **500 at the default or 1,000 at the ceiling**, and can be lower when rechecks reject candidates or categories use short batches. The default daily schedule provides one such budget per day; it does not guarantee that a deployment's incoming backlog can be drained. Compare measured reclamation with observed eligible-row volume before enabling pruning. Deployments that replace the default `CELERY_CONFIG` must carry the new beat entry forward (the task shares `superset.tasks.deletion_retention` with the purge task, so no new worker import is needed). When audit pruning is enabled, a missing schedule or worker import logs a startup warning even if `SOFT_DELETE` is disabled, because historical audit rows remain eligible for pruning. Audit creation, recovery, and pruning batches use a shared database coordination row held through commit. This serializes timestamp assignment with candidate deletion so an uncommitted writer cannot later publish a row into pruning's logical past. A missing coordination row fails pruning closed. Because a pruning batch holds this lock across its DELETE, on a very large audit table a concurrent scheduled purge's audit write can block on it until the batch commits. On PostgreSQL that write then succeeds (``lock_timeout`` is disabled by default); where a lock or statement timeout is configured — and on MySQL (``innodb_lock_wait_timeout``) or SQLite (which does not wait) — the write instead fails closed, so the affected purge cycle is skipped and retried on its next run rather than losing data. Batches are bounded by `PURGE_AUDIT_PRUNING_BATCH_SIZE` (default 50, maximum 100); lock-hold time is workload-dependent, not time-bounded. Run pruning off-peak if the overlap is noticeable. - The chart list applies the same type-aware datasource visibility as the dashboard list: charts on semantic views (and other non-table datasource types carrying a permission) are now listed for users holding `datasource_access` on the datasource or on its parent semantic layer — previously such charts never appeared in the chart list — and a chart on a non-table datasource is no longer listed to users whose only entitlement is a database/schema/catalog grant matching an unrelated table that shares its numeric id. Table-backed chart visibility, explicit viewer/editor grants, and embedded-guest scoping are unchanged. - `SAMPLES_ROW_LIMIT` is now the default for `/datasource/samples` requests without a valid explicit `per_page`, rather than a hard per-request ceiling; explicit limits are honored up to the existing global row-limit ceiling, matching `/chart/data` SAMPLES requests. - The `cockroachdb` extra (`pip install apache-superset[cockroachdb]`) now installs `sqlalchemy-cockroachdb` instead of the abandoned `cockroachdb` package, whose SQLAlchemy dialect could not be imported under SQLAlchemy 2.0. Existing environments with the old package installed must `pip uninstall cockroachdb` before reinstalling the extra -- both packages register the same `cockroachdb` SQLAlchemy dialect entry point, so leaving the old one in place can still load the abandoned implementation. - The `d1` extra (`pip install apache-superset[d1]`) installs only `sqlalchemy-d1`, 0.2.0 or later. That release supports SQLAlchemy 2 and is built on `sqlalchemy-cloudflare-d1`. The `superset-engine-d1` and `dbapi-d1` packages are retired: the engine spec ships with Superset, and their only releases do not install on Python 3.12 or later. Existing environments should `pip uninstall superset-engine-d1 dbapi-d1` and reinstall the extra. `d1://` connection strings are unchanged. File upload is now off for D1: D1 has no transactions, so a failed upload could leave a half-written table. ### Native Value filter "Select all" always targets the whole column The native "Value" filter's bulk "Select all" / "Clear" controls now operate on the entire loaded set of column values regardless of any text typed into the filter's search box. Previously the "Select all (N)" count briefly flickered to the search-scoped count before settling on the full-column count, and clicking "Select all" while searching could select only the currently matching subset. Search-scoped bulk selection was never a supported feature; the count is now stable and always matches what "Select all" selects (the full column). No configuration change is required. ### `PERMISSION_INSTRUCTIONS_LINK` no longer supports `{datasource_name}` The datasource access-denied error is returned to a user who was just denied that dataset, so the dataset's name is no longer templated into the "Request access" link — nor included in the error message or its `extra` payload. Deployments whose `PERMISSION_INSTRUCTIONS_LINK` still contains `{datasource_name}` get the placeholder back verbatim in the rendered URL and a warning in the logs; the substitution is deliberately not blanked so the broken link is visible rather than silently truncated. Use `{datasource_id}` (still supported) to identify the dataset to your access-request system, and resolve the name there. ### Semantic member selection versions Semantic providers can declare `selection_identity_version` when changing their member identity encoding. Cube's stable-member-ID adapter requires `cube-member-id-v1`. Deploy the compatible core library, host, frontend and Cube adapter together; do not roll back to title-keyed code after saving ID-based charts. Saved Cube selections without this version are rejected, even when a saved title happens to equal a member ID. No historical title mapping is recovered. Open each chart in Explore, choose **Start field selection**, reselect its metrics, dimensions, filters and sorting, then save. Reset also clears field-dependent formatting; it does not overwrite the saved chart until Save is chosen. Native filters and chart customizations have a separate reset in their configuration form that clears fields, pre-filters, sorting, defaults and dependencies. Dashboard filters retain their own saved generation. Old filter state and unversioned external member overrides cannot borrow a chart's version. Recreate old dashboard permalink/filter state after reselection. Cross-view overlays are rejected unless their source identity matches the receiving semantic view; matching display titles are not proof of matching members. Dynamic group-by display controls are unsupported on versioned semantic views, including Cube. Remove those charts from the control's scope; resetting or reselecting the group-by fields cannot repair this control's missing selection identity evidence. Static chart group-by selections remain available after the chart's explicit reselection. Supporting dynamic group-by requires preserving the version in target rebuilds and recording the source identity in the emitted mask; that follow-up is not included here. Versioned semantic-view value suggestions require `semantic_selection_version` from saved or explicitly initialized selections on the column-values request. Missing or stale markers return `unavailable_versioned_view` before cache access or provider value retrieval. Explore filter and Matrixify controls preserve this provenance; they never infer it from current datasource metadata. After explicit reselection, suggestions and Matrixify All mode work again. Manual entry remains available when selection provenance cannot be verified. The marker is a compatibility contract, not an authorization credential. API clients must rebuild their selections from current member IDs before supplying `extras.semantic_selection_version` on each query. Providers without a required version retain their existing behavior. No metadata-database migration or automatic rewrite of saved charts is performed. Programmatic clients must explicitly select current member IDs. Chart-data API queries send `extras.semantic_selection_version`; name-based datasource queries and MCP `get_table` requests send top-level `semantic_selection_version`. Discover the value from datasource metadata (or each external metric returned by MCP `list_metrics`). The server never adds a missing marker from metadata. Explore query controls remain unavailable until explicit field initialization, so legacy filter subjects cannot trigger value suggestions before reselection. ### Superset no longer auto-generates `type:`/`editor:`/`favorited_by:` tags With `TAGGING_SYSTEM` enabled, Superset used to auto-tag every chart, dashboard, saved query, and dataset with implicit tags derived from metadata (object type, editors, and who favorited it), and generate a `favorited_by:` tag on every favorite/unfavorite. Nothing in the UI ever surfaced these tags to users — every tags list and filter in the frontend explicitly excluded them — so the generation added continuous write overhead (13 SQLAlchemy event listeners across 5 models) with no user-visible benefit. That generation is removed. Manually-created (custom) tags are unaffected: creating, editing, listing, and filtering tags still works exactly as before, including the `custom_tag` API filter used to distinguish custom from implicit tags. Deployments already running with `TAGGING_SYSTEM` enabled keep any `type:`/`editor:`/`favorited_by:` tag rows created before upgrading — they remain queryable via the API and MCP's `list_tags`/`get_tag_info` tools, and are still exempt from bulk tag deletion — but no new ones are created, and the `superset sync_tags` CLI command that backfilled them has been removed. Nothing maintains those retained rows any more: un-favoriting no longer deletes a `favorited_by:` row, and editor changes no longer prune `editor:` rows, so they are a frozen snapshot that drifts from current favorites and editorship. No migration removes them; delete them manually. The `DASHBOARD_LIST_CUSTOM_TAGS_ONLY` config flag and the dashboard-list filtering it enables are kept, defaulting to `False`, so deployments that already set it to `True` to hide legacy implicit tags from dashboard-list responses keep that filtering; new deployments with no implicit tags left over have no reason to turn it on. ### MCP tool results preserve stored string values Structured MCP tool results no longer add `` wrappers or rewrite delimiter-looking text inside string fields. Tool-result content remains user-controlled data, but clients must convey that trust boundary outside domain values instead of recognizing or removing marker strings. Clients that handled the former delimiter convention should stop stripping marker text: the same text can be legitimate stored content. Response models and content types are unchanged, and no metadata-database migration is required. Automated read-modify-write workflows should be paused or pinned away from older instances until every serving instance is upgraded; a mixed-version response has no reliable signal that tells a client whether its text is decorated. Redis-backed MCP response caches use a new internal namespace after the upgrade, so upgraded instances do not reuse older cached results. Values that a client already wrote back with presentation wrappers cannot be distinguished safely from intentional content. Operators should review possible `` / `` wrappers and `[ESCAPED-UNTRUSTED-CONTENT-OPEN]` / `[ESCAPED-UNTRUSTED-CONTENT-CLOSE]` substitutions rather than applying an automatic marker-removal migration. ### OAuth2 database callback metrics include their outcome The unqualified `DatabaseRestApi.oauth2` StatsD counter has been replaced with `DatabaseRestApi.oauth2.success`, `DatabaseRestApi.oauth2.warning`, and `DatabaseRestApi.oauth2.error`. Update monitoring rules and dashboards that consume the old counter to use the outcome-specific replacements. - [42930](https://github.com/apache/superset/pull/42930): Dataset import data-URI fetches no longer honor an HTTP(S) proxy when `DATASET_IMPORT_ALLOW_INTERNAL_DATA_URLS` is `False` (the default): the connection is now made directly to the destination so the peer-address check validates the real target instead of a proxy's. Deployments that require an egress proxy to reach legitimate external data URLs for dataset import should set `DATASET_IMPORT_ALLOW_INTERNAL_DATA_URLS = True` or otherwise ensure those URLs resolve without one. - [42935](https://github.com/apache/superset/pull/42935): The MCP service now refuses to start (`MCPAuthConfigError`) when `MCP_JWT_ISSUER` trusts more than one issuer and no `MCP_USER_RESOLVER` is configured, instead of only logging a warning. This was already a documented misconfiguration (the default resolver isn't issuer-scoped, so distinct trusted issuers minting the same username/email would resolve to the same Superset user); deployments trusting multiple issuers must configure an `MCP_USER_RESOLVER` that derives its identity from the token's `iss` claim before upgrading. Single-issuer deployments are unaffected. - [42429](https://github.com/apache/superset/pull/42429): The Country Map chart's Iran GeoJSON now gives Alborz province its own ISO 3166-2 code, `IR-32`, instead of `IR-30`. `ISO` is the join key used to color/filter provinces on this chart, so any existing dataset keyed on `IR-30` for Alborz will silently stop matching after upgrading; re-key that data to `IR-32`. - [43388](https://github.com/apache/superset/pull/43388): The MCP service now refuses to start (`MCPAuthConfigError`) if `MCP_DEV_USERNAME` and `MCP_AUTH_ENABLED = True` are both set, and separately if `MCP_AUTH_ENABLED = True` but no usable JWT key material is configured (RSA key/JWKS, or an explicit `MCP_JWT_SECRET` for HMAC) — both previously started with authentication silently weaker than configured. Deployments combining a dev-mode username with JWT auth enabled, or enabling JWT auth without key material, must pick one before upgrading: unset `MCP_DEV_USERNAME` for a real auth deployment, or unset `MCP_AUTH_ENABLED` (or configure the key material) for a dev-mode one. Response caching (`MCP_CACHE_CONFIG["enabled"] = True`) now also excludes every tool with a side effect by default, not only a partial list, so a previously-cached mutating tool call is no longer served from cache; no config change is needed to pick this up. - [42393](https://github.com/apache/superset/pull/42393): Exported dataset YAML now carries a `uuid` for each metric and column so that custom folder assignments (which reference metrics/columns by UUID) survive an import into another workspace. This affects any export bundle that contains datasets, not just a dataset export: chart, dashboard, database and full-asset exports all embed the same dataset YAML, so a dashboard exported from this release also fails to import into an older one even though no dataset was exported directly. As with `folders` and `currency_code_column`, the affected `datasets/` files fail schema validation (`Unknown field: uuid`) when imported into Superset releases that predate this change; regenerate or hand-edit exports for older targets in mixed-version fleets. - [42300](https://github.com/apache/superset/pull/42300): Timeseries charts (line/area/bar) with a Y-axis bound in effect — either an explicit `yAxisBounds` or one derived from `truncateYAxis` — now clamp out-of-range data points to that bound instead of letting ECharts drop the point (and the line segments around it) entirely. Any existing chart with a configured Y-axis bound and data outside it will look different after upgrading: a gap becomes a point pinned to the boundary. The clamp also rewrites the value ECharts reads for that point's tooltip and data label, so the displayed value is the bound rather than the true observation. - [42087](https://github.com/apache/superset/pull/42087): Stored calculated-column and metric expressions are validated when a query is built, under the same sub-query policy already applied to adhoc expressions. Previously only the dataset update path checked them on save, so expressions written by v1 import, by dataset duplication, or before that check existed were never validated. Since `ALLOW_ADHOC_SUBQUERY` defaults to `False` (see [19242](https://github.com/apache/superset/pull/19242)), a dataset whose stored expression contains a sub-query works before upgrading and afterwards fails at chart render with `Custom SQL fields cannot contain sub-queries.` There is no migration step, and the error does not name the offending dataset column, so audit stored expressions before upgrading: either rewrite them without the sub-query, or set `ALLOW_ADHOC_SUBQUERY = True` to keep the previous behaviour for both stored and adhoc expressions. - [43020](https://github.com/apache/superset/pull/43020): The PostgreSQL SQL Lab query validator (`PostgreSQLValidator`) has been removed, along with its default `SQL_VALIDATORS_BY_ENGINE` mapping and the `pgsanity` dependency. It shelled out to the external `ecpg` binary, which had to be present in the runtime image and behaved differently across `ecpg`/PostgreSQL versions. PostgreSQL databases no longer get live syntax annotations in SQL Lab; syntax errors surface when the query is run. `PrestoDBSQLValidator` and `SQLiteSQLValidator` are unaffected. A deployment that explicitly sets `SQL_VALIDATORS_BY_ENGINE` with a `"postgresql": "PostgreSQLValidator"` entry must drop that entry, otherwise validation requests for those databases fail with `No validator named PostgreSQLValidator found`. ### Selenium support removed — Playwright is now required for screenshots Selenium support has been removed. **Playwright is now required** for all report and thumbnail screenshot generation. Install it with: ```bash pip install playwright && playwright install chromium ``` **Breaking config changes:** - `PLAYWRIGHT_REPORTS_AND_THUMBNAILS` feature flag removed (Playwright is the only backend now) - `WEBDRIVER_TYPE` config key removed (Playwright always uses Chromium) - `WEBDRIVER_CONFIGURATION` config key removed (Selenium-only) - `SCREENSHOT_PAGE_LOAD_WAIT` config key removed (Selenium-only) - `SCREENSHOT_SELENIUM_RETRIES` config key removed (Selenium-only) - `SCREENSHOT_WAIT_FOR_ERROR_MODAL_VISIBLE` config key removed (Selenium-only) - `SCREENSHOT_WAIT_FOR_ERROR_MODAL_INVISIBLE` config key removed (Selenium-only) - `MachineAuthProvider.authenticate_webdriver()` removed; use `authenticate_browser_context()` instead **What operators should do before upgrading:** 1. Install Playwright: `pip install playwright && playwright install chromium` 2. Remove any references to the removed config keys from custom `superset_config.py` 3. If you subclassed `MachineAuthProvider`, remove any `authenticate_webdriver` override and migrate auth logic to `authenticate_browser_context` ### CSV/XLSX report exports of Table charts keep raw numeric values Table and Pivot Table charts sent as text in a report email now apply the chart's number and currency formatting so the values match what a user sees in Explore. As part of this, the CSV and XLSX result formats return early before formatting: previously the Table post-processor applied `d3NumberFormat` to every result format, so CSV/XLSX exports contained pre-formatted strings. CSV/XLSX exports now preserve numeric values and column types, which is better for downstream analysis but is a visible change for anyone who relied on the formatted text in those files. The rendered email body (the only place the formatting is intended for) is unaffected. ### SQLAlchemy bumped to 2.0, flask-sqlalchemy to 3.1.1 Superset's core ORM dependencies move from SQLAlchemy 1.4 to 2.0 and flask-sqlalchemy `<3.0` to 3.1.1, completing the migration tracked in [discussion #40273](https://github.com/apache/superset/discussions/40273). **Custom `db_engine_specs`, plugins, or extensions that import SQLAlchemy internals directly** should review the [SQLAlchemy 1.4-to-2.0 migration guide](https://docs.sqlalchemy.org/en/20/changelog/migration_20.html) for API changes that affect them — most 1.4 code already runs unmodified under 2.0's compatibility mode, but patterns like `Engine.execute()`, string-keyed `Row` access, and `MetaData(bind=)` are removed outright. **Several optional DB-connector extras remain capped below their SQLAlchemy-2.0-only releases**, either because that bump is a separate follow-up ([#42891](https://github.com/apache/superset/pull/42891): dremio, exasol, firebird, redshift, risingwave) or because the upstream dialect package has no SQLAlchemy 2.0 support yet at all (aurora-data-api, kusto, solr; ocient's 2.0 compatibility is unverified). Installing one of these extras continues to pull a SQLAlchemy-1.4-line version of that dialect; each package's constraint in `pyproject.toml` documents why. No application-level configuration changes are required for deployments that don't touch SQLAlchemy directly. ### New metric aggregates: MEDIAN, Sample Standard Deviation, Sample Variance `MEDIAN`, `STDDEV_SAMP`, and `VAR_SAMP` are now available anywhere a metric aggregate is chosen (every chart type, SQL Lab, MCP), not only in Pivot Table's controls. Support is opt-in per database engine *spec class*: Postgres, MySQL (`STDDEV_SAMP`/`VAR_SAMP` only, no `MEDIAN`), DuckDB, and Redshift (inherits Postgres's support) were verified against a live instance; Databricks and Snowflake (both `MEDIAN`/`STDDEV_SAMP`/`VAR_SAMP`) were confirmed against each vendor's own documented SQL function reference, not yet against a live instance. Engine specs that subclass one of these (e.g. MariaDB, Aurora MySQL/Postgres, TimescaleDB) inherit the same support, on the same verification basis as their parent. Picking one of these aggregates on a database that has not opted in returns a clear "not supported on this database" error rather than a failed query. See `docs/sip/median-stddev-variance-aggregates.md` for the full design rationale, including why this is safe to add without reintroducing the totals/subtotals correctness bug fixed by #41184 (SIP-216). ### Soft delete is on by default, and purging is live `SOFT_DELETE` now ships **on** (`DEFAULT_FEATURE_FLAGS`), so deleting a dashboard, chart, or dataset archives it rather than removing it. Archived objects are hidden from normal listings, recoverable from **Recently Archived**, and permanently removed once the retention window elapses. `SOFT_DELETE_PURGE_DRY_RUN` also flips to `False`, so the nightly `deletion_retention.purge_soft_deleted` task deletes for real instead of only logging `would_purge` counts. **What operators should do before upgrading:** - **Size the first live purge.** The first real run removes every entity that aged past `SOFT_DELETE_RETENTION_DAYS` (default 30) since soft delete began capturing, which on a busy deployment can be a large batch in one window. To see the size first, set `SOFT_DELETE_PURGE_DRY_RUN = True`, read the `would_purge` counts from one nightly run, then set it back. - **Check a replaced `CELERY_CONFIG`.** A deployment that redefines it rather than inheriting must carry both `superset.tasks.deletion_retention` in `imports` and the `deletion_retention.purge_soft_deleted` beat entry; a startup warning now names whichever is absent. **Both switches are retained.** `SOFT_DELETE = False` restores hard-delete behaviour and `SOFT_DELETE_PURGE_DRY_RUN = True` suspends purging, at any time. One caveat on turning soft delete back off: objects archived while it was on are **resurrected** into normal listings, since the rows were never removed — an emergency stop rather than a clean rollback. ### Version history is on by default `VERSION_HISTORY` and `ENABLE_VERSIONING_CAPTURE` now both ship **on**. Every save of a chart, dashboard, or dataset writes version rows, and the version history panel appears on Explore and Dashboard pages. The two flip together deliberately: a panel with capture off renders an empty "No history yet" that misrepresents the entity as unchanged. **What operators should expect:** - **Storage growth.** Capture writes shadow rows per save, so the metadata database grows with edit volume. The `version_history.prune_old_versions` beat task removes rows whose transaction is older than `VERSION_HISTORY_RETENTION_DAYS` (default 30). - **Check a replaced `CELERY_CONFIG`.** Carry both the `superset.tasks.version_history_retention` import and the `version_history.prune_old_versions` beat entry; see [Version-history retention (pruning)](#version-history-retention-pruning) for the startup-warning behavior. - **`PUT` responses change shape.** Entity updates now return populated `old_version_uuid` / `new_version_uuid` fields and an `ETag` header, which were null or absent while capture was off. `ENABLE_VERSIONING_CAPTURE` is **retained permanently** as an operational kill-switch — not removed with the rollout toggles. Setting it to a falsy value stops capture within a restart, without a revert-and-redeploy. Unlike the soft-delete toggle, turning it off is a clean stop: existing version rows remain readable if `VERSION_HISTORY` is enabled and no entity state is altered. Restore is unavailable (404) while capture is off. A full rollback also sets `FEATURE_FLAGS = {"VERSION_HISTORY": False}` to disable the panel and history APIs — capture off with the panel left on shows an empty or stale history. ### Scheduled report execution now enforces one application deadline Scheduled report (not alert) executions are now governed by a single end-to-end deadline shared by browser readiness, capture/PDF generation, notification delivery, and terminal-state persistence, configured via `ALERT_REPORTS_EXECUTION_BUDGET_SECONDS` (with per-phase reserve settings). Behavior changes to be aware of: - The effective budget for a schedule is `min(ALERT_REPORTS_EXECUTION_BUDGET_SECONDS, working_timeout)`. The default budget (one hour) matches the historical `working_timeout` model default, so default installations see no change in how long a report may run — but reports now fail cleanly (with an error notification) at the deadline instead of being killed silently by Celery. - For REPORT schedules, the Celery `soft_time_limit`/`time_limit` are now derived from that same effective budget plus `ALERT_REPORTS_EXECUTION_HARD_TIMEOUT_GRACE_SECONDS`, replacing the previous `working_timeout + ALERT_REPORTS_WORKING_TIME_OUT_LAG` / `+ ALERT_REPORTS_WORKING_SOFT_TIME_OUT_LAG` derivation. Alert schedules keep the previous behavior. - A `working_timeout` smaller than the summed phase reserves is floored at the minimum viable budget (reserves + 30s) with a warning; such reports fail fast at the first phase check rather than erroring at setup. - Dashboard reports whose charts have not mounted are no longer captured blank: readiness is polled until the deadline, and the report fails loudly if charts never mount. Large tiled reports also retry Chromium screenshot stalls and suspicious uniform tiles, while persistent screenshot timeouts fail loudly. Large tiled thumbnails use the same bounded retries, but retain their previous failure contract after a persistent timeout. ### Embedded (guest token) API responses no longer echo database errors API responses served to a guest-token principal now carry a generic `An error occurred while fetching the data.` in place of the underlying error (`You don't have permission to access this resource.` on a 401/403), and drop the `stacktrace` and error `extra` payloads. Engine errors routinely quote catalog, schema, table and column names of the warehouse, which embedded viewers should not see. Errors Superset authors itself — access denials, OAuth2 redirects, timeouts, payload validation — keep their message and type, though their `extra` is still reduced to the fields the client needs. Responses to every non-guest principal are unchanged, and the full error is still logged server-side. ### `UnsavedChangesModal` no longer accepts a `zIndex` prop `@superset-ui/core`'s `UnsavedChangesModal` dropped its `zIndex` prop (and the hardcoded default it fed) in favor of letting Ant Design's own stacking handle placement. Callers passing `zIndex` to override the modal's layering will now get a TypeScript error and must remove the prop; keeping a manual override was exactly the footgun this change removes (see #42510). No callers in the Superset frontend codebase itself passed this prop. ### Row-level security now filters table reads a same-named CTE used to hide `extract_tables_from_statement()` decided whether a reference was a CTE by matching its bare name against the enclosing scope's CTE names; it now resolves the name through `Scope.cte_sources`. Three kinds of real table read whose bare name collided with a CTE's were mistaken for the CTE and dropped from a statement's tables, so they were neither RLS-filtered nor access-checked: a schema- or catalog-qualified reference, a non-recursive CTE's own name inside its body, and a forward reference to a later `WITH` item. ```sql WITH orders AS (SELECT 1 AS d) SELECT * FROM (SELECT * FROM public.orders) AS z WITH orders AS (SELECT * FROM orders) SELECT * FROM orders WITH q1 AS (SELECT key FROM q2), q2 AS (SELECT 1 AS key) SELECT * FROM q1 ``` Each read is now reported, so it is filtered when `RLS_IN_SQLLAB` is enabled, matched against `DISALLOWED_SQL_TABLES`, and requires dataset access under `raise_for_access(force_dataset_match=True)`. A query that previously ran, reading those rows unfiltered, may now be filtered or rejected. There is no opt-out — the previous behavior was a row-level-security bypass. ### Table aliases keep their quoting through the row-level security rewrite Both RLS transformers took the table alias as a string with its quoting stripped and emitted it verbatim; they now carry the parsed identifier. Emitted SQL is unchanged for an unquoted identifier; a quoted one keeps its quoting, and a column-alias list (`FROM t AS x (c1, c2)`) survives the rewrite instead of being dropped. This repairs row-level security for any aliased table on Snowflake, and for at least one statement shape on MSSQL where the rewrite previously raised `AttributeError`. ### Principal listing APIs now honour related-field filters Two authorization-related listing behaviors changed for API clients. Neither affects the Superset UI — `include_ids` and `security/subject` have no frontend callers — but external clients will notice: - `GET /api/v1//related/?include_ids=...` now applies the endpoint's `base_related_field_filters`. Previously the forced IDs were fetched with no filtering, so a caller could resolve principals the related-field filters deliberately hide; those IDs are now omitted from the response. - `GET /api/v1/security/subject/` now honours `EXCLUDE_USERS_FROM_LISTS` and `EXTRA_RELATED_QUERY_FILTERS`, matching every other principal-listing endpoint. List counts may drop, and fetching an excluded principal via `GET /api/v1/security/subject/` now returns `404`. ### v1 chart import no longer re-adds the importer as editor on overwrite Re-importing over an existing chart (an overwrite or a soft-delete restore) no longer appends the importing user to the chart's `editors`, matching the dashboard importer's behavior. This changes anything only for a user who was not already an editor of that chart — typically an admin overwriting a chart they do not own, who was previously added as an editor as a side effect of the import. Newly-created charts are unaffected. ### ClickHouse: system sampling queries retry with a bounded read System-generated sampling queries — filter-value dropdowns, the Samples tab/dataset preview, and datetime format detection — that ClickHouse rejects with a `max_rows_to_read` error (`TOO_MANY_ROWS`, code 158) are now retried once with `SETTINGS read_overflow_mode='break'` appended, so they return a partial result bounded by the operator's row cap instead of failing. The retry applies only to statements Superset generates for physical-table datasets; virtual datasets and user-authored SQL remain fully governed by configured read limits, and queries that already succeed are never altered. Operators who rely on `max_rows_to_read` as a hard failure gate for these system queries can restore the previous behavior per database with `"disable_sampling_read_limit_override": true` in the database's Extra JSON. Note that a retried query returns partial data with no truncation indicator (e.g. a filter dropdown may list only a subset of values on tables above the row cap). ### Dashboard "Export Data to Excel": direct downloads, and `EXCEL_EXPORT_S3_*` moves to `EXPORT_STORAGE` A new dashboard action exports every chart's data to a single multi-sheet `.xlsx`. Without export storage, Superset builds the workbook during the request and returns it to the browser. When `EXPORT_STORAGE` is configured with both a `bucket` and a `backend`, a Celery worker builds and uploads the workbook instead, and the browser downloads it once ready. There is no implicit storage default: ```python from superset.utils.s3 import S3ExportStorage # or superset.utils.gcs.GCSExportStorage EXPORT_STORAGE = { "bucket": "my-export-bucket", "backend": S3ExportStorage(), } ``` Direct downloads are limited by `EXCEL_EXPORT_SYNC_MAX_ROWS` (default `100_000`), based on the combined `row_limit` of the planned queries. Superset counts aggregate-only queries as one row, uses `ROW_LIMIT` when other queries omit it, and returns `400` before querying if the total exceeds the limit. Charts whose size can't be known before they run (grouping sets, which pivot tables use for non-additive metrics, and post-processing that can add rows such as resample, forecasts or custom operations) are left out of direct downloads and listed on the workbook's "Export Summary" sheet; if that leaves no chart to run, the request returns `400` instead of a summary-only workbook. Image exports are hidden without export storage because they require background webdriver rendering. `POST /api/v1/dashboard//export_xlsx/` returns either `202` with a queued job id or `200` with the workbook. It does not return `501` when storage is unset. **Upgrading from `EXCEL_EXPORT_S3_*`:** the S3-only config keys are removed and replaced by the pluggable `EXPORT_STORAGE` above. They are no longer read, so a deployment that had background exports working falls back to direct downloads (and loses image exports) until the config is ported: | Removed | Replacement | | --- | --- | | `EXCEL_EXPORT_S3_BUCKET = "my-bucket"` | `EXPORT_STORAGE["bucket"] = "my-bucket"` | | `EXCEL_EXPORT_S3_KEY_PREFIX = "prefix/"` | `EXPORT_STORAGE["key_prefix"] = "prefix/"` | | `EXCEL_EXPORT_S3_CLIENT_KWARGS = {...}` | `EXPORT_STORAGE["backend"] = S3ExportStorage(client_kwargs={...})` | `EXPORT_STORAGE["backend"]` has no default and must be set explicitly, which is the part an upgrade cannot infer: the previous config implied S3, so keep the same bucket with `S3ExportStorage()`. A bucket without a backend (or the reverse) logs a warning naming the missing key. `EXCEL_EXPORT_LINK_TTL_SECONDS` is unchanged in name, but it now bounds a Superset-issued link rather than a pre-signed S3 URL, so the AWS seven day ceiling no longer applies. The background path also requires a running Celery worker. With `CELERY_CONFIG = None`, exports download directly even when `EXPORT_STORAGE` is complete, and a warning says so. SMTP is optional and only used to additionally email logged-in users a download link; every session (including guest/Public ones, which have no email) gets the export through status polling and automatic download. Config keys: `EXPORT_STORAGE`, `EXCEL_EXPORT_LINK_TTL_SECONDS`, `EXCEL_EXPORT_SYNC_MAX_ROWS`, `EXCEL_EXPORT_TABLE_VIZ_TYPES`, and `EXCEL_EXPORT_QUERY_CONTEXT_BUILDER`. The storage backends depend on SDKs that are **not** installed by default: install `pip install apache-superset[excel-export]` (boto3) for `S3ExportStorage`, or `pip install apache-superset[excel-export-gcs]` (google-cloud-storage) for `GCSExportStorage`. A custom backend can be supplied by implementing `superset.utils.export_storage.ExportStorage`. The direct-download path uses neither. For `table`, `big_number_total`, `big_number`, and `pie` charts without a saved `query_context`, Superset rebuilds a single query from saved form data. Charts that need post-processing or multiple queries are skipped and listed on the workbook's "Export Summary" sheet. Use `EXCEL_EXPORT_QUERY_CONTEXT_BUILDER` to support more chart types. A second mode, **Export Images to Excel**, embeds non-table charts as rendered images (which viz types stay tabular is controlled by `EXCEL_EXPORT_TABLE_VIZ_TYPES`). It renders through the headless webdriver, so the menu option only appears when an export bucket is configured and the webdriver screenshot feature flags (`ENABLE_DASHBOARD_SCREENSHOT_ENDPOINTS`, `ENABLE_DASHBOARD_DOWNLOAD_WEBDRIVER_SCREENSHOT`) are enabled. Deployments that override `CELERY_CONFIG` must add `"superset.tasks.export_dashboard_excel"` to their `imports` tuple, or the task will not register and exports will silently never run. ### SQL_QUERY_MUTATOR now honors MUTATE_AFTER_SPLIT in SQL Lab SQL Lab now applies `SQL_QUERY_MUTATOR` according to `MUTATE_AFTER_SPLIT`, matching the documented semantics and the chart/query path. This only affects deployments that define `SQL_QUERY_MUTATOR` in `superset_config.py`: - With `MUTATE_AFTER_SPLIT = True`, the mutator previously never ran in SQL Lab; it now runs on each individual statement (including on engines like BigQuery and Kusto that execute multiple statements as one block, where each statement is mutated before the statements are joined). - With `MUTATE_AFTER_SPLIT = False` (the default), multi-statement SQL Lab queries previously applied the mutator to each statement separately; the mutator now runs once on the whole un-split query, as documented. Single-statement queries are unaffected. - With `MUTATE_AFTER_SPLIT = False` on engines that execute statements individually, the mutator's output is re-parsed to split it into statements. A mutator that emits SQL Superset's parser cannot parse will now fail with a clear parse error before execution, and one that strips a query down to nothing raises an invalid-SQL error instead of executing an empty query. ### Python 3.10 support removed Python 3.10 is no longer supported. Superset now requires **Python 3.11 or higher**. Update your environment (virtualenv, Docker base image, CI configuration, etc.) to Python 3.11+ before upgrading. The `apache/superset-cache:3.10-slim-trixie` and `py310` Docker image variants are no longer published. ### `from_dttm` and `to_dttm` Jinja template variables removed The `{{ from_dttm }}` and `{{ to_dttm }}` Jinja template variables, deprecated since v5.0, have been removed from the Jinja context and are no longer available in virtual dataset SQL or custom SQL expressions. Replace usages with the `get_time_filter()` function. For example: ```sql -- Before SELECT * FROM tbl WHERE dttm_col > '{{ from_dttm }}' AND dttm_col < '{{ to_dttm }}' -- After {% set tf = get_time_filter("dttm_col") %} SELECT * FROM tbl WHERE dttm_col > {{ tf.from_expr }} AND dttm_col <= {{ tf.to_expr }} ``` Note that `from_expr` and `to_expr` are already fully-formatted SQL expressions (e.g. `TO_TIMESTAMP('2024-01-01', ...)`) — do not wrap them in single quotes. ### Removed deck.gl JavaScript tooltip/data-mutator controls and ENABLE_JAVASCRIPT_CONTROLS The `ENABLE_JAVASCRIPT_CONTROLS` feature flag and the deck.gl chart controls it gated (`js_tooltip`, `js_onclick_href`, `js_data_mutator`, and the GeoJSON layer's label/icon JavaScript-mode generators) have been removed. These controls let users write arbitrary JavaScript, sandboxed via Node's `vm` module, to customize deck.gl tooltips, click behavior, and data transforms; the flag defaulted off and the feature saw negligible use. The deck.gl "Extra data for JS" control (`js_columns`) has also been removed. It only ever existed to feed extra columns into the JavaScript controls above; deck.gl's built-in field-based tooltips and cross-filtering already pull in any columns they need via `tooltip_contents`/`cross_filter_column`, so this control had no remaining purpose. Any chart layer whose "Advanced" control panel section only contained this control no longer has an "Advanced" section. Any saved charts with these fields set will simply ignore them going forward and fall back to deck.gl's built-in field-based tooltips (`tooltip_contents`/`tooltip_template`) and native click/cross-filter behavior. No migration is required; the fields are dropped silently on next save. ### Owners, dashboard roles, and RLS roles replaced by Subjects Superset now uses subject-based access assignments for dashboards, charts, datasets, alerts/reports, and Row Level Security. A Subject can represent a user, role, or group. This is a breaking API and metadata change: - `owners` is replaced by `editors` for dashboards, charts, datasets, and alerts/reports. - Dashboard `roles` and the `DASHBOARD_RBAC` feature flag are replaced by dashboard/chart `viewers`, enabled with `ENABLE_VIEWERS`. - RLS `roles` is replaced by `subjects`. - The legacy `dashboard_user`, `slice_user`, `sqlatable_user`, `report_schedule_user`, `dashboard_roles`, and `rls_filter_roles` tables are migrated into subject junction tables and dropped on upgrade. For deployments that previously used `DASHBOARD_RBAC` and later disabled it: remove stale rows from the legacy `dashboard_roles` table before upgrading, otherwise those role assignments will become active dashboard Viewers after migration. API clients and automation should send and read `editors`, `viewers`, and `subjects` instead of the legacy fields. Subject pickers support users, groups, and roles. Dashboard, chart, and alert/report pickers show only users and groups by default, while the RLS picker also shows roles to preserve role-based RLS workflows. Roles remain supported as Subject types for backwards compatibility with RLS role assignments and the previous `DASHBOARD_RBAC` model, but they are not recommended for new resource-specific assignments. Prefer groups for membership-based access and keep roles focused on capability grants. Existing Role subject assignments remain effective after migration even when Roles are hidden from a picker's dropdown values; configure the relevant `SUBJECTS_RELATED_TYPES_*` setting to make Roles selectable in non-RLS subject lists. See the [Security documentation](docs/admin_docs/security/security.mdx#subjects) for the full Subject model and picker configuration guidance. To make roles selectable everywhere: ```python from superset.subjects.types import SubjectType SUBJECTS_RELATED_TYPES = [ SubjectType.USER, SubjectType.GROUP, SubjectType.ROLE, ] ``` The RLS picker includes users, roles, and groups by default. To customize it, use the RLS-specific override. For example, to show only roles: ```python from superset.subjects.types import SubjectType SUBJECTS_RELATED_TYPES_RLS = [ SubjectType.ROLE, ] ``` Entity-specific `SUBJECTS_RELATED_TYPES_*` settings replace `SUBJECTS_RELATED_TYPES` for that picker. Deployments using `EXTRA_OWNERS_RESOLVER` must migrate to `EXTRA_EDITORS_RESOLVER`. The new resolver should return editor Subjects, subject IDs, or dicts with an `id` key instead of FAB User objects. API responses expose these dynamic assignments as `extra_editors` instead of `extra_owners`. `DASHBOARD_RBAC` has been removed. To preserve the previous Dashboard RBAC behavior, enable both subject viewers and viewer datasource bypass: ```python FEATURE_FLAGS = { "ENABLE_VIEWERS": True, } VIEWER_PROMISCUOUS_MODE = True ``` Enabling only `ENABLE_VIEWERS` allows assigning dashboard/chart viewer subjects, but viewers still need normal datasource permissions unless `VIEWER_PROMISCUOUS_MODE` is also enabled. For backwards compatibility, enabling `ENABLE_VIEWERS` does not change access for dashboards or charts that have no assigned viewers. Those resources continue to use the implicit dataset-access model: users who can access the underlying dataset can still see published dashboards that use that dataset and charts backed by that dataset. Assigning one or more viewers opts that resource into explicit viewer access for non-editors. To return a resource to the implicit dataset-access model, remove all viewers from it. Explicit Viewers are the intended model going forward; deprecating and removing implicit viewership can be considered 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/` 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, which restores the nvd3 renderer's interactive percent re-basing via a draggable baseline. The deck.gl Multiple Layers chart now fetches its layers entirely client-side, refitting the viewport as each layer's data arrives, and caps the number of sub-slices fanned out per chart at `DECK_MULTI_MAX_SLICES` (default 50, configurable); 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. - [39925](https://github.com/apache/superset/pull/39925): URL prefixing for `SUPERSET_APP_ROOT` subdirectory deployments is now handled automatically by helpers in `src/utils/navigationUtils` (`openInNewTab`, `redirect`, `getShareableUrl`, ``). Direct imports of `ensureAppRoot` / `makeUrl` from `src/utils/pathUtils` are forbidden outside `navigationUtils.ts` (enforced by a static-invariant test); contributors writing new code should use the focused helpers instead. No runtime behaviour change for existing callers — all 19 prior call sites have been migrated and four pre-existing double-prefix and missing-prefix bugs are fixed as part of the migration. - [39925](https://github.com/apache/superset/pull/39925): `SupersetClient.getUrl()` now strips a single leading application-root segment from the supplied `endpoint` before building the request URL, so a caller that accidentally pre-prefixes its endpoint (for example by wrapping it with `ensureAppRoot` before passing it to the client) no longer produces a doubled `/superset/superset/...` URL under subdirectory deployment. The strip is **single-pass** — a genuine `/superset/superset/` route is preserved, not collapsed — and **silent** (no console warning); the static-invariant test remains the primary signal for pre-prefixing at the call site, and this runtime strip is a safety net beneath it. Code that intentionally targeted a literal `///...` endpoint through `getUrl` (a configuration that has no legitimate use under the prefixing model) would have its first redundant segment removed. - **Breaking — `Superset` view class route prefix removed.** The `Superset` view in `superset/views/core.py` now declares `route_base = ""`, overriding Flask-AppBuilder's auto-derived `/superset` prefix. Routes that previously lived at `/superset/welcome/`, `/superset/dashboard//`, `/superset/dashboard/p//`, `/superset/explore/`, etc. now respond at `/welcome/`, `/dashboard//`, `/dashboard/p//`, `/explore/`, etc. Under subdirectory deployment (`SUPERSET_APP_ROOT=/superset`) the URLs are unchanged from end-user perspective — `AppRootMiddleware` re-applies the prefix via `SCRIPT_NAME`. Under root deployments, any external integration or bookmark that hard-codes `/superset//` paths must be updated to drop the prefix. This fixes the doubled `/superset/superset/...` URLs that `url_for` emitted for these endpoints under subdirectory deployment and the related 404s on the routes themselves. - **Breaking — Three sibling view classes route prefix removed.** Following the same rationale as the `Superset` class above, `ExplorePermalinkView` (`superset/views/explore.py`), `TagModelView`, and `TaggedObjectsModelView` (`superset/views/tags.py`, `superset/views/all_entities.py`) now mount at the application root rather than a hard-coded `/superset/...`. The user-visible URLs `/superset/explore/p//`, `/superset/tags/`, and `/superset/all_entities/` are unchanged under subdirectory deployment; under root deployments these views now serve `/explore/p//`, `/tags/`, and `/all_entities/`, so any external integration or bookmark must drop the `/superset/` prefix. `Dashboard.url` and `Dashboard.get_url` likewise return `/dashboard//` instead of the prior `/superset/dashboard//` literal so downstream consumers (DashboardList row hrefs, MCP service `dashboard_url`) emit a single, deployment-correct prefix. - **Legacy `/superset/*` path support.** A new outermost WSGI middleware `LegacyPrefixRedirectMiddleware` (`superset/middleware/legacy_prefix_redirect.py`) 308-redirects every enumerated legacy `/superset/` path to its post-`route_base=""` canonical location (e.g. `/superset/welcome/` → `/welcome/` under root; → `/superset/welcome/` under `SUPERSET_APP_ROOT=/superset`, because the canonical resolves through `AppRootMiddleware`). Bookmarks, email links, and external integrations survive the route-base collapse for one release cycle. POST against a GET-only canonical returns 410 Gone instead of 308 (308 would 405 on retry). The shim is removed at EOL `5.0.0`, matching the `@deprecated(eol_version="5.0.0")` gate on `Superset.explore` and `Superset.explore_json`. - **PWA web app manifest served dynamically.** The PWA manifest is now served at `/pwa-manifest.json` (under `APPLICATION_ROOT`) by a new `PwaManifestView` (`superset/views/pwa_manifest.py`) instead of the static file at `/static/assets/pwa-manifest.json`. The legacy static source at `superset-frontend/src/pwa-manifest.json` has been removed (along with its `webpack.config.js` `CopyPlugin` rule). The new endpoint resolves `APPLICATION_ROOT` and `STATIC_ASSETS_PREFIX` at request time so PWA install works under subdirectory deployments and split static-prefix / app-root deployments (where `STATIC_ASSETS_PREFIX` points to a CDN host while the Superset backend stays under `APPLICATION_ROOT`). The `` href in `superset/templates/superset/spa.html` was updated correspondingly (using a new `application_root_rstrip` template global). Operators with a forked `spa.html` should switch any manifest `` to `{{ application_root_rstrip }}/pwa-manifest.json`. - **Hard re-bookmark break — `/superset/sql//`.** SQL Lab moved to its own blueprint at `/sqllab/`. The legacy `/superset/sql//` shape changed to a query-string form (`/sqllab/?dbid=`); no 1:1 path mapping exists, so `LegacyPrefixRedirectMiddleware` does **not** redirect this route — it passes through and surfaces a 404. Users with bookmarks to `/superset/sql//` must update them to `/sqllab/?dbid=`. - **`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=`, the concatenation produced `/sqllab/?dbid=?table_name=` — a malformed second `?` that broke the query parser. External code that parsed the legacy `?table_name=` 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. - [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. ### SQL Lab denies large-object and information_schema access by default `DISALLOWED_SQL_FUNCTIONS` and `DISALLOWED_SQL_TABLES` now ship with additional default entries, so SQL Lab and chart-data queries that reference them are rejected where they were previously allowed: - PostgreSQL large-object routines (`lo_from_bytea`, `lo_export`, `lo_import`, `lo_put`, `lo_create`, `lo_creat`, `lowrite`, `lo_get`, `loread`, `lo_unlink`), which read and write bytes on the database server's filesystem. - The SQL-standard `information_schema` views (`tables`, `columns`, `routines`, `views`, the privilege/grant views, etc.), which expose table, column, privilege, and view-definition metadata across the whole database. Deployments that legitimately query these (for example tooling that introspects `information_schema`) can restore the previous behavior by overriding `DISALLOWED_SQL_FUNCTIONS` / `DISALLOWED_SQL_TABLES` in `superset_config.py` to drop the entries they need. Because the denylist now resolves the effective schema through the query-aware path, PostgreSQL queries that change the `search_path` (e.g. `SET search_path = ...`) are rejected on the SQL Lab execution and cost-estimate paths whenever any `DISALLOWED_SQL_TABLES` entry is configured (the default for PostgreSQL), matching the behavior previously applied only when `RLS_IN_SQLLAB` was enabled. ### SQL parser input length cap (SQL_MAX_PARSE_LENGTH) The SQL parser now rejects scripts whose UTF-8 byte length exceeds the new `SQL_MAX_PARSE_LENGTH` config option (default `1_000_000` bytes) before they are handed to sqlglot, which bounds parser memory and CPU usage. A single query larger than the cap (for example a very large `IN (...)` list or a big virtual-dataset SQL) raises a parse error in SQL Lab and dashboard-generated queries. Deployments that legitimately run queries above this size should raise the value, and `SQL_MAX_PARSE_LENGTH = None` disables the check entirely. ### Ant Design upgraded from v5 to v6 The frontend now builds against Ant Design 6, and `@superset-ui/core` / `@apache-superset/core` peer-depend on `antd ^6`. Custom plugins, extensions, and themes that interact with Ant Design need review: - **Internal DOM classes were renamed**, so any custom CSS targeting `.ant-*` internals silently stops matching. Notable renames: `.ant-tabs-content-holder` → `.ant-tabs-body-holder`, `.ant-tabs-content` → `.ant-tabs-body`, `.ant-tabs-tabpane` → `.ant-tabs-content`; `.ant-select-selector` → `.ant-select-content`, `.ant-select-selection-placeholder` → `.ant-select-placeholder`, `.ant-select-arrow` → `.ant-select-suffix`; `.ant-tooltip-inner` → `.ant-tooltip-container`; `.ant-popover-inner` → `.ant-popover-container`; `.ant-steps-item-tail` → `.ant-steps-item-rail`. - **Some component props changed or were removed** — e.g. `Select` no longer accepts `dropdownAlign`, `visible`/`onVisibleChange` are `open`/`onOpenChange`, `Dropdown` `overlay` is `menu`, `Steps.Step` children are the `items` prop, and `styles.body` on Tooltip/Popover is `styles.container`. - **CSS variables are on by default** in antd 6, and `ThemeConfig.cssVar` no longer accepts a boolean; Superset theme configs using `cssVar: true`/`false` are coerced (`true` → `{}`, `false` → omitted). Theme tokens are unaffected — antd 6 removed none of the tokens Superset exposes, so existing theme configurations continue to work. See the [Ant Design v6 migration guide](https://ant.design/docs/react/migration-v6) for the complete upstream list. ### 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. ### MCP service requires `MCP_JWT_AUDIENCE` when JWT auth is enabled When the MCP service has JWT auth enabled (`MCP_AUTH_ENABLED = True`), an audience must be configured via `MCP_JWT_AUDIENCE` so issued tokens are bound to this service. The service now fails to start with a clear configuration error when the audience is unset, instead of starting with audience validation skipped. Deployments that enable MCP JWT auth must set `MCP_JWT_AUDIENCE` to the audience value their identity provider issues for the MCP service. API-key-only MCP deployments (JWT auth disabled) are unaffected. ### Swagger UI is opt-in (off by default) `FAB_API_SWAGGER_UI` now defaults to `False` and is driven by the `SUPERSET_ENABLE_SWAGGER_UI` environment variable. The interactive Swagger UI / OpenAPI documentation endpoints (e.g. `/swagger/v1`) are therefore no longer exposed by default. To enable them, set `SUPERSET_ENABLE_SWAGGER_UI=true` (the bundled Docker development environment sets this) or override `FAB_API_SWAGGER_UI = True` in `superset_config.py`. ### Build details (git SHA / build number) are admin-only by default The git SHA and build number surfaced in the "About" section, the bootstrap payload, and the public `/version` endpoint are now only included for admin users by default; the release version string is still shown to everyone. To expose the build details to all users (the previous behavior), set the `SUPERSET_EXPOSE_BUILD_DETAILS` environment variable (or `EXPOSE_BUILD_DETAILS_TO_USERS = True` in `superset_config.py`). ### Helm chart adopts Kubernetes recommended labels (breaking upgrade) The Helm chart now labels and selects workloads using the [Kubernetes recommended labels](https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/) (`app.kubernetes.io/*`) instead of the legacy `app`/`release` labels. Because a Deployment's `spec.selector.matchLabels` is immutable, `helm upgrade` against an existing release will fail with a `field is immutable` error. To upgrade, delete the affected workloads (which selector labels changed) before upgrading, then run the upgrade so they are recreated with the new labels: ```bash kubectl delete deployment,statefulset -l release= -n helm upgrade superset/superset ``` Alternatively, perform a fresh install. This is a one-time migration; subsequent upgrades are unaffected. ### Time-series tooltips follow the selected time grain Tooltips on the Time-series and Mixed Time-series ECharts plugins now respect the chart's time grain (and any dashboard-level time-grain override delivered via `extra_form_data`) when the tooltip time format is left on Adaptive formatting (the default). Tooltips read grain-appropriate labels such as `Jan 2021` (month), `2021 Q1` (quarter), `2021` (year), and weekly date ranges, becoming grain-aware like the x-axis, though the two are formatted independently and their labels may not always match exactly. Only a custom, explicitly-set tooltip time format (a d3 format string) is unaffected — that always wins over the grain. ### Pivot table First/Last aggregations follow data order The pivot table chart's `First` and `Last` aggregations now return the first and last value in data (query result) order, instead of effectively returning the minimum and maximum. Existing pivot tables that use these aggregations for totals/subtotals may show different values after upgrading. For deterministic results, ensure the underlying query has a stable sort order. ### `FetchRetryOptions` callback parameters widened to allow `null` The `error` and `response` parameters of the `retryDelay` and `retryOn` callbacks in `FetchRetryOptions` (exported from `@superset-ui/core`) are now typed `Error | null` and `Response | null` to match the actual call-site signature provided by `fetch-retry`. Because these parameter types are contravariant, consumers who typed their callbacks with the non-nullable `(attempt: number, error: Error, response: Response) => number` will get a TypeScript compile error. Widen your callback signatures to accept `Error | null` / `Response | null`. ### Pivot Table totals are now computed by the database (per-metric "Aggregation function" control restored) Pivot Table subtotals and grand totals are now computed by the database at each rollup level instead of re-aggregating the already-aggregated cell values on the client. This fixes long-standing incorrect totals for non-additive metrics (ratios such as `SUM(a)/SUM(b)`, `COUNT_DISTINCT`, `AVG`, percentiles, etc.), which previously summed the displayed cell values. As a result the per-table **"Aggregation function"** control (which let you pick how totals were aggregated client-side, e.g. Sum/Average/Count) was temporarily removed: totals reflected the metric's own definition evaluated at the total's granularity, with no way to pick a different reducer for subtotals/totals. The "Sum as Fraction of Total/Rows/Columns" display options are back as a new, standalone **"Show values as"** control (below "Combine metrics" in the Options panel), since those were mathematically correct even before this change and are unrelated to the totals-correctness fix. A DB migration derives the new field from any still-present `aggregateFunction` Sum-fraction value, so a chart that had one of those options configured picks the equivalent "Show values as" setting back up automatically. The "Count as Fraction of ..." variants are **not** migrated: they divided a record count, while the new control divides the metric's own value, so translating them automatically would silently change what the chart displays rather than restore it; those charts need to be manually reconfigured if the value-based percentage is what's wanted. The **"Aggregation function"** control itself (form_data field `aggregateFunction`) is also back, as a second aggregation pass over a metric's own grouped results -- e.g. the median of a set of per-store `SUM(sales)` values -- computed correctly this time: every scope (subtotal, row/column total, grand total) reduces its own original contributing query results, never another scope's already-displayed value, so the previous non-additive-metric bug this section originally removed the control for does not return. It reuses the exact same field name and value spellings as before, so a chart that still had e.g. `aggregateFunction: "Median"` sitting unused in its saved `params` starts computing totals with that aggregation again automatically, with no action required and no value to reconfigure. The one exception is a chart that also has a percent "Show values as" selected: that control only exists since the per-table aggregation was removed, so the percent choice is the more recent, explicit intent (made on top of database-computed totals), and a result aggregation would both change those totals and hide the percent display. The migration drops the orphaned `aggregateFunction` from such a chart (and from its saved `query_context`) instead of restoring it, so it keeps rendering exactly as before. This drop is a one-time, upgrade-time heuristic, not a durable fact about the chart: if you later downgrade past this migration and then re-upgrade, a chart where you deliberately chose both an `aggregateFunction` and a percent "Show values as" *after* the control was restored looks identical to the original pre-restoration orphan, and the migration drops that deliberate choice again. Charts arriving through import are neither dropped nor tagged, so they render with the restored aggregation and no notice. A one-time migration tags every other such chart with a `legacy-pivot-aggregation-restored` custom tag; opening a tagged chart in Explore shows a notice that its totals may now look different, which clears once you review and accept it (or save the chart). Accepting only removes the tag; it does not rebuild the saved `query_context` that alerts, reports and cache warm-up keep using. If you have alerts/reports built on one of these charts, re-save each affected chart in Explore first, then validate its scheduled outputs, since a scheduled report render does not pass through that notice and Accept alone leaves the stale `query_context` in place. CSV/XLSX exports and CSV/text reports do apply the selected aggregation, but, unlike the chart, apply it to every cell rather than scoping it to subtotals and totals the way the chart does, so a leaf cell's own database-computed metric value can be overwritten by the aggregation instead of passed through (https://github.com/apache/superset/issues/45215) -- e.g. a `Count` aggregation showing `1` for every leaf, or `Sample Variance` showing `0`, where the chart still shows the real per-cell metric value. ### `thumbnail_url` removed from dashboard list API response The `thumbnail_url` field has been removed from `GET /api/v1/dashboard/` list responses. External consumers relying on this field must now construct the thumbnail URL client-side using `id` and `changed_on_utc`: ``` /api/v1/dashboard/{id}/thumbnail/{changed_on_utc}/ ``` The thumbnail endpoint redirects to the current digest URL regardless of whether the supplied digest is exact. If the image is not yet cached, that digest URL may return `202` and trigger async generation. Using `changed_on_utc` as the digest is sufficient for cache-busting purposes. ### Dashboard import can overwrite related charts, datasets, and databases Re-importing an existing dashboard previously overwrote only the dashboard itself; its related charts, datasets, and databases were never updated (the importer hardcoded `overwrite=False` for them). They can now be overwritten as part of the import. A new `overwrite_all` form field controls this, and defaults to `false` everywhere, so existing behavior is preserved: passing `overwrite=true` alone still overwrites only the dashboard, exactly as before. To also overwrite the related charts, datasets, and databases on the `/api/v1/dashboard/import/` endpoint, pass `overwrite_all=true` explicitly. The import modal in the UI exposes this as an "also overwrite all assets" checkbox, and the CLI `superset import-dashboards` and the `ImportDashboardsCommand` likewise default `overwrite_all` to `false`. ### Tagging fix for `create_all`-bootstrapped schemas Only affects deployments whose metadata schema was created with SQLAlchemy's `create_all` (rather than `superset db upgrade`) on a foreign-key-enforcing backend — PostgreSQL, or MySQL with `FOREIGN_KEY_CHECKS=1`. Such schemas carry three invalid foreign keys on `tagged_object.object_id` that break tagging (`TAGGING_SYSTEM = True`) with a `ForeignKeyViolation`. Schemas built via `superset db upgrade` are unaffected. This release stops the ORM from emitting these constraints, but it cannot drop ones already present in your schema. If affected, drop them manually (names vary by backend, so look them up first): ```sql -- PostgreSQL: names are typically tagged_object_object_id_fkey, _fkey1, _fkey2 ALTER TABLE tagged_object DROP CONSTRAINT ; -- MySQL: find names via `SHOW CREATE TABLE tagged_object;` ALTER TABLE tagged_object DROP FOREIGN KEY ; ``` ### Entity version-history infrastructure 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}//versions/` endpoints. Capture is governed by the `ENABLE_VERSIONING_CAPTURE` config value — an operational kill-switch (a release toggle that became a permanent ops switch), not a feature flag; see "Version history is on by default" above for the shipped default. With capture off, no save writes version rows; the endpoints continue to serve already-captured rows read-only if `VERSION_HISTORY` is enabled. 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). A few save- and import-path internals change **unconditionally** (independent of the flag), because the versioned mappers must behave correctly whether or not capture is enabled: - `DatasetDAO` column/metric updates move from bulk operations to per-row ORM operations, and a metadata refresh now preserves column primary keys via a natural-key (`column_name`) upsert instead of delete-and-reinsert — so charts that reference dataset columns by id keep working across a refresh (previously such references could be invalidated). - `ImportExportMixin.reset_ownership` stamps the current user onto `created_by`/`changed_by` when a request context is present (previously left null for the column default to fill). - `UpdateDashboardCommand` runs its body under `no_autoflush`. These are behavior changes that take effect on upgrade regardless of `ENABLE_VERSIONING_CAPTURE`; no operator action is required. ### Cross-entity version activity stream A read-only companion to the version-history endpoints: each entity type gains a `GET /api/v1/{chart,dashboard,dataset}//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) | 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 reflects captured history; with capture off it remains readable but stops accruing new entries. ### Version-history retention (pruning) Entity version history (the `version_transaction` / `*_version` shadow tables that back version capture) is aged out by a nightly Celery beat task, `version_history.prune_old_versions` (`superset.tasks.version_history_retention`). **Scheduled-run cap (behavior change):** `VERSION_HISTORY_PRUNE_MAX_TRANSACTIONS_PER_RUN` defaults to 1000 whole prunable transactions per invocation; associated shadow and change rows remain atomic. `0` or `None` restores unlimited runs. Invalid values skip the task before deletion. Capped results report `cap_reached`, `remaining_eligible`, and `remaining_count_complete`; the remainder is a lower bound from one candidate window after the live scan's stopping point when the probe does not cover the backlog, and `None` if the probe fails after committed deletion. Physical shadow-row counts remain separate. `VERSION_HISTORY_PRUNE_DRY_RUN=True` scans the entire prunable backlog without writes and reports the exact `eligible_backlog` and `estimated_capped_runs`; non-boolean values skip the scheduled task without deleting history. A backlog drains over successive scheduled runs only when successful pruning exceeds newly eligible inflow. At the default daily schedule, each task is configured for 1000 of its units per day (transactions for history, root entities for soft-delete purge). Raise the relevant cap or schedule more frequent runs to provide catch-up capacity; repeated `cap_reached=True` warrants checking the backlog trend, count completeness, and failure counters. The cap is per task invocation, not a quota shared across concurrent workers. When a commit outcome is classified as uncertain, the task reports an error and stops without retrying that window, so that outcome cannot prune past the cap. | Key | Default | Purpose | |---|---|---| | `VERSION_HISTORY_RETENTION_DAYS` | `30` | Version rows whose owning `version_transaction.issued_at` is older than this many days are pruned. Each entity's live row (`end_transaction_id IS NULL`) is always preserved, as are the live rows of its children and associations; closed historical rows (including the baseline) age out. `0` disables pruning; `-1` makes historical rows eligible on the next scheduled run. Other negative values are invalid and skip pruning. | The task ships in the default `CeleryConfig` (both the `superset.tasks.version_history_retention` import and the beat entry). A deployment that overrides `CELERY_CONFIG` without the beat entry logs a startup warning. When the override explicitly defines `imports`, a missing retention module is also reported; an absent `imports` setting is not diagnosed because Celery may register tasks through `include`, autodiscovery, or worker startup imports. Retention only prunes whatever history exists — capture itself is gated separately by `ENABLE_VERSIONING_CAPTURE`, which now ships on. ### Deletion retention (soft-deleted entities are eventually purged) `SOFT_DELETE_RETENTION_DAYS` also accepts an environment seed: an integer from -1 through 36500, defaulting to 30 when absent. Invalid or oversized supplied values defer scheduled purge with 0. An optional `SOFT_DELETE_RETENTION_DAYS_FUNC` host callback takes precedence over both the stored CLI value and config seed. It must return a nonboolean integer in that range; invalid results or callback failure defer scheduled purge with 0, without falling back to stored values. The client recovery-window display and CLI `show-window` use this same policy. Without a callback, stored CLI values retain their precedence over the config seed. This callback does not gate explicit force-purge or provide downgrade grace protection. Soft-deleted dashboards, charts, and datasets are now permanently removed after a retention window (default 30 days; `SOFT_DELETE_RETENTION_DAYS`, `0` disables; settable per workspace at runtime via the `deletion-retention set-window` CLI, which takes precedence when no host retention callback is installed). The `deletion_retention.purge_soft_deleted` Celery beat task runs daily and removes each aged-out entity together with its M:N join rows, owned children, datasource permission, and version-history shadow rows. After purge an entity is **unrecoverable** — its detail and `/restore` endpoints return 404 and its version history is gone. Purging is **live by default** (`SOFT_DELETE_PURGE_DRY_RUN=False`), so the retention promise above is real on a stock deployment. Set it to `True` to have the task log `would_purge` counts and delete nothing — the lever is retained, so an operator can return to dry-run at any time. Note `would_purge` is an **upper bound** — it counts every entity past the retention window without evaluating deletion blockers, so a real run may purge fewer (entities referenced by report schedules or set as a user's welcome dashboard are blocked and reported separately). The task only acts while the `SOFT_DELETE` rollout flag is on; it now ships on by default. **Scheduled-run cap (behavior change):** `SOFT_DELETE_PURGE_MAX_PER_RUN` defaults to 1000 successful root-entity purges across charts, dashboards, and datasets per invocation. One root plus its cascade counts as one; physical row counts are separate. The cap bounds committed deletions, not candidate evaluations: blocked roots are re-evaluated on every run, so a blocked-heavy backlog can still require substantial work. Set `0` or `None` for unlimited scheduled purging. Invalid values skip the task before deletion. Capped runs report `cap_reached` and `remaining_eligible` (aged supported roots, including blocked roots); `remaining_count_complete=False` and a null remainder mean the remainder is unknown: a model scan failed, a commit outcome is uncertain, or the remainder measurement failed. This can happen before any confirmed purge; confirmed purge totals are retained. Inspect `scan_failures`, `commit_uncertain`, the `deletion_retention.remainder_count_failed` counter, and task logs to distinguish these cases. Model priority rotates by day so a sustained backlog in one model does not indefinitely exclude the others. Dry-run still counts the entire eligible backlog without writes and reports `eligible_backlog` and `estimated_capped_runs`; this is an upper-bound estimate when references block deletion. The cap is per task invocation, not a quota shared across concurrent workers. The manual `superset deletion-retention force-purge` command stays uncapped. Deployments that replace the default `CELERY_CONFIG` must ensure workers register `superset.tasks.deletion_retention` and schedule the `deletion_retention.purge_soft_deleted` task themselves. The shipped Docker development config uses `imports` and includes both entries. While `SOFT_DELETE` is statically enabled, a missing beat entry logs a startup warning; when the override explicitly defines `imports`, a missing purge module is also reported. Operators can immediately erase a specific entity for compliance (GDPR) via `superset deletion-retention force-purge --uuid `; this applies legacy hard-delete semantics — a live chart referencing a force-purged dataset is left without a datasource until re-pointed (the chart is not modified), and it purges the named entity even when it was never soft-deleted. Every scheduled evaluation writes a provisional, content-free record to the new `purge_audit_log` table before the cascade starts. Meaningful retained outcomes survive the entity they name. Blocked audit records carry a stable machine-readable `reason` code (`report_schedule`, `user_attribute`, or `cascade_integrity_failure` for an unexpected cascade failure caused by a database integrity constraint) so the audit table alone answers why an entity was not purged; records finalized before the column existed keep a NULL reason. Apply the migration before rolling out the new code: the audit model declares the column, so a worker on the new code with an un-migrated table fails its write-ahead write and the scheduled purge fails closed until the migration lands. During a rolling deploy, workers still on the old code write reason-less blocked rows and suppress on status alone; both effects are self-healing, since a NULL-reason record never matches a reason code and the next all-new-code run re-anchors the entity. Consecutive scheduled evaluations blocked with the same status **and reason** suppress only the redundant current provisional record — a reason change writes one new blocked record carrying the new code; completed outcomes, outcome transitions, and every force-purge attempt remain independent and immutable. Retained transition records are not automatically expired, so entities whose block reason changes repeatedly can accumulate multiple audit rows. The **scheduled** purge fails closed when its provisional record cannot be written, while **force-purge** proceeds even if the audit write fails — the operator is present and deletion outranks audit for a compliance erasure. Operators can monitor `deletion_retention.blocked_audit_suppressed` and `deletion_retention.blocked_audit_dedupe_fallback` to verify suppression and fail-safe fallback behavior without changing the existing blocked-workload gauge. ### Recently Archived view and permanent delete (purge) endpoints Behind the same `SOFT_DELETE` flag, a **Recently Archived** page (Settings menu, `/archived/`) lists soft-deleted charts, dashboards, and datasets with their archive time and archiving user, and offers **Recover** and **Delete permanently** row actions. The page admits any viewer holding `can_read` on **any** of the three types and offers each viewer only the types they can read; a viewer with none of the three sees an explanatory empty state, and unauthenticated requests are redirected to login. **New endpoints** — `POST /api/v1/{chart,dashboard,dataset}//purge` permanently delete a single **soft-deleted** row, running the same cascade as the retention task. Irreversible. Requires `can_write` on the entity plus editorship of the row (or admin), mirroring `/restore`. The endpoints answer 404 while `SOFT_DELETE` is off (restore deliberately stays live so rows archived before a flag-off remain recoverable), 404 for rows that are not soft-deleted, and 422 with a reason when the purge is blocked (an alert/report references the entity, a user has the dashboard as their welcome page, or a restrictive foreign key intervenes). A purge that cannot write its audit record is refused with 422 rather than executed unrecorded — unlike the operator CLI, an end user's purge never outranks the audit. With the flag on, delete confirmations across the chart/dashboard/dataset list pages change shape: a recoverable archive is confirmed with a primary **Archive** button and no type-DELETE friction, and the copy states the retention window when one is configured. A bulk dataset selection that includes semantic views keeps the full danger treatment, because semantic views have no soft-delete — they are deleted permanently and the confirmation says so. This also resolves the limitation noted under *Soft delete and restore for datasets*: a database blocked by soft-deleted datasets can now be freed by purging those datasets (per-entity endpoint, retention task, or `force-purge` CLI) instead of hard-deleting `tables` rows out-of-band. Automatic pruning of the `purge_audit_log` table is available but **off by default**: set `PURGE_AUDIT_PRUNING_ENABLED = True` to enable the `deletion_retention.prune_purge_audit` Celery beat task (daily, 03:30), which collapses duplicate `blocked` records and ages out operational noise. That bounds the growth that comes from scheduled purges being repeatedly blocked or failing; it is **not** a bound on total table size. Force-purge (`force`-triggered) `blocked` records are retained permanently — exempt from both the duplicate collapse and the operational age-out, including in resolved streaks — so repeated `force-purge` attempts against a persistently blocked entity still add a record each; completed-destruction evidence is retained by default; and the first `blocked` record after each change of block reason is preserved. Left at its default (`PURGE_AUDIT_PRUNING_ENABLED = False`) the table is never pruned at all — enabling it is an explicit operator choice, and a second-phase one (see the rollout requirement in the release-note entry above). `PURGE_AUDIT_PRUNING_BATCH_SIZE` (default 50) caps the candidates per batch; how long a batch holds the audit coordination lock against concurrent audit writes grows with that cap and with the history depth of the entities in the batch — a workload-dependent trade-off against drain speed, not a time bound; see the release-note entry for capacity limits and measurement guidance. The policy is written to preserve the audit's meaning rather than trade it away: within an entity's current blockage streak the earliest — "blocked since" — record always survives (only redundant duplicate `blocked` records are collapsed), and completed-destruction evidence (`confirmed`, `target_absent`) is **never** removed unless the separate `PURGE_AUDIT_EVIDENCE_RETENTION_DAYS` opt-in is explicitly set. What ages out is operational noise — scheduled `blocked` records from already-resolved streaks and `failed` records — once older than `PURGE_AUDIT_OPERATIONAL_RETENTION_DAYS` (default 90). See the release-note entry above for the beat-schedule and `CELERY_CONFIG` details. ### Webhook alerts/reports block private/internal hosts by default Webhook alert/report dispatch (`WebhookNotification.send`) now validates the target URL's host against the same private/internal-IP block applied to dataset import URLs. If the resolved host is in a loopback, link-local, private (RFC-1918), shared-CGNAT, or multicast range, the webhook is rejected with `NotificationParamException`. Deployments that intentionally point webhooks at internal targets (chatops bridges, internal automation servers, on-premises Mattermost/Rocket.Chat, etc.) can opt out by setting `ALERT_REPORTS_WEBHOOK_ALLOW_INTERNAL_HOSTS = True` in `superset_config.py`. This mirrors the existing `DATASET_IMPORT_ALLOW_INTERNAL_DATA_URLS` opt-out for dataset imports. ### Impala cancel_query blocks private/internal hosts by default The Impala engine spec's `cancel_query` issues an HTTP request from the Superset backend to the host configured on the Impala database connection. That host is now validated before the request: if it resolves to a private/internal IP range, the cancel call is refused and a warning is logged. Operators whose Impala cluster runs on an internal network can opt out by setting `IMPALA_CANCEL_QUERY_ALLOW_INTERNAL_HOSTS = True` in `superset_config.py`. This mirrors the dataset-import and webhook opt-out flags. ### Map chart renderer and OpenStreetMap migration behavior The MapLibre migration for deck.gl charts preserves saved non-Mapbox styles on the MapLibre-compatible path. Saved styles such as OpenStreetMap, `tile://` tile templates, generic HTTPS style URLs, and charts without a saved style are not reclassified as Mapbox during migration and do not require `MAPBOX_API_KEY` only because of the migration. Saved true Mapbox styles whose value starts with `mapbox://` remain Mapbox-backed. If a Superset deployment does not configure `MAPBOX_API_KEY`, those saved Mapbox charts keep the existing missing-key message instead of silently falling back to MapLibre or another provider. In Explore, deck.gl and point-cluster renderer controls preserve saved Mapbox state, but the Mapbox choice is not available as a new working renderer without a configured key. The MapLibre style choices include `Streets (OSM)`, backed by `https://tile.openstreetmap.org/{z}/{x}/{y}.png`. This OpenStreetMap tile service requires visible `© OpenStreetMap contributors` attribution and should be used through normal browser map tile requests and caching; it is not intended for bulk prefetch or offline tile downloads. ### Password complexity policy enabled by default Superset now ships a default password-complexity policy, enforced (via Flask-AppBuilder) across self-registration, the user create/edit/reset forms, and the User REST API. The policy requires a minimum password length of 8 characters and rejects a built-in blocklist of common/guessable passwords. This is enabled by default (`FAB_PASSWORD_COMPLEXITY_ENABLED = True`), so new or reset passwords that are too short or appear in the blocklist will be rejected where they were previously accepted. Existing stored passwords are unaffected until they are next changed. Operators can tune or disable the policy via config: - `AUTH_PASSWORD_MIN_LENGTH` — minimum length (default `8`). - `AUTH_PASSWORD_COMMON_BLOCKLIST` — extra passwords to reject, in addition to the built-in list. - `FAB_PASSWORD_COMPLEXITY_VALIDATOR` — replace with your own callable for custom rules. - `FAB_PASSWORD_COMPLEXITY_ENABLED = False` — disable enforcement entirely. ### 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. Charts that relied on the previous always-suffix default for an unset position will render the symbol on the locale-appropriate side instead; set the position explicitly on the metric's currency control to pin it. ### Duration formatter precision The `DURATION` number formatter now uses `Intl.DurationFormat` for locale-aware output. By default, sub-second fields are omitted, so values that previously displayed fractional seconds with `pretty-ms`, such as `10500` milliseconds rendering as `10.5s`, now render as `10s`. To preserve sub-second precision in custom duration formatters, enable `formatSubMilliseconds`. ### Cache warmup authenticates via SUPERSET_CACHE_WARMUP_USER The `cache-warmup` Celery task now drives a real WebDriver session for reliable authentication and reads the user to authenticate as from the new `SUPERSET_CACHE_WARMUP_USER` config option. It no longer consults `CACHE_WARMUP_EXECUTORS` for the warmup path. `SUPERSET_CACHE_WARMUP_USER` defaults to `None`, so the task fails fast with a clear message until you set it. Operators who previously relied on `CACHE_WARMUP_EXECUTORS` for cache warmup must set `SUPERSET_CACHE_WARMUP_USER` to a dedicated least-privilege user with access to the dashboards they want warmed up before the next warmup run. ### YDB now uses a native sqlglot dialect YDB SQL parsing now relies on the dedicated [`ydb-sqlglot-plugin`](https://pypi.org/project/ydb-sqlglot-plugin/) dialect, which registers itself with sqlglot automatically. YDB users must install this plugin (e.g., via `pip install "apache-superset[ydb]"`) to avoid a `ValueError` when Superset parses YDB queries. ### Embedded dashboards enforce configured Allowed Domains for postMessage The embedded dashboard page now validates the origin of incoming `postMessage` events against the dashboard's configured **Allowed Domains**. The server-rendered embedded page exposes the configured domains in its bootstrap payload, and the frontend rejects message events whose origin is not in that list. Enforcement only applies when the Allowed Domains list is non-empty. If the list is empty (the default), any origin is accepted, so there is no behavior change for embeds that did not configure Allowed Domains. ### Default guest/async JWT secrets are rejected at startup Superset already refuses to start in production (non-debug, non-testing) when `SECRET_KEY` is left at its built-in default, and when `GUEST_TOKEN_JWT_SECRET` is left at its default while `EMBEDDED_SUPERSET` is enabled. This behavior is extended to `GLOBAL_ASYNC_QUERIES_JWT_SECRET`: if the `GLOBAL_ASYNC_QUERIES` feature flag is enabled and the secret is still the publicly known default (`test-secret-change-me`), Superset logs a clear error and refuses to start. As with the existing `SECRET_KEY` check, this only fails in production. In debug mode, testing mode, or under the test runner, a warning is logged instead of exiting, so local development is unaffected. To resolve the error, set a strong random value in `superset_config.py`: ```python GLOBAL_ASYNC_QUERIES_JWT_SECRET = "" ``` The check is only active when the relevant feature is enabled, so deployments that do not use global async queries (or embedding) are not affected. ### Guest token revocation (opt-in) Embedded guest tokens can be coarsely revoked at runtime via a new opt-in mechanism. A new config flag `GUEST_TOKEN_REVOCATION_ENABLED` (default `False`) gates the feature. When enabled, every minted guest token carries a revocation version, and tokens whose version is below the current expected version (stored in the metadata database) are rejected at validation time. Bump the expected version with the new CLI command to invalidate all outstanding guest tokens: ```bash superset revoke-guest-tokens ``` This change is backward compatible. The feature is off by default, and even when enabled nothing is revoked until an admin explicitly bumps the version: the expected version starts at `0`, and tokens minted before this change (which carry no version claim) are treated as version `0`. No database migration is required. ### Sessions are terminated when an account is disabled Disabling a user account (setting `active` to `False`, via the admin UI, REST API, or CLI) now terminates that user's outstanding sessions on their next request, instead of relying on a passive check. This works for both client-side cookie sessions and server-side session stores via a per-user invalidation epoch (`user_attribute.sessions_invalidated_at`, added by a migration). The mechanism is inert for users that were never disabled (NULL epoch), so there is no behavior change for active users. Re-enabling an account and logging in again starts a fresh, valid session. The migration backfills the epoch for accounts that are already disabled at upgrade time, so re-enabling such an account does not revive a session that predates this feature. ### Opt-in SSH tunnel server host key verification SSH tunnels can now optionally pin the expected SSH server host key as a defense-in-depth measure against man-in-the-middle attacks. paramiko's transport performs no known-hosts checking by default, so previously the SSH server's identity was not verified. This feature is opt-in and off by default; existing tunnels are unaffected. - A new nullable `server_host_key` column on the `ssh_tunnels` table stores the expected host key in authorized-key form (e.g. `ssh-ed25519 AAAA...`). It is a public key and is stored in plaintext. It can be set via the SSH tunnel POST/PUT payloads (`ssh_tunnel.server_host_key`). - When a tunnel has `server_host_key` set, Superset connects to the SSH server, reads the host key it presents, and rejects the tunnel if it does not match. - A new config flag `SSH_TUNNEL_STRICT_HOST_KEY_CHECKING` (default `False`) controls fail-closed behavior. When `True`, every tunnel must declare a `server_host_key`; a tunnel without one is rejected. Runbook to adopt: 1. Capture the SSH server's host key, e.g. `ssh-keyscan -t ed25519 ssh.example.com` (verify it out-of-band). 2. Set that value on the tunnel's `server_host_key` (via the database/SSH tunnel API or UI payload). 3. Optionally set `SSH_TUNNEL_STRICT_HOST_KEY_CHECKING = True` in `superset_config.py` to require host-key verification on all tunnels. ### SMTP server certificate validation enabled by default `SMTP_SSL_SERVER_AUTH` now defaults to `True` (previously `False`). With this default, STARTTLS/SSL connections to the configured SMTP server validate the server's TLS certificate against the system trusted CA store. This makes outbound email (alerts and reports) verify the mail server's identity out of the box. If your SMTP server presents a self-signed certificate, or a certificate that is not trusted by the system CA store, email delivery may now fail with a certificate verification error. To restore the previous behavior of skipping certificate validation, set the following in `superset_config.py`: ```python SMTP_SSL_SERVER_AUTH = False ``` The recommended fix is to add the SMTP server's certificate (or its issuing CA) to the system trust store rather than disabling validation. ### Dataset import validates catalog against the target connection Importing a dataset now validates the `catalog` field against the target database connection. When the connection has multi-catalog disabled (`allow_multi_catalog` off) and the dataset's catalog is not the connection's default catalog, the import fails instead of silently persisting the non-default catalog. This matches the validation already enforced on the dataset update path and prevents imported datasets from querying an unintended database. If you relied on importing datasets with a non-default catalog, enable "Allow changing catalogs" on the target connection, or set the dataset's catalog to the connection's default before importing. ### Extension supply-chain controls (denylist + version policy) Two opt-in static gates control which extensions are allowed to load: - `EXTENSION_DENYLIST` refuses extensions matching an id (every version) or `id@version` (a single version), e.g. `["compromised-extension", "other-ext@1.2.3"]`. - `EXTENSION_VERSION_POLICY` enforces a minimum version per extension id, e.g. `{"acme.widget": "1.2.0"}` (PEP 440 comparison); a release below the minimum is refused. Both default to empty (no behavior change). They apply to both the `LOCAL_EXTENSIONS` and `EXTENSIONS_PATH` load paths. ### Dynamic Group By respects the sort toggle for display values The Dynamic Group By chart customization now orders its display values according to the "Sort display control values" toggle: ascending (A–Z), descending (Z–A), or the dataset's source order when the toggle is unset. Previously the dropdown always sorted alphabetically. Existing dashboards where the toggle was never set will show options in source order instead of A–Z; open the customization and enable the toggle to restore alphabetical ordering. ### Selectable encryption engine for app-encrypted fields (AES-GCM) App-encrypted fields (database passwords, SSH tunnel credentials, OAuth tokens, etc.) can now use authenticated **AES-GCM** encryption instead of the historical unauthenticated **AES-CBC**. A new config selects the engine for the default adapter: ```python # "aes" (AES-CBC, historical default) | "aes-gcm" (authenticated, recommended for new installs) SQLALCHEMY_ENCRYPTED_FIELD_ENGINE = "aes" ``` **No action required / no behavior change:** the default remains `"aes"`, so existing installs are unaffected. **Opting in on an existing install:** flipping the engine on a populated database without re-encrypting first will make stored secrets undecryptable, because the two ciphertext formats are not compatible. A migrator is provided. Recommended runbook: 1. Take a metadata-DB backup. 2. Re-encrypt existing secrets into the new engine (the `SECRET_KEY` is unchanged): ```bash superset re-encrypt-secrets --engine aes-gcm ``` 3. Set `SQLALCHEMY_ENCRYPTED_FIELD_ENGINE = "aes-gcm"` in your config. 4. Restart Superset. 5. Re-run the migrator once more after the restart: ```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). 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. The migration is transactional (all-or-nothing) and idempotent — it can be safely re-run or resumed. Note that AES-GCM, unlike AES-CBC, does not support querying directly over encrypted columns; audit any code that filters on an encrypted column before switching. See the SIP at `docs/sip/authenticated-encryption-at-rest.md` for details. ### Soft delete and restore for datasets **The soft-delete behavior in this section applies only when the `SOFT_DELETE` feature flag is enabled. The flag defaults to `True`** (`@lifecycle: testing`), so on a default deployment `DELETE /api/v1/dataset/` uses the recoverable soft-delete behavior described below. Setting `SOFT_DELETE` to `False` restores legacy permanent hard-delete behavior for subsequent deletes. **Flag-toggle caveat:** the soft-delete visibility filter is evaluated per query while the flag is on. If datasets are soft-deleted during a flag-on window and the flag is later turned **off**, those rows reappear as live datasets in all lists, lookups, and relationship loads (including charts that reference them). The `POST //restore` endpoint and the `dataset_deleted_state` list filter remain functional regardless of the flag, deliberately, so rows soft-deleted during a flag-on window stay discoverable and restorable after a rollback of the flag. **Flag-independent parts of this work** (active even with `SOFT_DELETE` off): the restore endpoint and deleted-state filter (above); the database-deletion guard counting soft-deleted datasets; the `get_or_create_dataset` soft-deleted-twin pre-check; the combined datasource listing (`GET /api/v1/datasource/...`) always excluding soft-deleted datasets; and the two uniqueness-validation changes documented at the end of this section. Everything else — the soft DELETE itself and the visibility filtering — is flag-gated. With the flag enabled: `DELETE /api/v1/dataset/` no longer hard-deletes the dataset (the bulk-delete endpoint behaves the same way). The row is marked with a `deleted_at` timestamp and hidden from all list, detail, and lookup endpoints. Datasets in this state are excluded from default queries and from relationship loads (e.g. `database.tables`). **No cascade in v1.** Soft-delete does not propagate to dependent charts or dashboards: they remain visible. Loading a chart whose dataset is soft-deleted surfaces a "datasource not found" error at chart-load time. Restore the dataset to recover. **Database deletion is blocked by soft-deleted datasets.** Superset already refuses to delete a database that still has datasets (`DatabaseDeleteDatasetsExistFailedError`); that check now explicitly counts soft-deleted datasets too (it bypasses the visibility filter), since the soft-deleted `tables` rows still reference the database via `database_id` and must not be orphaned. Consequence: because dataset `DELETE` is soft and v1 ships no hard-delete/purge, **a database that has ever had datasets cannot be deleted through the API once those datasets are soft-deleted** — the rows remain and keep blocking the delete. Until a purge capability lands, operators who must remove such a database have to hard-delete the underlying `tables` rows out-of-band first. This is a deliberate trade-off (no orphaned rows / restorable datasets) and is expected to be resolved by the planned purge work. **Side-effect change for operators.** Because the row is no longer physically deleted, FAB `ab_view_menu` / permission-view rows tied to the dataset are also preserved. Downstream automation that relied on `DELETE /api/v1/dataset/` cleaning up those rows must now react to the new `POST /api/v1/dataset//restore` lifecycle, or call the eventual hard-delete endpoint. **New endpoint** — `POST /api/v1/dataset//restore` clears `deleted_at` and returns the dataset to active state. Requires `can_write on Dataset` and editorship of the row (or admin). Soft-deleted datasets can also be surfaced in the list endpoint via the new `dataset_deleted_state` rison filter: `include` returns both live and soft-deleted rows, `only` returns just the soft-deleted ones. Any other value is ignored. For non-admin users, soft-deleted rows are limited to datasets they can edit — the same audience that can restore them. **Permissions migration:** existing role grants of `can_write on Dataset` cover the new restore endpoint automatically; no role migration is required. **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. **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. **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=`, 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. ### Soft delete and restore for charts **Everything in this section applies only when the `SOFT_DELETE` feature flag is enabled. The flag defaults to `True`** (`@lifecycle: testing`), so on a default deployment `DELETE /api/v1/chart/` uses the recoverable soft-delete behavior described below. Setting `SOFT_DELETE` to `False` restores legacy permanent hard-delete behavior for subsequent deletes. **Flag-toggle caveat:** the soft-delete visibility filter is evaluated per query while the flag is on. If charts are soft-deleted during a flag-on window and the flag is later turned **off**, those rows reappear as live charts in all lists, lookups, and relationship loads (including dashboards that contained them). The `POST //restore` endpoint and the `chart_deleted_state` list filter remain functional regardless of the flag, deliberately, so rows soft-deleted during a flag-on window stay discoverable and restorable after a rollback of the flag. With the flag enabled: `DELETE /api/v1/chart/` no longer hard-deletes the chart (the bulk-delete endpoint behaves the same way). The row is marked with a `deleted_at` timestamp and hidden from all list, detail, and lookup endpoints. Charts in this state are excluded from default queries and from relationship loads (e.g. `dashboard.slices`). **Operational notes:** a report schedule whose target chart is soft-deleted now fails its runs with an explicit error ("The chart this report targets was deleted...") until the chart is restored or the report re-pointed — chart deletion is blocked while a report references the chart, but a validate/commit race or a flag toggle can still produce this state. Dashboards **preserve** their membership rows for soft-deleted charts: saving a dashboard does not sever a trashed member, and restoring the chart re-attaches it to its dashboards. **New endpoint** — `POST /api/v1/chart//restore` clears `deleted_at` and returns the chart to active state. Requires `can_write on Chart` and editorship of the row (or admin). Soft-deleted charts can also be surfaced in the list endpoint via the new `chart_deleted_state` rison filter: `include` returns both live and soft-deleted rows, `only` returns just the soft-deleted ones. Any other value is ignored. For non-admin users, soft-deleted rows are limited to charts they can edit — the same audience that can restore them. **Permissions migration:** existing role grants of `can_write on Chart` cover the new restore endpoint automatically; no role migration is required. **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. **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. - [42089](https://github.com/apache/superset/pull/42089) automatically upgrades resolvable Slack v1 recipients, preserves text-only v1 delivery with execution warnings when migration cannot finish, and rejects retired v1 file uploads with actionable scope guidance. Slack delivery uses at-most-once terminal writes and a schedule-wide retry budget configured by `SLACK_SEND_RETRY_MAX_TIME`, clamped to the report's remaining working timeout. Deployments using `SupersetMetastoreCache` for the Slack channel cache must schedule the `slack.cache_channels` Celery task to repopulate misses outside report transactions; see [Alerts and Reports](https://superset.apache.org/admin-docs/configuration/alerts-reports#slack-delivery-timeouts-and-retries). ### Soft delete and restore for dashboards **Everything in this section applies only when the `SOFT_DELETE` feature flag is enabled. The flag defaults to `True`** (`@lifecycle: testing`), so on a default deployment `DELETE /api/v1/dashboard/` uses the recoverable soft-delete behavior described below. Setting `SOFT_DELETE` to `False` restores legacy permanent hard-delete behavior for subsequent deletes. **Flag-toggle caveat:** the soft-delete visibility filter is evaluated per query while the flag is on. If dashboards are soft-deleted during a flag-on window and the flag is later turned **off**, those rows reappear as live dashboards in all lists and lookups (including slug lookups — if a soft-deleted dashboard's slug was reused while the flag was on, both rows become visible with the same slug). The `POST //restore` endpoint and the `dashboard_deleted_state` list filter remain functional regardless of the flag, deliberately, so rows soft-deleted during a flag-on window stay discoverable and restorable after a rollback of the flag. With the flag enabled: `DELETE /api/v1/dashboard/` no longer hard-deletes the dashboard (the bulk-delete endpoint behaves the same way). The row is marked with a `deleted_at` timestamp and hidden from the dashboard API's list, detail, and lookup endpoints, which return 404 for soft-deleted dashboards. The embedded-dashboard iframe URL (`/embedded/`) keeps rendering because it reads only `embedded.allowed_domains` and `embedded.dashboard_id` (the FK column) without dereferencing the parent dashboard; the frontend's subsequent dashboard-API fetch is what sees the 404 and surfaces "dashboard not found" to the user. **New endpoint** — `POST /api/v1/dashboard//restore` clears `deleted_at` and returns the dashboard to active state. Requires `can_write on Dashboard` and editorship of the row (or admin). Soft-deleted dashboards can also be surfaced in the list endpoint via the new `dashboard_deleted_state` rison filter: `include` returns both live and soft-deleted rows, `only` returns just the soft-deleted ones. Any other value is ignored. For non-admin users, soft-deleted rows are limited to dashboards they can edit — the same audience that can restore them. **Permissions migration:** existing role grants of `can_write on Dashboard` cover the new restore endpoint automatically; no role migration is required. **Schema migration:** the migration adds a nullable `deleted_at` column and an index on it (`ix_dashboards_deleted_at`) to the `dashboards` table, and **replaces the full unique constraint on `slug`** with a partial unique index (`ix_dashboards_active_slug`) enforcing slug uniqueness only among active (non-soft-deleted) rows. The column add is instant. On Postgres the constraint swap briefly blocks reads and writes during `ALTER TABLE ... DROP CONSTRAINT` (acquires `ACCESS EXCLUSIVE`), then blocks writes only during `CREATE UNIQUE INDEX` (acquires `ShareLock`); reads pass through during the index build. Both windows are sub-second on a typical `dashboards` table. MySQL InnoDB builds the functional index online (no blocking). **Rollback note:** the downgrade restores the original full unique constraint on `slug`. If the partial-index window allowed slug reuse (a soft-deleted row and an active row holding the same slug), `ALTER TABLE ... ADD CONSTRAINT idx_unique_slug UNIQUE (slug)` will abort with a unique-constraint violation. Before downgrading, hard-delete the soft-deleted duplicates (or rename one side) so each slug appears at most once across all rows. Rolling back the application code while leaving the new migration in place is also possible but exposes soft-deleted rows to the older code path; pair the rollback with a data decision (restore, hard-delete, or migrate-down). The partial-index replacement is dialect-dependent: PostgreSQL uses a native `WHERE deleted_at IS NULL` partial index; MySQL 8.0.13+ uses a functional index over `(CASE WHEN deleted_at IS NULL THEN slug END)` (8.0.13 is the first release with functional key parts). **MySQL <8.0.13, MariaDB, and SQLite keep the original full unique constraint** (functional indexes / column-level UNIQUE recreation aren't supported cleanly — MariaDB is excluded even at 10.x because its `CASE`-expression index semantics differ), so on those backends a soft-deleted dashboard continues to reserve its slug for the lifetime of the row. **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. ### Engine spec capability flag: `supports_offset` A new `BaseEngineSpec.supports_offset` attribute (default `True`) indicates whether a database engine supports the SQL `OFFSET` clause. Engines that do not support `OFFSET` — such as Elasticsearch SQL and OpenDistro — opt out by setting it to `False`, and Superset uses each engine's cursor API to paginate drill-to-detail samples instead of emitting `OFFSET`. Downstream forks maintaining custom engine specs may set the flag to `False` (and implement `fetch_data_with_cursor`) to avoid crashes when paginated drill-to-detail queries are run against engines without `OFFSET` support. **Note on deep-pagination cost:** Cursor-based engines (including Elasticsearch and OpenDistro) are forward-only, so reaching page `N` of a drill-to-detail view issues `N` round trips to the cluster. Deep pagination is therefore linear in page number; users paginating into the hundreds or thousands will notice added latency compared to `OFFSET`-capable engines. ### Granular Export Controls 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 | | `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. **Migration behavior:** All three new permissions are granted to every role that currently has `can_csv`, preserving existing access. Admins can then selectively revoke individual export permissions from specific roles as needed. ### Deck.gl MapBox viewport and opacity controls are functional The Deck.gl MapBox chart's **Opacity**, **Default longitude**, **Default latitude**, and **Zoom** controls were previously non-functional — changing them had no effect on the rendered map. These controls are now wired up correctly. **Behavior change for existing charts:** Previously, the viewport controls had hard-coded default values (`-122.405293`, `37.772123`, zoom `11` — San Francisco) that were stored in each chart's `form_data` but never applied. The map always used `fitBounds` to center on the data. With this fix, those stored values are now respected, which means existing MapBox charts may open centered on the old default coordinates instead of fitting to data bounds. **To restore fit-to-data behavior:** Open the chart in Explore, clear the **Default longitude**, **Default latitude**, and **Zoom** fields in the Viewport section, and re-save the chart. ### Combined datasource list endpoint Added a new combined datasource list endpoint at `GET /api/v1/datasource/` to serve datasets and semantic views in one response. - The endpoint is available to users with at least one of `can_read` on `Dataset` or `SemanticView`. - Semantic views are included only when the `SEMANTIC_LAYERS` feature flag is enabled. - The endpoint enforces strict `order_column` validation and returns `400` for invalid sort columns. ### Custom time range "Now"/"Today" anchors resolve in local time Custom time ranges that use the "Now" or "Today" anchor (for the Start, End, or the relative anchor itself) previously resolved that anchor in UTC before formatting it into a naive datetime string, which was then re-parsed elsewhere as local time. For users outside UTC, this made the resolved anchor drift by their browser's UTC offset. "Now"/"Today" now resolve directly in local time, matching the later local re-parse. Charts and dashboards using these anchors will compute a different (correct) timestamp after upgrading; if a chart's filters or drill-downs were tuned to compensate for the old offset, review them after upgrading. - [43916](https://github.com/apache/superset/pull/43916): The `docker-compose` dev loop now skips re-running `superset load_examples` on every `docker compose up` once the example data and dashboards are present in the databases (set `SUPERSET_FORCE_LOAD_EXAMPLES=yes` to reload them anyway), and the `superset-node` service now defaults `DISABLE_TS_CHECKER=true` like `docker-compose-light.yml` already did, skipping webpack's TypeScript type-checking pass in dev by default. - `NOT IN` filters on SQLAlchemy-backed datasets now also return rows where the column is NULL. Previously these rows were dropped by SQL three-valued logic (`NULL NOT IN (...)` is not true), so a filter such as "Exclude selected values" removed NULL rows in addition to the selected values. Dashboards, charts and reports that use `NOT IN` may therefore show additional NULL rows. A `NOT IN` list that explicitly contains NULL still excludes NULL rows. `!=`, `NOT LIKE` and `NOT ILIKE` are unchanged. ## 6.1.0 ### ClickHouse minimum driver version bump The minimum required version of `clickhouse-connect` has been raised to `>=0.13.0`. If you are using the ClickHouse connector, please upgrade your `clickhouse-connect` package. The `_mutate_label` workaround that appended hash suffixes to column aliases has also been removed, as it is no longer needed with modern versions of the driver. ### Kenya Country Map: Updated Administrative Divisions The Kenya country map has been updated to reflect the 47 counties established under Kenya's 2010 constitution, replacing the outdated 8-province boundaries from the Natural Earth dataset. County keys now use ISO 3166-2:KE codes (`KE-01` through `KE-47`), replacing the former province codes (`KE-110`, `KE-200`, ..., `KE-800`). Dashboards that join on the old province codes will need to re-key their datasets to use the new county codes. ### MCP Tool Observability MCP (Model Context Protocol) tools now include enhanced observability instrumentation for monitoring and debugging: **Two-layer instrumentation:** 1. **Middleware layer** (`LoggingMiddleware`): Automatically logs all MCP tool calls with `duration_ms` and `success` status in the audit log (Action Log UI, logs table) 2. **Sub-operation tracking**: All 19 MCP tools include granular `event_logger.log_context()` blocks for tracking individual operations like validation, database writes, and query execution **Action naming convention:** - Tool-level logs: `mcp_tool_call` (via middleware) - Sub-operation logs: `mcp.{tool_name}.{operation}` (e.g., `mcp.generate_chart.validation`, `mcp.execute_sql.query_execution`) **Querying MCP logs:** ```sql -- Top slowest MCP operations SELECT action, COUNT(*) as calls, AVG(duration_ms) as avg_ms FROM logs WHERE action LIKE 'mcp.%' GROUP BY action ORDER BY avg_ms DESC LIMIT 20; -- MCP tool success rate SELECT json_extract(curated_payload, '$.tool') as tool, COUNT(*) as total_calls, SUM(CASE WHEN json_extract(curated_payload, '$.success') = 'true' THEN 1 ELSE 0 END) as successful, ROUND(100.0 * SUM(CASE WHEN json_extract(curated_payload, '$.success') = 'true' THEN 1 ELSE 0 END) / COUNT(*), 2) as success_rate FROM logs WHERE action = 'mcp_tool_call' GROUP BY tool ORDER BY total_calls DESC; ``` **Security note:** Sensitive parameters (passwords, API keys, tokens) are automatically redacted in logs as `[REDACTED]`. ### Distributed Coordination Backend A new `DISTRIBUTED_COORDINATION_CONFIG` configuration provides a unified Redis-based backend for real-time coordination features in Superset. This backend enables: - **Pub/sub messaging** for real-time event notifications between workers - **Atomic distributed locking** using Redis SET NX EX (more performant than database-backed locks) - **Event-based coordination** for background task management 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", "CACHE_KEY_PREFIX": "signal_", "CACHE_REDIS_URL": "redis://localhost:6379/1", "CACHE_DEFAULT_TIMEOUT": 300, } ``` 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 services: superset-websocket: volumes: - ./superset-websocket:/home/superset-websocket - /home/superset-websocket/node_modules - /home/superset-websocket/dist - ./docker/superset-websocket/config.json:/home/superset-websocket/config.json:ro ``` ### 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 ├── birth_names/ # Each example is self-contained │ ├── data.parquet # Dataset (Parquet format) │ ├── dataset.yaml # Dataset metadata │ ├── dashboard.yaml # Dashboard config (optional) │ └── charts/ # Chart configs (optional) └── ... ``` #### Simplified Parquet-based Loading - Auto-discovery: create `superset/examples/my_dataset/data.parquet` to add a new example - Parquet is an Apache project format: compressed (~27% smaller), self-describing schema - YAML configs define datasets, charts, and dashboards declaratively - Removed Python-based data generation from individual example files #### Test Data Reorganization - Moved `big_data.py` to `superset/cli/test_loaders.py` - better reflects its purpose as a test utility - Fixed inverted logic for `--load-test-data` flag (now correctly includes .test.yaml files when flag is set) - Clarified CLI flags: - `--force` / `-f`: Force reload even if tables exist - `--only-metadata` / `-m`: Create table metadata without loading data - `--load-test-data` / `-t`: Include test dashboards and .test.yaml configs - `--load-big-data` / `-b`: Generate synthetic stress-test data #### Bug Fixes - Fixed numpy array serialization for PostgreSQL (converts complex types to JSON strings) - Fixed KeyError for `allow_csv_upload` field in database configs (now optional with default) - Fixed test data loading logic that was incorrectly filtering files ### MCP Service The MCP (Model Context Protocol) service enables AI assistants and automation tools to interact programmatically with Superset. #### New Features - MCP service infrastructure with FastMCP framework - Tools for dashboards, charts, datasets, SQL Lab, and instance metadata - Optional dependency: install with `pip install apache-superset[fastmcp]` - Runs as separate process from Superset web server - JWT-based authentication for production deployments #### New Configuration Options **Development** (single-user, local testing): ```python # superset_config.py MCP_DEV_USERNAME = "admin" # User for MCP authentication MCP_SERVICE_HOST = "localhost" MCP_SERVICE_PORT = 5008 ``` **Production** (JWT-based, multi-user): ```python # superset_config.py MCP_AUTH_ENABLED = True MCP_JWT_ISSUER = "https://your-auth-provider.com" MCP_JWT_AUDIENCE = "superset-mcp" MCP_JWT_ALGORITHM = "RS256" # or "HS256" for shared secrets # Option 1: Use JWKS endpoint (recommended for RS256) MCP_JWKS_URI = "https://auth.example.com/.well-known/jwks.json" # Option 2: Use static public key (RS256) MCP_JWT_PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----..." # Option 3: Use shared secret (HS256) MCP_JWT_ALGORITHM = "HS256" MCP_JWT_SECRET = "your-shared-secret-key" # Optional overrides MCP_SERVICE_HOST = "0.0.0.0" MCP_SERVICE_PORT = 5008 MCP_SESSION_CONFIG = { "SESSION_COOKIE_SECURE": True, "SESSION_COOKIE_HTTPONLY": True, "SESSION_COOKIE_SAMESITE": "Strict", } ``` #### Running the MCP Service ```bash # Development superset mcp run --port 5008 --debug # Production superset mcp run --port 5008 # With factory config superset mcp run --port 5008 --use-factory-config ``` #### Deployment Considerations 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] # Or add to requirements.txt apache-superset[fastmcp]>=X.Y.Z ``` **Process Management**: 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 #### Documentation - Architecture: `superset/mcp_service/ARCHITECTURE.md` - Security: `superset/mcp_service/SECURITY.md` - Production: `superset/mcp_service/PRODUCTION.md` - Developer Guide: `superset/mcp_service/CLAUDE.md` - Quick Start: `superset/mcp_service/README.md` --- - [38358](https://github.com/apache/superset/pull/38358): Switched CrateDB PyPI package from `crate[sqlalchemy]` to `sqlalchemy-cratedb`. - [35621](https://github.com/apache/superset/pull/35621): The default hash algorithm has changed from MD5 to SHA-256 for improved security and FedRAMP compliance. This affects cache keys for thumbnails, dashboard digests, chart digests, and filter option names. Existing cached data will be invalidated upon upgrade. To opt out of this change and maintain backward compatibility, set `HASH_ALGORITHM = "md5"` in your `superset_config.py`. - [35062](https://github.com/apache/superset/pull/35062): Changed the function signature of `setupExtensions` to `setupCodeOverrides` with options as arguments. ### Breaking Changes - [37370](https://github.com/apache/superset/pull/37370): The `APP_NAME` configuration variable no longer controls the browser window/tab title or other frontend branding. Application names should now be configured using the theme system with the `brandAppName` token. The `APP_NAME` config is still used for backend contexts (MCP service, logs, etc.) and serves as a fallback if `brandAppName` is not set. - **Migration:** ```python # Before (Superset 5.x) APP_NAME = "My Custom App" # After (Superset 6.x) - Option 1: Use theme system (recommended) THEME_DEFAULT = { "token": { "brandAppName": "My Custom App", # Window titles "brandLogoAlt": "My Custom App", # Logo alt text "brandLogoUrl": "/static/assets/images/custom_logo.png" } } # After (Superset 6.x) - Option 2: Temporary fallback # Keep APP_NAME for now (will be used as fallback for brandAppName) APP_NAME = "My Custom App" # But you should migrate to THEME_DEFAULT.token.brandAppName ``` - **Note:** For dark mode, set the same tokens in `THEME_DARK` configuration. - [36317](https://github.com/apache/superset/pull/36317): The `CUSTOM_FONT_URLS` configuration option has been removed. Use the new per-theme `fontUrls` token in `THEME_DEFAULT` or database-managed themes instead. - **Before:** ```python CUSTOM_FONT_URLS = [ "https://fonts.example.com/myfont.css", ] ``` - **After:** ```python THEME_DEFAULT = { "token": { "fontUrls": [ "https://fonts.example.com/myfont.css", ], # ... other tokens } } ``` ### Composite primary keys on many-to-many association tables 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)` | | `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)` | **Before upgrading:** - The migration **deletes** two classes of pre-existing rows the composite PK cannot accommodate: duplicate `(fk1, fk2)` pairs (it keeps the lowest `id` and removes the rest) and rows with `NULL` in either FK column. Both are meaningless for `secondary=` association tables, but export the affected rows first if you need an audit record. - External tooling (BI tools, backup scripts) that references the surrogate `id` on these tables will break; no application code references it. - Downgrade restores the `id` column (and the original `UNIQUE` on the two tables that had it) but leaves the FK columns `NOT NULL` (intentional — a `NULL` FK in a junction row is meaningless). 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). - [34536](https://github.com/apache/superset/pull/34536): The `ENVIRONMENT_TAG_CONFIG` color values have changed to support only Ant Design semantic colors. Update your `superset_config.py`: - Change `"error.base"` to just `"error"` after this PR - 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). - [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. - [34204](https://github.com/apache/superset/pull/33603) OpenStreetView has been promoted as the new default for Deck.gl visualization since it can be enabled by default without requiring an API key. If you have Mapbox set up and want to disable OpenStreeView in your environment, please follow the steps documented here [https://superset.apache.org/docs/configuration/map-tiles]. - [33116](https://github.com/apache/superset/pull/33116) In Echarts Series charts (e.g. Line, Area, Bar, etc.) charts, the `x_axis_sort_series` and `x_axis_sort_series_ascending` form data items have been renamed with `x_axis_sort` and `x_axis_sort_asc`. There's a migration added that can potentially affect a significant number of existing charts. - [32317](https://github.com/apache/superset/pull/32317) The horizontal filter bar feature is now out of testing/beta development and its feature flag `HORIZONTAL_FILTER_BAR` has been removed. - [31590](https://github.com/apache/superset/pull/31590) Marks the beginning of intricate work around supporting dynamic Theming, and breaks support for [THEME_OVERRIDES](https://github.com/apache/superset/blob/732de4ac7fae88e29b7f123b6cbb2d7cd411b0e4/superset/config.py#L671) in favor of a new theming system based on AntD V5. Likely this will be in disrepair until settling over the 5.x lifecycle. - [32432](https://github.com/apache/superset/pull/32432) Moves the List Roles FAB view to the frontend and requires `FAB_ADD_SECURITY_API` to be enabled in the configuration and `superset init` to be executed. - [34319](https://github.com/apache/superset/pull/34319) Drill to Detail and Drill By is now supported in Embedded mode, and also with the `DASHBOARD_RBAC` FF. If you don't want to expose these features in Embedded / `DASHBOARD_RBAC`, make sure the roles used for Embedded / `DASHBOARD_RBAC`don't have the required permissions to perform D2D actions. ## 5.0.0 - [31976](https://github.com/apache/superset/pull/31976) Removed the `DISABLE_LEGACY_DATASOURCE_EDITOR` feature flag. The previous value of the feature flag was `True` and now the feature is permanently removed. - [32000](https://github.com/apache/superset/pull/32000) Removes CSV_UPLOAD_MAX_SIZE config, use your web server to control file upload size. - [31959](https://github.com/apache/superset/pull/31959) Removes the following endpoints from data uploads: `/api/v1/database//_upload` and `/api/v1/database/_metadata`, in favour of new one (Details on the PR). And simplifies permissions. - [31844](https://github.com/apache/superset/pull/31844) The `ALERT_REPORTS_EXECUTE_AS` and `THUMBNAILS_EXECUTE_AS` config parameters have been renamed to `ALERT_REPORTS_EXECUTORS` and `THUMBNAILS_EXECUTORS` respectively. A new config flag `CACHE_WARMUP_EXECUTORS` has also been introduced to be able to control which user is used to execute cache warmup tasks. Finally, the config flag `THUMBNAILS_SELENIUM_USER` has been removed. To use a fixed executor for async tasks, use the new `FixedExecutor` class. See the config and docs for more info on setting up different executor profiles. - [31894](https://github.com/apache/superset/pull/31894) Domain sharding is deprecated in favor of HTTP2. The `SUPERSET_WEBSERVER_DOMAINS` configuration will be removed in the next major version (6.0) - [31794](https://github.com/apache/superset/pull/31794) Removed the previously deprecated `DASHBOARD_CROSS_FILTERS` feature flag - [31774](https://github.com/apache/superset/pull/31774): Fixes the spelling of the `USE-ANALAGOUS-COLORS` feature flag. Please update any scripts/configuration item to use the new/corrected `USE-ANALOGOUS-COLORS` flag spelling. - [31582](https://github.com/apache/superset/pull/31582) Removed the legacy Area, Bar, Event Flow, Heatmap, Histogram, Line, Sankey, and Sankey Loop charts. They were all automatically migrated to their ECharts counterparts with the exception of the Event Flow and Sankey Loop charts which were removed as they were not actively maintained and not widely used. If you were using the Event Flow or Sankey Loop charts, you will need to find an alternative solution. - [31198](https://github.com/apache/superset/pull/31198) Disallows by default the use of the following ClickHouse functions: "version", "currentDatabase", "hostName". - [29798](https://github.com/apache/superset/pull/29798) Since 3.1.0, the initial schedule for an alert or report was mistakenly offset by the specified timezone's relation to UTC. The initial schedule should now begin at the correct time. - [30021](https://github.com/apache/superset/pull/30021) The `dev` layer in our Dockerfile no long includes firefox binaries, only Chromium to reduce bloat/docker-build-time. - [30099](https://github.com/apache/superset/pull/30099) Translations are no longer included in the default docker image builds. If your environment requires translations, you'll want to set the docker build arg `BUILD_TRANSLATIONS=true`. - [31262](https://github.com/apache/superset/pull/31262) NOTE: deprecated `pylint` in favor of `ruff` as our only python linter. Only affect development workflows positively (not the release itself). It should cover most important rules, be much faster, but some things linting rules that were enforced before may not be enforce in the exact same way as before. - [31173](https://github.com/apache/superset/pull/31173) Modified `fetch_csrf_token` to align with HTTP standards, particularly regarding how cookies are handled. If you encounter any issues related to CSRF functionality, please report them as a new issue and reference this PR for context. - [31413](https://github.com/apache/superset/pull/31413) Enable the DATE_FORMAT_IN_EMAIL_SUBJECT feature flag to allow users to specify a date format for the email subject, which will then be replaced with the actual date. - [31385](https://github.com/apache/superset/pull/31385) Significant docker refactor, reducing access levels for the `superset` user, streamlining layer building, ... - [31503](https://github.com/apache/superset/pull/31503) Deprecating python 3.9.x support, 3.11 is now the recommended version and 3.10 is still supported over the Superset 5.0 lifecycle. - [29121](https://github.com/apache/superset/pull/29121) Removed the `css`, `position_json`, and `json_metadata` from the payload of the dashboard list endpoint (`GET api/v1/dashboard`) for performance reasons. - [29163](https://github.com/apache/superset/pull/29163) Removed the `SHARE_QUERIES_VIA_KV_STORE` and `KV_STORE` feature flags and changed the way Superset shares SQL Lab queries to use permalinks. The legacy `/kv` API was removed but we still support legacy links in 5.0. In 6.0, only permalinks will be supported. - [25166](https://github.com/apache/superset/pull/25166) Changed the default configuration of `UPLOAD_FOLDER` from `/app/static/uploads/` to `/static/uploads/`. It also removed the unused `IMG_UPLOAD_FOLDER` and `IMG_UPLOAD_URL` configuration options. - [30284](https://github.com/apache/superset/pull/30284) Deprecated GLOBAL_ASYNC_QUERIES_REDIS_CONFIG in favor of the new GLOBAL_ASYNC_QUERIES_CACHE_BACKEND configuration. To leverage Redis Sentinel, set CACHE_TYPE to RedisSentinelCache, or use RedisCache for standalone Redis - [31961](https://github.com/apache/superset/pull/31961) Upgraded React from version 16.13.1 to 17.0.2. If you are using custom frontend extensions or plugins, you may need to update them to be compatible with React 17. - [31260](https://github.com/apache/superset/pull/31260) Docker images now use `uv pip install` instead of `pip install` to manage the python environment. Most docker-based deployments will be affected, whether you derive one of the published images, or have custom bootstrap script that install python libraries (drivers) ### Potential Downtime ## 4.1.2 - [31198](https://github.com/apache/superset/pull/31198) Disallows by default the use of the following ClickHouse functions: "version", "currentDatabase", "hostName". - [31173](https://github.com/apache/superset/pull/31173) Modified `fetch_csrf_token` to align with HTTP standards, particularly regarding how cookies are handled. If you encounter any issues related to CSRF functionality, please report them as a new issue and reference this PR for context. ## 4.1.0 - [29274](https://github.com/apache/superset/pull/29274): We made it easier to trigger CI on your forks, whether they are public or private. Simply push to a branch that fits `[0-9].[0-9]*` and should run on your fork, giving you flexibility on naming your release branches and triggering CI - [27505](https://github.com/apache/superset/pull/27505): We simplified the files under `requirements/` folder. If you use these files for your builds you may want to double check that your builds are not affected. `base.txt` should be the same as before, though `development.txt` becomes a bigger set, incorporating the now defunct local,testing,integration, and docker - [27434](https://github.com/apache/superset/pull/27434/files): DO NOT USE our docker compose.\* files for production use cases! While we never really supported or should have tried to support docker compose for production use cases, we now actively have taken a stance against supporting it. See the PR for details. - [24112](https://github.com/apache/superset/pull/24112): Python 3.10 is now the recommended python version to use, 3.9 still supported but getting deprecated in the nearish future. CI/CD runs on py310 so you probably want to align. If you use official dockers, upgrade should happen automatically. - [27697](https://github.com/apache/superset/pull/27697) [minor] flask-session bump leads to them deprecating `SESSION_USE_SIGNER`, check your configs as this flag won't do anything moving forward. - [27849](https://github.com/apache/superset/pull/27849/) More of an FYI, but we have a new config `SLACK_ENABLE_AVATARS` (False by default) that works in conjunction with set `SLACK_API_TOKEN` to fetch and serve Slack avatar links - [28134](https://github.com/apache/superset/pull/28134/) The default logging level was changed from DEBUG to INFO - which is the normal/sane default logging level for most software. - [27777](https://github.com/apache/superset/pull/27777) Moves debug logging logic to config.py. See `LOG_LEVEL` in `superset/config.py` for the recommended default. - [28205](https://github.com/apache/superset/pull/28205) The permission `all_database_access` now more clearly provides access to all databases, as specified in its name. Before it only allowed listing all databases in CRUD-view and dropdown and didn't provide access to data as it seemed the name would imply. - [28483](https://github.com/apache/superset/pull/28483) Starting with this version we bundle translations inside the python package. This includes the .mo files needed by pybabel on the backend, as well as the .json files used by the frontend. If you were doing anything before as part of your bundling to expose translation packages, it's probably not needed anymore. - [29264](https://github.com/apache/superset/pull/29264) Slack has updated its file upload api, and we are now supporting this new api in Superset, although the Slack api is not backward compatible. The original Slack integration is deprecated and we will require a new Slack scope `channels:read` to be added to Slack workspaces in order to use this new api. In an upcoming release, we will make this new Slack scope mandatory and remove the old Slack functionality. - [30274](https://github.com/apache/superset/pull/30274) Moved SLACK_ENABLE_AVATAR from config.py to the feature flag framework, please adapt your configs. ### Potential Downtime - [27392](https://github.com/apache/superset/pull/27392): Adds an index to `query.sql_editor_id` to improve performance. This may cause downtime on large deployments. ## 4.0.0 - [27119](https://github.com/apache/superset/pull/27119): Updates various database columns to use the `MediumText` type, potentially requiring a table lock on MySQL dbs or taking some time to complete on large deployments. - [26450](https://github.com/apache/superset/pull/26450): Deprecates the `KV_STORE` feature flag and its related assets such as the API endpoint and `keyvalue` table. The main dependency of this feature is the `SHARE_QUERIES_VIA_KV_STORE` feature flag which allows sharing SQL Lab queries without the necessity of saving the query. Our intention is to use the permalink feature to implement this use case before 5.0 and that's why we are deprecating the feature flag now. ### Breaking Changes - [27130](https://github.com/apache/superset/pull/27130): Fixes the DELETE `/database/{id}/ssh_tunnel/` endpoint to now correctly accept a database ID as a parameter, rather than an SSH tunnel ID. - [27117](https://github.com/apache/superset/pull/27117): Removes the following deprecated endpoints: `/superset/sqllab`, `/superset/sqllab/history`, `/sqllab/my_queries` use `/sqllab`, `/sqllab/history`, `/savedqueryview/list/?_flt_0_user={get_user_id()}` instead. - [26347](https://github.com/apache/superset/issues/26347): Removes the deprecated `VERSIONED_EXPORT` feature flag. The previous value of the feature flag was `True` and now the feature is permanently enabled. - [26328](https://github.com/apache/superset/issues/26328): Removes the deprecated Filter Box code and it's associated dependencies `react-select` and `array-move`. It also removes the `DeprecatedSelect` and `AsyncSelect` components that were exclusively used by filter boxes. Existing filter boxes will be automatically migrated to native filters. - [26330](https://github.com/apache/superset/issues/26330): Removes the deprecated `DASHBOARD_FILTERS_EXPERIMENTAL` feature flag. The previous value of the feature flag was `False` and now the feature is permanently removed. - [26344](https://github.com/apache/superset/issues/26344): Removes the deprecated `ENABLE_EXPLORE_JSON_CSRF_PROTECTION` feature flag. The previous value of the feature flag was `False` and now the feature is permanently removed. - [26345](https://github.com/apache/superset/issues/26345): Removes the deprecated `ENABLE_TEMPLATE_REMOVE_FILTERS` feature flag. The previous value of the feature flag was `True` and now the feature is permanently enabled. - [26346](https://github.com/apache/superset/issues/26346): Removes the deprecated `REMOVE_SLICE_LEVEL_LABEL_COLORS` feature flag. The previous value of the feature flag was `False` and now the feature is permanently removed. - [26348](https://github.com/apache/superset/issues/26348): Removes the deprecated `CLIENT_CACHE` feature flag. The previous value of the feature flag was `False` and now the feature is permanently removed. - [26349](https://github.com/apache/superset/issues/26349): Removes the deprecated `DASHBOARD_CACHE` feature flag. The previous value of the feature flag was `False` and now the feature is permanently removed. - [26369](https://github.com/apache/superset/issues/26369): Removes the Filter Sets feature including the deprecated `DASHBOARD_NATIVE_FILTERS_SET` feature flag and all related API endpoints. The feature is permanently removed as it was not being actively maintained, it was not widely used, and it was full of bugs. We also considered that if we were to provide a similar feature, it would be better to re-implement it from scratch given the amount of technical debt that the current implementation has. The previous value of the feature flag was `False` and now the feature is permanently removed. - [26343](https://github.com/apache/superset/issues/26343): Removes the deprecated `ENABLE_EXPLORE_DRAG_AND_DROP` feature flag. The previous value of the feature flag was `True` and now the feature is permanently enabled. - [26331](https://github.com/apache/superset/issues/26331): Removes the deprecated `DISABLE_DATASET_SOURCE_EDIT` feature flag. The previous value of the feature flag was `False` and now the feature is permanently removed. - [26636](https://github.com/apache/superset/issues/26636): Sets the `DASHBOARD_VIRTUALIZATION` feature flag to `True` by default. This feature was introduced by [21438](https://github.com/apache/superset/pull/21438) and will enable virtualization when rendering a dashboard's charts in an attempt to reduce the number of elements (DOM nodes) rendered at once. This is especially useful for large dashboards. - [26637](https://github.com/apache/superset/issues/26637): Sets the `DRILL_BY` feature flag to `True` by default given that the feature has been tested for a while and reached a stable state. - [26462](https://github.com/apache/superset/issues/26462): Removes the Profile feature given that it's not actively maintained and not widely used. - [26377](https://github.com/apache/superset/pull/26377): Removes the deprecated Redirect API that supported short URLs used before the permalink feature. - [26329](https://github.com/apache/superset/issues/26329): Removes the deprecated `DASHBOARD_NATIVE_FILTERS` feature flag. The previous value of the feature flag was `True` and now the feature is permanently enabled. - [25510](https://github.com/apache/superset/pull/25510): Reinforces that any newly defined Python data format (other than epoch) must adhere to the ISO 8601 standard (enforced by way of validation at the API and database level) after a previous relaxation to include slashes in addition to dashes. From now on when specifying new columns, dataset owners will need to use a SQL expression instead to convert their string columns of the form %Y/%m/%d etc. to a `DATE`, `DATETIME`, etc. type. - [26372](https://github.com/apache/superset/issues/26372): Removes the deprecated `GENERIC_CHART_AXES` feature flag. The previous value of the feature flag was `True` and now the feature is permanently enabled. ### Potential Downtime - [26416](https://github.com/apache/superset/pull/26416): Adds two database indexes to the `report_execution_log` table and one database index to the `report_recipient` to improve performance. Scheduled downtime may be required for large deployments. - [28482](https://github.com/apache/superset/pull/28482): Potentially augments the `query.executed_sql` and `query.select_sql` columns for MySQL from `MEDIUMTEXT` to `LONGTEXT`. Potential downtime may be required for large deployments which previously ran [27119](https://github.com/apache/superset/pull/27119). ## 3.1.0 - [24657](https://github.com/apache/superset/pull/24657): Bumps the cryptography package to augment the OpenSSL security vulnerability. ### Other - [24982](https://github.com/apache/superset/pull/24982): By default, physical datasets on Oracle-like dialects like Snowflake will now use denormalized column names. However, existing datasets won't be affected. To change this behavior, the "Advanced" section on the dataset modal has a "Normalize column names" flag which can be changed to change this behavior. ## 3.0.3 - [26034](https://github.com/apache/superset/issues/26034): Fixes a problem where numeric x-axes were being treated as categorical values. As a consequence of that, the way labels are displayed might change given that ECharts has a different treatment for numerical and categorical values. To revert to the old behavior, users need to manually convert numerical columns to text so that they are treated as categories. Check https://github.com/apache/superset/issues/26159 for more details. ## 3.0.0 - [25053](https://github.com/apache/superset/pull/25053): Extends the `ab_user.email` column from 64 to 320 characters which has an associated unique key constraint. This will be problematic for MySQL metadata databases which use the InnoDB storage engine with the `innodb_large_prefix` parameter disabled as the key prefix limit is 767 bytes. Enabling said parameter and ensuring that the table uses either the `DYNAMIC` or `COMPRESSED` row format should remedy the problem. See [here](https://dev.mysql.com/doc/refman/5.7/en/innodb-limits.html) for more details. - [24911](https://github.com/apache/superset/pull/24911): Changes the column type from `TEXT` to `MediumText` in table `logs`, potentially requiring a table lock on MySQL dbs or taking some time to complete on large deployments. - [24939](https://github.com/apache/superset/pull/24939): Augments the foreign key constraints for the `embedded_dashboards` table to include an explicit CASCADE ON DELETE to ensure the relevant records are deleted when a dashboard is deleted. Scheduled downtime may be advised. - [24938](https://github.com/apache/superset/pull/24938): Augments the foreign key constraints for the `dashboard_slices` table to include an explicit CASCADE ON DELETE to ensure the relevant records are deleted when a dashboard or slice is deleted. Scheduled downtime may be advised. - [24628](https://github.com/apache/superset/pull/24628): Augments the foreign key constraints for the `dashboard_owner`, `report_schedule_owner`, and `slice_owner` tables to include an explicit CASCADE ON DELETE to ensure the relevant ownership records are deleted when a dataset is deleted. Scheduled downtime may be advised. - [24488](https://github.com/apache/superset/pull/24488): Augments the foreign key constraints for the `sql_metrics`, `sqlatable_user`, and `table_columns` tables which reference the `tables` table to include an explicit CASCADE ON DELETE to ensure the relevant records are deleted when a dataset is deleted. Scheduled downtime may be advised. - [24232](https://github.com/apache/superset/pull/24232): Enables ENABLE_TEMPLATE_REMOVE_FILTERS, DRILL_TO_DETAIL, DASHBOARD_CROSS_FILTERS by default, marks VERSIONED_EXPORT and ENABLE_TEMPLATE_REMOVE_FILTERS as deprecated. - [23652](https://github.com/apache/superset/pull/23652): Enables GENERIC_CHART_AXES feature flag by default. - [23226](https://github.com/apache/superset/pull/23226): Migrated endpoint `/estimate_query_cost/` to `/api/v1/sqllab/estimate/`. Corresponding permissions are can estimate query cost on SQLLab. Make sure you add/replace the necessary permissions on any custom roles you may have. - [23890](https://github.com/apache/superset/pull/23890): Removes Python 3.8 support. - [24404](https://github.com/apache/superset/pull/24404): FLASK_ENV is getting deprecated, we recommend using SUPERSET_ENV and reviewing your config for ENVIRONMENT_TAG_CONFIG, which enables adding a tag in the navbar to make it more clear which environment your are in. `SUPERSET_ENV=production` and `SUPERSET_ENV=development` are the two supported switches based on the default config. - [19242](https://github.com/apache/superset/pull/19242): Adhoc subqueries are now disabled by default for security reasons. To enable them, set the feature flag `ALLOW_ADHOC_SUBQUERY` to `True`. ### Breaking Changes - [24686](https://github.com/apache/superset/pull/24686): All dataset's custom explore_url are handled as relative URLs on the frontend, behaviour controlled by PREVENT_UNSAFE_DEFAULT_URLS_ON_DATASET. - [24262](https://github.com/apache/superset/pull/24262): Enabled `TALISMAN_ENABLED` flag by default and provided stricter default Content Security Policy - [24415](https://github.com/apache/superset/pull/24415): Removed the obsolete Druid NoSQL REGEX operator. - [24423](https://github.com/apache/superset/pull/24423): Removed deprecated APIs `/superset/slice_json/...`, `/superset/annotation_json/...` - [24400](https://github.com/apache/superset/pull/24400): Removed deprecated APIs `/superset/recent_activity/...`, `/superset/fave_dashboards_by_username/...`, `/superset/fave_dashboards/...`, `/superset/created_dashboards/...`, `/superset/user_slices/`, `/superset/created_slices/...`, `/superset/fave_slices/...`, `/superset/favstar/...`, - [24401](https://github.com/apache/superset/pull/24401): Removes the deprecated `metrics` column (which was blossomed in [20732](https://github.com/apache/superset/pull/20732)) from the `/api/v1/dataset/` API. - [24375](https://github.com/apache/superset/pull/24375): Removed deprecated API `/superset/get_or_create_table/...`, `/superset/sqllab_viz` - [24360](https://github.com/apache/superset/pull/24360): Removed deprecated APIs `/superset/stop_query/...`, `/superset/queries/...`, `/superset/search_queries` - [24353](https://github.com/apache/superset/pull/24353): Removed deprecated APIs `/copy_dash/int:dashboard_id/`, `/save_dash/int:dashboard_id/`, `/add_slices/int:dashboard_id/`. - [24198](https://github.com/apache/superset/pull/24198) The FAB views `User Registrations` and `User's Statistics` have been changed to Admin only. To re-enable them for non-admin users, please add the following perms to your custom role: `menu access on User's Statistics` and `menu access on User Registrations`. - [24354](https://github.com/apache/superset/pull/24354): Removed deprecated APIs `/superset/testconn`, `/superset/validate_sql_json/`, `/superset/schemas_access_for_file_upload`, `/superset/extra_table_metadata` - [24381](https://github.com/apache/superset/pull/24381): Removed deprecated API `/superset/available_domains/` - [24359](https://github.com/apache/superset/pull/24359): Removed deprecated APIs `/superset/estimate_query_cost/..`, `/superset/results/..`, `/superset/sql_json/..`, `/superset/csv/..` - [24345](https://github.com/apache/superset/pull/24345) Converts `ENABLE_BROAD_ACTIVITY_ACCESS` and `MENU_HIDE_USER_INFO` into feature flags and changes the value of `ENABLE_BROAD_ACTIVITY_ACCESS` to `False` as it's more secure. - [24342](https://github.com/apache/superset/pull/24342): Removed deprecated API `/superset/tables///...` - [24335](https://github.com/apache/superset/pull/24335): Removed deprecated API `/superset/filter////` - [24333](https://github.com/apache/superset/pull/24333): Removed deprecated API `/superset/datasources` - [24266](https://github.com/apache/superset/pull/24266) Remove the `ENABLE_ACCESS_REQUEST` config parameter and the associated request/approval workflows. - [24330](https://github.com/apache/superset/pull/24330) Removes `getUiOverrideRegistry` from `ExtensionsRegistry`. - [23933](https://github.com/apache/superset/pull/23933) Removes the deprecated Multiple Line Charts. - [23741](https://github.com/apache/superset/pull/23741) Migrates the TreeMap chart and removes the legacy Treemap code. - [23712](https://github.com/apache/superset/pull/23712) Migrates the Pivot Table v1 chart to v2 and removes v1 code. - [24029](https://github.com/apache/superset/pull/24029) Removes the `user` and `username` arguments for the `QUERY_LOGGER` and `SQL_QUERY_MUTATOR` methods respectively. If the username for the current user is required, the `superset.utils.core.get_username` method should be used. - [24128](https://github.com/apache/superset/pull/24128) The `RLS_BASE_RELATED_FIELD_FILTERS` config parameter has been removed. Now the Tables dropdown will feature the same tables that the user is able to see elsewhere in the application using the standard `DatasourceFilter`, and the Roles dropdown will be filtered using the filter defined in `EXTRA_RELATED_QUERY_FILTERS["role"]`. - [23785](https://github.com/apache/superset/pull/23785) Deprecated the following feature flags: `CLIENT_CACHE`, `DASHBOARD_CACHE`, `DASHBOARD_FILTERS_EXPERIMENTAL`, `DASHBOARD_NATIVE_FILTERS`, `DASHBOARD_NATIVE_FILTERS_SET`, `DISABLE_DATASET_SOURCE_EDIT`, `ENABLE_EXPLORE_JSON_CSRF_PROTECTION`, `REMOVE_SLICE_LEVEL_LABEL_COLORS`. It also removed `DASHBOARD_EDIT_CHART_IN_NEW_TAB` as the feature is supported without the need for a feature flag. - [22801](https://github.com/apache/superset/pull/22801): The Thumbnails feature has been changed to execute as the currently logged in user by default, falling back to the selenium user for anonymous users. To continue always using the selenium user, please add the following to your `superset_config.py`: `THUMBNAILS_EXECUTE_AS = ["selenium"]` - [22799](https://github.com/apache/superset/pull/22799): Alerts & Reports has been changed to execute as the owner of the alert/report by default, giving priority to the last modifier and then the creator if either is contained within the list of owners, otherwise the first owner will be used. To continue using the selenium user, please add the following to your `superset_config.py`: `ALERT_REPORTS_EXECUTE_AS = ["selenium"]` - [23651](https://github.com/apache/superset/pull/23651): Removes UX_BETA feature flag. - [23663](https://github.com/apache/superset/pull/23663): Removes deprecated feature flags `ALLOW_DASHBOARD_DOMAIN_SHARDING`, `DISPLAY_MARKDOWN_HTML`, and `FORCE_DATABASE_CONNECTIONS_SSL`. - [22325](https://github.com/apache/superset/pull/22325): "RLS_FORM_QUERY_REL_FIELDS" is replaced by "RLS_BASE_RELATED_FIELD_FILTERS" feature flag. Its value format stays same. ## 2.1.1 - [24185](https://github.com/apache/superset/pull/24185): `/api/v1/database/test_connection` and `api/v1/database/validate_parameters` permissions changed from `can_read` to `can_write`. Only Admin user's have access. ### Other - [23888](https://github.com/apache/superset/pull/23888): Database Migration for json serialization instead of pickle should upgrade/downgrade correctly when bumping to/from this patch version ## 2.1.0 - [22809](https://github.com/apache/superset/pull/22809): Migrated endpoint `/superset/sql_json` and `/superset/results/` to `/api/v1/sqllab/execute/` and `/api/v1/sqllab/results/` respectively. Corresponding permissions are `can sql_json on Superset` to `can execute on SQLLab`, `can results on Superset` to `can results on SQLLab`. Make sure you add/replace the necessary permissions on any custom roles you may have. - [22931](https://github.com/apache/superset/pull/22931): Migrated endpoint `/superset/get_or_create_table/` to `/api/v1/dataset/get_or_create/`. Corresponding permissions are `can get or create table on Superset` to `can get or create dataset on Dataset`. Make sure you add/replace the necessary permissions on any custom roles you may have. - [22882](https://github.com/apache/superset/pull/22882): Migrated endpoint `/superset/filter////` to `/api/v1/datasource///column//values/`. Corresponding permissions are `can filter on Superset` to `can get column values on Datasource`. Make sure you add/replace the necessary permissions on any custom roles you may have. - [22789](https://github.com/apache/superset/pull/22789): Migrated endpoint `/superset/recent_activity//` to `/api/v1/log/recent_activity//`. Corresponding permissions are `can recent activity on Superset` to `can recent activity on Log`. Make sure you add/replace the necessary permissions on any custom roles you may have. - [22913](https://github.com/apache/superset/pull/22913): Migrated endpoint `/superset/csv` to `/api/v1/sqllab/export/`. Corresponding permissions are `can csv on Superset` to `can export csv on SQLLab`. Make sure you add/replace the necessary permissions on any custom roles you may have. - [22496](https://github.com/apache/superset/pull/22496): Migrated endpoint `/superset/slice_json/` to `/api/v1/chart//data/`. Corresponding permissions are `can slice json on Superset` to `can read on Chart`. Make sure you add/replace the necessary permissions on any custom roles you may have. - [22624](https://github.com/apache/superset/pull/22624): Migrated endpoint `/superset/stop_query/` to `/api/v1/query/stop`. Corresponding permissions are `can stop query on Superset` to `can read on Query`. Make sure you add/replace the necessary permissions on any custom roles you may have. - [22579](https://github.com/apache/superset/pull/22579): Migrated endpoint `/superset/search_queries/` to `/api/v1/query/`. Corresponding permissions are `can search queries on Superset` to `can read on Query`. Make sure you add/replace the necessary permissions on any custom roles you may have. - [22501](https://github.com/apache/superset/pull/22501): Migrated endpoint `/superset/tables///` to `/api/v1/database//tables/`. Corresponding permissions are `can tables on Superset` to `can read on Database`. Make sure you add/replace the necessary permissions on any custom roles you may have. - [22611](https://github.com/apache/superset/pull/22611): Migrated endpoint `/superset/queries/` to `api/v1/query/updated_since`. Corresponding permissions are `can queries on Superset` to `can read on Query`. Make sure you add/replace the necessary permissions on any custom roles you may have. - [23186](https://github.com/apache/superset/pull/23186): Superset will refuse to start if a default `SECRET_KEY` is detected on a non Flask debug setting. - [22022](https://github.com/apache/superset/pull/22022): HTTP API endpoints `/superset/approve` and `/superset/request_access` have been deprecated and their HTTP methods were changed from GET to POST - [20606](https://github.com/apache/superset/pull/20606): When user clicks on chart title or "Edit chart" button in Dashboard page, Explore opens in the same tab. Clicking while holding cmd/ctrl opens Explore in a new tab. To bring back the old behaviour (always opening Explore in a new tab), flip feature flag `DASHBOARD_EDIT_CHART_IN_NEW_TAB` to `True`. - [20799](https://github.com/apache/superset/pull/20799): Presto and Trino engine will now display tracking URL for running queries in SQL Lab. If for some reason you don't want to show the tracking URL (for example, when your data warehouse hasn't enabled access for to Presto or Trino UI), update `TRACKING_URL_TRANSFORMER` in `config.py` to return `None`. - [21002](https://github.com/apache/superset/pull/21002): Support Python 3.10 and bump pandas 1.4 and pyarrow 6. - [21163](https://github.com/apache/superset/pull/21163): The time grain will be decoupled from the time filter column and the time grain control will move below the X-Axis control when `GENERIC_CHART_AXES` feature flags set to `True`. The time grain will be applied on the time column in the column-like controls(x axis, dimensions) instead of the time column in the time section. - [21284](https://github.com/apache/superset/pull/21284): The non-functional `MAX_TABLE_NAMES` config key has been removed. - [21794](https://github.com/apache/superset/pull/21794): Deprecates the undocumented `PRESTO_SPLIT_VIEWS_FROM_TABLES` feature flag. Now for Presto, like other engines, only physical tables are treated as tables. - [22798](https://github.com/apache/superset/pull/22798): To make the welcome page more relevant in production environments, the last tab on the welcome page has been changed from to feature all charts/dashboards the user has access to (previously only examples were shown). To keep current behavior unchanged, add the following to your `superset_config.py`: `WELCOME_PAGE_LAST_TAB = "examples"` - [22328](https://github.com/apache/superset/pull/22328): For deployments that have enabled the "THUMBNAILS" feature flag, the function that calculates dashboard digests has been updated to consider additional properties to more accurately identify changes in the dashboard metadata. This change will invalidate all currently cached dashboard thumbnails. - [21765](https://github.com/apache/superset/pull/21765): For deployments that have enabled the "ALERT_REPORTS" feature flag, Gamma users will no longer have read and write access to Alerts & Reports by default. To give Gamma users the ability to schedule reports from the Dashboard and Explore view like before, create an additional role with "can read on ReportSchedule" and "can write on ReportSchedule" permissions. To further give Gamma users access to the "Alerts & Reports" menu and CRUD view, add "menu access on Manage" and "menu access on Alerts & Report" permissions to the role. ### Potential Downtime - [21284](https://github.com/apache/superset/pull/21284): A change which drops the unused `dbs.allow_multi_schema_metadata_fetch` column via a (potentially locking) DDL operation. ### Other - [23118](https://github.com/apache/superset/pull/23118): Previously the "database access on " permission granted access to all datasets on the underlying database, but they didn't show up on the list views. Now all dashboards, charts and datasets that are accessible via this permission will also show up on their respective list views. ## 2.0.1 - [21895](https://github.com/apache/superset/pull/21895): Markdown components had their security increased by adhering to the same sanitization process enforced by GitHub. This means that some HTML elements found in markdowns are not allowed anymore due to the security risks they impose. If you're deploying Superset in a trusted environment and wish to use some of the blocked elements, then you can use the HTML_SANITIZATION_SCHEMA_EXTENSIONS configuration to extend the default sanitization schema. There's also the option to disable HTML sanitization using the HTML_SANITIZATION configuration but we do not recommend this approach because of the security risks. Given the provided configurations, we don't view the improved sanitization as a breaking change but as a security patch. ## Breaking Changes ## Potential Downtime ## Other ## 2.0.0 - [19046](https://github.com/apache/superset/pull/19046): Enables the drag and drop interface in Explore control panel by default. Flips `ENABLE_EXPLORE_DRAG_AND_DROP` and `ENABLE_DND_WITH_CLICK_UX` feature flags to `True`. - [18936](https://github.com/apache/superset/pull/18936): Removes legacy SIP-15 interim logic/flags—specifically the `SIP_15_ENABLED`, `SIP_15_GRACE_PERIOD_END`, `SIP_15_DEFAULT_TIME_RANGE_ENDPOINTS`, and `SIP_15_TOAST_MESSAGE` flags. Time range endpoints are no longer configurable and strictly adhere to the `[start, end)` paradigm, i.e., inclusive of the start and exclusive of the end. Additionally this change removes the now obsolete `time_range_endpoints` from the form-data and resulting in the cache being busted. - [19570](https://github.com/apache/superset/pull/19570): makes [sqloxide](https://pypi.org/project/sqloxide/) optional so the SIP-68 migration can be run on aarch64. If the migration is taking too long installing sqloxide manually should improve the performance. - [20170](https://github.com/apache/superset/pull/20170): Introduced a new endpoint for getting datasets samples. ### Breaking Changes - [19981](https://github.com/apache/superset/pull/19981): Per [SIP-81](https://github.com/apache/superset/issues/19953) the /explore/form_data api now requires a `datasource_type` in addition to a `datasource_id` for POST and PUT requests - [19770](https://github.com/apache/superset/pull/19770): Per [SIP-11](https://github.com/apache/superset/issues/6032) and [SIP-68](https://github.com/apache/superset/issues/14909), the native NoSQL Druid connector is deprecated and has been removed. Druid is still supported through SQLAlchemy via pydruid. The config keys `DRUID_IS_ACTIVE` and `DRUID_METADATA_LINKS_ENABLED` have also been removed. - [19274](https://github.com/apache/superset/pull/19274): The `PUBLIC_ROLE_LIKE_GAMMA` config key has been removed, set `PUBLIC_ROLE_LIKE = "Gamma"` to have the same functionality. - [19273](https://github.com/apache/superset/pull/19273): The `SUPERSET_CELERY_WORKERS` and `SUPERSET_WORKERS` config keys has been removed. Configure Celery directly using `CELERY_CONFIG` on Superset. - [19231](https://github.com/apache/superset/pull/19231): The `ENABLE_REACT_CRUD_VIEWS` feature flag has been removed (permanently enabled). Any deployments which had set this flag to false will need to verify that the React views support their use case. - [19230](https://github.com/apache/superset/pull/19230): The `ROW_LEVEL_SECURITY` feature flag has been removed (permanently enabled). Any deployments which had set this flag to false will need to verify that the presence of the Row Level Security feature does not interfere with their use case. - [19168](https://github.com/apache/superset/pull/19168): Celery upgrade to 5.X resulted in breaking changes to its command line invocation. html#step-1-adjust-your-command-line-invocation) instructions for adjustments. Also consider migrating you Celery config per [here](https://docs.celeryq.dev/en/stable/userguide/configuration.html#conf-old-settings-map). - [19142](https://github.com/apache/superset/pull/19142): The `VERSIONED_EXPORT` config key is now `True` by default. - [19113](https://github.com/apache/superset/pull/19113): The `ENABLE_JAVASCRIPT_CONTROLS` config key has moved from an app config to a feature flag. Any deployments who overrode this setting will now need to override the feature flag from here onward. - [19107](https://github.com/apache/superset/pull/19107): The `SQLLAB_BACKEND_PERSISTENCE` feature flag is now `True` by default, which enables persisting SQL Lab tabs in the backend instead of the browser's `localStorage`. - [19083](https://github.com/apache/superset/pull/19083): Updates the mutator function in the config file to take a SQL argument and a list of kwargs. Any `SQL_QUERY_MUTATOR` config function overrides will need to be updated to match the new set of params. It is advised regardless of the dictionary args that you list in your function arguments, to keep `**kwargs` as the last argument to allow for any new kwargs to be passed in. - [19049](https://github.com/apache/superset/pull/19049): The `APP_ICON_WIDTH` config key has been removed. Superset should now be able to handle different logo sizes without having to explicitly set an `APP_ICON_WIDTH`. This might affect the size of existing custom logos as the UI will now resize them according to the specified space of maximum 148px and not according to the value of `APP_ICON_WIDTH`. - [19017](https://github.com/apache/superset/pull/19017): Removes Python 3.7 support. - [18970](https://github.com/apache/superset/pull/18970): The `DISABLE_LEGACY_DATASOURCE_EDITOR` feature flag is now `True` by default which disables the legacy datasource editor from being shown in the client. ## 1.5.3 ### Other - [22022](https://github.com/apache/superset/pull/22022): HTTP API endpoints `/superset/approve` and `/superset/request_access` have been deprecated and their HTTP methods were changed from GET to POST - [21895](https://github.com/apache/superset/pull/21895): Markdown components had their security increased by adhering to the same sanitization process enforced by GitHub. This means that some HTML elements found in markdowns are not allowed anymore due to the security risks they impose. If you're deploying Superset in a trusted environment and wish to use some of the blocked elements, then you can use the HTML_SANITIZATION_SCHEMA_EXTENSIONS configuration to extend the default sanitization schema. There's also the option to disable HTML sanitization using the HTML_SANITIZATION configuration but we do not recommend this approach because of the security risks. Given the provided configurations, we don't view the improved sanitization as a breaking change but as a security patch. ## 1.5.2 ### Other - [19570](https://github.com/apache/superset/pull/19570): makes [sqloxide](https://pypi.org/project/sqloxide/) optional so the SIP-68 migration can be run on aarch64. If the migration is taking too long installing sqloxide manually should improve the performance. ## 1.5.0 ### Breaking Changes - [18976](https://github.com/apache/superset/pull/18976): When running the app in debug mode, the app will default to use `SimpleCache` for `FILTER_STATE_CACHE_CONFIG` and `EXPLORE_FORM_DATA_CACHE_CONFIG`. When running in non-debug mode, a cache backend will need to be defined, otherwise the application will fail to start. For installations using Redis or other caching backends, it is recommended to use the same backend for both cache configs. - [17881](https://github.com/apache/superset/pull/17881): Previously simple adhoc filter values on string columns were stripped of enclosing single and double quotes. To fully support literal quotes in filters, both single and double quotes will no longer be removed from filter values. - [17556](https://github.com/apache/superset/pull/17556): Bumps `mysqlclient` from v1 to v2. - [17539](https://github.com/apache/superset/pull/17539): All Superset CLI commands, e.g. `init`, `load_examples`, etc. require setting the `FLASK_APP` environment variable (which is set by default when `.flaskenv` is loaded). - [15254](https://github.com/apache/superset/pull/15254): The `QUERY_COST_FORMATTERS_BY_ENGINE`, `SQL_VALIDATORS_BY_ENGINE` and `SCHEDULED_QUERIES` feature flags are now defined as config keys given that feature flags are reserved for boolean only values. ### Potential Downtime - [16756](https://github.com/apache/incubator-superset/pull/16756): a change which renames the `dbs.allow_csv_upload` column to `dbs.allow_file_upload` via a (potentially locking) DDL operation. - [17539](https://github.com/apache/superset/pull/17539): all Superset CLI commands (init, load_examples and etc) require setting the FLASK_APP environment variable (which is set by default when .flaskenv is loaded) - [17360](https://github.com/apache/superset/pull/17360): changes the column type from `VARCHAR(32)` to `TEXT` in table `table_columns`, potentially requiring a table lock on MySQL dbs or taking some time to complete on large deployments. - [17543](https://github.com/apache/superset/pull/17543): introduces new models from SIP-68. The database migration migrates the old models (`SqlaTable`, `TableColumn`, `SqlMetric`) to the new models (`Column`, `Table`, `Dataset`), and the PR introduces logic to keep the old models in sync with the new ones until they are fully removed. The migration might take considerable time depending on the number of datasets. ### Deprecations - [18960](https://github.com/apache/superset/pull/18960): Persisting URL params in chart metadata is no longer supported. To set a default value for URL params in Jinja code, use the optional second argument: `url_param("my-param", "my-default-value")`. ### Other - [17589](https://github.com/apache/superset/pull/17589): It is now possible to limit access to users' recent activity data by setting the `ENABLE_BROAD_ACTIVITY_ACCESS` config flag to false, or customizing the `raise_for_user_activity_access` method in the security manager. - [17536](https://github.com/apache/superset/pull/17536): introduced a key-value endpoint to store dashboard filter state. This endpoint is backed by Flask-Caching and the default configuration assumes that the values will be stored in the file system. If you are already using another cache backend like Redis or Memcached, you'll probably want to change this setting in `superset_config.py`. The key is `FILTER_STATE_CACHE_CONFIG` and the available settings can be found in Flask-Caching [docs](https://flask-caching.readthedocs.io/en/latest/). - [17882](https://github.com/apache/superset/pull/17882): introduced a key-value endpoint to store Explore form data. This endpoint is backed by Flask-Caching and the default configuration assumes that the values will be stored in the file system. If you are already using another cache backend like Redis or Memcached, you'll probably want to change this setting in `superset_config.py`. The key is `EXPLORE_FORM_DATA_CACHE_CONFIG` and the available settings can be found in Flask-Caching [docs](https://flask-caching.readthedocs.io/en/latest/). ## 1.4.1 ### Breaking Changes - [17984](https://github.com/apache/superset/pull/17984): Default Flask SECRET_KEY has changed for security reasons. You should always override with your own secret. Set `PREVIOUS_SECRET_KEY` (ex: PREVIOUS_SECRET_KEY = "\2\1thisismyscretkey\1\2\\e\\y\\y\\h") with your previous key and use `superset re-encrypt-secrets` to rotate you current secrets ### Potential Downtime ### Deprecations ### Other ## 1.4.0 ### Breaking Changes - [16660](https://github.com/apache/superset/pull/16660): The `columns` Jinja parameter has been renamed `table_columns` to make the `columns` query object parameter available in the Jinja context. - [16711](https://github.com/apache/superset/pull/16711): The `url_param` Jinja function will now by default escape the result. For instance, the value `O'Brien` will now be changed to `O''Brien`. To disable this behavior, call `url_param` with `escape_result` set to `False`: `url_param("my_key", "my default", escape_result=False)`. ### Potential Downtime ### Deprecations ### Other - [16809](https://github.com/apache/superset/pull/16809): When building the superset frontend assets manually, you should now use Node 16 (previously Node 14 was required/recommended). Node 14 will most likely still work for at least some time, but is no longer actively tested for on CI. ## 1.3.0 ### Breaking Changes - [15909](https://github.com/apache/superset/pull/15909): a change which drops a uniqueness criterion (which may or may not have existed) to the tables table. This constraint was obsolete as it is handled by the ORM due to differences in how MySQL, PostgreSQL, etc. handle uniqueness for NULL values. ### Potential Downtime - [14234](https://github.com/apache/superset/pull/14234): Adds the `limiting_factor` column to the `query` table. Give the migration includes a DDL operation on a heavily trafficked table, potential service downtime may be required. - [16454](https://github.com/apache/superset/pull/16454): Adds the `extra` column to the `table_columns` table. Users using MySQL will either need to schedule downtime or use the percona toolkit (or similar) to perform the migration. ## 1.2.0 ### Deprecations - [13440](https://github.com/apache/superset/pull/13440): Dashboard/Charts reports and old Alerts is deprecated. The following config keys are deprecated: - ENABLE_ALERTS - SCHEDULED_EMAIL_DEBUG_MODE - EMAIL_REPORTS_CRON_RESOLUTION - EMAIL_ASYNC_TIME_LIMIT_SEC - EMAIL_REPORT_BCC_ADDRESS - EMAIL_REPORTS_USER ### Other - [13772](https://github.com/apache/superset/pull/13772): Row level security (RLS) is now enabled by default. To activate the feature, please run `superset init` to expose the RLS menus to Admin users. - [13980](https://github.com/apache/superset/pull/13980): Data health checks no longer use the metadata database as an interim cache. Though non-breaking, deployments which implement complex logic should likely memoize the callback function. Refer to documentation in the config.py file for more detail. - [14255](https://github.com/apache/superset/pull/14255): The default `CSV_TO_HIVE_UPLOAD_DIRECTORY_FUNC` callable logic has been updated to leverage the specified database and schema to ensure the upload S3 key prefix is unique. Previously tables generated via upload from CSV with the same name but differ schema and/or cluster would use the same S3 key prefix. Note this change does not impact previously imported tables. ## 1.1.0 ### Breaking Changes - This is the first release since we adopted semantic versioning ([SIP-57](https://github.com/apache/superset/issues/12566)). There are no breaking changes in 1.1.0 since this is a minor release. ### Potential Downtime - [13111](https://github.com/apache/superset/pull/13111) has a database migration that replaces `directed_force` charts with newer `graph_chart` charts based on Apache ECharts. - [13216](https://github.com/apache/superset/pull/13216) adds a UUID column to models that are missing it. The original migration script that added the column would incorrectly complete when the column couldn't be added, resulting in a broken schema. The script is optimized for MySQL and Postgres, so depending on the database and the number of objects this migration might take considerable time. - [12960](https://github.com/apache/superset/pull/12960) populates the granularity parameter in existing charts. Depending on the number of charts without a `granularity` or `granularity_sqla param` this might take considerable time. - [13052](https://github.com/apache/superset/pull/13052) updates the label in existing pie charts, setting `label_type` from `pie_label_type`. Depending on the number of pie charts this might take considerable time. - [12680](https://github.com/apache/superset/pull/12680) creates a new table, `dashboard_roles`, for role based dashboard level access. - [12552](https://github.com/apache/superset/pull/12552) updates charts that have the time range defined using "until" and "since". Depending on the number of charts this might take considerable time. ### Deprecations - [12552](https://github.com/apache/superset/pull/12552) removes the use of unclear time offsets, eg, "30 days". An error message is displayed if the user doesn't specify "ago" or "later", instructing the user of the correct format. - [12627](https://github.com/apache/superset/pull/12627) deprecates the legacy alerts module. ### Other - [shillelagh](https://github.com/betodealmeida/shillelagh/) is now the recommended module to connect Superset to Google Spreadsheets since it's more robust and has extensive test coverage. You should uninstall the `gsheetsdb` module and install the `shillelagh` module in its place. Shillelagh is a drop-in replacement, so no modifications are needed to be done on existing queries, datasets, or charts. ## 1.0.0 ### Breaking Changes - [11509](https://github.com/apache/superset/pull/12491): Dataset metadata updates check user ownership, only owners or an Admin are allowed. - Security simplification (SIP-19), the following permission domains were simplified: - [12072](https://github.com/apache/superset/pull/12072): `Query` with `can_read`, `can_write` - [12036](https://github.com/apache/superset/pull/12036): `Database` with `can_read`, `can_write`. - [12012](https://github.com/apache/superset/pull/12036): `Dashboard` with `can_read`, `can_write`. - [12061](https://github.com/apache/superset/pull/12061): `Log` with `can_read`, `can_write`. - [12000](https://github.com/apache/superset/pull/12000): `Dataset` with `can_read`, `can_write`. - [12014](https://github.com/apache/superset/pull/12014): `Annotation` with `can_read`, `can_write`. - [11981](https://github.com/apache/superset/pull/11981): `Chart` with `can_read`, `can_write`. - [11853](https://github.com/apache/superset/pull/11853): `ReportSchedule` with `can_read`, `can_write`. - [11856](https://github.com/apache/superset/pull/11856): `CssTemplate` with `can_read`, `can_write`. - [11764](https://github.com/apache/superset/pull/11764): `SavedQuery` with `can_read`, `can_write`. Old permissions will be automatically migrated to these new permissions and applied to all existing security Roles. - [11499](https://github.com/apache/superset/pull/11499): Breaking change: `STORE_CACHE_KEYS_IN_METADATA_DB` config flag added (default=`False`) to write `CacheKey` records to the metadata DB. `CacheKey` recording was enabled by default previously. - [11704](https://github.com/apache/superset/pull/11704) Breaking change: Jinja templating for SQL queries has been updated, removing default modules such as `datetime` and `random` and enforcing static template values. To restore or extend functionality, use `JINJA_CONTEXT_ADDONS` and `CUSTOM_TEMPLATE_PROCESSORS` in `superset_config.py`. - [11509](https://github.com/apache/superset/pull/11509): Config value `TABLE_NAMES_CACHE_CONFIG` has been renamed to `DATA_CACHE_CONFIG`, which will now also hold query results cache from connected datasources (previously held in `CACHE_CONFIG`), in addition to the table names. If you will set `DATA_CACHE_CONFIG` to a new cache backend different than your previous `CACHE_CONFIG`, plan for additional cache warmup to avoid degrading charting performance for the end users. - [11575](https://github.com/apache/superset/pull/11575) The Row Level Security (RLS) config flag has been moved to a feature flag. To migrate, add `ROW_LEVEL_SECURITY: True` to the `FEATURE_FLAGS` dict in `superset_config.py`. - [11259](https://github.com/apache/superset/pull/11259): config flag ENABLE_REACT_CRUD_VIEWS has been set to `True` by default, set to `False` if you prefer to the vintage look and feel. However, we may discontinue support on the vintage list view in the future. - [11244](https://github.com/apache/superset/pull/11244): The `REDUCE_DASHBOARD_BOOTSTRAP_PAYLOAD` feature flag has been removed after being set to True for multiple months. - [11172](https://github.com/apache/superset/pull/11172): Turning off language selectors by default as i18n is incomplete in most languages and requires more work. You can easily turn on the languages you want to expose in your environment in superset_config.py - [11172](https://github.com/apache/superset/pull/11172): Breaking change: SQL templating is turned off by default. To turn it on set `ENABLE_TEMPLATE_PROCESSING` to True on `FEATURE_FLAGS` ### Potential Downtime - [11920](https://github.com/apache/superset/pull/11920): Undoes the DB migration from [11714](https://github.com/apache/superset/pull/11714) to prevent adding new columns to the logs table. Deploying a sha between these two PRs may result in locking your DB. - [11714](https://github.com/apache/superset/pull/11714): Logs significantly more analytics events (roughly double?), and when using DBEventLogger (default) could result in stressing the metadata database more. - [11098](https://github.com/apache/superset/pull/11098): includes a database migration that adds a `uuid` column to most models, and updates `Dashboard.position_json` to include chart UUIDs. Depending on number of objects, the migration may take up to 5 minutes, requiring planning for downtime. ### Deprecations - [11155](https://github.com/apache/superset/pull/11155): The `FAB_UPDATE_PERMS` config parameter is no longer required as the Superset application correctly informs FAB under which context permissions should be updated. ## 0.38.0 - [10887](https://github.com/apache/superset/pull/10887): Breaking change: The custom cache backend changed in order to support the Flask-Caching factory method approach and thus must be registered as a custom type. See [here](https://flask-caching.readthedocs.io/en/latest/#custom-cache-backends) for specifics. - [10674](https://github.com/apache/superset/pull/10674): Breaking change: PUBLIC_ROLE_LIKE_GAMMA was removed is favour of the new PUBLIC_ROLE_LIKE so it can be set to whatever role you want. - [10590](https://github.com/apache/superset/pull/10590): Breaking change: this PR will convert iframe chart into dashboard markdown component, and remove all `iframe`, `separator`, and `markup` slices (and support) from Superset. If you have important data in those slices, please backup manually. - [10562](https://github.com/apache/superset/pull/10562): EMAIL_REPORTS_WEBDRIVER is deprecated use WEBDRIVER_TYPE instead. - [10567](https://github.com/apache/superset/pull/10567): Default WEBDRIVER_OPTION_ARGS are Chrome-specific. If you're using FF, should be `--headless` only - [10241](https://github.com/apache/superset/pull/10241): change on Alpha role, users started to have access to "Annotation Layers", "Css Templates" and "Import Dashboards". - [10324](https://github.com/apache/superset/pull/10324): Facebook Prophet has been introduced as an optional dependency to add support for timeseries forecasting in the chart data API. To enable this feature, install Superset with the optional dependency `prophet` or directly `pip install fbprophet`. - [10320](https://github.com/apache/superset/pull/10320): References to blacklist/whitelist language have been replaced with more appropriate alternatives. All configs referencing containing `WHITE`/`BLACK` have been replaced with `ALLOW`/`DENY`. Affected config variables that need to be updated: `TIME_GRAIN_BLACKLIST`, `VIZ_TYPE_BLACKLIST`, `DRUID_DATA_SOURCE_BLACKLIST`. ## 0.37.1 - [10794](https://github.com/apache/superset/pull/10794): Breaking change: `uuid` python package is not supported on Jinja2 anymore, only uuid functions are exposed eg: `uuid1`, `uuid3`, `uuid4`, `uuid5`. ## 0.37.0 - [9964](https://github.com/apache/superset/pull/9964): Breaking change on Flask-AppBuilder 3. If you're using OAuth, find out what needs to be changed [here](https://github.com/dpgaspar/Flask-AppBuilder/blob/master/README.rst#change-log). - [10233](https://github.com/apache/superset/pull/10233): a change which deprecates the `ENABLE_FLASK_COMPRESS` config option in favor of the Flask-Compress `COMPRESS_REGISTER` config option which serves the same purpose. - [10222](https://github.com/apache/superset/pull/10222): a change which changes how payloads are cached. Previous cached objects cannot be decoded and thus will be reloaded from source. - [10130](https://github.com/apache/superset/pull/10130): a change which deprecates the `dbs.perm` column in favor of SQLAlchemy [hybrid attributes](https://docs.sqlalchemy.org/en/13/orm/extensions/hybrid.html). - [10034](https://github.com/apache/superset/pull/10034): a change which deprecates the public security manager `assert_datasource_permission`, `assert_query_context_permission`, `assert_viz_permission`, and `rejected_tables` methods with the `raise_for_access` method which also handles assertion logic for SQL tables. - [10031](https://github.com/apache/superset/pull/10030): a change which renames the following public security manager methods: `can_access_datasource` to `can_access_table`, `all_datasource_access` to `can_access_all_datasources`, `all_database_access` to `can_access_all_databases`, `database_access` to `can_access_database`, `schema_access` to `can_access_schema`, and `datasource_access` to `can_access_datasource`. Regrettably it is not viable to provide aliases for the deprecated methods as this would result in a name clash. Finally the `can_access_table` (previously `can_access_database`) method signature has changed, i.e., the optional `schema` argument no longer exists. - [10030](https://github.com/apache/superset/pull/10030): a change which renames the public security manager `schemas_accessible_by_user` method to `get_schemas_accessible_by_user`. - [9786](https://github.com/apache/superset/pull/9786): with the upgrade of `werkzeug` from version `0.16.0` to `1.0.1`, the `werkzeug.contrib.cache` module has been moved to a standalone package [cachelib](https://pypi.org/project/cachelib/). For example, to import the `RedisCache` class, please use the following import: `from cachelib.redis import RedisCache`. - [9794](https://github.com/apache/superset/pull/9794): introduces `create view as` functionality in the sqllab. This change will require the `query` table migration and potential service downtime as that table has quite some traffic. - [9572](https://github.com/apache/superset/pull/9572): a change which by default means that the Jinja `current_user_id`, `current_username`, and `url_param` context calls no longer need to be wrapped via `cache_key_wrapper` in order to be included in the cache key. The `cache_key_wrapper` function should only be required for Jinja add-ons. ## 0.36.0 - [8867](https://github.com/apache/superset/pull/8867): a change which adds the `tmp_schema_name` column to the `query` table which requires locking the table. Given the `query` table is heavily used performance may be degraded during the migration. Scheduled downtime may be advised. - [9238](https://github.com/apache/superset/pull/9238): the config option `TIME_GRAIN_FUNCTIONS` has been renamed to `TIME_GRAIN_EXPRESSIONS` to better reflect the content of the dictionary. - [9218](https://github.com/apache/superset/pull/9218): SQLite connections have been disabled by default for analytics databases. You can optionally enable SQLite by setting `PREVENT_UNSAFE_DB_CONNECTIONS` to `False`. It is not recommended to change this setting, as arbitrary SQLite connections can lead to security vulnerabilities. - [9133](https://github.com/apache/superset/pull/9133): Security list of permissions and list views has been disable by default. You can optionally enable them back again by setting the following config keys: `FAB_ADD_SECURITY_PERMISSION_VIEW`, `FAB_ADD_SECURITY_VIEW_MENU_VIEW`, `FAB_ADD_SECURITY_PERMISSION_VIEWS_VIEW` to `True`. - [9173](https://github.com/apache/superset/pull/9173): Changes the encoding of the query source from an int to an enum. - [9120](https://github.com/apache/superset/pull/9120): Changes the default behavior of ad-hoc sharing of queries in SQLLab to one that links to the saved query rather than one that copies the query data into the KVStore model and links to the record there. This is a security-related change that makes SQLLab query sharing respect the existing role-based access controls. Should you wish to retain the existing behavior, set two feature flags: `"KV_STORE": True` will re-enable the `/kv/` and `/kv/store/` endpoints, and `"SHARE_QUERIES_VIA_KV_STORE": True` will tell the front-end to utilize them for query sharing. - [9109](https://github.com/apache/superset/pull/9109): Expire `filter_immune_slices` and `filter_immune_filter_fields` to favor dashboard scoped filter metadata `filter_scopes`. - [9046](https://github.com/apache/superset/pull/9046): Replaces `can_only_access_owned_queries` by `all_query_access` favoring a white list approach. Since a new permission is introduced use `superset init` to create and associate it by default to the `Admin` role. Note that, by default, all non `Admin` users will not be able to access queries they do not own. - [8901](https://github.com/apache/superset/pull/8901): The datasource's update timestamp has been added to the query object's cache key to ensure updates to datasources are always reflected in associated query results. As a consequence all previously cached results will be invalidated when updating to the next version. - [8699](https://github.com/apache/superset/pull/8699): A `row_level_security_filters` table has been added, which is many-to-many with `tables` and `ab_roles`. The applicable filters are added to the sqla query, and the RLS ids are added to the query cache keys. If RLS is enabled in config.py (`ENABLE_ROW_LEVEL_SECURITY = True`; by default, it is disabled), they can be accessed through the `Security` menu, or when editing a table. - [8732](https://github.com/apache/superset/pull/8732): Swagger user interface is now enabled by default. A new permission `show on SwaggerView` is created by `superset init` and given to the `Admin` Role. To disable the UI, set `FAB_API_SWAGGER_UI = False` on config. - [8721](https://github.com/apache/superset/pull/8721): When using the cache warmup Celery task you should now specify the `SUPERSET_WEBSERVER_PROTOCOL` variable in your configuration (probably either "http" or "https"). This defaults to "http". - [8512](https://github.com/apache/superset/pull/8512): `DRUID_IS_ACTIVE` now defaults to False. To enable Druid-API-based functionality, override the `DRUID_IS_ACTIVE` configuration variable by setting it to `True` for your deployment. - [8450](https://github.com/apache/superset/pull/8450): The time range picker now uses UTC for the tooltips and default placeholder timestamps (sans timezone). - [8418](https://github.com/apache/superset/pull/8418): FLASK_APP / Worker App have changed. FLASK_APP should be updated to `superset.app:create_app()` and Celery Workers should be started with `--app=superset.tasks.celery_app:app` - [9017](https://github.com/apache/superset/pull/9017): `SIP_15_ENABLED` now defaults to True which ensures that for all new SQL charts the time filter will behave like [start, end). Existing deployments should either disable this feature to keep the status quo or inform their users of this change prior to enabling the flag. The `SIP_15_GRACE_PERIOD_END` option provides a mechanism for specifying how long chart owners have to migrate their charts (the default is indefinite). ## 0.35.0 - [8370](https://github.com/apache/superset/pull/8370): Deprecates the `HTTP_HEADERS` variable in favor of `DEFAULT_HTTP_HEADERS` and `OVERRIDE_HTTP_HEADERS`. To retain the same behavior you should use `OVERRIDE_HTTP_HEADERS` instead of `HTTP_HEADERS`. `HTTP_HEADERS` will still work but may be removed in a future update. - We're deprecating the concept of "restricted metric", this feature was not fully working anyhow. - [8117](https://github.com/apache/superset/pull/8117): If you are using `ENABLE_PROXY_FIX = True`, review the newly-introduced variable, `PROXY_FIX_CONFIG`, which changes the proxy behavior in accordance with Werkzeug. - [8069](https://github.com/apache/superset/pull/8069): introduces [MessagePack](https://github.com/msgpack/msgpack-python) and [PyArrow](https://arrow.apache.org/docs/python/) for async query results backend serialization. To disable set `RESULTS_BACKEND_USE_MSGPACK = False` in your configuration. - [8371](https://github.com/apache/superset/pull/8371): makes `tables.table_name`, `dbs.database_name`, `datasources.cluster_name`, and `clusters.cluster_name` non-nullable. Depending on the integrity of the data, manual intervention may be required. ## 0.34.0 - [7848](https://github.com/apache/superset/pull/7848): If you are running redis with celery, celery bump to 4.3.0 requires redis-py upgrade to 3.2.0 or later. - [7667](https://github.com/apache/superset/pull/7667): a change to make all Unix timestamp (which by definition are in UTC) comparisons refer to a timestamp in UTC as opposed to local time. - [7653](https://github.com/apache/superset/pull/7653): a change which deprecates the table_columns.database_expression column. Expressions should be handled by the DB engine spec conversion, Python date format, or custom column expression/type. - The repo no longer contains translation binaries (`.mo`) files. If you want translations in your build, you now have to run the command `babel-compile --target superset/translations` as part of your builds - [5451](https://github.com/apache/superset/pull/5451): a change which adds missing non-nullable fields to the `datasources` table. Depending on the integrity of the data, manual intervention may be required. - [5452](https://github.com/apache/superset/pull/5452): a change which adds missing non-nullable fields and uniqueness constraints (which may be case insensitive depending on your database configuration) to the `columns`and `table_columns` tables. Depending on the integrity of the data, manual intervention may be required. - `fabmanager` command line is deprecated since Flask-AppBuilder 2.0.0, use the new `flask fab ` integrated with _Flask cli_. - `SUPERSET_UPDATE_PERMS` environment variable was replaced by `FAB_UPDATE_PERMS` config boolean key. To disable automatic creation of permissions set `FAB_UPDATE_PERMS = False` on config. - [5453](https://github.com/apache/superset/pull/5453): a change which adds missing non-nullable fields and uniqueness constraints (which may be case insensitive depending on your database configuration) to the metrics and sql_metrics tables. Depending on the integrity of the data, manual intervention may be required. - [7616](https://github.com/apache/superset/pull/7616): this bug fix changes time_compare deltas to correctly evaluate to the number of days prior instead of number of days in the future. It will change the data for advanced analytics time_compare so `1 year` from 5/1/2019 will be calculated as 365 days instead of 366 days. ## Superset 0.32.0 - `npm run backend-sync` is deprecated and no longer needed, will fail if called - [5445](https://github.com/apache/superset/pull/5445): a change which prevents encoding of empty string from form data in the database. This involves a non-schema changing migration which does potentially impact a large number of records. Scheduled downtime may be advised. ## Superset 0.31.0 - If you use `Hive` or `Presto`, we've moved some dependencies that were in the main package as optional now. To get these packages, run `pip install superset[presto]` and/or `pip install superset[hive]` as required. - Similarly, if you use Celery's `flower`, `gsheetsdb`, `thrift` or `thrift-sasl`, those dependencies have now been made optional in our package, meaning you may have to install them in your environment post 0.31.0 - boto3 / botocore was removed from the dependency list. If you use s3 as a place to store your SQL Lab result set or Hive uploads, you may have to rely on an alternate requirements.txt file to install those dependencies. - From 0.31.0 onwards, we recommend not using the npm package `yarn` in favor of good old `npm install`. While yarn should still work just fine, you should probably align to guarantee builds similar to the ones we use in testing and across the community in general. ## Superset 0.30.0 - 0.30.0 includes a db_migration that removes allow_run_sync. This may require downtime because during the migration if the db is migrated first, superset will get 500 errors when the code can't find the field (until the deploy finishes). ## Superset 0.29.0 - India was removed from the "Country Map" visualization as the geojson file included in the package was very large - [5933](https://github.com/apache/superset/pull/5933)/[6078](https://github.com/apache/superset/pull/6078): changes which add schema and table metadata cache timeout logic at the database level. If left undefined caching of metadata is disabled. ## Superset 0.28.0 - Support for Python 2 is deprecated, we only support >=3.6 from `0.28.0` onwards - Superset 0.28 deprecates the previous dashboard layout. While 0.27 offered a migration workflow to users and allowed them to validate and publish their migrated dashboards individually, 0.28 forces the migration of all dashboards through an automated db migration script. We do recommend that you take a backup prior to this migration. - Superset 0.28 deprecates the `median` cluster label aggregator for mapbox visualizations. This particular aggregation is not supported on mapbox visualizations going forward. - Superset 0.28 upgrades `flask-login` to `>=0.3`, which includes a backwards-incompatible change: `g.user.is_authenticated`, `g.user.is_anonymous`, and `g.user.is_active` are now properties instead of methods. ## Superset 0.27.0 - Superset 0.27 start to use nested layout for dashboard builder, which is not backward-compatible with earlier dashboard grid data. We provide migration script to automatically convert dashboard grid to nested layout data. To be safe, please take a database backup prior to this upgrade. It's the only way people could go back to a previous state. ## Superset 0.26.0 - Superset 0.26.0 deprecates the `superset worker` CLI, which is a simple wrapper around the `celery worker` command, forcing you into crafting your own native `celery worker` command. Your command should look something like `celery worker --app=superset.sql_lab:celery_app --pool=gevent -Ofair` ## Superset 0.25.0 Superset 0.25.0 contains a backwards incompatible changes. If you run a production system you should schedule downtime for this upgrade. The PRs below have more information around the breaking changes: - [9825](https://github.com/apache/superset/pull/9825): Support for Excel sheet upload added. To enable support, install Superset with the optional dependency `excel` - [4587](https://github.com/apache/superset/pull/4587) : a backward incompatible database migration that requires downtime. Once the db migration succeeds, the web server needs to be restarted with the new version. The previous version will fail - [4565](https://github.com/apache/superset/pull/4565) : we've changed the security model a bit where in the past you would have to define your authentication scheme by inheriting from Flask App Builder's `from flask_appbuilder.security.sqla.manager import SecurityManager`, you now have to derive Superset's own derivative `superset.security.SupersetSecurityManager`. This can provide you with more hooks to define your own logic and/or defer permissions to another system as needed. For all implementation, you simply have to import and derive `SupersetSecurityManager` in place of the `SecurityManager` - [4835](https://github.com/apache/superset/pull/4835) : our `setup.py` now only pins versions where required, giving you more latitude in using versions of libraries as needed. We do now provide a `requirements.txt` with pinned versions if you want to run the suggested versions that `Superset` builds and runs tests against. Simply `pip install -r requirements.txt` in your build pipeline, likely prior to `pip install superset==0.25.0`