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)

Reply via email to