This is an automated email from the ASF dual-hosted git repository. vavila pushed a commit to branch fix/form_data_fix in repository https://gitbox.apache.org/repos/asf/superset.git
commit 2f34292bf48b75c91347f3d512d455064c2be439 Author: Vitor Avila <[email protected]> AuthorDate: Tue Dec 16 15:43:23 2025 -0300 fix: store form_data as dict during viz type migration --- superset/commands/chart/importers/v1/utils.py | 2 +- ...8d8526_fix_form_data_string_in_query_context.py | 108 +++++++++++++++++++++ .../charts/commands/importers/v1/utils_test.py | 41 ++++++++ 3 files changed, 150 insertions(+), 1 deletion(-) diff --git a/superset/commands/chart/importers/v1/utils.py b/superset/commands/chart/importers/v1/utils.py index dacef7c1e3..33e95279a2 100644 --- a/superset/commands/chart/importers/v1/utils.py +++ b/superset/commands/chart/importers/v1/utils.py @@ -123,7 +123,7 @@ def migrate_chart(config: dict[str, Any]) -> dict[str, Any]: except (json.JSONDecodeError, TypeError): query_context = {} if "form_data" in query_context: - query_context["form_data"] = output["params"] + query_context["form_data"] = params output["query_context"] = json.dumps(query_context) return output diff --git a/superset/migrations/versions/2025-12-16_12-00_f5b5f88d8526_fix_form_data_string_in_query_context.py b/superset/migrations/versions/2025-12-16_12-00_f5b5f88d8526_fix_form_data_string_in_query_context.py new file mode 100644 index 0000000000..c45faf7801 --- /dev/null +++ b/superset/migrations/versions/2025-12-16_12-00_f5b5f88d8526_fix_form_data_string_in_query_context.py @@ -0,0 +1,108 @@ +# 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. +"""fix_form_data_string_in_query_context + +Revision ID: f5b5f88d8526 +Revises: a9c01ec10479 +Create Date: 2025-12-16 12:00:00.000000 + +""" + +import logging + +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 = "f5b5f88d8526" +down_revision = "a9c01ec10479" + +Base = declarative_base() +logger = logging.getLogger(__name__) + +# Viz types that have migrations that were going through the bug +MIGRATED_VIZ_TYPES = [ + "treemap_v2", + "pivot_table_v2", + "mixed_timeseries", + "sunburst_v2", + "echarts_timeseries_line", + "echarts_timeseries_smooth", + "echarts_timeseries_step", + "echarts_area", + "echarts_timeseries_bar", + "bubble_v2", + "heatmap_v2", + "histogram_v2", + "sankey_v2", +] + + +class Slice(Base): + __tablename__ = "slices" + + id = Column(Integer, primary_key=True) + viz_type = Column(String(250)) + query_context = Column(Text) + + +def upgrade(): + """ + Fix charts where form_data in query_context was stored as a JSON string + instead of a dict during chart import migration. + """ + bind = op.get_bind() + session = db.Session(bind=bind) + + for slc in paginated_update( + session.query(Slice).filter( + Slice.viz_type.in_(MIGRATED_VIZ_TYPES), + Slice.query_context.isnot(None), + ) + ): + try: + query_context = json.loads(slc.query_context) + form_data = query_context.get("form_data") + + # Check if form_data is a non-empty string (the bug) + if form_data and isinstance(form_data, str): + try: + query_context["form_data"] = json.loads(form_data) + slc.query_context = json.dumps(query_context, sort_keys=True) + except json.JSONDecodeError: + logger.warning( + "Could not parse form_data for slice %s, skipping", slc.id + ) + except json.JSONDecodeError: + logger.warning( + "Could not parse query_context for slice %s, skipping", slc.id + ) + except Exception: # noqa: S110 + logger.warning("Could not update form_data for slice %s, skipping", slc.id) + + session.commit() + session.close() + + +def downgrade(): + # This migration fixes data corruption, downgrade is not meaningful + pass diff --git a/tests/unit_tests/charts/commands/importers/v1/utils_test.py b/tests/unit_tests/charts/commands/importers/v1/utils_test.py index 754742e0dc..3770cd0d4d 100644 --- a/tests/unit_tests/charts/commands/importers/v1/utils_test.py +++ b/tests/unit_tests/charts/commands/importers/v1/utils_test.py @@ -171,3 +171,44 @@ def test_migrate_pivot_table() -> None: "version": "1.0.0", "dataset_uuid": "a18b9cb0-b8d3-42ed-bd33-0f0fadbf0f6d", } + + +def test_migrate_chart_query_context_form_data_is_dict() -> None: + """ + Test that form_data in query_context remains a dict after migration. + """ + chart_config = { + "slice_name": "Pivot Table with Query Context", + "description": None, + "certified_by": None, + "certification_details": None, + "viz_type": "pivot_table", + "params": json.dumps( + { + "columns": ["state"], + "groupby": ["name"], + "metrics": ["count"], + "viz_type": "pivot_table", + } + ), + "query_context": json.dumps( + { + "form_data": { + "slice_id": 123, + "viz_type": "pivot_table", + "columns": ["state"], + }, + "queries": [{"columns": ["state"]}], + } + ), + "cache_timeout": None, + "uuid": "a18b9cb0-b8d3-42ed-bd33-0f0fadbf0f6d", + "version": "1.0.0", + "dataset_uuid": "ffd15af2-2188-425c-b6b4-df28aac45872", + } + + new_config = migrate_chart(chart_config) + query_context = json.loads(new_config["query_context"]) + + assert query_context["form_data"]["viz_type"] == "pivot_table_v2" + assert isinstance(query_context["form_data"], dict)
