You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by mi...@apache.org on 2023/11/06 12:47:05 UTC
(superset) branch 3.0 updated (28272527fc -> a368c8dd82)
This is an automated email from the ASF dual-hosted git repository.
michaelsmolina pushed a change to branch 3.0
in repository https://gitbox.apache.org/repos/asf/superset.git
from 28272527fc fix(sqllab): infinite fetching status after results are landed (#25814)
new 925c63d4a6 fix: Fires onChange when clearing all values of single select (#25853)
new d5901140a7 fix: the temporal x-axis results in a none time_range. (#25429)
new 4534a070df fix(table chart): Show Cell Bars correctly #25625 (#25707)
new a368c8dd82 fix: remove `update_charts_owners` (#25843)
The 4 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
.../plugins/plugin-chart-table/src/TableChart.tsx | 12 ++++-
.../plugin-chart-table/test/TableChart.test.tsx | 59 ++++++++++++++++++++++
.../src/components/Select/AsyncSelect.test.tsx | 28 ++++++++++
.../src/components/Select/Select.test.tsx | 28 ++++++++++
superset-frontend/src/components/Select/Select.tsx | 2 +-
superset/common/query_object_factory.py | 45 +++++++++++++++--
superset/daos/dashboard.py | 9 ----
superset/dashboards/commands/create.py | 1 -
superset/dashboards/commands/update.py | 1 -
tests/integration_tests/dashboards/api_tests.py | 49 ------------------
tests/unit_tests/common/test_process_time_range.py | 48 ++++++++++++++++++
11 files changed, 216 insertions(+), 66 deletions(-)
create mode 100644 tests/unit_tests/common/test_process_time_range.py
(superset) 01/04: fix: Fires onChange when clearing all values of single select (#25853)
Posted by mi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
michaelsmolina pushed a commit to branch 3.0
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 925c63d4a6c74b77aa11a924b411b3c8156ef3da
Author: Michael S. Molina <70...@users.noreply.github.com>
AuthorDate: Fri Nov 3 10:35:43 2023 -0300
fix: Fires onChange when clearing all values of single select (#25853)
(cherry picked from commit 8061d5cce982b0b828f5de69647a1f5b75f41a46)
---
.../src/components/Select/AsyncSelect.test.tsx | 28 ++++++++++++++++++++++
.../src/components/Select/Select.test.tsx | 28 ++++++++++++++++++++++
superset-frontend/src/components/Select/Select.tsx | 2 +-
3 files changed, 57 insertions(+), 1 deletion(-)
diff --git a/superset-frontend/src/components/Select/AsyncSelect.test.tsx b/superset-frontend/src/components/Select/AsyncSelect.test.tsx
index e49f00be53..c1442a6b70 100644
--- a/superset-frontend/src/components/Select/AsyncSelect.test.tsx
+++ b/superset-frontend/src/components/Select/AsyncSelect.test.tsx
@@ -840,6 +840,34 @@ test('does not fire onChange when searching but no selection', async () => {
expect(onChange).toHaveBeenCalledTimes(1);
});
+test('fires onChange when clearing the selection in single mode', async () => {
+ const onChange = jest.fn();
+ render(
+ <AsyncSelect
+ {...defaultProps}
+ onChange={onChange}
+ mode="single"
+ value={OPTIONS[0]}
+ />,
+ );
+ clearAll();
+ expect(onChange).toHaveBeenCalledTimes(1);
+});
+
+test('fires onChange when clearing the selection in multiple mode', async () => {
+ const onChange = jest.fn();
+ render(
+ <AsyncSelect
+ {...defaultProps}
+ onChange={onChange}
+ mode="multiple"
+ value={OPTIONS[0]}
+ />,
+ );
+ clearAll();
+ expect(onChange).toHaveBeenCalledTimes(1);
+});
+
test('does not duplicate options when using numeric values', async () => {
render(
<AsyncSelect
diff --git a/superset-frontend/src/components/Select/Select.test.tsx b/superset-frontend/src/components/Select/Select.test.tsx
index 52e566df17..a6b8307582 100644
--- a/superset-frontend/src/components/Select/Select.test.tsx
+++ b/superset-frontend/src/components/Select/Select.test.tsx
@@ -957,6 +957,34 @@ test('does not fire onChange when searching but no selection', async () => {
expect(onChange).toHaveBeenCalledTimes(1);
});
+test('fires onChange when clearing the selection in single mode', async () => {
+ const onChange = jest.fn();
+ render(
+ <Select
+ {...defaultProps}
+ onChange={onChange}
+ mode="single"
+ value={OPTIONS[0]}
+ />,
+ );
+ clearAll();
+ expect(onChange).toHaveBeenCalledTimes(1);
+});
+
+test('fires onChange when clearing the selection in multiple mode', async () => {
+ const onChange = jest.fn();
+ render(
+ <Select
+ {...defaultProps}
+ onChange={onChange}
+ mode="multiple"
+ value={OPTIONS[0]}
+ />,
+ );
+ clearAll();
+ expect(onChange).toHaveBeenCalledTimes(1);
+});
+
test('does not duplicate options when using numeric values', async () => {
render(
<Select
diff --git a/superset-frontend/src/components/Select/Select.tsx b/superset-frontend/src/components/Select/Select.tsx
index 907850a456..6ccc1e1715 100644
--- a/superset-frontend/src/components/Select/Select.tsx
+++ b/superset-frontend/src/components/Select/Select.tsx
@@ -281,8 +281,8 @@ const Select = forwardRef(
: option.value,
),
);
- fireOnChange();
}
+ fireOnChange();
};
const handleOnDeselect: SelectProps['onDeselect'] = (value, option) => {
(superset) 02/04: fix: the temporal x-axis results in a none time_range. (#25429)
Posted by mi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
michaelsmolina pushed a commit to branch 3.0
in repository https://gitbox.apache.org/repos/asf/superset.git
commit d5901140a7a2c56b6fbb031bef30ee15db254131
Author: mapledan <ma...@gmail.com>
AuthorDate: Sat Nov 4 00:57:47 2023 +0800
fix: the temporal x-axis results in a none time_range. (#25429)
Co-authored-by: Elizabeth Thompson <es...@gmail.com>
(cherry picked from commit ae619b169c2b02bbc2a72fd10fd23ad3322c45f6)
---
superset/common/query_object_factory.py | 45 ++++++++++++++++++--
tests/unit_tests/common/test_process_time_range.py | 48 ++++++++++++++++++++++
2 files changed, 90 insertions(+), 3 deletions(-)
diff --git a/superset/common/query_object_factory.py b/superset/common/query_object_factory.py
index ae85912cdf..9246aed9e0 100644
--- a/superset/common/query_object_factory.py
+++ b/superset/common/query_object_factory.py
@@ -16,12 +16,21 @@
# under the License.
from __future__ import annotations
-from typing import Any, TYPE_CHECKING
+from typing import Any, cast, TYPE_CHECKING
from superset.common.chart_data import ChartDataResultType
from superset.common.query_object import QueryObject
from superset.common.utils.time_range_utils import get_since_until_from_time_range
-from superset.utils.core import apply_max_row_limit, DatasourceDict, DatasourceType
+from superset.constants import NO_TIME_RANGE
+from superset.superset_typing import Column
+from superset.utils.core import (
+ apply_max_row_limit,
+ DatasourceDict,
+ DatasourceType,
+ FilterOperator,
+ get_xaxis_label,
+ QueryObjectFilterClause,
+)
if TYPE_CHECKING:
from sqlalchemy.orm import sessionmaker
@@ -61,8 +70,11 @@ class QueryObjectFactory: # pylint: disable=too-few-public-methods
processed_extras = self._process_extras(extras)
result_type = kwargs.setdefault("result_type", parent_result_type)
row_limit = self._process_row_limit(row_limit, result_type)
+ processed_time_range = self._process_time_range(
+ time_range, kwargs.get("filters"), kwargs.get("columns")
+ )
from_dttm, to_dttm = get_since_until_from_time_range(
- time_range, time_shift, processed_extras
+ processed_time_range, time_shift, processed_extras
)
kwargs["from_dttm"] = from_dttm
kwargs["to_dttm"] = to_dttm
@@ -99,6 +111,33 @@ class QueryObjectFactory: # pylint: disable=too-few-public-methods
)
return apply_max_row_limit(row_limit or default_row_limit)
+ @staticmethod
+ def _process_time_range(
+ time_range: str | None,
+ filters: list[QueryObjectFilterClause] | None = None,
+ columns: list[Column] | None = None,
+ ) -> str:
+ if time_range is None:
+ time_range = NO_TIME_RANGE
+ temporal_flt = [
+ flt
+ for flt in filters or []
+ if flt.get("op") == FilterOperator.TEMPORAL_RANGE
+ ]
+ if temporal_flt:
+ # Use the temporal filter as the time range.
+ # if the temporal filters uses x-axis as the temporal filter
+ # then use it or use the first temporal filter
+ xaxis_label = get_xaxis_label(columns or [])
+ match_flt = [
+ flt for flt in temporal_flt if flt.get("col") == xaxis_label
+ ]
+ if match_flt:
+ time_range = cast(str, match_flt[0].get("val"))
+ else:
+ time_range = cast(str, temporal_flt[0].get("val"))
+ return time_range
+
# light version of the view.utils.core
# import view.utils require application context
# Todo: move it and the view.utils.core to utils package
diff --git a/tests/unit_tests/common/test_process_time_range.py b/tests/unit_tests/common/test_process_time_range.py
new file mode 100644
index 0000000000..12ee6d21aa
--- /dev/null
+++ b/tests/unit_tests/common/test_process_time_range.py
@@ -0,0 +1,48 @@
+# 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.
+from superset.common.query_object_factory import QueryObjectFactory
+from superset.constants import NO_TIME_RANGE
+
+
+def test_process_time_range():
+ """
+ correct empty time range
+ """
+ assert QueryObjectFactory._process_time_range(None) == NO_TIME_RANGE
+
+ """
+ Use the first temporal filter as time range
+ """
+ filters = [
+ {"col": "dttm", "op": "TEMPORAL_RANGE", "val": "2001 : 2002"},
+ {"col": "dttm2", "op": "TEMPORAL_RANGE", "val": "2002 : 2003"},
+ ]
+ assert QueryObjectFactory._process_time_range(None, filters) == "2001 : 2002"
+
+ """
+ Use the BASE_AXIS temporal filter as time range
+ """
+ columns = [
+ {
+ "columnType": "BASE_AXIS",
+ "label": "dttm2",
+ "sqlExpression": "dttm",
+ }
+ ]
+ assert (
+ QueryObjectFactory._process_time_range(None, filters, columns) == "2002 : 2003"
+ )
(superset) 04/04: fix: remove `update_charts_owners` (#25843)
Posted by mi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
michaelsmolina pushed a commit to branch 3.0
in repository https://gitbox.apache.org/repos/asf/superset.git
commit a368c8dd82cc166301f580d06007b3f61e8d91e9
Author: Beto Dealmeida <ro...@dealmeida.net>
AuthorDate: Fri Nov 3 13:06:31 2023 -0400
fix: remove `update_charts_owners` (#25843)
---
superset/daos/dashboard.py | 9 -----
superset/dashboards/commands/create.py | 1 -
superset/dashboards/commands/update.py | 1 -
tests/integration_tests/dashboards/api_tests.py | 49 -------------------------
4 files changed, 60 deletions(-)
diff --git a/superset/daos/dashboard.py b/superset/daos/dashboard.py
index 2c03711f25..8cbdfcd77d 100644
--- a/superset/daos/dashboard.py
+++ b/superset/daos/dashboard.py
@@ -181,15 +181,6 @@ class DashboardDAO(BaseDAO[Dashboard]):
return not db.session.query(dashboard_query.exists()).scalar()
return True
- @staticmethod
- def update_charts_owners(model: Dashboard, commit: bool = True) -> Dashboard:
- owners = list(model.owners)
- for slc in model.slices:
- slc.owners = list(set(owners) | set(slc.owners))
- if commit:
- db.session.commit()
- return model
-
@classmethod
def delete(cls, items: Dashboard | list[Dashboard], commit: bool = True) -> None:
item_ids = [item.id for item in get_iterable(items)]
diff --git a/superset/dashboards/commands/create.py b/superset/dashboards/commands/create.py
index 98ecd6eb78..5d08a65f95 100644
--- a/superset/dashboards/commands/create.py
+++ b/superset/dashboards/commands/create.py
@@ -41,7 +41,6 @@ class CreateDashboardCommand(CreateMixin, BaseCommand):
self.validate()
try:
dashboard = DashboardDAO.create(self._properties, commit=False)
- dashboard = DashboardDAO.update_charts_owners(dashboard, commit=True)
except DAOCreateFailedError as ex:
logger.exception(ex.exception)
raise DashboardCreateFailedError() from ex
diff --git a/superset/dashboards/commands/update.py b/superset/dashboards/commands/update.py
index c880eebe89..f9975c0dd2 100644
--- a/superset/dashboards/commands/update.py
+++ b/superset/dashboards/commands/update.py
@@ -58,7 +58,6 @@ class UpdateDashboardCommand(UpdateMixin, BaseCommand):
data=json.loads(self._properties.get("json_metadata", "{}")),
commit=False,
)
- dashboard = DashboardDAO.update_charts_owners(dashboard, commit=False)
db.session.commit()
except DAOUpdateFailedError as ex:
logger.exception(ex.exception)
diff --git a/tests/integration_tests/dashboards/api_tests.py b/tests/integration_tests/dashboards/api_tests.py
index f676e873b7..cc7bc109b4 100644
--- a/tests/integration_tests/dashboards/api_tests.py
+++ b/tests/integration_tests/dashboards/api_tests.py
@@ -1378,55 +1378,6 @@ class TestDashboardApi(SupersetTestCase, ApiOwnersTestCaseMixin, InsertChartMixi
db.session.delete(model)
db.session.commit()
- @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
- def test_update_dashboard_chart_owners(self):
- """
- Dashboard API: Test update chart owners
- """
- user_alpha1 = self.create_user(
- "alpha1", "password", "Alpha", email="alpha1@superset.org"
- )
- user_alpha2 = self.create_user(
- "alpha2", "password", "Alpha", email="alpha2@superset.org"
- )
- admin = self.get_user("admin")
- slices = []
- slices.append(
- db.session.query(Slice).filter_by(slice_name="Girl Name Cloud").first()
- )
- slices.append(db.session.query(Slice).filter_by(slice_name="Trends").first())
- slices.append(db.session.query(Slice).filter_by(slice_name="Boys").first())
-
- dashboard = self.insert_dashboard(
- "title1",
- "slug1",
- [admin.id],
- slices=slices,
- )
- self.login(username="admin")
- uri = f"api/v1/dashboard/{dashboard.id}"
- dashboard_data = {"owners": [user_alpha1.id, user_alpha2.id]}
- rv = self.client.put(uri, json=dashboard_data)
- self.assertEqual(rv.status_code, 200)
-
- # verify slices owners include alpha1 and alpha2 users
- slices_ids = [slice.id for slice in slices]
- # Refetch Slices
- slices = db.session.query(Slice).filter(Slice.id.in_(slices_ids)).all()
- for slice in slices:
- self.assertIn(user_alpha1, slice.owners)
- self.assertIn(user_alpha2, slice.owners)
- self.assertNotIn(admin, slice.owners)
- # Revert owners on slice
- slice.owners = []
- db.session.commit()
-
- # Rollback changes
- db.session.delete(dashboard)
- db.session.delete(user_alpha1)
- db.session.delete(user_alpha2)
- db.session.commit()
-
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
def test_update_dashboard_chart_owners_propagation(self):
"""
(superset) 03/04: fix(table chart): Show Cell Bars correctly #25625 (#25707)
Posted by mi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
michaelsmolina pushed a commit to branch 3.0
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 4534a070dffc47a436ac2c377177a38610b69d3d
Author: Arko <90...@users.noreply.github.com>
AuthorDate: Mon Nov 6 03:36:21 2023 -0800
fix(table chart): Show Cell Bars correctly #25625 (#25707)
(cherry picked from commit 916f7bcbbae6786bc6320f31b8e5af49ad119ac9)
---
.../plugins/plugin-chart-table/src/TableChart.tsx | 12 ++++-
.../plugin-chart-table/test/TableChart.test.tsx | 59 ++++++++++++++++++++++
2 files changed, 69 insertions(+), 2 deletions(-)
diff --git a/superset-frontend/plugins/plugin-chart-table/src/TableChart.tsx b/superset-frontend/plugins/plugin-chart-table/src/TableChart.tsx
index 72bcb71d29..917abb929a 100644
--- a/superset-frontend/plugins/plugin-chart-table/src/TableChart.tsx
+++ b/superset-frontend/plugins/plugin-chart-table/src/TableChart.tsx
@@ -409,7 +409,15 @@ export default function TableChart<D extends DataRecord = DataRecord>(
const getColumnConfigs = useCallback(
(column: DataColumnMeta, i: number): ColumnWithLooseAccessor<D> => {
- const { key, label, isNumeric, dataType, isMetric, config = {} } = column;
+ const {
+ key,
+ label,
+ isNumeric,
+ dataType,
+ isMetric,
+ isPercentMetric,
+ config = {},
+ } = column;
const columnWidth = Number.isNaN(Number(config.columnWidth))
? config.columnWidth
: Number(config.columnWidth);
@@ -438,7 +446,7 @@ export default function TableChart<D extends DataRecord = DataRecord>(
(config.showCellBars === undefined
? showCellBars
: config.showCellBars) &&
- (isMetric || isRawRecords) &&
+ (isMetric || isRawRecords || isPercentMetric) &&
getValueRange(key, alignPositiveNegative);
let className = '';
diff --git a/superset-frontend/plugins/plugin-chart-table/test/TableChart.test.tsx b/superset-frontend/plugins/plugin-chart-table/test/TableChart.test.tsx
index 9bc3d90a09..d6998476ba 100644
--- a/superset-frontend/plugins/plugin-chart-table/test/TableChart.test.tsx
+++ b/superset-frontend/plugins/plugin-chart-table/test/TableChart.test.tsx
@@ -76,6 +76,7 @@ describe('plugin-chart-table', () => {
wrap = mount(
<TableChart {...transformProps(testData.basic)} sticky={false} />,
);
+
tree = wrap.render(); // returns a CheerioWrapper with jQuery-like API
const cells = tree.find('td');
expect(cells).toHaveLength(12);
@@ -158,6 +159,7 @@ describe('plugin-chart-table', () => {
}),
);
const cells = document.querySelectorAll('td');
+
expect(document.querySelectorAll('th')[0]).toHaveTextContent('num');
expect(cells[0]).toHaveTextContent('$ 1.23k');
expect(cells[1]).toHaveTextContent('$ 10k');
@@ -242,4 +244,61 @@ describe('plugin-chart-table', () => {
expect(getComputedStyle(screen.getByText('N/A')).background).toBe('');
});
});
+
+ it('render cell bars properly, and only when it is toggled on in both regular and percent metrics', () => {
+ const props = transformProps({
+ ...testData.raw,
+ rawFormData: { ...testData.raw.rawFormData },
+ });
+
+ props.columns[0].isMetric = true;
+
+ render(
+ ProviderWrapper({
+ children: <TableChart {...props} sticky={false} />,
+ }),
+ );
+ let cells = document.querySelectorAll('div.cell-bar');
+ cells.forEach(cell => {
+ expect(cell).toHaveClass('positive');
+ });
+ props.columns[0].isMetric = false;
+ props.columns[0].isPercentMetric = true;
+
+ render(
+ ProviderWrapper({
+ children: <TableChart {...props} sticky={false} />,
+ }),
+ );
+ cells = document.querySelectorAll('div.cell-bar');
+ cells.forEach(cell => {
+ expect(cell).toHaveClass('positive');
+ });
+
+ props.showCellBars = false;
+
+ render(
+ ProviderWrapper({
+ children: <TableChart {...props} sticky={false} />,
+ }),
+ );
+ cells = document.querySelectorAll('td');
+
+ cells.forEach(cell => {
+ expect(cell).toHaveClass('test-c7w8t3');
+ });
+
+ props.columns[0].isPercentMetric = false;
+ props.columns[0].isMetric = true;
+
+ render(
+ ProviderWrapper({
+ children: <TableChart {...props} sticky={false} />,
+ }),
+ );
+ cells = document.querySelectorAll('td');
+ cells.forEach(cell => {
+ expect(cell).toHaveClass('test-c7w8t3');
+ });
+ });
});