This is an automated email from the ASF dual-hosted git repository.
beto 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 22de26cd77 fix: `metric.currency` should be JSON, not string (#33303)
22de26cd77 is described below
commit 22de26cd77199495ff1cd50672f584d7e45f5a9c
Author: Beto Dealmeida <[email protected]>
AuthorDate: Thu May 1 18:16:51 2025 -0400
fix: `metric.currency` should be JSON, not string (#33303)
---
docs/static/resources/openapi.json | 10 --
.../superset-ui-chart-controls/src/fixtures.ts | 1 -
.../superset-ui-chart-controls/src/types.ts | 1 -
.../test/utils/columnChoices.test.tsx | 2 -
.../test/utils/defineSavedMetrics.test.tsx | 1 -
superset-frontend/src/dashboard/constants.ts | 1 -
.../src/explore/actions/datasourcesActions.test.ts | 2 -
.../src/explore/controlUtils/controlUtils.test.tsx | 1 -
...etControlValuesCompatibleWithDatasource.test.ts | 1 -
superset-frontend/src/explore/fixtures.tsx | 2 -
superset-frontend/src/features/datasets/types.ts | 1 -
.../src/utils/getDatasourceUid.test.ts | 1 -
superset/connectors/sqla/models.py | 17 +--
superset/dashboards/schemas.py | 1 -
superset/datasets/api.py | 1 -
superset/explore/schemas.py | 1 -
superset/migrations/shared/utils.py | 125 ++++++++++++++++++++-
..._f1edd4a4d4f2_metric_currency_should_be_json.py | 46 ++++++++
18 files changed, 171 insertions(+), 44 deletions(-)
diff --git a/docs/static/resources/openapi.json
b/docs/static/resources/openapi.json
index 6389dec60d..9c360c71e4 100644
--- a/docs/static/resources/openapi.json
+++ b/docs/static/resources/openapi.json
@@ -3565,9 +3565,6 @@
},
"type": "array"
},
- "currency_formats": {
- "type": "object"
- },
"database": {
"$ref": "#/components/schemas/Database"
},
@@ -5030,10 +5027,6 @@
},
"type": "array"
},
- "currency_formats": {
- "description": "Currency formats.",
- "type": "object"
- },
"database": {
"description": "Database associated with the dataset.",
"type": "object"
@@ -5559,9 +5552,6 @@
"created_on_humanized": {
"readOnly": true
},
- "currency_formats": {
- "readOnly": true
- },
"database": {
"$ref": "#/components/schemas/DatasetRestApi.get.Database"
},
diff --git
a/superset-frontend/packages/superset-ui-chart-controls/src/fixtures.ts
b/superset-frontend/packages/superset-ui-chart-controls/src/fixtures.ts
index 05b4d4df3d..ead279c0e7 100644
--- a/superset-frontend/packages/superset-ui-chart-controls/src/fixtures.ts
+++ b/superset-frontend/packages/superset-ui-chart-controls/src/fixtures.ts
@@ -21,7 +21,6 @@ import { Dataset } from './types';
export const TestDataset: Dataset = {
column_formats: {},
- currency_formats: {},
columns: [
{
advanced_data_type: undefined,
diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/types.ts
b/superset-frontend/packages/superset-ui-chart-controls/src/types.ts
index 62ac944688..71517884da 100644
--- a/superset-frontend/packages/superset-ui-chart-controls/src/types.ts
+++ b/superset-frontend/packages/superset-ui-chart-controls/src/types.ts
@@ -69,7 +69,6 @@ export interface Dataset {
columns: ColumnMeta[];
metrics: Metric[];
column_formats: Record<string, string>;
- currency_formats: Record<string, Currency>;
verbose_map: Record<string, string>;
main_dttm_col: string;
// eg. ['["ds", true]', 'ds [asc]']
diff --git
a/superset-frontend/packages/superset-ui-chart-controls/test/utils/columnChoices.test.tsx
b/superset-frontend/packages/superset-ui-chart-controls/test/utils/columnChoices.test.tsx
index f27365e956..646ae093fb 100644
---
a/superset-frontend/packages/superset-ui-chart-controls/test/utils/columnChoices.test.tsx
+++
b/superset-frontend/packages/superset-ui-chart-controls/test/utils/columnChoices.test.tsx
@@ -53,7 +53,6 @@ describe('columnChoices()', () => {
],
verbose_map: {},
column_formats: { fiz: 'NUMERIC', about: 'STRING', foo: 'DATE' },
- currency_formats: {},
datasource_name: 'my_datasource',
description: 'this is my datasource',
}),
@@ -105,7 +104,6 @@ describe('columnChoices()', () => {
],
verbose_map: {},
column_formats: { fiz: 'NUMERIC', about: 'STRING', foo: 'DATE' },
- currency_formats: {},
datasource_name: 'my_datasource',
description: 'this is my datasource',
}),
diff --git
a/superset-frontend/packages/superset-ui-chart-controls/test/utils/defineSavedMetrics.test.tsx
b/superset-frontend/packages/superset-ui-chart-controls/test/utils/defineSavedMetrics.test.tsx
index 682075b5c1..f1c64ad4a7 100644
---
a/superset-frontend/packages/superset-ui-chart-controls/test/utils/defineSavedMetrics.test.tsx
+++
b/superset-frontend/packages/superset-ui-chart-controls/test/utils/defineSavedMetrics.test.tsx
@@ -41,7 +41,6 @@ describe('defineSavedMetrics', () => {
columns: [],
verbose_map: {},
column_formats: {},
- currency_formats: {},
datasource_name: 'my_datasource',
description: 'this is my datasource',
};
diff --git a/superset-frontend/src/dashboard/constants.ts
b/superset-frontend/src/dashboard/constants.ts
index d512c2a8a6..60c8847513 100644
--- a/superset-frontend/src/dashboard/constants.ts
+++ b/superset-frontend/src/dashboard/constants.ts
@@ -29,7 +29,6 @@ export const PLACEHOLDER_DATASOURCE: Datasource = {
column_types: [],
metrics: [],
column_formats: {},
- currency_formats: {},
verbose_map: {},
main_dttm_col: '',
description: '',
diff --git a/superset-frontend/src/explore/actions/datasourcesActions.test.ts
b/superset-frontend/src/explore/actions/datasourcesActions.test.ts
index cf599c6b4f..ace4913c9b 100644
--- a/superset-frontend/src/explore/actions/datasourcesActions.test.ts
+++ b/superset-frontend/src/explore/actions/datasourcesActions.test.ts
@@ -41,7 +41,6 @@ const CURRENT_DATASOURCE = {
columns: [],
metrics: [],
column_formats: {},
- currency_formats: {},
verbose_map: {},
main_dttm_col: '__timestamp',
// eg. ['["ds", true]', 'ds [asc]']
@@ -55,7 +54,6 @@ const NEW_DATASOURCE = {
columns: [],
metrics: [],
column_formats: {},
- currency_formats: {},
verbose_map: {},
main_dttm_col: '__timestamp',
// eg. ['["ds", true]', 'ds [asc]']
diff --git a/superset-frontend/src/explore/controlUtils/controlUtils.test.tsx
b/superset-frontend/src/explore/controlUtils/controlUtils.test.tsx
index e623fcb3f7..24fb753b8f 100644
--- a/superset-frontend/src/explore/controlUtils/controlUtils.test.tsx
+++ b/superset-frontend/src/explore/controlUtils/controlUtils.test.tsx
@@ -56,7 +56,6 @@ describe('controlUtils', () => {
{ metric_name: 'second', uuid: '2' },
],
column_formats: {},
- currency_formats: {},
verbose_map: {},
main_dttm_col: '',
datasource_name: '1__table',
diff --git
a/superset-frontend/src/explore/controlUtils/getControlValuesCompatibleWithDatasource.test.ts
b/superset-frontend/src/explore/controlUtils/getControlValuesCompatibleWithDatasource.test.ts
index b26ba8c1b4..cd8444346f 100644
---
a/superset-frontend/src/explore/controlUtils/getControlValuesCompatibleWithDatasource.test.ts
+++
b/superset-frontend/src/explore/controlUtils/getControlValuesCompatibleWithDatasource.test.ts
@@ -35,7 +35,6 @@ const sampleDatasource: Dataset = {
],
metrics: [{ metric_name: 'saved_metric_2', uuid: '1' }],
column_formats: {},
- currency_formats: {},
verbose_map: {},
main_dttm_col: '',
datasource_name: 'Sample Dataset',
diff --git a/superset-frontend/src/explore/fixtures.tsx
b/superset-frontend/src/explore/fixtures.tsx
index 211519590e..1e4afe9b61 100644
--- a/superset-frontend/src/explore/fixtures.tsx
+++ b/superset-frontend/src/explore/fixtures.tsx
@@ -138,7 +138,6 @@ export const exploreInitialData: ExplorePageInitialData = {
{ metric_name: 'second', uuid: '2' },
],
column_formats: {},
- currency_formats: {},
verbose_map: {},
main_dttm_col: '',
datasource_name: '8__table',
@@ -157,7 +156,6 @@ export const fallbackExploreInitialData:
ExplorePageInitialData = {
columns: [],
metrics: [],
column_formats: {},
- currency_formats: {},
verbose_map: {},
main_dttm_col: '',
owners: [],
diff --git a/superset-frontend/src/features/datasets/types.ts
b/superset-frontend/src/features/datasets/types.ts
index 63f1678d79..d343ad153d 100644
--- a/superset-frontend/src/features/datasets/types.ts
+++ b/superset-frontend/src/features/datasets/types.ts
@@ -78,7 +78,6 @@ export type DatasetObject = {
always_filter_main_dttm: boolean;
type: DatasourceType;
column_formats: Record<string, string>;
- currency_formats: Record<string, Currency>;
datasource_name: string | null;
verbose_map: Record<string, string>;
};
diff --git a/superset-frontend/src/utils/getDatasourceUid.test.ts
b/superset-frontend/src/utils/getDatasourceUid.test.ts
index ed7ec6256b..d3a629efcf 100644
--- a/superset-frontend/src/utils/getDatasourceUid.test.ts
+++ b/superset-frontend/src/utils/getDatasourceUid.test.ts
@@ -26,7 +26,6 @@ const TEST_DATASOURCE = {
columns: [],
metrics: [],
column_formats: {},
- currency_formats: {},
verbose_map: {},
main_dttm_col: '__timestamp',
// eg. ['["ds", true]', 'ds [asc]']
diff --git a/superset/connectors/sqla/models.py
b/superset/connectors/sqla/models.py
index d74b2ef5c0..275cc0cdbf 100644
--- a/superset/connectors/sqla/models.py
+++ b/superset/connectors/sqla/models.py
@@ -313,10 +313,6 @@ class BaseDatasource(AuditMixinNullable,
ImportExportMixin): # pylint: disable=
def column_formats(self) -> dict[str, str | None]:
return {m.metric_name: m.d3format for m in self.metrics if m.d3format}
- @property
- def currency_formats(self) -> dict[str, dict[str, str | None] | None]:
- return {m.metric_name: m.currency_json for m in self.metrics if
m.currency_json}
-
def add_missing_metrics(self, metrics: list[SqlMetric]) -> None:
existing_metrics = {m.metric_name for m in self.metrics}
for metric in metrics:
@@ -379,7 +375,6 @@ class BaseDatasource(AuditMixinNullable,
ImportExportMixin): # pylint: disable=
"id": self.id,
"uid": self.uid,
"column_formats": self.column_formats,
- "currency_formats": self.currency_formats,
"description": self.description,
"database": self.database.data, # pylint: disable=no-member
"default_endpoint": self.default_endpoint,
@@ -1046,7 +1041,7 @@ class SqlMetric(AuditMixinNullable, ImportExportMixin,
CertificationMixin, Model
metric_type = Column(String(32))
description = Column(utils.MediumText())
d3format = Column(String(128))
- currency = Column(String(128))
+ currency = Column(JSON, nullable=True)
warning_text = Column(Text)
table_id = Column(Integer, ForeignKey("tables.id", ondelete="CASCADE"))
expression = Column(utils.MediumText(), nullable=False)
@@ -1101,16 +1096,6 @@ class SqlMetric(AuditMixinNullable, ImportExportMixin,
CertificationMixin, Model
def get_perm(self) -> str | None:
return self.perm
- @property
- def currency_json(self) -> dict[str, str | None] | None:
- try:
- return json.loads(self.currency or "{}") or None
- except (TypeError, json.JSONDecodeError) as exc:
- logger.error(
- "Unable to load currency json: %r. Leaving empty.", exc,
exc_info=True
- )
- return None
-
@property
def data(self) -> dict[str, Any]:
attrs = (
diff --git a/superset/dashboards/schemas.py b/superset/dashboards/schemas.py
index db581f42df..c9fa1e3617 100644
--- a/superset/dashboards/schemas.py
+++ b/superset/dashboards/schemas.py
@@ -259,7 +259,6 @@ class DashboardDatasetSchema(Schema):
id = fields.Int()
uid = fields.Str()
column_formats = fields.Dict()
- currency_formats = fields.Dict()
database = fields.Nested(DatabaseSchema)
default_endpoint = fields.String()
filter_select = fields.Bool()
diff --git a/superset/datasets/api.py b/superset/datasets/api.py
index 85cc075667..9b84885b4c 100644
--- a/superset/datasets/api.py
+++ b/superset/datasets/api.py
@@ -221,7 +221,6 @@ class DatasetRestApi(BaseSupersetModelRestApi):
"datasource_name",
"name",
"column_formats",
- "currency_formats",
"granularity_sqla",
"time_grain_sqla",
"order_by_choices",
diff --git a/superset/explore/schemas.py b/superset/explore/schemas.py
index 5149f98ff5..0c2ffd9386 100644
--- a/superset/explore/schemas.py
+++ b/superset/explore/schemas.py
@@ -25,7 +25,6 @@ class DatasetSchema(Schema):
}
)
column_formats = fields.Dict(metadata={"description": "Column formats."})
- currency_formats = fields.Dict(metadata={"description": "Currency
formats."})
columns = fields.List(fields.Dict(), metadata={"description": "Columns
metadata."})
database = fields.Dict(
metadata={"description": "Database associated with the dataset."}
diff --git a/superset/migrations/shared/utils.py
b/superset/migrations/shared/utils.py
index 293dae59a0..a0a096168a 100644
--- a/superset/migrations/shared/utils.py
+++ b/superset/migrations/shared/utils.py
@@ -22,7 +22,17 @@ from typing import Any, Callable, Optional, Union
from uuid import uuid4
from alembic import op
-from sqlalchemy import Column, inspect
+from sqlalchemy import (
+ Column,
+ inspect,
+ JSON,
+ MetaData,
+ select,
+ String,
+ Table,
+ text,
+ update,
+)
from sqlalchemy.dialects.mysql.base import MySQLDialect
from sqlalchemy.dialects.postgresql.base import PGDialect
from sqlalchemy.dialects.sqlite.base import SQLiteDialect # noqa: E402
@@ -468,3 +478,116 @@ def create_fks_for_table(
remote_cols,
ondelete=ondelete,
)
+
+
+def cast_text_column_to_json(
+ table: str,
+ column: str,
+ pk: str = "id",
+ nullable: bool = True,
+ suffix: str = "_tmp",
+) -> None:
+ """
+ Cast a text column to JSON.
+
+ SQLAlchemy now has a nice abstraction for JSON columns, even if the
underlying
+ database doesn't support the type natively. We should always use it when
storing
+ JSON payloads.
+
+ :param table: The name of the table.
+ :param column: The name of the column to be cast.
+ :param pk: The name of the primary key column.
+ :param nullable: Whether the new column should be nullable.
+ :param suffix: The suffix to be added to the temporary column name.
+ """
+ conn = op.get_bind()
+
+ if isinstance(conn.dialect, PGDialect):
+ conn.execute(
+ text(
+ f"""
+ ALTER TABLE {table}
+ ALTER COLUMN {column} TYPE jsonb
+ USING {column}::jsonb
+ """
+ )
+ )
+ return
+
+ tmp_column = column + suffix
+ op.add_column(
+ table,
+ Column(tmp_column, JSON(), nullable=nullable),
+ )
+
+ meta = MetaData()
+ t = Table(table, meta, autoload_with=conn)
+ stmt_select = select(t.c[pk], t.c[column]).where(t.c[column].is_not(None))
+
+ for row_pk, value in conn.execute(stmt_select):
+ stmt_update = update(t).where(t.c[pk] == row_pk).values({tmp_column:
value})
+ conn.execute(stmt_update)
+
+ op.drop_column(table, column)
+ op.alter_column(table, tmp_column, existing_type=JSON(),
new_column_name=column)
+
+ return
+
+
+def cast_json_column_to_text(
+ table: str,
+ column: str,
+ pk: str = "id",
+ nullable: bool = True,
+ suffix: str = "_tmp",
+ length: int = 128,
+) -> None:
+ """
+ Cast a JSON column back to text.
+
+ :param table: The name of the table.
+ :param column: The name of the column to be cast.
+ :param pk: The name of the primary key column.
+ :param nullable: Whether the new column should be nullable.
+ :param suffix: The suffix to be added to the temporary column name.
+ :param length: The length of the text column.
+ """
+ conn = op.get_bind()
+
+ if isinstance(conn.dialect, PGDialect):
+ conn.execute(
+ text(
+ f"""
+ ALTER TABLE {table}
+ ALTER COLUMN {column} TYPE text
+ USING {column}::text
+ """
+ )
+ )
+ return
+
+ tmp_column = column + suffix
+ op.add_column(
+ table,
+ Column(tmp_column, String(length=length), nullable=nullable),
+ )
+
+ meta = MetaData()
+ t = Table(table, meta, autoload_with=conn)
+ stmt_select = select(t.c[pk], t.c[column]).where(t.c[column].is_not(None))
+
+ for row_pk, value in conn.execute(stmt_select):
+ stmt_update = (
+ update(t).where(t.c[pk] == row_pk).values({tmp_column:
json.dumps(value)})
+ )
+ conn.execute(stmt_update)
+
+ op.drop_column(table, column)
+ op.alter_column(
+ table,
+ tmp_column,
+ existing_type=String(length=length),
+ new_column_name=column,
+ )
+
+ return
diff --git
a/superset/migrations/versions/2025-04-30_11-04_f1edd4a4d4f2_metric_currency_should_be_json.py
b/superset/migrations/versions/2025-04-30_11-04_f1edd4a4d4f2_metric_currency_should_be_json.py
new file mode 100644
index 0000000000..c01f2a33e9
--- /dev/null
+++
b/superset/migrations/versions/2025-04-30_11-04_f1edd4a4d4f2_metric_currency_should_be_json.py
@@ -0,0 +1,46 @@
+# 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.
+"""metric currency should be JSON
+
+Revision ID: f1edd4a4d4f2
+Revises: 378cecfdba9f
+Create Date: 2025-04-30 11:04:39.105229
+
+"""
+
+from superset.migrations.shared.utils import (
+ cast_json_column_to_text,
+ cast_text_column_to_json,
+)
+
+# revision identifiers, used by Alembic.
+revision = "f1edd4a4d4f2"
+down_revision = "378cecfdba9f"
+
+
+def upgrade():
+ """
+ Convert the currency column to JSON.
+ """
+ cast_text_column_to_json("sql_metrics", "currency")
+
+
+def downgrade():
+ """
+ Convert the currency column back to text.
+ """
+ cast_json_column_to_text("sql_metrics", "currency")