From 39f12786a22648146c4197d901a45321d3987122 Mon Sep 17 00:00:00 2001 From: Joe Li Date: Thu, 23 Apr 2026 15:38:36 -0700 Subject: [PATCH] refactor(chart): replace word cloud sort_by_series migration with code defaults (#39575) Co-authored-by: Claude Opus 4.7 (1M context) --- .../src/plugin/buildQuery.ts | 2 +- .../src/plugin/controlPanel.tsx | 2 +- .../test/buildQuery.test.ts | 11 +++ .../test/controlPanel.test.ts | 22 +++++ ...add_sort_by_series_to_word_cloud_charts.py | 89 ------------------- 5 files changed, 35 insertions(+), 91 deletions(-) delete mode 100644 superset/migrations/versions/2026-04-13_19-28_fd0c8583b46d_add_sort_by_series_to_word_cloud_charts.py diff --git a/superset-frontend/plugins/plugin-chart-word-cloud/src/plugin/buildQuery.ts b/superset-frontend/plugins/plugin-chart-word-cloud/src/plugin/buildQuery.ts index e90aab66ed7..7d4a31412fc 100644 --- a/superset-frontend/plugins/plugin-chart-word-cloud/src/plugin/buildQuery.ts +++ b/superset-frontend/plugins/plugin-chart-word-cloud/src/plugin/buildQuery.ts @@ -30,7 +30,7 @@ export default function buildQuery(formData: WordCloudFormData) { if (sort_by_metric && metric) { orderby.push([metric, false]); } - if (sort_by_series && series) { + if (sort_by_series !== false && series) { orderby.push([series, true]); } diff --git a/superset-frontend/plugins/plugin-chart-word-cloud/src/plugin/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-word-cloud/src/plugin/controlPanel.tsx index 98e5e624b7f..1cf42d21117 100644 --- a/superset-frontend/plugins/plugin-chart-word-cloud/src/plugin/controlPanel.tsx +++ b/superset-frontend/plugins/plugin-chart-word-cloud/src/plugin/controlPanel.tsx @@ -41,7 +41,7 @@ const config: ControlPanelConfig = { config: { type: 'CheckboxControl', label: t('Sort by series'), - default: false, + default: true, description: t( 'Sort results by series name in ascending order. ' + 'When combined with "Sort by metric", this acts as a tiebreaker ' + diff --git a/superset-frontend/plugins/plugin-chart-word-cloud/test/buildQuery.test.ts b/superset-frontend/plugins/plugin-chart-word-cloud/test/buildQuery.test.ts index 7ce8cab7be1..3cb5937660b 100644 --- a/superset-frontend/plugins/plugin-chart-word-cloud/test/buildQuery.test.ts +++ b/superset-frontend/plugins/plugin-chart-word-cloud/test/buildQuery.test.ts @@ -86,5 +86,16 @@ describe('plugin-chart-word-cloud', () => { ['foo', true], ]); }); + + test('should order by series ASC when sort_by_series is undefined (legacy chart)', () => { + const queryContext = buildQuery({ + ...basicFormData, + metric: 'count', + sort_by_metric: false, + row_limit: 100, + }); + const [query] = queryContext.queries; + expect(query.orderby).toEqual([['foo', true]]); + }); }); }); diff --git a/superset-frontend/plugins/plugin-chart-word-cloud/test/controlPanel.test.ts b/superset-frontend/plugins/plugin-chart-word-cloud/test/controlPanel.test.ts index 56794dd5f29..b792581815b 100644 --- a/superset-frontend/plugins/plugin-chart-word-cloud/test/controlPanel.test.ts +++ b/superset-frontend/plugins/plugin-chart-word-cloud/test/controlPanel.test.ts @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ +import { isCustomControlItem } from '@superset-ui/chart-controls'; import controlPanel from '../src/plugin/controlPanel'; import React, { ReactElement } from 'react'; @@ -45,3 +46,24 @@ test('control panel has rotation and color_scheme controls', () => { ); expect(colorSchemeRow).toBeDefined(); }); + +test('sort_by_series defaults to true to preserve legacy ordering', () => { + const querySection = controlPanel.controlPanelSections.find( + (section): section is NonNullable => + Boolean(section && section.label === 'Query'), + ); + expect(querySection).toBeDefined(); + if (!querySection) { + throw new Error('Query section missing'); + } + + const sortBySeriesEntry = querySection.controlSetRows + .flat() + .find(item => isCustomControlItem(item) && item.name === 'sort_by_series'); + + expect(isCustomControlItem(sortBySeriesEntry)).toBe(true); + if (!isCustomControlItem(sortBySeriesEntry)) { + throw new Error('sort_by_series control missing'); + } + expect(sortBySeriesEntry.config.default).toBe(true); +}); diff --git a/superset/migrations/versions/2026-04-13_19-28_fd0c8583b46d_add_sort_by_series_to_word_cloud_charts.py b/superset/migrations/versions/2026-04-13_19-28_fd0c8583b46d_add_sort_by_series_to_word_cloud_charts.py deleted file mode 100644 index bf66a2264f3..00000000000 --- a/superset/migrations/versions/2026-04-13_19-28_fd0c8583b46d_add_sort_by_series_to_word_cloud_charts.py +++ /dev/null @@ -1,89 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -"""add sort_by_series to word_cloud charts - -Revision ID: fd0c8583b46d -Revises: ce6bd21901ab -Create Date: 2026-04-13 19:28:19.021839 - -""" - -from alembic import op -from sqlalchemy import Column, Integer, String, Text -from sqlalchemy.ext.declarative import declarative_base - -from superset import db -from superset.migrations.shared.utils import paginated_update -from superset.utils import json - -# revision identifiers, used by Alembic. -revision = "fd0c8583b46d" -down_revision = "ce6bd21901ab" - -Base = declarative_base() - - -class Slice(Base): - __tablename__ = "slices" - id = Column(Integer, primary_key=True) - viz_type = Column(String(250)) - params = Column(Text) - - -def upgrade_params(params: dict) -> dict: - if "sort_by_series" not in params: - params["sort_by_series"] = True - return params - - -def downgrade_params(params: dict) -> dict: - params.pop("sort_by_series", None) - return params - - -def upgrade(): - bind = op.get_bind() - session = db.Session(bind=bind) - - for slc in paginated_update( - session.query(Slice).filter(Slice.viz_type == "word_cloud") - ): - try: - params = json.loads(slc.params or "{}") - if not isinstance(params, dict): - continue - slc.params = json.dumps(upgrade_params(params)) - except (json.JSONDecodeError, TypeError): - continue - session.close() - - -def downgrade(): - bind = op.get_bind() - session = db.Session(bind=bind) - - for slc in paginated_update( - session.query(Slice).filter(Slice.viz_type == "word_cloud") - ): - try: - params = json.loads(slc.params or "{}") - if not isinstance(params, dict): - continue - slc.params = json.dumps(downgrade_params(params)) - except (json.JSONDecodeError, TypeError): - continue - session.close()