diff --git a/.github/workflows/bump-python-package.yml b/.github/workflows/bump-python-package.yml index 50cc0d2a805c..1ba8bba2ef1b 100644 --- a/.github/workflows/bump-python-package.yml +++ b/.github/workflows/bump-python-package.yml @@ -48,7 +48,7 @@ jobs: python-version: "3.11" - name: Install uv - uses: astral-sh/setup-uv@ae62891fec2bb8e7d6c99fc78c9fec3a63790f8d # v10.0.0 + uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 with: python-version: "3.11" enable-cache: true diff --git a/.github/workflows/superset-e2e.yml b/.github/workflows/superset-e2e.yml index cd7198374da8..7c00ce36c3a4 100644 --- a/.github/workflows/superset-e2e.yml +++ b/.github/workflows/superset-e2e.yml @@ -78,14 +78,14 @@ jobs: USE_DASHBOARD: ${{ github.event.inputs.use_dashboard == 'true' || 'false' }} services: postgres: - image: postgres:17-alpine + image: ghcr.io/apache/superset/ci/postgres:17-alpine env: POSTGRES_USER: superset POSTGRES_PASSWORD: superset ports: - 15432:5432 redis: - image: redis:7-alpine + image: ghcr.io/apache/superset/ci/redis:7-alpine ports: - 16379:6379 steps: @@ -186,14 +186,14 @@ jobs: GITHUB_TOKEN: ${{ github.token }} services: postgres: - image: postgres:17-alpine + image: ghcr.io/apache/superset/ci/postgres:17-alpine env: POSTGRES_USER: superset POSTGRES_PASSWORD: superset ports: - 15432:5432 redis: - image: redis:7-alpine + image: ghcr.io/apache/superset/ci/redis:7-alpine ports: - 16379:6379 steps: diff --git a/.github/workflows/superset-python-integrationtest.yml b/.github/workflows/superset-python-integrationtest.yml index 62239f6425aa..ee898202b50b 100644 --- a/.github/workflows/superset-python-integrationtest.yml +++ b/.github/workflows/superset-python-integrationtest.yml @@ -53,9 +53,7 @@ jobs: mysql+mysqldb://superset:superset@127.0.0.1:13306/superset?charset=utf8mb4&binary_prefix=true services: mysql: - image: mysql:8.0 - # Authenticated pulls use our higher Docker Hub rate limit. Empty on - # fork PRs (secrets unavailable) -> runner falls back to anonymous. + image: ghcr.io/apache/superset/ci/mysql:8.0 env: MYSQL_ROOT_PASSWORD: root ports: @@ -66,7 +64,7 @@ jobs: --health-timeout=5s --health-retries=5 redis: - image: redis:7-alpine + image: ghcr.io/apache/superset/ci/redis:7-alpine options: --entrypoint redis-server ports: - 16379:6379 @@ -143,7 +141,7 @@ jobs: SUPERSET__SQLALCHEMY_DATABASE_URI: postgresql+psycopg2://superset:superset@127.0.0.1:15432/superset services: postgres: - image: postgres:17-alpine + image: ghcr.io/apache/superset/ci/postgres:17-alpine env: POSTGRES_USER: superset POSTGRES_PASSWORD: superset @@ -152,7 +150,7 @@ jobs: # GitHub action runner's default installations - 15432:5432 redis: - image: redis:7-alpine + image: ghcr.io/apache/superset/ci/redis:7-alpine ports: - 16379:6379 steps: @@ -202,7 +200,7 @@ jobs: sqlite:///${{ github.workspace }}/.temp/examples.db?check_same_thread=true services: redis: - image: redis:7-alpine + image: ghcr.io/apache/superset/ci/redis:7-alpine ports: - 16379:6379 steps: diff --git a/.github/workflows/superset-python-presto-hive.yml b/.github/workflows/superset-python-presto-hive.yml index 363dea34e027..373970d2d2ba 100644 --- a/.github/workflows/superset-python-presto-hive.yml +++ b/.github/workflows/superset-python-presto-hive.yml @@ -52,7 +52,7 @@ jobs: SUPERSET__SQLALCHEMY_EXAMPLES_URI: presto://localhost:15433/memory/default services: postgres: - image: postgres:17-alpine + image: ghcr.io/apache/superset/ci/postgres:17-alpine env: POSTGRES_USER: superset POSTGRES_PASSWORD: superset @@ -61,7 +61,7 @@ jobs: # GitHub action runner's default installations - 15432:5432 presto: - image: starburstdata/presto:350-e.6 + image: ghcr.io/apache/superset/ci/presto:350-e.6 env: POSTGRES_USER: superset POSTGRES_PASSWORD: superset @@ -70,7 +70,7 @@ jobs: # GitHub action runner's default installations - 15433:8080 redis: - image: redis:7-alpine + image: ghcr.io/apache/superset/ci/redis:7-alpine ports: - 16379:6379 steps: @@ -116,7 +116,7 @@ jobs: UPLOAD_FOLDER: /tmp/.superset/uploads/ services: postgres: - image: postgres:17-alpine + image: ghcr.io/apache/superset/ci/postgres:17-alpine env: POSTGRES_USER: superset POSTGRES_PASSWORD: superset @@ -125,7 +125,7 @@ jobs: # GitHub action runner's default installations - 15432:5432 redis: - image: redis:7-alpine + image: ghcr.io/apache/superset/ci/redis:7-alpine ports: - 16379:6379 steps: diff --git a/UPDATING.md b/UPDATING.md index 11de4acf7f2f..a4adec96cf3b 100644 --- a/UPDATING.md +++ b/UPDATING.md @@ -125,6 +125,23 @@ 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*, +verified against a live instance before being enabled: Postgres, MySQL +(`STDDEV_SAMP`/`VAR_SAMP` only, no `MEDIAN`), DuckDB, and Redshift (inherits +Postgres's support, not yet separately verified) ship enabled in this +release. Engine specs that subclass one of those (e.g. MariaDB, Aurora +MySQL/Postgres, TimescaleDB) inherit the same support, on the same +not-yet-independently-verified basis. 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 diff --git a/docs/package.json b/docs/package.json index cbd50bdd617e..82c1d539d467 100644 --- a/docs/package.json +++ b/docs/package.json @@ -58,7 +58,7 @@ "@fontsource/inter": "^5.3.0", "@mdx-js/react": "^3.1.1", "@saucelabs/theme-github-codeblock": "^0.3.0", - "@storybook/addon-docs": "^10.5.7", + "@storybook/addon-docs": "^10.5.8", "@superset-ui/core": "^0.20.4", "@swc/core": "^1.15.47", "antd": "^6.6.0", @@ -77,7 +77,7 @@ "react-table": "^7.8.0", "remark-import-partial": "^0.0.2", "reselect": "^5.2.0", - "storybook": "^10.5.7", + "storybook": "^10.5.8", "swagger-ui-react": "^5.32.13", "swc-loader": "^0.2.7", "tinycolor2": "^1.4.2", diff --git a/docs/sip/median-stddev-variance-aggregates.md b/docs/sip/median-stddev-variance-aggregates.md new file mode 100644 index 000000000000..08a514818f48 --- /dev/null +++ b/docs/sip/median-stddev-variance-aggregates.md @@ -0,0 +1,236 @@ + + +# SIP: System-wide MEDIAN, Sample Standard Deviation, and Sample Variance metric aggregates + +## [DRAFT — proposal for discussion] + +This document now has an accompanying implementation in this PR, for the +proposed mechanism plus a first, empirically-verified engine set (Postgres, +MySQL, DuckDB, Redshift by inheritance). It is intended to seed discussion on +whether this is the right shape and scope before it goes up for a formal SIP +vote, not to pre-empt that discussion, the code exists so reviewers have a +concrete design to react to rather than a description of one. + +## Motivation + +Before #41184 (SIP-216, the non-additive-totals fix), the Pivot Table chart +exposed an "Aggregation function" control with 18 choices, including +`Median`, `Sample Standard Deviation`, `Sample Variance`, `First`, `Last`, +`Count Unique Values`, and `List Unique Values`. #41184 deleted that control +wholesale, and deliberately so: it re-aggregated already-aggregated cell +values to compute totals/subtotals, which is exactly the class of bug +SIP-216 fixed (summing per-group averages, averaging per-group medians, etc. +produces silently wrong totals). #42761 subsequently restored the one piece +of that control's functionality that was cleanly separable from the +correctness bug, the "show as % of row/column/total" display option, +redesigned as a decoupled, post-hoc-only `showValuesAs` control. + +A user has since noticed that several of the other pre-#41184 options never +came back. Checking today's metric aggregate list (`AVG, COUNT, +COUNT_DISTINCT, MAX, MIN, SUM`, see +`superset-frontend/packages/superset-ui-core/src/query/types/Metric.ts`), +most of these have a reasonable equivalent already: `Count Unique Values` +maps to `COUNT_DISTINCT`; `Count`/`Average`/`Max`/`Min` are already standard +aggregates; the two "fraction of" variants are already covered by +`showValuesAs`. But `Median`, `Sample Standard Deviation`, and `Sample +Variance` have no equivalent today anywhere in Superset, not just in Pivot +Table, in any chart type, since the aggregate list is shared across the +whole app. + +This is a real, currently-live gap, not a hypothetical one: +`superset/mcp_service/chart/chart_utils.py`, `schemas.py`, and +`prompts/create_chart_guided.py` already treat `STDDEV`, `VAR`, `MEDIAN`, +and `PERCENTILE` as valid aggregate values in their own validation and +documentation, but those values are never recognized by +`superset/connectors/sqla/models.py`'s `sqla_aggregations` dict (the actual +mapping from aggregate name to SQL), so an AI agent using the MCP tool to +build a chart with `"aggregate": "STDDEV"` today creates a chart that +**errors at query time** with "Adhoc metric aggregate is invalid." This SIP +proposes closing that gap for real, at the source, rather than patching +around it in MCP. + +## Proposed change + +Add `MEDIAN`, `STDDEV_SAMP`, and `VAR_SAMP` as first-class, system-wide +metric aggregates, available anywhere a metric aggregate is chosen (every +chart type, SQL Lab metric picker, MCP), not as a Pivot-Table-specific +control. + +**Why this is safe with respect to SIP-216, and needs no Pivot-Table-specific +code at all:** Pivot Table's non-additive-totals machinery +(`superset-frontend/plugins/plugin-chart-pivot-table/src/plugin/utilities.ts`) +already classifies any metric aggregate not in `ADDITIVE_AGGREGATES = {SUM, +COUNT, MIN, MAX}` as non-additive, which routes totals/subtotals through the +correct DB-`GROUPING SETS`-rollup path rather than client-side +re-aggregation (`AVG` and `COUNT_DISTINCT` already go through this path +today). `MEDIAN`/`STDDEV_SAMP`/`VAR_SAMP` fall into that bucket +automatically, with zero changes needed to the additivity logic. So once +these are valid, buildable SQL aggregates, Pivot Table (and every other +chart) gets correct behavior for free. This is the version of "restore the +control" that does not reopen the bug that was just fixed. + +**Where the actual change needs to land, and what this PR does:** + +1. **Done.** `superset-frontend/packages/superset-ui-core/src/query/types/Metric.ts`, + extended the `Aggregate` type. +2. **Done.** `superset-frontend/src/explore/constants.ts`, added to `AGGREGATES` + (drives `AGGREGATES_OPTIONS`, the dropdown in `AdhocMetricEditPopover`). +3. **Done**, but not consolidated. `superset/connectors/sqla/models.py` + (`sqla_aggregations`) and `superset/models/helpers.py` + (`ExploreMixin.sqla_aggregations`) are both wired to consult the new + `BaseEngineSpec.get_extended_aggregation_func`, in addition to their + existing 6-aggregate dict, so neither's original, already-tested behavior + changed. They remain two separate dicts, consolidating them into one + source of truth is left as a follow-up (see Open questions). +4. **Done**, and it surfaced a second, smaller bug on top of the one this SIP + opened with: MCP's own aggregate names (`STDDEV`, `VAR`) never matched any + real Superset aggregate, before or after this PR, they were always going + to error regardless of what this SIP does. `superset/mcp_service/chart/*` + now accepts the old shorthand as an alias, normalized to the real, + unambiguous names (`STDDEV_SAMP`, `VAR_SAMP`) this PR ships, and the guided + prompt text points at the correct names going forward. `MEDIAN`/ + `PERCENTILE` were already spelled correctly in MCP; `PERCENTILE` remains + unimplemented (it needs a parameter this schema has no field for) and is + unchanged by this PR, out of scope here. + +**The part that needs real engineering care, this must not be a blind +`sa.func.MEDIAN` / `sa.func.STDDEV_SAMP` / `sa.func.VAR_SAMP`:** + +`sqla_aggregations` today is a flat, engine-unaware dict (`sa.func.AVG`, +etc., SQLAlchemy emits whatever function name it is given, with zero +validation that the target dialect actually has it). Superset already has +precedent for exactly this class of per-engine capability difference: +`BaseEngineSpec.supports_grouping_sets` and `_time_grain_expressions`, both +introduced by #41184 itself. This SIP proposes the same shape, a new +per-engine-overridable mechanism (for example +`BaseEngineSpec.get_aggregate_sql(aggregate, column)` with a sensible +default, overridden per engine spec where the default does not hold), +rather than a single hardcoded dict. + +Verified findings so far (via `sqlglot.transpile`, cross-checked against +known engine docs; **not** exhaustively tested against live databases, that +is necessary follow-up work this SIP alone cannot complete): + +| Engine | `MEDIAN(x)` | `STDDEV_SAMP(x)` | `VAR_SAMP(x)` | +|---|---|---|---| +| Postgres | `PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY x)` | native | native | +| MySQL | no native equivalent, needs explicit "unsupported" handling, not a blind emit | native | MySQL's `VARIANCE()` is an alias for `VAR_POP` (population), not `VAR_SAMP` (sample); a naive dialect-name substitution would silently compute the wrong statistic and needs an explicit, verified expression instead | +| SQLite | only if the specific build was compiled with the (non-default) `SQLITE_ENABLE_PERCENTILE` extension (added in SQLite 3.43, 2023), cannot be assumed available | not available in core SQLite | not available in core SQLite | +| BigQuery / Snowflake / DuckDB / Redshift / Oracle / T-SQL / Databricks / Spark | native `MEDIAN(x)` | native | native on BigQuery/Snowflake/Databricks/Spark, where `VARIANCE` is correctly sample variance; T-SQL has no function named `VARIANCE` at all and needs `VAR(x)` instead | +| Trino / Presto / Hive | `PERCENTILE_CONT` / `approx_percentile` (dialect- and exactness-dependent) | native | `variance` is correctly sample variance per Trino/Presto docs | + +This table is deliberately not exhaustive, Superset has roughly 75 +`db_engine_specs` files. The proposed default (`BaseEngineSpec`) should be +the safe choice (mark unsupported, surface a clear user-facing error) rather +than an optimistic one, with individual engine specs opting in once +verified. Ship for the handful of engines above first, extend +opportunistically. + +**`Count Unique Values`, `First`, `Last`, `List Unique Values`, explicitly +out of scope for this SIP:** + +- `Count Unique Values` needs no work, it is already `COUNT_DISTINCT`. +- `First`/`Last` have no well-defined, unambiguous meaning as a plain + `GROUP BY` aggregate without an explicit ordering; most engines only + support this via window functions (`FIRST_VALUE`/`LAST_VALUE` `OVER + (ORDER BY ...)`) or do not support it as a simple aggregate at all + (Postgres has neither built in). Restoring this properly would mean + designing an "order by" sub-control on the metric, a real, separate + feature, not a one-line aggregate addition. Proposed as a follow-up SIP if + there is demand. +- `List Unique Values` maps to the `STRING_AGG`/`GROUP_CONCAT`/`LISTAGG`/ + `ARRAY_AGG(DISTINCT ...)` family, real dialect differences, plus an open + UX question (unbounded cell content for high-cardinality columns). + Proposed as a follow-up SIP. + +## New or changed public interfaces + +- New `Aggregate` values (`MEDIAN`, `STDDEV_SAMP`, `VAR_SAMP`) selectable + anywhere the standard metric control appears, every chart type, not just + Pivot Table. +- New `BaseEngineSpec` extensibility point for per-engine aggregate SQL + generation (exact shape TBD in implementation, likely mirrors + `_time_grain_expressions`). +- No REST API surface changes beyond the existing metric aggregate field + accepting new values. + +## Migration plan and compatibility + +No new tables/columns needed for the aggregate addition itself. + +Restoring prior chart settings, the way #42761 restored `show_values_as` for +charts that had it before #41184, is murkier here than it was for that PR +and needs its own design pass: the old `aggregate_function` was a single +Pivot-Table-level setting applied uniformly to every metric on the chart, +not a per-metric property. A chart that had `aggregate_function: Median` +before #41184, with a metric of `SUM(sales)`, was already silently wrong +under the old architecture (that is the bug that was fixed); mechanically +rewriting its metric to `MEDIAN(sales)` on upgrade would change what the +chart's leaf cells display, not just its totals, which may not match user +intent. This SIP proposes a best-effort, flagged-for-review migration +(surface affected charts to admins rather than silently rewriting them) +rather than a fully automatic one-to-one restoration. + +## Rejected alternatives + +- **Restoring the old `aggregateFunction` Pivot-Table control as-is.** + Rejected: this is the literal mechanism SIP-216 removed because it + reintroduces incorrect totals for non-additive metrics. Any fix has to go + through the metric's own aggregate, not a separate pivot-level override. +- **Routing all metric SQL generation through `sqlglot` expression-building + instead of SQLAlchemy's `sa.func`.** More architecturally thorough (would + give correct dialect syntax for free across more of the roughly 75 engine + specs), but a much larger, more invasive change to a hot path used by + every chart query. Noted as a possible future direction, not this SIP's + scope; this SIP proposes the smaller, `supports_grouping_sets`-shaped + extensibility point instead. + +## Open questions + +- **Resolved for this PR, worth confirming as the community's preferred + shape:** implemented as `BaseEngineSpec._extended_aggregations` (a + `{aggregate_name: sqla_column -> sqla_column}` dict) plus a + `get_extended_aggregation_func` accessor, set on the concrete or shared + base engine spec class per engine (e.g. on `PostgresBaseEngineSpec` so + Redshift inherits it, but *not* on `PrestoBaseEngineSpec` so Hive/Spark/ + Databricks don't silently inherit unverified behavior, mirroring how + `supports_grouping_sets` is opted into per-concrete-engine there today). + Did not route through the `superset/sql/dialects/` sqlglot-based layer; + that layer is for SQL Lab parsing, wiring it into chart-metric query + building felt like a separate, larger change from this SIP's scope. +- **Still open, not addressed in this PR:** how aggressively should + `MEDIAN` degrade on engines without a native or exact equivalent? + Trino/Presto/Hive were left unimplemented (unsupported) specifically to + avoid silently answering this with an approximate function + (`approx_percentile`) that changes the semantics of what a user asked + for. If someone wants `MEDIAN` on those engines, this needs a real + decision: require explicit opt-in, show a UI warning, or keep it + disallowed. +- **Resolved for this PR:** left the two `sqla_aggregations` dicts + (`connectors/sqla/models.py` and `models/helpers.py`) unconsolidated, + both now separately wired to the same new `get_extended_aggregation_func` + hook. Consolidating them into one source of truth is real but unrelated + cleanup, not bundled here to keep the diff reviewable. +- **New, from implementation:** only Postgres, MySQL (partial), DuckDB, and + Redshift (by inheritance, unverified) ship enabled. BigQuery, Snowflake, + Trino, Presto, Hive, Spark, Databricks, Oracle, and T-SQL all have + documented (not live-verified) support per the table above but are not + yet wired up, each needs the same live-instance verification treatment + before being enabled, this PR intentionally didn't guess. diff --git a/docs/yarn.lock b/docs/yarn.lock index 6c22e5ee02bc..e8394d070862 100644 --- a/docs/yarn.lock +++ b/docs/yarn.lock @@ -4122,23 +4122,23 @@ resolved "https://registry.yarnpkg.com/@standard-schema/utils/-/utils-0.3.0.tgz#3d5e608f16c2390c10528e98e59aef6bf73cae7b" integrity sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g== -"@storybook/addon-docs@^10.5.7": - version "10.5.7" - resolved "https://registry.yarnpkg.com/@storybook/addon-docs/-/addon-docs-10.5.7.tgz#6d599c94fc871c248ce06a5c081f57655c83f40a" - integrity sha512-KNARJfjICaizinsR3INMEiipZm1ObYo+xw+E26gteu50Bcy2dIZUtk5uHY5XdtardU3AXX6yRXoBZ2HCY3lbHA== +"@storybook/addon-docs@^10.5.8": + version "10.5.8" + resolved "https://registry.yarnpkg.com/@storybook/addon-docs/-/addon-docs-10.5.8.tgz#767c10c7a4cc1b625b93f869b2a2b09fc8514f2e" + integrity sha512-NlHiMKW/UvW/uL8HXFDCEVwoH3qZeGYZ/qlWax4d7H471b/T54MBq2KcB4ZrdA785FfIH3numAJdBb5jwn00Mg== dependencies: "@mdx-js/react" "^3.0.0" - "@storybook/csf-plugin" "10.5.7" + "@storybook/csf-plugin" "10.5.8" "@storybook/icons" "^2.0.2" - "@storybook/react-dom-shim" "10.5.7" + "@storybook/react-dom-shim" "10.5.8" react "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" react-dom "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" ts-dedent "^2.0.0" -"@storybook/csf-plugin@10.5.7": - version "10.5.7" - resolved "https://registry.yarnpkg.com/@storybook/csf-plugin/-/csf-plugin-10.5.7.tgz#bc73f164d1b5f8e2931b2774f4b389a06453cf6e" - integrity sha512-IaX8FlM0H36HNFhJ2+4L9bCldqfvHGqcLg841SJNyK/DhfMlM7JsvY/GDH2ZFuWrUf8FSOx96GRRnHq6XfRKag== +"@storybook/csf-plugin@10.5.8": + version "10.5.8" + resolved "https://registry.yarnpkg.com/@storybook/csf-plugin/-/csf-plugin-10.5.8.tgz#c626c5bfe55d0e279b2457e5cf150a788d1fc637" + integrity sha512-/FHiMyOWWEXfwK/lM0WxmkP9GLzbSJJuzGtfeuNWSOVDnvAMbjavitxfHb5wSbWKIQo0XYC1EJ2Y7x91XNYP4w== dependencies: unplugin "^2.3.5" @@ -4152,10 +4152,10 @@ resolved "https://registry.yarnpkg.com/@storybook/icons/-/icons-2.1.0.tgz#edfc2450a39c5e780f28c6cbc49acd7bff59b41a" integrity sha512-Fxh9vYpX9bQqFeHRiY8h2ApeRGDzRSMLwJwNZ/AIRqnyOKHxRKL+yFe+ctEkVJmuptRE9u1Hrn8ZZNHyfDKKNg== -"@storybook/react-dom-shim@10.5.7": - version "10.5.7" - resolved "https://registry.yarnpkg.com/@storybook/react-dom-shim/-/react-dom-shim-10.5.7.tgz#9a5aa0e0f89c09e71c6cbfc6bb1abeb537e5aabf" - integrity sha512-lxOkyh+wu/MiBXvYQHjZfD+DRKOa4bHBzbuGuiHXnHXmdOcTRdcrQTsoeN2FPtfugmmOG66cZUEgDwNX+k5eRA== +"@storybook/react-dom-shim@10.5.8": + version "10.5.8" + resolved "https://registry.yarnpkg.com/@storybook/react-dom-shim/-/react-dom-shim-10.5.8.tgz#40cc3e32af424baa2e4109a325dae2ede29999e2" + integrity sha512-N8D13/Xny+V3kfe1KBgsAHS0nKWXLLdgOOXS9poKdYzVwVCN+CGEGBxWX0zMMtdCptqa6/57em9coPlZMoO+bg== "@superset-ui/core@^0.20.4": version "0.20.4" @@ -14783,10 +14783,10 @@ stop-iteration-iterator@^1.1.0: es-errors "^1.3.0" internal-slot "^1.1.0" -storybook@^10.5.7: - version "10.5.7" - resolved "https://registry.yarnpkg.com/storybook/-/storybook-10.5.7.tgz#adfc465e51f337291c095278c23f1b8024ef2da7" - integrity sha512-oiKvWIwIoOhFP1i6dASYyMXwPHKEtVZMshqSB7EvIVYjWRh0l9H7gHEt1z4Gh2rLGFMekWdsm4s94rvwpR7gkg== +storybook@^10.5.8: + version "10.5.8" + resolved "https://registry.yarnpkg.com/storybook/-/storybook-10.5.8.tgz#d5f051983e6232c0a73ea02149a72c7bafb43275" + integrity sha512-rR4oFMSiWBSqI0lvsJPtcQUPj8+hzj3TkLu+Mw61Wo6YxPSb5FsLSHai0jZnuaIdKIlmu25KCfwlSQl4e1uvnA== dependencies: "@storybook/global" "^5.0.0" "@storybook/icons" "^2.0.2" diff --git a/superset-frontend/package-lock.json b/superset-frontend/package-lock.json index 8b91fcd51bdf..a41060313c59 100644 --- a/superset-frontend/package-lock.json +++ b/superset-frontend/package-lock.json @@ -180,9 +180,9 @@ "@istanbuljs/nyc-config-typescript": "^1.0.1", "@playwright/test": "^1.62.1", "@pmmmwh/react-refresh-webpack-plugin": "^0.6.2", - "@storybook/addon-docs": "10.5.7", - "@storybook/addon-links": "10.5.7", - "@storybook/react-webpack5": "10.5.7", + "@storybook/addon-docs": "10.5.8", + "@storybook/addon-links": "10.5.8", + "@storybook/react-webpack5": "10.5.8", "@storybook/test-runner": "0.24.4", "@svgr/webpack": "^8.1.0", "@swc/core": "^1.15.47", @@ -218,7 +218,7 @@ "babel-loader": "^10.1.1", "babel-plugin-dynamic-import-node": "^2.3.3", "babel-plugin-jsx-remove-data-test-id": "^3.0.0", - "baseline-browser-mapping": "^2.11.13", + "baseline-browser-mapping": "^2.11.14", "cheerio": "1.2.0", "concurrently": "^10.0.4", "copy-webpack-plugin": "^14.0.0", @@ -235,7 +235,7 @@ "eslint-plugin-no-only-tests": "^3.4.0", "eslint-plugin-react-prefer-function-component": "^5.0.0", "eslint-plugin-react-you-might-not-need-an-effect": "^1.0.1", - "eslint-plugin-storybook": "10.5.7", + "eslint-plugin-storybook": "10.5.8", "eslint-plugin-testing-library": "^7.16.2", "eslint-plugin-theme-colors": "file:eslint-rules/eslint-plugin-theme-colors", "fetch-mock": "^12.6.0", @@ -266,7 +266,7 @@ "source-map": "^0.8.0", "source-map-support": "^0.5.21", "speed-measure-webpack-plugin": "^1.6.0", - "storybook": "10.5.7", + "storybook": "10.5.8", "style-loader": "^4.0.0", "stylelint": "^17.14.1", "swc-loader": "^0.2.7", @@ -10765,16 +10765,16 @@ "license": "MIT" }, "node_modules/@storybook/addon-docs": { - "version": "10.5.7", - "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-10.5.7.tgz", - "integrity": "sha512-KNARJfjICaizinsR3INMEiipZm1ObYo+xw+E26gteu50Bcy2dIZUtk5uHY5XdtardU3AXX6yRXoBZ2HCY3lbHA==", + "version": "10.5.8", + "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-10.5.8.tgz", + "integrity": "sha512-NlHiMKW/UvW/uL8HXFDCEVwoH3qZeGYZ/qlWax4d7H471b/T54MBq2KcB4ZrdA785FfIH3numAJdBb5jwn00Mg==", "dev": true, "license": "MIT", "dependencies": { "@mdx-js/react": "^3.0.0", - "@storybook/csf-plugin": "10.5.7", + "@storybook/csf-plugin": "10.5.8", "@storybook/icons": "^2.0.2", - "@storybook/react-dom-shim": "10.5.7", + "@storybook/react-dom-shim": "10.5.8", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "ts-dedent": "^2.0.0" @@ -10785,7 +10785,7 @@ }, "peerDependencies": { "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "storybook": "10.5.7" + "storybook": "10.5.8" }, "peerDependenciesMeta": { "@types/react": { @@ -10794,9 +10794,9 @@ } }, "node_modules/@storybook/addon-docs/node_modules/@storybook/csf-plugin": { - "version": "10.5.7", - "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-10.5.7.tgz", - "integrity": "sha512-IaX8FlM0H36HNFhJ2+4L9bCldqfvHGqcLg841SJNyK/DhfMlM7JsvY/GDH2ZFuWrUf8FSOx96GRRnHq6XfRKag==", + "version": "10.5.8", + "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-10.5.8.tgz", + "integrity": "sha512-/FHiMyOWWEXfwK/lM0WxmkP9GLzbSJJuzGtfeuNWSOVDnvAMbjavitxfHb5wSbWKIQo0XYC1EJ2Y7x91XNYP4w==", "dev": true, "license": "MIT", "dependencies": { @@ -10809,7 +10809,7 @@ "peerDependencies": { "esbuild": "*", "rollup": "*", - "storybook": "10.5.7", + "storybook": "10.5.8", "vite": "*", "webpack": "*" }, @@ -10829,9 +10829,9 @@ } }, "node_modules/@storybook/addon-docs/node_modules/@storybook/react-dom-shim": { - "version": "10.5.7", - "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-10.5.7.tgz", - "integrity": "sha512-lxOkyh+wu/MiBXvYQHjZfD+DRKOa4bHBzbuGuiHXnHXmdOcTRdcrQTsoeN2FPtfugmmOG66cZUEgDwNX+k5eRA==", + "version": "10.5.8", + "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-10.5.8.tgz", + "integrity": "sha512-N8D13/Xny+V3kfe1KBgsAHS0nKWXLLdgOOXS9poKdYzVwVCN+CGEGBxWX0zMMtdCptqa6/57em9coPlZMoO+bg==", "dev": true, "license": "MIT", "funding": { @@ -10843,7 +10843,7 @@ "@types/react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "storybook": "10.5.7" + "storybook": "10.5.8" }, "peerDependenciesMeta": { "@types/react": { @@ -10855,9 +10855,9 @@ } }, "node_modules/@storybook/addon-links": { - "version": "10.5.7", - "resolved": "https://registry.npmjs.org/@storybook/addon-links/-/addon-links-10.5.7.tgz", - "integrity": "sha512-17PxEOocLhAEaPeQ4q+8yul/LF9YEIePS1arknCAS7U1pQXTe0uj+R0pB6uPLVflM5gECQMiP4WzIj4tEiL6+A==", + "version": "10.5.8", + "resolved": "https://registry.npmjs.org/@storybook/addon-links/-/addon-links-10.5.8.tgz", + "integrity": "sha512-mpWw4alBJVGqgVh897LZ2keN/xnMHcH93wKJG+oGg4+cdEUA+06hCs5T4k+AS5Aa+EZ6LvdOoi2VPHssyQlCCA==", "dev": true, "license": "MIT", "dependencies": { @@ -10870,7 +10870,7 @@ "peerDependencies": { "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "storybook": "10.5.7" + "storybook": "10.5.8" }, "peerDependenciesMeta": { "@types/react": { @@ -10964,15 +10964,15 @@ } }, "node_modules/@storybook/react-webpack5": { - "version": "10.5.7", - "resolved": "https://registry.npmjs.org/@storybook/react-webpack5/-/react-webpack5-10.5.7.tgz", - "integrity": "sha512-vvl07oXp2qfmHJHZ77Aw1F3LFOo7XubOta+lC8UmlEw3rDDJhQxJN3erJJVHavNhdA2jBTK6VUXQKdqQh7X7nQ==", + "version": "10.5.8", + "resolved": "https://registry.npmjs.org/@storybook/react-webpack5/-/react-webpack5-10.5.8.tgz", + "integrity": "sha512-HkPi42WaoNSHC0DAERsJEF7Vhnluzsp/aiuhnH65GGYG5TmdLL9G8KDiYvXHGDCyb4RfoAPYrtzxaLMFfPPFvQ==", "dev": true, "license": "MIT", "dependencies": { - "@storybook/builder-webpack5": "10.5.7", - "@storybook/preset-react-webpack": "10.5.7", - "@storybook/react": "10.5.7" + "@storybook/builder-webpack5": "10.5.8", + "@storybook/preset-react-webpack": "10.5.8", + "@storybook/react": "10.5.8" }, "funding": { "type": "opencollective", @@ -10981,7 +10981,7 @@ "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "storybook": "10.5.7", + "storybook": "10.5.8", "typescript": ">= 4.9.x" }, "peerDependenciesMeta": { @@ -10991,13 +10991,13 @@ } }, "node_modules/@storybook/react-webpack5/node_modules/@storybook/builder-webpack5": { - "version": "10.5.7", - "resolved": "https://registry.npmjs.org/@storybook/builder-webpack5/-/builder-webpack5-10.5.7.tgz", - "integrity": "sha512-4n4c60LihFivZnjAcXGO5+XbgZthoUtKb/nPKVgypj3MpEetzjq6XR83A4UNnRsXYmjqfn6bsDWNgEJ/RvQg5A==", + "version": "10.5.8", + "resolved": "https://registry.npmjs.org/@storybook/builder-webpack5/-/builder-webpack5-10.5.8.tgz", + "integrity": "sha512-ke5x27gtWQ4gpXCLWxdGkr8ZlJwBykV/KjbBTAlC04dmS9OkI9MBzGj+TteUlgrcaN7LwoNTR5zRmxKStOZYzQ==", "dev": true, "license": "MIT", "dependencies": { - "@storybook/core-webpack": "10.5.7", + "@storybook/core-webpack": "10.5.8", "case-sensitive-paths-webpack-plugin": "^2.4.0", "cjs-module-lexer": "^1.2.3", "css-loader": "^7.1.2", @@ -11019,7 +11019,7 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "10.5.7" + "storybook": "10.5.8" }, "peerDependenciesMeta": { "typescript": { @@ -11028,9 +11028,9 @@ } }, "node_modules/@storybook/react-webpack5/node_modules/@storybook/builder-webpack5/node_modules/@storybook/core-webpack": { - "version": "10.5.7", - "resolved": "https://registry.npmjs.org/@storybook/core-webpack/-/core-webpack-10.5.7.tgz", - "integrity": "sha512-0dtDw/FNPREoeCHX2RgZz0OecxaAGol1R7bCobFevArxyFIPJisTfjDMUFHKr+3B7BilTd3vnatl7Nlvgs0EiA==", + "version": "10.5.8", + "resolved": "https://registry.npmjs.org/@storybook/core-webpack/-/core-webpack-10.5.8.tgz", + "integrity": "sha512-HccINB0UbTtnyJtKpaX+C35BRTSnAwnreIMwwI+LpeUd4x9mQg0G9orB7lfBBZwd5LQf8YhM2Vkjiawzo41GLg==", "dev": true, "license": "MIT", "dependencies": { @@ -11041,17 +11041,17 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "10.5.7" + "storybook": "10.5.8" } }, "node_modules/@storybook/react-webpack5/node_modules/@storybook/preset-react-webpack": { - "version": "10.5.7", - "resolved": "https://registry.npmjs.org/@storybook/preset-react-webpack/-/preset-react-webpack-10.5.7.tgz", - "integrity": "sha512-xwNRcoVlIDx1/YYCFBAxfh/91vFiOgrVI+0Ir4u9eO87SH2leehRnJh619QEOrlQEU5px487y2BmL2ZVtmTpYA==", + "version": "10.5.8", + "resolved": "https://registry.npmjs.org/@storybook/preset-react-webpack/-/preset-react-webpack-10.5.8.tgz", + "integrity": "sha512-0JjgVoX5t9Wb+gwddYHx/Ej7KFqwd65lpHXEhBoT4pFWRqVI0pvfHu42M+DRGjsgOye3uE+3pH4yHR3+0/fCHA==", "dev": true, "license": "MIT", "dependencies": { - "@storybook/core-webpack": "10.5.7", + "@storybook/core-webpack": "10.5.8", "@storybook/react-docgen-typescript-plugin": "1.0.6--canary.9.0c3f3b7.0", "@types/semver": "^7.7.1", "magic-string": "^0.30.5", @@ -11068,7 +11068,7 @@ "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "storybook": "10.5.7" + "storybook": "10.5.8" }, "peerDependenciesMeta": { "typescript": { @@ -11077,9 +11077,9 @@ } }, "node_modules/@storybook/react-webpack5/node_modules/@storybook/preset-react-webpack/node_modules/@storybook/core-webpack": { - "version": "10.5.7", - "resolved": "https://registry.npmjs.org/@storybook/core-webpack/-/core-webpack-10.5.7.tgz", - "integrity": "sha512-0dtDw/FNPREoeCHX2RgZz0OecxaAGol1R7bCobFevArxyFIPJisTfjDMUFHKr+3B7BilTd3vnatl7Nlvgs0EiA==", + "version": "10.5.8", + "resolved": "https://registry.npmjs.org/@storybook/core-webpack/-/core-webpack-10.5.8.tgz", + "integrity": "sha512-HccINB0UbTtnyJtKpaX+C35BRTSnAwnreIMwwI+LpeUd4x9mQg0G9orB7lfBBZwd5LQf8YhM2Vkjiawzo41GLg==", "dev": true, "license": "MIT", "dependencies": { @@ -11090,18 +11090,18 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "10.5.7" + "storybook": "10.5.8" } }, "node_modules/@storybook/react-webpack5/node_modules/@storybook/react": { - "version": "10.5.7", - "resolved": "https://registry.npmjs.org/@storybook/react/-/react-10.5.7.tgz", - "integrity": "sha512-uFvty2MMdFXzW5PcQe1JqDAZkz6cQq7q/9G/cbGVnBEvP6zsOVeL+bmrQ0/WBlFQN0Ko9+ZoCTvaQ9s65zBa5g==", + "version": "10.5.8", + "resolved": "https://registry.npmjs.org/@storybook/react/-/react-10.5.8.tgz", + "integrity": "sha512-6qqkmqX6imtL+0Z9Uan2tIfYivOI0FiVmWr0zpqqQR15AkJ18JfNcNTQoyjeAlCO0Kei56SWqnu2qLq52TYplg==", "dev": true, "license": "MIT", "dependencies": { "@storybook/global": "^5.0.0", - "@storybook/react-dom-shim": "10.5.7", + "@storybook/react-dom-shim": "10.5.8", "react-docgen": "^8.0.2", "react-docgen-typescript": "^2.2.2" }, @@ -11114,7 +11114,7 @@ "@types/react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "storybook": "10.5.7", + "storybook": "10.5.8", "typescript": ">= 4.9.x" }, "peerDependenciesMeta": { @@ -11130,9 +11130,9 @@ } }, "node_modules/@storybook/react-webpack5/node_modules/@storybook/react/node_modules/@storybook/react-dom-shim": { - "version": "10.5.7", - "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-10.5.7.tgz", - "integrity": "sha512-lxOkyh+wu/MiBXvYQHjZfD+DRKOa4bHBzbuGuiHXnHXmdOcTRdcrQTsoeN2FPtfugmmOG66cZUEgDwNX+k5eRA==", + "version": "10.5.8", + "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-10.5.8.tgz", + "integrity": "sha512-N8D13/Xny+V3kfe1KBgsAHS0nKWXLLdgOOXS9poKdYzVwVCN+CGEGBxWX0zMMtdCptqa6/57em9coPlZMoO+bg==", "dev": true, "license": "MIT", "funding": { @@ -11144,7 +11144,7 @@ "@types/react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "storybook": "10.5.7" + "storybook": "10.5.8" }, "peerDependenciesMeta": { "@types/react": { @@ -15697,9 +15697,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.11.13", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.13.tgz", - "integrity": "sha512-k9HNuUVMlqVjQ9UHzfPjIqiDbWw7WqT1AoT7GL8VwvF3r0ZfArtgiSPAlmupyNquNgOJHTuH4CKYf8ttMTWBTQ==", + "version": "2.11.14", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.14.tgz", + "integrity": "sha512-JyJ954WzuIR8/FFzX0o5krdSTrBAkcCSRfWSleRsIHSWV+cZe2FI1PKggVkFke1hBldRs+LRxUczzE9iPmgZww==", "dev": true, "license": "Apache-2.0", "bin": { @@ -20219,9 +20219,9 @@ } }, "node_modules/eslint-plugin-storybook": { - "version": "10.5.7", - "resolved": "https://registry.npmjs.org/eslint-plugin-storybook/-/eslint-plugin-storybook-10.5.7.tgz", - "integrity": "sha512-mLpamG1Rsica2jYbUzIZOEuy7Fm1IMtVLMvvxGTpjTVKUMxTXJsANx3MBpH2VSbGQB8Yzlt5399WL/O07K97Ig==", + "version": "10.5.8", + "resolved": "https://registry.npmjs.org/eslint-plugin-storybook/-/eslint-plugin-storybook-10.5.8.tgz", + "integrity": "sha512-bf9W5nZyWdIaCUZf4aEZnEeD1mn+csNYX8dYUQjAo6L7/DkSLtr65R4zFZ1xeS4m6dOXO6UtUySesCSw4e8w1g==", "dev": true, "license": "MIT", "dependencies": { @@ -20230,7 +20230,7 @@ }, "peerDependencies": { "eslint": ">=8", - "storybook": "10.5.7" + "storybook": "10.5.8" } }, "node_modules/eslint-plugin-testing-library": { @@ -37711,9 +37711,9 @@ } }, "node_modules/storybook": { - "version": "10.5.7", - "resolved": "https://registry.npmjs.org/storybook/-/storybook-10.5.7.tgz", - "integrity": "sha512-oiKvWIwIoOhFP1i6dASYyMXwPHKEtVZMshqSB7EvIVYjWRh0l9H7gHEt1z4Gh2rLGFMekWdsm4s94rvwpR7gkg==", + "version": "10.5.8", + "resolved": "https://registry.npmjs.org/storybook/-/storybook-10.5.8.tgz", + "integrity": "sha512-rR4oFMSiWBSqI0lvsJPtcQUPj8+hzj3TkLu+Mw61Wo6YxPSb5FsLSHai0jZnuaIdKIlmu25KCfwlSQl4e1uvnA==", "dev": true, "license": "MIT", "dependencies": { @@ -43073,6 +43073,15 @@ "node": ">=12" } }, + "packages/superset-ui-core/node_modules/dompurify": { + "version": "3.4.13", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.13.tgz", + "integrity": "sha512-2vmYIoqjze2d+kakP8S/nS5shfsl587kzwEjcGlTdiksUVgFHnFCsLYDVj/JNqJVOQZGSYBTmuycv0PodwmnMQ==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, "packages/superset-ui-core/node_modules/react-ace": { "version": "14.0.1", "resolved": "https://registry.npmjs.org/react-ace/-/react-ace-14.0.1.tgz", diff --git a/superset-frontend/package.json b/superset-frontend/package.json index be48a33313f6..5ca3e1a2ba8a 100644 --- a/superset-frontend/package.json +++ b/superset-frontend/package.json @@ -257,9 +257,9 @@ "@istanbuljs/nyc-config-typescript": "^1.0.1", "@playwright/test": "^1.62.1", "@pmmmwh/react-refresh-webpack-plugin": "^0.6.2", - "@storybook/addon-docs": "10.5.7", - "@storybook/addon-links": "10.5.7", - "@storybook/react-webpack5": "10.5.7", + "@storybook/addon-docs": "10.5.8", + "@storybook/addon-links": "10.5.8", + "@storybook/react-webpack5": "10.5.8", "@storybook/test-runner": "0.24.4", "@svgr/webpack": "^8.1.0", "@swc/core": "^1.15.47", @@ -295,7 +295,7 @@ "babel-loader": "^10.1.1", "babel-plugin-dynamic-import-node": "^2.3.3", "babel-plugin-jsx-remove-data-test-id": "^3.0.0", - "baseline-browser-mapping": "^2.11.13", + "baseline-browser-mapping": "^2.11.14", "cheerio": "1.2.0", "concurrently": "^10.0.4", "copy-webpack-plugin": "^14.0.0", @@ -312,7 +312,7 @@ "eslint-plugin-no-only-tests": "^3.4.0", "eslint-plugin-react-prefer-function-component": "^5.0.0", "eslint-plugin-react-you-might-not-need-an-effect": "^1.0.1", - "eslint-plugin-storybook": "10.5.7", + "eslint-plugin-storybook": "10.5.8", "eslint-plugin-testing-library": "^7.16.2", "eslint-plugin-theme-colors": "file:eslint-rules/eslint-plugin-theme-colors", "fetch-mock": "^12.6.0", @@ -343,7 +343,7 @@ "source-map": "^0.8.0", "source-map-support": "^0.5.21", "speed-measure-webpack-plugin": "^1.6.0", - "storybook": "10.5.7", + "storybook": "10.5.8", "style-loader": "^4.0.0", "stylelint": "^17.14.1", "swc-loader": "^0.2.7", diff --git a/superset-frontend/packages/superset-ui-core/src/query/types/Metric.ts b/superset-frontend/packages/superset-ui-core/src/query/types/Metric.ts index 15b59fb22ab3..d9b7f82be3ec 100644 --- a/superset-frontend/packages/superset-ui-core/src/query/types/Metric.ts +++ b/superset-frontend/packages/superset-ui-core/src/query/types/Metric.ts @@ -25,8 +25,11 @@ export type Aggregate = | 'COUNT' | 'COUNT_DISTINCT' | 'MAX' + | 'MEDIAN' | 'MIN' - | 'SUM'; + | 'STDDEV_SAMP' + | 'SUM' + | 'VAR_SAMP'; export interface AdhocMetricBase { hasCustomLabel?: boolean; diff --git a/superset-frontend/plugins/plugin-chart-pivot-table/test/plugin/utilities.test.ts b/superset-frontend/plugins/plugin-chart-pivot-table/test/plugin/utilities.test.ts index bdd3d2ea6973..6fd67b9f97f9 100644 --- a/superset-frontend/plugins/plugin-chart-pivot-table/test/plugin/utilities.test.ts +++ b/superset-frontend/plugins/plugin-chart-pivot-table/test/plugin/utilities.test.ts @@ -290,6 +290,25 @@ test('isAdditiveMetric: non-additive aggregates, SQL, and saved metrics are not expect(isAdditiveMetric('count')).toBe(false); }); +test('isAdditiveMetric: MEDIAN/STDDEV_SAMP/VAR_SAMP are non-additive, with no dedicated code needed', () => { + // Regression guard: MEDIAN/STDDEV_SAMP/VAR_SAMP are new system-wide metric + // aggregates (not pivot-table-specific). They must fall outside + // ADDITIVE_AGGREGATES so totals/subtotals route through the correct + // DB-rollup path automatically, same as AVG/COUNT_DISTINCT already do -- + // averaging per-group medians (or variances) is exactly the class of bug + // SIP-216 fixed for AVG, and would be equally wrong here. + (['MEDIAN', 'STDDEV_SAMP', 'VAR_SAMP'] as const).forEach(aggregate => { + expect( + isAdditiveMetric({ + expressionType: 'SIMPLE', + aggregate, + column: { column_name: 'num' }, + label: `${aggregate.toLowerCase()}_num`, + } as QueryFormMetric), + ).toBe(false); + }); +}); + test('allMetricsAdditive: all additive vs any non-additive vs empty', () => { const sum = { expressionType: 'SIMPLE', diff --git a/superset-frontend/src/SqlLab/components/SaveDatasetActionButton/SaveDatasetActionButton.test.tsx b/superset-frontend/src/SqlLab/components/SaveDatasetActionButton/SaveDatasetActionButton.test.tsx index faaa5cb67fca..5df12e95a0c6 100644 --- a/superset-frontend/src/SqlLab/components/SaveDatasetActionButton/SaveDatasetActionButton.test.tsx +++ b/superset-frontend/src/SqlLab/components/SaveDatasetActionButton/SaveDatasetActionButton.test.tsx @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { render, screen } from 'spec/helpers/testing-library'; +import { render, screen, userEvent } from 'spec/helpers/testing-library'; import SaveDatasetActionButton from 'src/SqlLab/components/SaveDatasetActionButton'; // eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks @@ -27,6 +27,7 @@ describe('SaveDatasetActionButton', () => { true} onSaveAsExplore={onSaveAsExplore} + canSaveDataset />, ); @@ -41,4 +42,27 @@ describe('SaveDatasetActionButton', () => { expect(saveBtn).toBeVisible(); expect(saveDatasetBtn).toBeVisible(); }); + + test('disables the save dataset button when the query did not run successfully', async () => { + render( + true} + onSaveAsExplore={jest.fn()} + canSaveDataset={false} + />, + ); + + const saveDatasetBtn = screen.getByRole('button', { + name: /save dataset/i, + }); + expect(saveDatasetBtn).toBeDisabled(); + + // the disabled button is wrapped in a span so the tooltip still triggers + userEvent.hover(saveDatasetBtn.parentElement as HTMLElement); + expect( + await screen.findByRole('tooltip', { + name: 'You must run the query successfully first', + }), + ).toBeInTheDocument(); + }); }); diff --git a/superset-frontend/src/SqlLab/components/SaveDatasetActionButton/index.tsx b/superset-frontend/src/SqlLab/components/SaveDatasetActionButton/index.tsx index a592c786b95f..774ef5b99fda 100644 --- a/superset-frontend/src/SqlLab/components/SaveDatasetActionButton/index.tsx +++ b/superset-frontend/src/SqlLab/components/SaveDatasetActionButton/index.tsx @@ -23,11 +23,13 @@ import { Button } from '@superset-ui/core/components'; interface SaveDatasetActionButtonProps { setShowSave: (arg0: boolean) => void; onSaveAsExplore?: () => void; + canSaveDataset: boolean; } const SaveDatasetActionButton = ({ setShowSave, onSaveAsExplore, + canSaveDataset, }: SaveDatasetActionButtonProps) => ( <>