Skip to content

Commit abb8e28

Browse files
feat: migrate charts on import (apache#24703)
1 parent e210da9 commit abb8e28

3 files changed

Lines changed: 228 additions & 0 deletions

File tree

superset/charts/commands/importers/v1/utils.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,18 @@
1515
# specific language governing permissions and limitations
1616
# under the License.
1717

18+
import copy
1819
import json
20+
from inspect import isclass
1921
from typing import Any
2022

2123
from flask import g
2224
from sqlalchemy.orm import Session
2325

2426
from superset import security_manager
2527
from superset.commands.exceptions import ImportFailedError
28+
from superset.migrations.shared.migrate_viz import processors
29+
from superset.migrations.shared.migrate_viz.base import MigrateViz
2630
from superset.models.slice import Slice
2731

2832

@@ -46,6 +50,9 @@ def import_chart(
4650
# TODO (betodealmeida): move this logic to import_from_dict
4751
config["params"] = json.dumps(config["params"])
4852

53+
# migrate old viz types to new ones
54+
config = migrate_chart(config)
55+
4956
chart = Slice.import_from_dict(session, config, recursive=False)
5057
if chart.id is None:
5158
session.flush()
@@ -54,3 +61,47 @@ def import_chart(
5461
chart.owners.append(g.user)
5562

5663
return chart
64+
65+
66+
def migrate_chart(config: dict[str, Any]) -> dict[str, Any]:
67+
"""
68+
Used to migrate old viz types to new ones.
69+
"""
70+
migrators = {
71+
class_.source_viz_type: class_
72+
for class_ in processors.__dict__.values()
73+
if isclass(class_)
74+
and issubclass(class_, MigrateViz)
75+
and hasattr(class_, "source_viz_type")
76+
and class_ != processors.MigrateAreaChart # incomplete
77+
}
78+
79+
output = copy.deepcopy(config)
80+
if config["viz_type"] not in migrators:
81+
return output
82+
83+
migrator = migrators[config["viz_type"]](output["params"])
84+
# pylint: disable=protected-access
85+
migrator._pre_action()
86+
migrator._migrate()
87+
migrator._post_action()
88+
params = migrator.data
89+
90+
params["viz_type"] = migrator.target_viz_type
91+
output.update(
92+
{
93+
"params": json.dumps(params),
94+
"viz_type": migrator.target_viz_type,
95+
}
96+
)
97+
98+
# also update `query_context`
99+
try:
100+
query_context = json.loads(output.get("query_context", "{}"))
101+
except json.decoder.JSONDecodeError:
102+
query_context = {}
103+
if "form_data" in query_context:
104+
query_context["form_data"] = output["params"]
105+
output["query_context"] = json.dumps(query_context)
106+
107+
return output

superset/migrations/shared/migrate_viz/processors.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,15 @@ def _pre_action(self) -> None:
3535

3636

3737
class MigrateAreaChart(MigrateViz):
38+
"""
39+
Migrate area charts.
40+
41+
This migration is incomplete, see https://github.com/apache/superset/pull/24703#discussion_r1265222611
42+
for more details. If you fix this migration, please update the ``migrate_chart``
43+
function in ``superset/charts/commands/importers/v1/utils.py`` so that it gets
44+
applied in chart imports.
45+
"""
46+
3847
source_viz_type = "area"
3948
target_viz_type = "echarts_area"
4049
remove_keys = {"contribution", "stacked_style", "x_axis_label"}
@@ -51,6 +60,9 @@ def _pre_action(self) -> None:
5160
self.data["show_extra_controls"] = True
5261
self.data["stack"] = stacked_map.get(stacked)
5362

63+
if x_axis := self.data.get("granularity_sqla"):
64+
self.data["x_axis"] = x_axis
65+
5466
if x_axis_label := self.data.get("x_axis_label"):
5567
self.data["x_axis_title"] = x_axis_label
5668
self.data["x_axis_title_margin"] = 30
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
18+
import json
19+
20+
from superset.charts.commands.importers.v1.utils import migrate_chart
21+
22+
23+
def test_migrate_chart_area() -> None:
24+
"""
25+
Test the ``migrate_chart`` command when importing an area chart.
26+
27+
This is currently a no-op since the migration is not complete.
28+
"""
29+
chart_config = {
30+
"slice_name": "Birth names by state",
31+
"description": None,
32+
"certified_by": None,
33+
"certification_details": None,
34+
"viz_type": "area",
35+
"params": json.dumps(
36+
{
37+
"adhoc_filters": [],
38+
"annotation_layers": [],
39+
"bottom_margin": "auto",
40+
"color_scheme": "supersetColors",
41+
"comparison_type": "values",
42+
"dashboards": [],
43+
"datasource": "21__table",
44+
"extra_form_data": {},
45+
"granularity_sqla": "ds",
46+
"groupby": ["state"],
47+
"line_interpolation": "linear",
48+
"metrics": ["count"],
49+
"order_desc": True,
50+
"rich_tooltip": True,
51+
"rolling_type": "None",
52+
"row_limit": 10000,
53+
"show_brush": "auto",
54+
"show_legend": True,
55+
"stacked_style": "stack",
56+
"time_grain_sqla": "P1D",
57+
"time_range": "No filter",
58+
"viz_type": "area",
59+
"x_axis_format": "smart_date",
60+
"x_ticks_layout": "auto",
61+
"y_axis_bounds": [None, None],
62+
"y_axis_format": "SMART_NUMBER",
63+
}
64+
),
65+
"cache_timeout": None,
66+
"uuid": "ffd15af2-2188-425c-b6b4-df28aac45872",
67+
"version": "1.0.0",
68+
"dataset_uuid": "a18b9cb0-b8d3-42ed-bd33-0f0fadbf0f6d",
69+
}
70+
71+
new_config = migrate_chart(chart_config)
72+
assert new_config == chart_config
73+
74+
75+
def test_migrate_pivot_table() -> None:
76+
"""
77+
Test the ``migrate_chart`` command when importing an old pivot table.
78+
"""
79+
chart_config = {
80+
"slice_name": "Pivot Table",
81+
"description": None,
82+
"certified_by": None,
83+
"certification_details": None,
84+
"viz_type": "pivot_table",
85+
"params": json.dumps(
86+
{
87+
"columns": ["state"],
88+
"compare_lag": "10",
89+
"compare_suffix": "o10Y",
90+
"granularity_sqla": "ds",
91+
"groupby": ["name"],
92+
"limit": "25",
93+
"markup_type": "markdown",
94+
"metrics": [
95+
{
96+
"aggregate": "SUM",
97+
"column": {
98+
"column_name": "num",
99+
"type": "BIGINT",
100+
},
101+
"expressionType": "SIMPLE",
102+
"label": "Births",
103+
"optionName": "metric_11",
104+
},
105+
],
106+
"row_limit": 50000,
107+
"since": "100 years ago",
108+
"time_range": "No filter",
109+
"time_range_endpoints": ["inclusive", "exclusive"],
110+
"until": "now",
111+
"viz_type": "pivot_table",
112+
},
113+
),
114+
"cache_timeout": None,
115+
"uuid": "ffd15af2-2188-425c-b6b4-df28aac45872",
116+
"version": "1.0.0",
117+
"dataset_uuid": "a18b9cb0-b8d3-42ed-bd33-0f0fadbf0f6d",
118+
}
119+
120+
new_config = migrate_chart(chart_config)
121+
assert new_config == {
122+
"slice_name": "Pivot Table",
123+
"description": None,
124+
"certified_by": None,
125+
"certification_details": None,
126+
"viz_type": "pivot_table_v2",
127+
"params": json.dumps(
128+
{
129+
"groupbyColumns": ["state"],
130+
"compare_lag": "10",
131+
"compare_suffix": "o10Y",
132+
"groupbyRows": ["name"],
133+
"limit": "25",
134+
"markup_type": "markdown",
135+
"metrics": [
136+
{
137+
"aggregate": "SUM",
138+
"column": {"column_name": "num", "type": "BIGINT"},
139+
"expressionType": "SIMPLE",
140+
"label": "Births",
141+
"optionName": "metric_11",
142+
}
143+
],
144+
"series_limit": 50000,
145+
"since": "100 years ago",
146+
"time_range_endpoints": ["inclusive", "exclusive"],
147+
"until": "now",
148+
"viz_type": "pivot_table_v2",
149+
"rowOrder": "value_z_to_a",
150+
"adhoc_filters": [
151+
{
152+
"clause": "WHERE",
153+
"subject": "ds",
154+
"operator": "TEMPORAL_RANGE",
155+
"comparator": "No filter",
156+
"expressionType": "SIMPLE",
157+
}
158+
],
159+
}
160+
),
161+
"cache_timeout": None,
162+
"uuid": "ffd15af2-2188-425c-b6b4-df28aac45872",
163+
"version": "1.0.0",
164+
"dataset_uuid": "a18b9cb0-b8d3-42ed-bd33-0f0fadbf0f6d",
165+
}

0 commit comments

Comments
 (0)