This is an automated email from the ASF dual-hosted git repository.
vavila pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/master by this push:
new d14f502126 fix: store form_data as dict during viz type migration
(#36680)
d14f502126 is described below
commit d14f502126e31008698ebd98c37bdd64ac96a5fe
Author: Vitor Avila <[email protected]>
AuthorDate: Tue Dec 16 18:32:29 2025 -0300
fix: store form_data as dict during viz type migration (#36680)
---
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)