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/08/10 16:16:34 UTC

[superset] branch 3.0 updated (abead484e1 -> 181f3fd06e)

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 abead484e1 fix: Migration to fix out of sync schema_perm in charts and datasets (#24884)
     new ed56375d5f fix(logs): increase json field for logs table (#24911)
     new 42451880a8 fix(explore): double resize triggered (#24886)
     new df92cc2d55 fix: Tooltip of area chart shows undefined total (#24916)
     new 804cc36080 chore: Refactor dashboard security access (#24804)
     new dba72c4197 chore: Refine native dashboard cleanup logic (#24864)
     new 309582516d fix: Dashboard aware RBAC "Save as" menu item (#24806)
     new 9b3ec806cd chore: Add explicit ON DELETE CASCADE for dashboard_slices (#24938)
     new 1e20c0bf8a chore: Add explicit ON DELETE CASCADE for embedded_dashboards (#24939)
     new 2574e11544 chore: Removes duplicated featureFlags.ts (#24935)
     new 181f3fd06e fix: remove unused file (#24946)

The 10 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:
 UPDATING.md                                        |   2 +
 .../superset-ui-core/src/utils/featureFlags.ts     |  10 +-
 .../test/utils/featureFlag.test.ts                 |  52 ++++++---
 .../plugins/legacy-preset-chart-nvd3/src/utils.js  |   9 +-
 superset-frontend/src/SqlLab/App.jsx               |   8 +-
 superset-frontend/src/SqlLab/actions/sqlLab.js     |   8 +-
 .../src/SqlLab/actions/sqlLab.test.js              |   6 +-
 .../ShareSqlLabQuery/ShareSqlLabQuery.test.tsx     |   6 +-
 .../SqlLab/components/ShareSqlLabQuery/index.tsx   |   9 +-
 .../src/SqlLab/components/SouthPane/index.tsx      |   3 +-
 .../src/SqlLab/components/SqlEditor/index.jsx      |   2 +-
 .../SqlLab/components/TabbedSqlEditors/index.jsx   |   3 +-
 .../components/TableElement/TableElement.test.tsx  |   4 +-
 superset-frontend/src/components/Chart/Chart.jsx   |   2 +-
 .../src/components/Chart/chartAction.js            |   9 +-
 .../src/components/Datasource/DatasourceEditor.jsx |   2 +-
 .../Datasource/DatasourceEditor.test.jsx           |   6 +-
 .../components/Datasource/DatasourceModal.test.jsx |   5 +-
 .../src/components/Datasource/DatasourceModal.tsx  |   3 +-
 .../src/components/DynamicPlugins/index.tsx        |   2 +-
 .../src/components/ErrorMessage/types.ts           |   1 +
 .../components/ReportModal/ReportModal.test.tsx    |   6 +-
 .../src/dashboard/actions/dashboardState.js        |   2 +-
 .../src/dashboard/actions/dashboardState.test.js   |   4 +-
 superset-frontend/src/dashboard/actions/hydrate.js |  10 +-
 .../src/dashboard/actions/sliceEntities.ts         |   2 +-
 .../DashboardBuilder/DashboardBuilder.test.tsx     |  42 ++++----
 .../DashboardBuilder/DashboardBuilder.tsx          |   2 +-
 .../dashboard/components/DashboardBuilder/state.ts |   3 +-
 .../HeaderActionsDropdown.test.tsx                 |  10 +-
 .../Header/HeaderActionsDropdown/index.jsx         |  10 +-
 .../src/dashboard/components/Header/index.jsx      |   2 +-
 .../PropertiesModal/PropertiesModal.test.tsx       |   3 +-
 .../dashboard/components/PropertiesModal/index.tsx |   2 +-
 .../src/dashboard/components/SaveModal.tsx         |   2 +-
 .../components/SliceHeaderControls/index.tsx       |   2 +-
 .../dashboard/components/gridComponents/Chart.jsx  |  14 ++-
 .../FilterBar/FilterControls/FilterValue.tsx       |   2 +-
 .../nativeFilters/FilterBar/Vertical.tsx           |   2 +-
 .../FiltersConfigForm/FiltersConfigForm.tsx        |   2 +-
 .../components/nativeFilters/utils.test.ts         |   8 +-
 .../dashboard/components/nativeFilters/utils.ts    |   2 +-
 .../src/dashboard/util/permissionUtils.test.ts     |  93 ++++++++++++++---
 .../src/dashboard/util/permissionUtils.ts          |  11 ++
 superset-frontend/src/dataMask/actions.ts          |   2 +-
 superset-frontend/src/dataMask/reducer.ts          |   2 +-
 .../components/DataTablesPane/DataTablesPane.tsx   |   9 +-
 .../explore/components/DatasourcePanel/index.tsx   |   2 +-
 .../explore/components/ExploreChartPanel/index.jsx |   2 +-
 .../src/explore/components/SaveModal.tsx           |   2 +-
 ...AdhocFilterEditPopoverSimpleTabContent.test.tsx |   4 +-
 .../index.tsx                                      |   2 +-
 .../useExploreAdditionalActionsMenu/index.jsx      |  10 +-
 superset-frontend/src/featureFlags.ts              |  37 -------
 .../src/features/alerts/AlertReportModal.tsx       |   2 +-
 .../src/features/charts/ChartCard.tsx              |   3 +-
 .../src/features/dashboards/DashboardCard.tsx      |   3 +-
 superset-frontend/src/features/tags/TagCard.tsx    |   3 +-
 .../src/middleware/asyncEvent.test.ts              |   4 +-
 superset-frontend/src/middleware/asyncEvent.ts     |   2 +-
 .../src/pages/ChartCreation/index.tsx              |   2 +-
 .../src/pages/ChartList/ChartList.test.jsx         |   6 +-
 superset-frontend/src/pages/ChartList/index.tsx    |   2 +-
 .../src/pages/DashboardList/DashboardList.test.jsx |   6 +-
 .../src/pages/DashboardList/index.tsx              |   9 +-
 superset-frontend/src/pages/DatabaseList/index.tsx |   2 +-
 .../src/pages/DatasetList/DatasetList.test.tsx     |   4 +-
 superset-frontend/src/pages/DatasetList/index.tsx  |   2 +-
 superset-frontend/src/pages/Home/Home.test.tsx     |   4 +-
 superset-frontend/src/pages/Home/index.tsx         |   2 +-
 .../pages/SavedQueryList/SavedQueryList.test.jsx   |   4 +-
 .../src/pages/SavedQueryList/index.tsx             |   9 +-
 superset-frontend/src/pages/Tags/index.tsx         |   3 +-
 superset-frontend/src/preamble.ts                  |   8 +-
 superset-frontend/src/utils/hostNamesConfig.js     |   2 +-
 superset-frontend/src/views/routes.test.tsx        |   4 -
 superset/cli/native_filters.py                     |  39 +++----
 superset/daos/chart.py                             |   3 -
 superset/daos/dashboard.py                         |  26 +++--
 superset/dashboards/api.py                         |   6 +-
 superset/errors.py                                 |   2 +-
 ...y => 2023-08-08_14-14_2e826adca42c_log_json.py} |  20 ++--
 ..._add_on_delete_cascade_for_dashboard_slices.py} |  37 +++----
 ...n_delete_cascade_for_embedded_dashboards.py.py} |  36 ++-----
 superset/models/dashboard.py                       |  13 ++-
 superset/models/embedded_dashboard.py              |   6 +-
 superset/security/manager.py                       | 116 +++++++++++----------
 .../utils/dashboard_filter_scopes_converter.py     |  17 +--
 superset/views/core.py                             |  12 ++-
 tests/integration_tests/charts/api_tests.py        |   1 -
 tests/integration_tests/dashboard_tests.py         |   3 -
 .../dashboards/security/security_rbac_tests.py     |  90 +++++++++++++++-
 .../security/guest_token_security_tests.py         |  19 ++--
 tests/integration_tests/tagging_tests.py           |   2 -
 94 files changed, 589 insertions(+), 405 deletions(-)
 delete mode 100644 superset-frontend/src/featureFlags.ts
 copy superset/migrations/versions/{2018-08-13_11-30_1a1d627ebd8e_position_json.py => 2023-08-08_14-14_2e826adca42c_log_json.py} (78%)
 copy superset/migrations/versions/{2023-06-22_13-39_6fbe660cac39_add_on_delete_cascade_for_tables_references.py => 2023-08-09_14-17_8ace289026f3_add_on_delete_cascade_for_dashboard_slices.py} (64%)
 copy superset/migrations/versions/{2023-06-22_13-39_6fbe660cac39_add_on_delete_cascade_for_tables_references.py => 2023-08-09_15-39_4448fa6deeb1__dd_on_delete_cascade_for_embedded_dashboards.py.py} (61%)


[superset] 10/10: fix: remove unused file (#24946)

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 181f3fd06eacd2f62a06fa53aaac190cb60a2bcc
Author: Multazim Deshmukh <57...@users.noreply.github.com>
AuthorDate: Thu Aug 10 20:38:54 2023 +0530

    fix: remove unused file (#24946)
    
    (cherry picked from commit bcd24936bce276c6b4b149055f211abfe2dab396)
---
 _update-notifier-last-checked | 0
 1 file changed, 0 insertions(+), 0 deletions(-)

diff --git a/_update-notifier-last-checked b/_update-notifier-last-checked
deleted file mode 100644
index e69de29bb2..0000000000


[superset] 08/10: chore: Add explicit ON DELETE CASCADE for embedded_dashboards (#24939)

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 1e20c0bf8a08c7e946f8cb2c9bf33145d97bb63e
Author: John Bodley <45...@users.noreply.github.com>
AuthorDate: Thu Aug 10 07:41:14 2023 -0700

    chore: Add explicit ON DELETE CASCADE for embedded_dashboards (#24939)
    
    Co-authored-by: Michael S. Molina <70...@users.noreply.github.com>
---
 UPDATING.md                                        |  1 +
 superset/daos/dashboard.py                         |  5 ---
 ...on_delete_cascade_for_embedded_dashboards.py.py | 48 ++++++++++++++++++++++
 superset/models/embedded_dashboard.py              |  6 ++-
 4 files changed, 54 insertions(+), 6 deletions(-)

diff --git a/UPDATING.md b/UPDATING.md
index 9542f9709e..bd462524ff 100644
--- a/UPDATING.md
+++ b/UPDATING.md
@@ -32,6 +32,7 @@ assists people when migrating to a new version.
 
 ## 3.0.0
 
+- [24939](https://github.com/apache/superset/pull/24939): Augments the foreign key constraints for the `embedded_dashboards` table to include an explicit CASCADE ON DELETE to ensure the relevant records are deleted when a dashboard is deleted. Scheduled downtime may be advised.
 - [24938](https://github.com/apache/superset/pull/24938): Augments the foreign key constraints for the `dashboard_slices` table to include an explicit CASCADE ON DELETE to ensure the relevant records are deleted when a dashboard or slice is deleted. Scheduled downtime may be advised.
 - [24628]https://github.com/apache/superset/pull/24628): Augments the foreign key constraints for the `dashboard_owner`, `report_schedule_owner`, and `slice_owner` tables to include an explicit CASCADE ON DELETE to ensure the relevant ownership records are deleted when a dataset is deleted. Scheduled downtime may be advised.
 - [24488](https://github.com/apache/superset/pull/24488): Augments the foreign key constraints for the `sql_metrics`, `sqlatable_user`, and `table_columns` tables which reference the `tables` table to include an explicit CASCADE ON DELETE to ensure the relevant records are deleted when a dataset is deleted. Scheduled downtime may be advised.
diff --git a/superset/daos/dashboard.py b/superset/daos/dashboard.py
index 425d640e77..f9544aa53d 100644
--- a/superset/daos/dashboard.py
+++ b/superset/daos/dashboard.py
@@ -195,11 +195,6 @@ class DashboardDAO(BaseDAO[Dashboard]):
     @classmethod
     def delete(cls, items: Dashboard | list[Dashboard], commit: bool = True) -> None:
         item_ids = [item.id for item in get_iterable(items)]
-        # bulk delete, first delete related data
-        for item in get_iterable(items):
-            item.embedded = []
-            db.session.merge(item)
-        # bulk delete itself
         try:
             db.session.query(Dashboard).filter(Dashboard.id.in_(item_ids)).delete(
                 synchronize_session="fetch"
diff --git a/superset/migrations/versions/2023-08-09_15-39_4448fa6deeb1__dd_on_delete_cascade_for_embedded_dashboards.py.py b/superset/migrations/versions/2023-08-09_15-39_4448fa6deeb1__dd_on_delete_cascade_for_embedded_dashboards.py.py
new file mode 100644
index 0000000000..b50f637514
--- /dev/null
+++ b/superset/migrations/versions/2023-08-09_15-39_4448fa6deeb1__dd_on_delete_cascade_for_embedded_dashboards.py.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.
+"""add on delete cascade for embedded dashboards
+
+Revision ID: 4448fa6deeb1
+Revises: 8ace289026f3
+Create Date: 2023-08-09 15:39:58.130228
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = "4448fa6deeb1"
+down_revision = "8ace289026f3"
+
+from superset.migrations.shared.constraints import ForeignKey, redefine
+
+foreign_keys = [
+    ForeignKey(
+        table="embedded_dashboards",
+        referent_table="dashboards",
+        local_cols=["dashboard_id"],
+        remote_cols=["id"],
+    ),
+]
+
+
+def upgrade():
+    for foreign_key in foreign_keys:
+        redefine(foreign_key, on_delete="CASCADE")
+
+
+def downgrade():
+    for foreign_key in foreign_keys:
+        redefine(foreign_key)
diff --git a/superset/models/embedded_dashboard.py b/superset/models/embedded_dashboard.py
index 32a8e4abce..3aed89534b 100644
--- a/superset/models/embedded_dashboard.py
+++ b/superset/models/embedded_dashboard.py
@@ -40,7 +40,11 @@ class EmbeddedDashboard(Model, AuditMixinNullable):
 
     uuid = Column(UUIDType(binary=True), default=uuid.uuid4, primary_key=True)
     allow_domain_list = Column(Text)  # reference the `allowed_domains` property instead
-    dashboard_id = Column(Integer, ForeignKey("dashboards.id"), nullable=False)
+    dashboard_id = Column(
+        Integer,
+        ForeignKey("dashboards.id", ondelete="CASCADE"),
+        nullable=False,
+    )
     dashboard = relationship(
         "Dashboard",
         back_populates="embedded",


[superset] 07/10: chore: Add explicit ON DELETE CASCADE for dashboard_slices (#24938)

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 9b3ec806cdac5fd5291918aa4ce8dea071e421dd
Author: John Bodley <45...@users.noreply.github.com>
AuthorDate: Thu Aug 10 06:56:11 2023 -0700

    chore: Add explicit ON DELETE CASCADE for dashboard_slices (#24938)
---
 UPDATING.md                                        |  1 +
 superset/daos/chart.py                             |  3 --
 superset/daos/dashboard.py                         |  1 -
 ...3_add_on_delete_cascade_for_dashboard_slices.py | 55 ++++++++++++++++++++++
 superset/models/dashboard.py                       |  4 +-
 tests/integration_tests/charts/api_tests.py        |  1 -
 tests/integration_tests/dashboard_tests.py         |  3 --
 .../security/guest_token_security_tests.py         |  2 -
 tests/integration_tests/tagging_tests.py           |  2 -
 9 files changed, 58 insertions(+), 14 deletions(-)

diff --git a/UPDATING.md b/UPDATING.md
index 27f3f36892..9542f9709e 100644
--- a/UPDATING.md
+++ b/UPDATING.md
@@ -32,6 +32,7 @@ assists people when migrating to a new version.
 
 ## 3.0.0
 
+- [24938](https://github.com/apache/superset/pull/24938): Augments the foreign key constraints for the `dashboard_slices` table to include an explicit CASCADE ON DELETE to ensure the relevant records are deleted when a dashboard or slice is deleted. Scheduled downtime may be advised.
 - [24628]https://github.com/apache/superset/pull/24628): Augments the foreign key constraints for the `dashboard_owner`, `report_schedule_owner`, and `slice_owner` tables to include an explicit CASCADE ON DELETE to ensure the relevant ownership records are deleted when a dataset is deleted. Scheduled downtime may be advised.
 - [24488](https://github.com/apache/superset/pull/24488): Augments the foreign key constraints for the `sql_metrics`, `sqlatable_user`, and `table_columns` tables which reference the `tables` table to include an explicit CASCADE ON DELETE to ensure the relevant records are deleted when a dataset is deleted. Scheduled downtime may be advised.
 - [24335](https://github.com/apache/superset/pull/24335): Removed deprecated API `/superset/filter/<datasource_type>/<int:datasource_id>/<column>/`
diff --git a/superset/daos/chart.py b/superset/daos/chart.py
index c8dc216a4d..a99e80da40 100644
--- a/superset/daos/chart.py
+++ b/superset/daos/chart.py
@@ -43,9 +43,6 @@ class ChartDAO(BaseDAO[Slice]):
     def delete(cls, items: Slice | list[Slice], commit: bool = True) -> None:
         item_ids = [item.id for item in get_iterable(items)]
         # bulk delete, first delete related data
-        for item in get_iterable(items):
-            item.dashboards = []
-            db.session.merge(item)
         # bulk delete itself
         try:
             db.session.query(Slice).filter(Slice.id.in_(item_ids)).delete(
diff --git a/superset/daos/dashboard.py b/superset/daos/dashboard.py
index f01d610448..425d640e77 100644
--- a/superset/daos/dashboard.py
+++ b/superset/daos/dashboard.py
@@ -197,7 +197,6 @@ class DashboardDAO(BaseDAO[Dashboard]):
         item_ids = [item.id for item in get_iterable(items)]
         # bulk delete, first delete related data
         for item in get_iterable(items):
-            item.slices = []
             item.embedded = []
             db.session.merge(item)
         # bulk delete itself
diff --git a/superset/migrations/versions/2023-08-09_14-17_8ace289026f3_add_on_delete_cascade_for_dashboard_slices.py b/superset/migrations/versions/2023-08-09_14-17_8ace289026f3_add_on_delete_cascade_for_dashboard_slices.py
new file mode 100644
index 0000000000..caac489bd1
--- /dev/null
+++ b/superset/migrations/versions/2023-08-09_14-17_8ace289026f3_add_on_delete_cascade_for_dashboard_slices.py
@@ -0,0 +1,55 @@
+# 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.
+"""add on delete cascade for dashboard slices
+
+Revision ID: 8ace289026f3
+Revises: 2e826adca42c
+Create Date: 2023-08-09 14:17:53.326191
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = "8ace289026f3"
+down_revision = "2e826adca42c"
+
+
+from superset.migrations.shared.constraints import ForeignKey, redefine
+
+foreign_keys = [
+    ForeignKey(
+        table="dashboard_slices",
+        referent_table="dashboards",
+        local_cols=["dashboard_id"],
+        remote_cols=["id"],
+    ),
+    ForeignKey(
+        table="dashboard_slices",
+        referent_table="slices",
+        local_cols=["slice_id"],
+        remote_cols=["id"],
+    ),
+]
+
+
+def upgrade():
+    for foreign_key in foreign_keys:
+        redefine(foreign_key, on_delete="CASCADE")
+
+
+def downgrade():
+    for foreign_key in foreign_keys:
+        redefine(foreign_key)
diff --git a/superset/models/dashboard.py b/superset/models/dashboard.py
index 0fecf15a55..719a6df8e4 100644
--- a/superset/models/dashboard.py
+++ b/superset/models/dashboard.py
@@ -105,8 +105,8 @@ dashboard_slices = Table(
     "dashboard_slices",
     metadata,
     Column("id", Integer, primary_key=True),
-    Column("dashboard_id", Integer, ForeignKey("dashboards.id")),
-    Column("slice_id", Integer, ForeignKey("slices.id")),
+    Column("dashboard_id", Integer, ForeignKey("dashboards.id", ondelete="CASCADE")),
+    Column("slice_id", Integer, ForeignKey("slices.id", ondelete="CASCADE")),
     UniqueConstraint("dashboard_id", "slice_id"),
 )
 
diff --git a/tests/integration_tests/charts/api_tests.py b/tests/integration_tests/charts/api_tests.py
index c5e88426fa..ae64eba807 100644
--- a/tests/integration_tests/charts/api_tests.py
+++ b/tests/integration_tests/charts/api_tests.py
@@ -181,7 +181,6 @@ class TestChartApi(SupersetTestCase, ApiOwnersTestCaseMixin, InsertChartMixin):
             self.new_dashboard.dashboard_title = "New Dashboard"
             self.new_dashboard.slug = "new_slug"
             self.new_dashboard.owners = [admin]
-            self.new_dashboard.slices = []
             self.new_dashboard.published = False
             db.session.add(self.new_dashboard)
 
diff --git a/tests/integration_tests/dashboard_tests.py b/tests/integration_tests/dashboard_tests.py
index a8cbd97aa2..fef4edd6cc 100644
--- a/tests/integration_tests/dashboard_tests.py
+++ b/tests/integration_tests/dashboard_tests.py
@@ -207,12 +207,10 @@ class TestDashboard(SupersetTestCase):
         dash.dashboard_title = "My Dashboard"
         dash.slug = my_dash_slug
         dash.owners = [user]
-        dash.slices = []
 
         hidden_dash = Dashboard()
         hidden_dash.dashboard_title = "Not My Dashboard"
         hidden_dash.slug = not_my_dash_slug
-        hidden_dash.slices = []
 
         db.session.add(dash)
         db.session.add(hidden_dash)
@@ -277,7 +275,6 @@ class TestDashboard(SupersetTestCase):
         dash.dashboard_title = "My Dashboard"
         dash.slug = slug
         dash.owners = [admin_user]
-        dash.slices = []
         dash.published = False
         db.session.add(dash)
         db.session.commit()
diff --git a/tests/integration_tests/security/guest_token_security_tests.py b/tests/integration_tests/security/guest_token_security_tests.py
index cb6095c0e7..5f50bf4b50 100644
--- a/tests/integration_tests/security/guest_token_security_tests.py
+++ b/tests/integration_tests/security/guest_token_security_tests.py
@@ -186,8 +186,6 @@ class TestGuestUserDashboardAccess(SupersetTestCase):
         # Create a draft dashboard that is not embedded
         dash = Dashboard()
         dash.dashboard_title = "My Dashboard"
-        dash.owners = []
-        dash.slices = []
         dash.published = False
         db.session.add(dash)
         db.session.commit()
diff --git a/tests/integration_tests/tagging_tests.py b/tests/integration_tests/tagging_tests.py
index 72ba577d9f..4ecfd1049f 100644
--- a/tests/integration_tests/tagging_tests.py
+++ b/tests/integration_tests/tagging_tests.py
@@ -136,7 +136,6 @@ class TestTagging(SupersetTestCase):
         test_dashboard = Dashboard()
         test_dashboard.dashboard_title = "test_dashboard"
         test_dashboard.slug = "test_slug"
-        test_dashboard.slices = []
         test_dashboard.published = True
 
         db.session.add(test_dashboard)
@@ -264,7 +263,6 @@ class TestTagging(SupersetTestCase):
         test_dashboard = Dashboard()
         test_dashboard.dashboard_title = "test_dashboard"
         test_dashboard.slug = "test_slug"
-        test_dashboard.slices = []
         test_dashboard.published = True
 
         # Create a saved query and add it to the db


[superset] 09/10: chore: Removes duplicated featureFlags.ts (#24935)

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 2574e11544a1b175db72efb407fdd162809bfe85
Author: Michael S. Molina <70...@users.noreply.github.com>
AuthorDate: Thu Aug 10 10:55:44 2023 -0300

    chore: Removes duplicated featureFlags.ts (#24935)
    
    (cherry picked from commit 284c12697b5052b1cd46d0003474fc4072cfd552)
---
 .../superset-ui-core/src/utils/featureFlags.ts     | 10 ++++-
 .../test/utils/featureFlag.test.ts                 | 52 ++++++++++++++++------
 superset-frontend/src/SqlLab/App.jsx               |  8 +++-
 superset-frontend/src/SqlLab/actions/sqlLab.js     |  8 +++-
 .../src/SqlLab/actions/sqlLab.test.js              |  6 +--
 .../ShareSqlLabQuery/ShareSqlLabQuery.test.tsx     |  6 +--
 .../SqlLab/components/ShareSqlLabQuery/index.tsx   |  9 +++-
 .../src/SqlLab/components/SouthPane/index.tsx      |  3 +-
 .../src/SqlLab/components/SqlEditor/index.jsx      |  2 +-
 .../SqlLab/components/TabbedSqlEditors/index.jsx   |  3 +-
 .../components/TableElement/TableElement.test.tsx  |  4 +-
 superset-frontend/src/components/Chart/Chart.jsx   |  2 +-
 .../src/components/Chart/chartAction.js            |  9 +++-
 .../src/components/Datasource/DatasourceEditor.jsx |  2 +-
 .../Datasource/DatasourceEditor.test.jsx           |  6 +--
 .../components/Datasource/DatasourceModal.test.jsx |  5 +--
 .../src/components/Datasource/DatasourceModal.tsx  |  3 +-
 .../src/components/DynamicPlugins/index.tsx        |  2 +-
 .../components/ReportModal/ReportModal.test.tsx    |  6 +--
 .../src/dashboard/actions/dashboardState.js        |  2 +-
 .../src/dashboard/actions/dashboardState.test.js   |  4 +-
 superset-frontend/src/dashboard/actions/hydrate.js |  3 +-
 .../src/dashboard/actions/sliceEntities.ts         |  2 +-
 .../DashboardBuilder/DashboardBuilder.test.tsx     | 42 ++++++++---------
 .../DashboardBuilder/DashboardBuilder.tsx          |  2 +-
 .../dashboard/components/DashboardBuilder/state.ts |  3 +-
 .../HeaderActionsDropdown.test.tsx                 |  6 +--
 .../Header/HeaderActionsDropdown/index.jsx         | 10 +++--
 .../src/dashboard/components/Header/index.jsx      |  2 +-
 .../PropertiesModal/PropertiesModal.test.tsx       |  3 +-
 .../dashboard/components/PropertiesModal/index.tsx |  2 +-
 .../components/SliceHeaderControls/index.tsx       |  2 +-
 .../FilterBar/FilterControls/FilterValue.tsx       |  2 +-
 .../nativeFilters/FilterBar/Vertical.tsx           |  2 +-
 .../FiltersConfigForm/FiltersConfigForm.tsx        |  2 +-
 .../components/nativeFilters/utils.test.ts         |  8 ++--
 .../dashboard/components/nativeFilters/utils.ts    |  2 +-
 .../src/dashboard/util/permissionUtils.test.ts     | 20 +++++----
 .../src/dashboard/util/permissionUtils.ts          |  3 +-
 superset-frontend/src/dataMask/actions.ts          |  2 +-
 superset-frontend/src/dataMask/reducer.ts          |  2 +-
 .../components/DataTablesPane/DataTablesPane.tsx   |  9 +++-
 .../explore/components/DatasourcePanel/index.tsx   |  2 +-
 .../explore/components/ExploreChartPanel/index.jsx |  2 +-
 .../src/explore/components/SaveModal.tsx           |  2 +-
 ...AdhocFilterEditPopoverSimpleTabContent.test.tsx |  4 +-
 .../index.tsx                                      |  2 +-
 .../useExploreAdditionalActionsMenu/index.jsx      | 10 ++++-
 superset-frontend/src/featureFlags.ts              | 37 ---------------
 .../src/features/alerts/AlertReportModal.tsx       |  2 +-
 .../src/features/charts/ChartCard.tsx              |  3 +-
 .../src/features/dashboards/DashboardCard.tsx      |  3 +-
 superset-frontend/src/features/tags/TagCard.tsx    |  3 +-
 .../src/middleware/asyncEvent.test.ts              |  4 +-
 superset-frontend/src/middleware/asyncEvent.ts     |  2 +-
 .../src/pages/ChartCreation/index.tsx              |  2 +-
 .../src/pages/ChartList/ChartList.test.jsx         |  6 +--
 superset-frontend/src/pages/ChartList/index.tsx    |  2 +-
 .../src/pages/DashboardList/DashboardList.test.jsx |  6 +--
 .../src/pages/DashboardList/index.tsx              |  9 +++-
 superset-frontend/src/pages/DatabaseList/index.tsx |  2 +-
 .../src/pages/DatasetList/DatasetList.test.tsx     |  4 +-
 superset-frontend/src/pages/DatasetList/index.tsx  |  2 +-
 superset-frontend/src/pages/Home/Home.test.tsx     |  4 +-
 superset-frontend/src/pages/Home/index.tsx         |  2 +-
 .../pages/SavedQueryList/SavedQueryList.test.jsx   |  4 +-
 .../src/pages/SavedQueryList/index.tsx             |  9 +++-
 superset-frontend/src/pages/Tags/index.tsx         |  3 +-
 superset-frontend/src/preamble.ts                  |  8 +++-
 superset-frontend/src/utils/hostNamesConfig.js     |  2 +-
 superset-frontend/src/views/routes.test.tsx        |  4 --
 71 files changed, 224 insertions(+), 202 deletions(-)

diff --git a/superset-frontend/packages/superset-ui-core/src/utils/featureFlags.ts b/superset-frontend/packages/superset-ui-core/src/utils/featureFlags.ts
index 48e54d1841..6bc77e0e87 100644
--- a/superset-frontend/packages/superset-ui-core/src/utils/featureFlags.ts
+++ b/superset-frontend/packages/superset-ui-core/src/utils/featureFlags.ts
@@ -16,6 +16,8 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+import logger from './logging';
+
 // We can codegen the enum definition based on a list of supported flags that we
 // check into source control. We're hardcoding the supported flags for now.
 export enum FeatureFlag {
@@ -85,11 +87,17 @@ declare global {
   }
 }
 
+export function initFeatureFlags(featureFlags?: FeatureFlagMap) {
+  if (!window.featureFlags) {
+    window.featureFlags = featureFlags || {};
+  }
+}
+
 export function isFeatureEnabled(feature: FeatureFlag): boolean {
   try {
     return !!window.featureFlags[feature];
   } catch (error) {
-    console.error(`Failed to query feature flag ${feature}`);
+    logger.error(`Failed to query feature flag ${feature}`);
   }
   return false;
 }
diff --git a/superset-frontend/packages/superset-ui-core/test/utils/featureFlag.test.ts b/superset-frontend/packages/superset-ui-core/test/utils/featureFlag.test.ts
index ef6e0c3729..8c6a45ef0c 100644
--- a/superset-frontend/packages/superset-ui-core/test/utils/featureFlag.test.ts
+++ b/superset-frontend/packages/superset-ui-core/test/utils/featureFlag.test.ts
@@ -16,29 +16,52 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import mockConsole from 'jest-mock-console';
-import { isFeatureEnabled, FeatureFlag } from '@superset-ui/core';
+import * as uiCore from '@superset-ui/core';
 
-it('returns false and raises console error if feature flags have not been initialized', () => {
-  mockConsole();
+it('initializes feature flags', () => {
   Object.defineProperty(window, 'featureFlags', {
     value: undefined,
   });
+  uiCore.initFeatureFlags();
+  expect(window.featureFlags).toEqual({});
+});
 
-  expect(isFeatureEnabled(FeatureFlag.DRILL_BY)).toEqual(false);
-  expect(console.error).toHaveBeenCalled();
-  // @ts-expect-error
-  expect(console.error.mock.calls[0][0]).toEqual(
-    'Failed to query feature flag DRILL_BY',
-  );
+it('initializes feature flags with predefined values', () => {
+  Object.defineProperty(window, 'featureFlags', {
+    value: undefined,
+  });
+  const featureFlags = {
+    CLIENT_CACHE: true,
+    DRILL_BY: false,
+  };
+  uiCore.initFeatureFlags(featureFlags);
+  expect(window.featureFlags).toEqual(featureFlags);
+});
+
+it('does nothing if feature flags are already initialized', () => {
+  const featureFlags = { DRILL_BY: false };
+  Object.defineProperty(window, 'featureFlags', {
+    value: featureFlags,
+  });
+  uiCore.initFeatureFlags({ DRILL_BY: true });
+  expect(window.featureFlags).toEqual(featureFlags);
+});
+
+it('returns false and raises console error if feature flags have not been initialized', () => {
+  const logging = jest.spyOn(uiCore.logging, 'error');
+  Object.defineProperty(window, 'featureFlags', {
+    value: undefined,
+  });
+  expect(uiCore.isFeatureEnabled(uiCore.FeatureFlag.DRILL_BY)).toEqual(false);
+  expect(uiCore.logging.error).toHaveBeenCalled();
+  expect(logging).toHaveBeenCalledWith('Failed to query feature flag DRILL_BY');
 });
 
 it('returns false for unset feature flag', () => {
   Object.defineProperty(window, 'featureFlags', {
     value: {},
   });
-
-  expect(isFeatureEnabled(FeatureFlag.DRILL_BY)).toEqual(false);
+  expect(uiCore.isFeatureEnabled(uiCore.FeatureFlag.DRILL_BY)).toEqual(false);
 });
 
 it('returns true for set feature flag', () => {
@@ -47,6 +70,7 @@ it('returns true for set feature flag', () => {
       CLIENT_CACHE: true,
     },
   });
-
-  expect(isFeatureEnabled(FeatureFlag.CLIENT_CACHE)).toEqual(true);
+  expect(uiCore.isFeatureEnabled(uiCore.FeatureFlag.CLIENT_CACHE)).toEqual(
+    true,
+  );
 });
diff --git a/superset-frontend/src/SqlLab/App.jsx b/superset-frontend/src/SqlLab/App.jsx
index deaf177265..8f36b7e2ab 100644
--- a/superset-frontend/src/SqlLab/App.jsx
+++ b/superset-frontend/src/SqlLab/App.jsx
@@ -20,9 +20,13 @@ import React from 'react';
 import persistState from 'redux-localstorage';
 import { Provider } from 'react-redux';
 import { hot } from 'react-hot-loader/root';
-import { FeatureFlag, ThemeProvider } from '@superset-ui/core';
+import {
+  FeatureFlag,
+  ThemeProvider,
+  initFeatureFlags,
+  isFeatureEnabled,
+} from '@superset-ui/core';
 import { GlobalStyles } from 'src/GlobalStyles';
-import { initFeatureFlags, isFeatureEnabled } from 'src/featureFlags';
 import { setupStore } from 'src/views/store';
 import setupExtensions from 'src/setup/setupExtensions';
 import getBootstrapData from 'src/utils/getBootstrapData';
diff --git a/superset-frontend/src/SqlLab/actions/sqlLab.js b/superset-frontend/src/SqlLab/actions/sqlLab.js
index 886307ad3e..5d9ecdacdf 100644
--- a/superset-frontend/src/SqlLab/actions/sqlLab.js
+++ b/superset-frontend/src/SqlLab/actions/sqlLab.js
@@ -18,10 +18,14 @@
  */
 import shortid from 'shortid';
 import rison from 'rison';
-import { FeatureFlag, SupersetClient, t } from '@superset-ui/core';
+import {
+  FeatureFlag,
+  SupersetClient,
+  t,
+  isFeatureEnabled,
+} from '@superset-ui/core';
 import invert from 'lodash/invert';
 import mapKeys from 'lodash/mapKeys';
-import { isFeatureEnabled } from 'src/featureFlags';
 
 import { now } from 'src/utils/dates';
 import {
diff --git a/superset-frontend/src/SqlLab/actions/sqlLab.test.js b/superset-frontend/src/SqlLab/actions/sqlLab.test.js
index 2f26ec16d5..fc94a44645 100644
--- a/superset-frontend/src/SqlLab/actions/sqlLab.test.js
+++ b/superset-frontend/src/SqlLab/actions/sqlLab.test.js
@@ -22,7 +22,7 @@ import fetchMock from 'fetch-mock';
 import configureMockStore from 'redux-mock-store';
 import thunk from 'redux-thunk';
 import shortid from 'shortid';
-import * as featureFlags from 'src/featureFlags';
+import * as uiCore from '@superset-ui/core';
 import * as actions from 'src/SqlLab/actions/sqlLab';
 import { LOG_EVENT } from 'src/logger/actions';
 import {
@@ -492,7 +492,7 @@ describe('async actions', () => {
 
     beforeEach(() => {
       isFeatureEnabledMock = jest
-        .spyOn(featureFlags, 'isFeatureEnabled')
+        .spyOn(uiCore, 'isFeatureEnabled')
         .mockImplementation(
           feature => feature === 'SQLLAB_BACKEND_PERSISTENCE',
         );
@@ -758,7 +758,7 @@ describe('async actions', () => {
       describe('with backend persistence flag off', () => {
         it('does not update the tab state in the backend', () => {
           const backendPersistenceOffMock = jest
-            .spyOn(featureFlags, 'isFeatureEnabled')
+            .spyOn(uiCore, 'isFeatureEnabled')
             .mockImplementation(
               feature => !(feature === 'SQLLAB_BACKEND_PERSISTENCE'),
             );
diff --git a/superset-frontend/src/SqlLab/components/ShareSqlLabQuery/ShareSqlLabQuery.test.tsx b/superset-frontend/src/SqlLab/components/ShareSqlLabQuery/ShareSqlLabQuery.test.tsx
index 6e9775c3a5..877edb76c8 100644
--- a/superset-frontend/src/SqlLab/components/ShareSqlLabQuery/ShareSqlLabQuery.test.tsx
+++ b/superset-frontend/src/SqlLab/components/ShareSqlLabQuery/ShareSqlLabQuery.test.tsx
@@ -20,7 +20,7 @@ import React from 'react';
 import configureStore from 'redux-mock-store';
 import thunk from 'redux-thunk';
 import fetchMock from 'fetch-mock';
-import * as featureFlags from 'src/featureFlags';
+import * as uiCore from '@superset-ui/core';
 import { Provider } from 'react-redux';
 import { supersetTheme, ThemeProvider } from '@superset-ui/core';
 import { render, screen, act } from '@testing-library/react';
@@ -108,7 +108,7 @@ describe('ShareSqlLabQuery', () => {
   describe('via /kv/store', () => {
     beforeAll(() => {
       isFeatureEnabledMock = jest
-        .spyOn(featureFlags, 'isFeatureEnabled')
+        .spyOn(uiCore, 'isFeatureEnabled')
         .mockImplementation(() => true);
     });
 
@@ -150,7 +150,7 @@ describe('ShareSqlLabQuery', () => {
   describe('via saved query', () => {
     beforeAll(() => {
       isFeatureEnabledMock = jest
-        .spyOn(featureFlags, 'isFeatureEnabled')
+        .spyOn(uiCore, 'isFeatureEnabled')
         .mockImplementation(() => false);
     });
 
diff --git a/superset-frontend/src/SqlLab/components/ShareSqlLabQuery/index.tsx b/superset-frontend/src/SqlLab/components/ShareSqlLabQuery/index.tsx
index fe9990ad3f..a123576c3e 100644
--- a/superset-frontend/src/SqlLab/components/ShareSqlLabQuery/index.tsx
+++ b/superset-frontend/src/SqlLab/components/ShareSqlLabQuery/index.tsx
@@ -17,14 +17,19 @@
  * under the License.
  */
 import React from 'react';
-import { FeatureFlag, styled, t, useTheme } from '@superset-ui/core';
+import {
+  FeatureFlag,
+  styled,
+  t,
+  useTheme,
+  isFeatureEnabled,
+} from '@superset-ui/core';
 import Button from 'src/components/Button';
 import Icons from 'src/components/Icons';
 import withToasts from 'src/components/MessageToasts/withToasts';
 import CopyToClipboard from 'src/components/CopyToClipboard';
 import { storeQuery } from 'src/utils/common';
 import { getClientErrorObject } from 'src/utils/getClientErrorObject';
-import { isFeatureEnabled } from 'src/featureFlags';
 import useQueryEditor from 'src/SqlLab/hooks/useQueryEditor';
 
 interface ShareSqlLabQueryProps {
diff --git a/superset-frontend/src/SqlLab/components/SouthPane/index.tsx b/superset-frontend/src/SqlLab/components/SouthPane/index.tsx
index 8b773986be..c2b0cc3beb 100644
--- a/superset-frontend/src/SqlLab/components/SouthPane/index.tsx
+++ b/superset-frontend/src/SqlLab/components/SouthPane/index.tsx
@@ -22,10 +22,9 @@ import shortid from 'shortid';
 import Alert from 'src/components/Alert';
 import Tabs from 'src/components/Tabs';
 import { EmptyStateMedium } from 'src/components/EmptyState';
-import { FeatureFlag, styled, t } from '@superset-ui/core';
+import { FeatureFlag, styled, t, isFeatureEnabled } from '@superset-ui/core';
 
 import { setActiveSouthPaneTab } from 'src/SqlLab/actions/sqlLab';
-import { isFeatureEnabled } from 'src/featureFlags';
 
 import Label from 'src/components/Label';
 import { SqlLabRootState } from 'src/SqlLab/types';
diff --git a/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx b/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx
index 093b90e686..69f9f36ee3 100644
--- a/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx
+++ b/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx
@@ -33,6 +33,7 @@ import Split from 'react-split';
 import {
   css,
   FeatureFlag,
+  isFeatureEnabled,
   styled,
   t,
   useTheme,
@@ -84,7 +85,6 @@ import {
   LocalStorageKeys,
   setItem,
 } from 'src/utils/localStorageHelpers';
-import { isFeatureEnabled } from 'src/featureFlags';
 import { EmptyStateBig } from 'src/components/EmptyState';
 import getBootstrapData from 'src/utils/getBootstrapData';
 import { isEmpty } from 'lodash';
diff --git a/superset-frontend/src/SqlLab/components/TabbedSqlEditors/index.jsx b/superset-frontend/src/SqlLab/components/TabbedSqlEditors/index.jsx
index 28f2133a75..d914715630 100644
--- a/superset-frontend/src/SqlLab/components/TabbedSqlEditors/index.jsx
+++ b/superset-frontend/src/SqlLab/components/TabbedSqlEditors/index.jsx
@@ -23,8 +23,7 @@ import { EditableTabs } from 'src/components/Tabs';
 import { connect } from 'react-redux';
 import { bindActionCreators } from 'redux';
 import URI from 'urijs';
-import { FeatureFlag, styled, t } from '@superset-ui/core';
-import { isFeatureEnabled } from 'src/featureFlags';
+import { FeatureFlag, styled, t, isFeatureEnabled } from '@superset-ui/core';
 import { Tooltip } from 'src/components/Tooltip';
 import { detectOS } from 'src/utils/common';
 import * as Actions from 'src/SqlLab/actions/sqlLab';
diff --git a/superset-frontend/src/SqlLab/components/TableElement/TableElement.test.tsx b/superset-frontend/src/SqlLab/components/TableElement/TableElement.test.tsx
index 89c98cfd33..e25d88f5dc 100644
--- a/superset-frontend/src/SqlLab/components/TableElement/TableElement.test.tsx
+++ b/superset-frontend/src/SqlLab/components/TableElement/TableElement.test.tsx
@@ -18,7 +18,7 @@
  */
 import React from 'react';
 import fetchMock from 'fetch-mock';
-import * as featureFlags from 'src/featureFlags';
+import * as uiCore from '@superset-ui/core';
 import { FeatureFlag } from '@superset-ui/core';
 import TableElement, { Column } from 'src/SqlLab/components/TableElement';
 import { table, initialState } from 'src/SqlLab/fixtures';
@@ -140,7 +140,7 @@ test('removes the table', async () => {
   const updateTableSchemaEndpoint = 'glob:*/tableschemaview/*';
   fetchMock.delete(updateTableSchemaEndpoint, {});
   const isFeatureEnabledMock = jest
-    .spyOn(featureFlags, 'isFeatureEnabled')
+    .spyOn(uiCore, 'isFeatureEnabled')
     .mockImplementation(
       featureFlag => featureFlag === FeatureFlag.SQLLAB_BACKEND_PERSISTENCE,
     );
diff --git a/superset-frontend/src/components/Chart/Chart.jsx b/superset-frontend/src/components/Chart/Chart.jsx
index 74bde69c5c..af90ae6b0a 100644
--- a/superset-frontend/src/components/Chart/Chart.jsx
+++ b/superset-frontend/src/components/Chart/Chart.jsx
@@ -21,11 +21,11 @@ import React from 'react';
 import {
   ensureIsArray,
   FeatureFlag,
+  isFeatureEnabled,
   logging,
   styled,
   t,
 } from '@superset-ui/core';
-import { isFeatureEnabled } from 'src/featureFlags';
 import { PLACEHOLDER_DATASOURCE } from 'src/dashboard/constants';
 import Loading from 'src/components/Loading';
 import { EmptyStateBig } from 'src/components/EmptyState';
diff --git a/superset-frontend/src/components/Chart/chartAction.js b/superset-frontend/src/components/Chart/chartAction.js
index d2e8b3d327..c204d58ebe 100644
--- a/superset-frontend/src/components/Chart/chartAction.js
+++ b/superset-frontend/src/components/Chart/chartAction.js
@@ -19,9 +19,14 @@
 /* eslint no-undef: 'error' */
 /* eslint no-param-reassign: ["error", { "props": false }] */
 import moment from 'moment';
-import { FeatureFlag, isDefined, SupersetClient, t } from '@superset-ui/core';
+import {
+  FeatureFlag,
+  isDefined,
+  SupersetClient,
+  t,
+  isFeatureEnabled,
+} from '@superset-ui/core';
 import { getControlsState } from 'src/explore/store';
-import { isFeatureEnabled } from 'src/featureFlags';
 import {
   getAnnotationJsonUrl,
   getExploreUrl,
diff --git a/superset-frontend/src/components/Datasource/DatasourceEditor.jsx b/superset-frontend/src/components/Datasource/DatasourceEditor.jsx
index d95839d972..5977f44058 100644
--- a/superset-frontend/src/components/Datasource/DatasourceEditor.jsx
+++ b/superset-frontend/src/components/Datasource/DatasourceEditor.jsx
@@ -26,6 +26,7 @@ import Badge from 'src/components/Badge';
 import shortid from 'shortid';
 import {
   css,
+  isFeatureEnabled,
   getCurrencySymbol,
   ensureIsArray,
   FeatureFlag,
@@ -51,7 +52,6 @@ import TextControl from 'src/explore/components/controls/TextControl';
 import TextAreaControl from 'src/explore/components/controls/TextAreaControl';
 import SpatialControl from 'src/explore/components/controls/SpatialControl';
 import withToasts from 'src/components/MessageToasts/withToasts';
-import { isFeatureEnabled } from 'src/featureFlags';
 import Icons from 'src/components/Icons';
 import CollectionTable from './CollectionTable';
 import Fieldset from './Fieldset';
diff --git a/superset-frontend/src/components/Datasource/DatasourceEditor.test.jsx b/superset-frontend/src/components/Datasource/DatasourceEditor.test.jsx
index abb77c7f19..322be68f96 100644
--- a/superset-frontend/src/components/Datasource/DatasourceEditor.test.jsx
+++ b/superset-frontend/src/components/Datasource/DatasourceEditor.test.jsx
@@ -22,7 +22,7 @@ import userEvent from '@testing-library/user-event';
 import { render, screen, waitFor } from 'spec/helpers/testing-library';
 import DatasourceEditor from 'src/components/Datasource/DatasourceEditor';
 import mockDatasource from 'spec/fixtures/mockDatasource';
-import * as featureFlags from 'src/featureFlags';
+import * as uiCore from '@superset-ui/core';
 
 const props = {
   datasource: mockDatasource['7__table'],
@@ -151,7 +151,7 @@ describe('DatasourceEditor', () => {
   describe('enable edit Source tab', () => {
     beforeAll(() => {
       isFeatureEnabledMock = jest
-        .spyOn(featureFlags, 'isFeatureEnabled')
+        .spyOn(uiCore, 'isFeatureEnabled')
         .mockImplementation(() => false);
     });
 
@@ -189,7 +189,7 @@ describe('DatasourceEditor', () => {
   describe('render editor with feature flag false', () => {
     beforeAll(() => {
       isFeatureEnabledMock = jest
-        .spyOn(featureFlags, 'isFeatureEnabled')
+        .spyOn(uiCore, 'isFeatureEnabled')
         .mockImplementation(() => true);
     });
 
diff --git a/superset-frontend/src/components/Datasource/DatasourceModal.test.jsx b/superset-frontend/src/components/Datasource/DatasourceModal.test.jsx
index d5619c5527..5bcb705b68 100644
--- a/superset-frontend/src/components/Datasource/DatasourceModal.test.jsx
+++ b/superset-frontend/src/components/Datasource/DatasourceModal.test.jsx
@@ -29,7 +29,7 @@ import { defaultStore as store } from 'spec/helpers/testing-library';
 import Modal from 'src/components/Modal';
 import { DatasourceModal } from 'src/components/Datasource';
 import DatasourceEditor from 'src/components/Datasource/DatasourceEditor';
-import * as featureFlags from 'src/featureFlags';
+import * as uiCore from '@superset-ui/core';
 import mockDatasource from 'spec/fixtures/mockDatasource';
 import { api } from 'src/hooks/apiResources/queryApi';
 
@@ -69,7 +69,7 @@ describe('DatasourceModal', () => {
   let wrapper;
   let isFeatureEnabledMock;
   beforeEach(async () => {
-    isFeatureEnabledMock = jest.spyOn(featureFlags, 'isFeatureEnabled');
+    isFeatureEnabledMock = jest.spyOn(uiCore, 'isFeatureEnabled');
     fetchMock.reset();
     wrapper = await mountAndWait();
   });
@@ -122,7 +122,6 @@ describe('DatasourceModal', () => {
   });
 
   it('renders a legacy data source btn', () => {
-    featureFlags.DISABLE_LEGACY_DATASOURCE_EDITOR = false;
     expect(
       wrapper.find('button[data-test="datasource-modal-legacy-edit"]'),
     ).toExist();
diff --git a/superset-frontend/src/components/Datasource/DatasourceModal.tsx b/superset-frontend/src/components/Datasource/DatasourceModal.tsx
index c5063890cb..78859f4a2f 100644
--- a/superset-frontend/src/components/Datasource/DatasourceModal.tsx
+++ b/superset-frontend/src/components/Datasource/DatasourceModal.tsx
@@ -22,6 +22,7 @@ import Button from 'src/components/Button';
 import {
   FeatureFlag,
   isDefined,
+  isFeatureEnabled,
   Metric,
   styled,
   SupersetClient,
@@ -30,8 +31,6 @@ import {
 
 import Modal from 'src/components/Modal';
 import AsyncEsmComponent from 'src/components/AsyncEsmComponent';
-import { isFeatureEnabled } from 'src/featureFlags';
-
 import { getClientErrorObject } from 'src/utils/getClientErrorObject';
 import withToasts from 'src/components/MessageToasts/withToasts';
 import { useSelector } from 'react-redux';
diff --git a/superset-frontend/src/components/DynamicPlugins/index.tsx b/superset-frontend/src/components/DynamicPlugins/index.tsx
index 87701d2462..9134e73fda 100644
--- a/superset-frontend/src/components/DynamicPlugins/index.tsx
+++ b/superset-frontend/src/components/DynamicPlugins/index.tsx
@@ -20,12 +20,12 @@ import React, { useContext, useEffect, useReducer } from 'react';
 import {
   ChartMetadata,
   defineSharedModules,
+  isFeatureEnabled,
   FeatureFlag,
   getChartMetadataRegistry,
   logging,
   makeApi,
 } from '@superset-ui/core';
-import { isFeatureEnabled } from 'src/featureFlags';
 import { omitBy } from 'lodash';
 
 const metadataRegistry = getChartMetadataRegistry();
diff --git a/superset-frontend/src/components/ReportModal/ReportModal.test.tsx b/superset-frontend/src/components/ReportModal/ReportModal.test.tsx
index 2755d832d7..63365a6bac 100644
--- a/superset-frontend/src/components/ReportModal/ReportModal.test.tsx
+++ b/superset-frontend/src/components/ReportModal/ReportModal.test.tsx
@@ -21,7 +21,7 @@ import userEvent from '@testing-library/user-event';
 import sinon from 'sinon';
 import fetchMock from 'fetch-mock';
 import { render, screen, waitFor } from 'spec/helpers/testing-library';
-import * as featureFlags from 'src/featureFlags';
+import * as uiCore from '@superset-ui/core';
 import * as actions from 'src/reports/actions/reports';
 import { FeatureFlag } from '@superset-ui/core';
 import ReportModal from '.';
@@ -54,7 +54,7 @@ const defaultProps = {
 describe('Email Report Modal', () => {
   beforeAll(() => {
     isFeatureEnabledMock = jest
-      .spyOn(featureFlags, 'isFeatureEnabled')
+      .spyOn(uiCore, 'isFeatureEnabled')
       .mockImplementation(
         (featureFlag: FeatureFlag) => featureFlag === FeatureFlag.ALERT_REPORTS,
       );
@@ -118,7 +118,7 @@ describe('Email Report Modal', () => {
 
     beforeEach(async () => {
       isFeatureEnabledMock = jest
-        .spyOn(featureFlags, 'isFeatureEnabled')
+        .spyOn(uiCore, 'isFeatureEnabled')
         .mockImplementation(() => true);
       dispatch = sinon.spy();
     });
diff --git a/superset-frontend/src/dashboard/actions/dashboardState.js b/superset-frontend/src/dashboard/actions/dashboardState.js
index 54c5294fba..dcf1020e6d 100644
--- a/superset-frontend/src/dashboard/actions/dashboardState.js
+++ b/superset-frontend/src/dashboard/actions/dashboardState.js
@@ -21,6 +21,7 @@ import { ActionCreators as UndoActionCreators } from 'redux-undo';
 import rison from 'rison';
 import {
   ensureIsArray,
+  isFeatureEnabled,
   FeatureFlag,
   getSharedLabelColor,
   SupersetClient,
@@ -51,7 +52,6 @@ import serializeActiveFilterValues from 'src/dashboard/util/serializeActiveFilte
 import serializeFilterScopes from 'src/dashboard/util/serializeFilterScopes';
 import { getActiveFilters } from 'src/dashboard/util/activeDashboardFilters';
 import { safeStringify } from 'src/utils/safeStringify';
-import { isFeatureEnabled } from 'src/featureFlags';
 import { logEvent } from 'src/logger/actions';
 import { LOG_ACTIONS_CONFIRM_OVERWRITE_DASHBOARD_METADATA } from 'src/logger/LogUtils';
 import { UPDATE_COMPONENTS_PARENTS_LIST } from './dashboardLayout';
diff --git a/superset-frontend/src/dashboard/actions/dashboardState.test.js b/superset-frontend/src/dashboard/actions/dashboardState.test.js
index 5fbe28cb1f..1ef85f0b99 100644
--- a/superset-frontend/src/dashboard/actions/dashboardState.test.js
+++ b/superset-frontend/src/dashboard/actions/dashboardState.test.js
@@ -27,7 +27,7 @@ import {
   SET_OVERRIDE_CONFIRM,
 } from 'src/dashboard/actions/dashboardState';
 import { REMOVE_FILTER } from 'src/dashboard/actions/dashboardFilters';
-import * as featureFlags from 'src/featureFlags';
+import * as uiCore from '@superset-ui/core';
 import { UPDATE_COMPONENTS_PARENTS_LIST } from 'src/dashboard/actions/dashboardLayout';
 import {
   DASHBOARD_GRID_ID,
@@ -145,7 +145,7 @@ describe('dashboardState actions', () => {
       let isFeatureEnabledMock;
       beforeEach(() => {
         isFeatureEnabledMock = jest
-          .spyOn(featureFlags, 'isFeatureEnabled')
+          .spyOn(uiCore, 'isFeatureEnabled')
           .mockImplementation(feature => feature === 'CONFIRM_DASHBOARD_DIFF');
       });
 
diff --git a/superset-frontend/src/dashboard/actions/hydrate.js b/superset-frontend/src/dashboard/actions/hydrate.js
index 6fd06c8f12..699c2041c8 100644
--- a/superset-frontend/src/dashboard/actions/hydrate.js
+++ b/superset-frontend/src/dashboard/actions/hydrate.js
@@ -17,7 +17,7 @@
  * under the License.
  */
 /* eslint-disable camelcase */
-import { FeatureFlag } from '@superset-ui/core';
+import { FeatureFlag, isFeatureEnabled } from '@superset-ui/core';
 import { chart } from 'src/components/Chart/chartReducer';
 import { initSliceEntities } from 'src/dashboard/reducers/sliceEntities';
 import { getInitialState as getInitialNativeFilterState } from 'src/dashboard/reducers/nativeFilters';
@@ -56,7 +56,6 @@ import { TIME_RANGE } from 'src/visualizations/FilterBox/FilterBox';
 import { URL_PARAMS } from 'src/constants';
 import { getUrlParam } from 'src/utils/urlUtils';
 import { ResourceStatus } from 'src/hooks/apiResources/apiResources';
-import { isFeatureEnabled } from '../../featureFlags';
 import extractUrlParams from '../util/extractUrlParams';
 import { updateColorSchema } from './dashboardInfo';
 import updateComponentParentsList from '../util/updateComponentParentsList';
diff --git a/superset-frontend/src/dashboard/actions/sliceEntities.ts b/superset-frontend/src/dashboard/actions/sliceEntities.ts
index a30470aad1..562d90657e 100644
--- a/superset-frontend/src/dashboard/actions/sliceEntities.ts
+++ b/superset-frontend/src/dashboard/actions/sliceEntities.ts
@@ -19,13 +19,13 @@
 import rison from 'rison';
 import {
   DatasourceType,
+  isFeatureEnabled,
   FeatureFlag,
   SupersetClient,
   t,
 } from '@superset-ui/core';
 import { addDangerToast } from 'src/components/MessageToasts/actions';
 import { getClientErrorObject } from 'src/utils/getClientErrorObject';
-import { isFeatureEnabled } from 'src/featureFlags';
 import { Dispatch } from 'redux';
 import { Slice } from '../types';
 
diff --git a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.test.tsx b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.test.tsx
index 388f8ee3c1..7c3dd23392 100644
--- a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.test.tsx
+++ b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.test.tsx
@@ -16,13 +16,11 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { Store } from 'redux';
 import React from 'react';
 import fetchMock from 'fetch-mock';
 import { render } from 'spec/helpers/testing-library';
 import { fireEvent, within } from '@testing-library/react';
-import { FeatureFlag } from '@superset-ui/core';
-import { isFeatureEnabled } from 'src/featureFlags';
+import * as uiCore from '@superset-ui/core';
 import DashboardBuilder from 'src/dashboard/components/DashboardBuilder/DashboardBuilder';
 import useStoredSidebarWidth from 'src/components/ResizableSidebar/useStoredSidebarWidth';
 import {
@@ -34,7 +32,7 @@ import {
   dashboardLayout as undoableDashboardLayout,
   dashboardLayoutWithTabs as undoableDashboardLayoutWithTabs,
 } from 'spec/fixtures/mockDashboardLayout';
-import { mockStoreWithTabs, storeWithState } from 'spec/fixtures/mockStore';
+import { storeWithState } from 'spec/fixtures/mockStore';
 import mockState from 'spec/fixtures/mockState';
 import { DASHBOARD_ROOT_ID } from 'src/dashboard/util/constants';
 
@@ -46,7 +44,6 @@ jest.mock('src/dashboard/actions/dashboardState', () => ({
   setActiveTabs: jest.fn(),
   setDirectPathToChild: jest.fn(),
 }));
-jest.mock('src/featureFlags');
 jest.mock('src/components/ResizableSidebar/useStoredSidebarWidth');
 
 // mock following dependant components to fix the prop warnings
@@ -100,7 +97,6 @@ describe('DashboardBuilder', () => {
       100,
       jest.fn(),
     ]);
-    (isFeatureEnabled as jest.Mock).mockImplementation(() => false);
   });
 
   afterAll(() => {
@@ -109,7 +105,7 @@ describe('DashboardBuilder', () => {
     (useStoredSidebarWidth as jest.Mock).mockReset();
   });
 
-  function setup(overrideState = {}, overrideStore?: Store) {
+  function setup(overrideState = {}) {
     return render(<DashboardBuilder />, {
       useRedux: true,
       store: storeWithState({
@@ -142,10 +138,9 @@ describe('DashboardBuilder', () => {
   });
 
   it('should render a Sticky top-level Tabs if the dashboard has tabs', async () => {
-    const { findAllByTestId } = setup(
-      { dashboardLayout: undoableDashboardLayoutWithTabs },
-      mockStoreWithTabs,
-    );
+    const { findAllByTestId } = setup({
+      dashboardLayout: undoableDashboardLayoutWithTabs,
+    });
     const sticky = await findAllByTestId('nav-list');
 
     expect(sticky.length).toBe(1);
@@ -229,12 +224,9 @@ describe('DashboardBuilder', () => {
       type: 'type',
       arg0,
     }));
-    const { findByRole } = setup(
-      {
-        dashboardLayout: undoableDashboardLayoutWithTabs,
-      },
-      mockStoreWithTabs,
-    );
+    const { findByRole } = setup({
+      dashboardLayout: undoableDashboardLayoutWithTabs,
+    });
     const tabList = await findByRole('tablist');
     const tabs = within(tabList).getAllByRole('tab');
     expect(setDirectPathToChild).toHaveBeenCalledTimes(0);
@@ -262,13 +254,17 @@ describe('DashboardBuilder', () => {
   });
 
   describe('when nativeFiltersEnabled', () => {
-    beforeEach(() => {
-      (isFeatureEnabled as jest.Mock).mockImplementation(
-        flag => flag === FeatureFlag.DASHBOARD_NATIVE_FILTERS,
-      );
+    let isFeatureEnabledMock: jest.MockInstance<boolean, [string]>;
+    beforeAll(() => {
+      isFeatureEnabledMock = jest
+        .spyOn(uiCore, 'isFeatureEnabled')
+        .mockImplementation(
+          flag => flag === uiCore.FeatureFlag.DASHBOARD_NATIVE_FILTERS,
+        );
     });
-    afterEach(() => {
-      (isFeatureEnabled as jest.Mock).mockReset();
+
+    afterAll(() => {
+      isFeatureEnabledMock.mockRestore();
     });
 
     it('should set FilterBar width by useStoredSidebarWidth', () => {
diff --git a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx
index 3af76c5c1c..a17b168374 100644
--- a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx
+++ b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx
@@ -29,6 +29,7 @@ import React, {
 import {
   addAlpha,
   css,
+  isFeatureEnabled,
   FeatureFlag,
   JsonObject,
   styled,
@@ -58,7 +59,6 @@ import {
   setDirectPathToChild,
   setEditMode,
 } from 'src/dashboard/actions/dashboardState';
-import { isFeatureEnabled } from 'src/featureFlags';
 import {
   deleteTopLevelTabs,
   handleComponentDrop,
diff --git a/superset-frontend/src/dashboard/components/DashboardBuilder/state.ts b/superset-frontend/src/dashboard/components/DashboardBuilder/state.ts
index d213d292ee..1936e331c3 100644
--- a/superset-frontend/src/dashboard/components/DashboardBuilder/state.ts
+++ b/superset-frontend/src/dashboard/components/DashboardBuilder/state.ts
@@ -17,8 +17,7 @@
  * under the License.
  */
 import { useSelector } from 'react-redux';
-import { FeatureFlag } from '@superset-ui/core';
-import { isFeatureEnabled } from 'src/featureFlags';
+import { isFeatureEnabled, FeatureFlag } from '@superset-ui/core';
 import { useCallback, useEffect, useState } from 'react';
 import { URL_PARAMS } from 'src/constants';
 import { getUrlParam } from 'src/utils/urlUtils';
diff --git a/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/HeaderActionsDropdown.test.tsx b/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/HeaderActionsDropdown.test.tsx
index a89bccb480..218e2e4546 100644
--- a/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/HeaderActionsDropdown.test.tsx
+++ b/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/HeaderActionsDropdown.test.tsx
@@ -25,7 +25,7 @@ import fetchMock from 'fetch-mock';
 import { HeaderDropdownProps } from 'src/dashboard/components/Header/types';
 import injectCustomCss from 'src/dashboard/util/injectCustomCss';
 import { FeatureFlag } from '@superset-ui/core';
-import * as featureFlags from 'src/featureFlags';
+import * as uiCore from '@superset-ui/core';
 import HeaderActionsDropdown from '.';
 
 let isFeatureEnabledMock: jest.MockInstance<boolean, [feature: FeatureFlag]>;
@@ -136,7 +136,7 @@ test('should render the menu items in edit mode', async () => {
 describe('with native filters feature flag disabled', () => {
   beforeAll(() => {
     isFeatureEnabledMock = jest
-      .spyOn(featureFlags, 'isFeatureEnabled')
+      .spyOn(uiCore, 'isFeatureEnabled')
       .mockImplementation(
         (featureFlag: FeatureFlag) =>
           featureFlag !== FeatureFlag.DASHBOARD_NATIVE_FILTERS,
@@ -162,7 +162,7 @@ describe('with native filters feature flag disabled', () => {
 describe('with native filters feature flag enabled', () => {
   beforeAll(() => {
     isFeatureEnabledMock = jest
-      .spyOn(featureFlags, 'isFeatureEnabled')
+      .spyOn(uiCore, 'isFeatureEnabled')
       .mockImplementation(
         (featureFlag: FeatureFlag) =>
           featureFlag === FeatureFlag.DASHBOARD_NATIVE_FILTERS,
diff --git a/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/index.jsx b/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/index.jsx
index 7c375caf5b..3f2caab72a 100644
--- a/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/index.jsx
+++ b/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/index.jsx
@@ -19,9 +19,12 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { isEmpty } from 'lodash';
-
-import { FeatureFlag, SupersetClient, t } from '@superset-ui/core';
-
+import {
+  isFeatureEnabled,
+  FeatureFlag,
+  SupersetClient,
+  t,
+} from '@superset-ui/core';
 import { Menu } from 'src/components/Menu';
 import { URL_PARAMS } from 'src/constants';
 import ShareMenuItems from 'src/dashboard/components/menu/ShareMenuItems';
@@ -37,7 +40,6 @@ import getDashboardUrl from 'src/dashboard/util/getDashboardUrl';
 import { getActiveFilters } from 'src/dashboard/util/activeDashboardFilters';
 import { getUrlParam } from 'src/utils/urlUtils';
 import { LOG_ACTIONS_DASHBOARD_DOWNLOAD_AS_IMAGE } from 'src/logger/LogUtils';
-import { isFeatureEnabled } from 'src/featureFlags';
 
 const propTypes = {
   addSuccessToast: PropTypes.func.isRequired,
diff --git a/superset-frontend/src/dashboard/components/Header/index.jsx b/superset-frontend/src/dashboard/components/Header/index.jsx
index e9cf63b5a4..7d96ad6b54 100644
--- a/superset-frontend/src/dashboard/components/Header/index.jsx
+++ b/superset-frontend/src/dashboard/components/Header/index.jsx
@@ -23,13 +23,13 @@ import PropTypes from 'prop-types';
 import {
   styled,
   css,
+  isFeatureEnabled,
   FeatureFlag,
   t,
   getSharedLabelColor,
   getExtensionsRegistry,
 } from '@superset-ui/core';
 import { Global } from '@emotion/react';
-import { isFeatureEnabled } from 'src/featureFlags';
 import {
   LOG_ACTIONS_PERIODIC_RENDER_DASHBOARD,
   LOG_ACTIONS_FORCE_REFRESH_DASHBOARD,
diff --git a/superset-frontend/src/dashboard/components/PropertiesModal/PropertiesModal.test.tsx b/superset-frontend/src/dashboard/components/PropertiesModal/PropertiesModal.test.tsx
index 386467e8de..401e0dc320 100644
--- a/superset-frontend/src/dashboard/components/PropertiesModal/PropertiesModal.test.tsx
+++ b/superset-frontend/src/dashboard/components/PropertiesModal/PropertiesModal.test.tsx
@@ -21,11 +21,10 @@ import { render, screen, waitFor } from 'spec/helpers/testing-library';
 import fetchMock from 'fetch-mock';
 import userEvent from '@testing-library/user-event';
 import * as ColorSchemeControlWrapper from 'src/dashboard/components/ColorSchemeControlWrapper';
-import * as FF from 'src/featureFlags';
 import * as SupersetCore from '@superset-ui/core';
 import PropertiesModal from '.';
 
-const spyIsFeatureEnabled = jest.spyOn(FF, 'isFeatureEnabled');
+const spyIsFeatureEnabled = jest.spyOn(SupersetCore, 'isFeatureEnabled');
 const spyColorSchemeControlWrapper = jest.spyOn(
   ColorSchemeControlWrapper,
   'default',
diff --git a/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx b/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx
index 2e9f54cffc..3bc67bc6e8 100644
--- a/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx
+++ b/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx
@@ -27,6 +27,7 @@ import rison from 'rison';
 import {
   CategoricalColorNamespace,
   ensureIsArray,
+  isFeatureEnabled,
   FeatureFlag,
   getCategoricalSchemeRegistry,
   getSharedLabelColor,
@@ -42,7 +43,6 @@ import ColorSchemeControlWrapper from 'src/dashboard/components/ColorSchemeContr
 import FilterScopeModal from 'src/dashboard/components/filterscope/FilterScopeModal';
 import { getClientErrorObject } from 'src/utils/getClientErrorObject';
 import withToasts from 'src/components/MessageToasts/withToasts';
-import { isFeatureEnabled } from 'src/featureFlags';
 import TagType from 'src/types/TagType';
 import {
   addTag,
diff --git a/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx b/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx
index e395875517..287d83692f 100644
--- a/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx
+++ b/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx
@@ -33,6 +33,7 @@ import moment from 'moment';
 import {
   Behavior,
   css,
+  isFeatureEnabled,
   FeatureFlag,
   getChartMetadataRegistry,
   QueryFormData,
@@ -45,7 +46,6 @@ import { Menu } from 'src/components/Menu';
 import { NoAnimationDropdown } from 'src/components/Dropdown';
 import ShareMenuItems from 'src/dashboard/components/menu/ShareMenuItems';
 import downloadAsImage from 'src/utils/downloadAsImage';
-import { isFeatureEnabled } from 'src/featureFlags';
 import { getSliceHeaderTooltip } from 'src/dashboard/util/getSliceHeaderTooltip';
 import { Tooltip } from 'src/components/Tooltip';
 import Icons from 'src/components/Icons';
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterValue.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterValue.tsx
index b600553669..b64f1bc8d9 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterValue.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterValue.tsx
@@ -27,6 +27,7 @@ import {
   ChartDataResponseResult,
   Behavior,
   DataMask,
+  isFeatureEnabled,
   FeatureFlag,
   getChartMetadataRegistry,
   JsonObject,
@@ -41,7 +42,6 @@ import { getChartDataRequest } from 'src/components/Chart/chartAction';
 import Loading from 'src/components/Loading';
 import BasicErrorAlert from 'src/components/ErrorMessage/BasicErrorAlert';
 import ErrorMessageWithStackTrace from 'src/components/ErrorMessage/ErrorMessageWithStackTrace';
-import { isFeatureEnabled } from 'src/featureFlags';
 import { waitForAsyncData } from 'src/middleware/asyncEvent';
 import {
   ClientErrorObject,
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Vertical.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Vertical.tsx
index d9212f4058..7d7f846e75 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Vertical.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Vertical.tsx
@@ -31,13 +31,13 @@ import cx from 'classnames';
 import {
   FeatureFlag,
   HandlerFunction,
+  isFeatureEnabled,
   isNativeFilter,
   styled,
   t,
 } from '@superset-ui/core';
 import Icons from 'src/components/Icons';
 import { AntdTabs } from 'src/components';
-import { isFeatureEnabled } from 'src/featureFlags';
 import Loading from 'src/components/Loading';
 import { EmptyStateSmall } from 'src/components/EmptyState';
 import { getFilterBarTestId } from './utils';
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FiltersConfigForm.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FiltersConfigForm.tsx
index 240c9e41b8..dcfbf3d43d 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FiltersConfigForm.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FiltersConfigForm.tsx
@@ -27,6 +27,7 @@ import {
   Behavior,
   ChartDataResponseResult,
   Column,
+  isFeatureEnabled,
   FeatureFlag,
   Filter,
   GenericDataType,
@@ -71,7 +72,6 @@ import {
 } from 'src/dashboard/types';
 import DateFilterControl from 'src/explore/components/controls/DateFilterControl';
 import AdhocFilterControl from 'src/explore/components/controls/FilterControl/AdhocFilterControl';
-import { isFeatureEnabled } from 'src/featureFlags';
 import { waitForAsyncData } from 'src/middleware/asyncEvent';
 import {
   ClientErrorObject,
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/utils.test.ts b/superset-frontend/src/dashboard/components/nativeFilters/utils.test.ts
index 142153c278..7219ae360b 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/utils.test.ts
+++ b/superset-frontend/src/dashboard/components/nativeFilters/utils.test.ts
@@ -17,7 +17,7 @@
  * under the License.
  */
 import { Behavior, FeatureFlag } from '@superset-ui/core';
-import * as featureFlags from 'src/featureFlags';
+import * as uiCore from '@superset-ui/core';
 import { DashboardLayout } from 'src/dashboard/types';
 import { nativeFilterGate, findTabsWithChartsInScope } from './utils';
 
@@ -27,7 +27,7 @@ describe('nativeFilterGate', () => {
   describe('with all feature flags disabled', () => {
     beforeAll(() => {
       isFeatureEnabledMock = jest
-        .spyOn(featureFlags, 'isFeatureEnabled')
+        .spyOn(uiCore, 'isFeatureEnabled')
         .mockImplementation(() => false);
     });
 
@@ -58,7 +58,7 @@ describe('nativeFilterGate', () => {
   describe('with only native filters feature flag enabled', () => {
     beforeAll(() => {
       isFeatureEnabledMock = jest
-        .spyOn(featureFlags, 'isFeatureEnabled')
+        .spyOn(uiCore, 'isFeatureEnabled')
         .mockImplementation(
           (featureFlag: FeatureFlag) =>
             featureFlag === FeatureFlag.DASHBOARD_NATIVE_FILTERS,
@@ -92,7 +92,7 @@ describe('nativeFilterGate', () => {
   describe('with native filters and experimental feature flag enabled', () => {
     beforeAll(() => {
       isFeatureEnabledMock = jest
-        .spyOn(featureFlags, 'isFeatureEnabled')
+        .spyOn(uiCore, 'isFeatureEnabled')
         .mockImplementation((featureFlag: FeatureFlag) =>
           [
             FeatureFlag.DASHBOARD_CROSS_FILTERS,
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/utils.ts b/superset-frontend/src/dashboard/components/nativeFilters/utils.ts
index 1af395a5d9..7086ac8512 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/utils.ts
+++ b/superset-frontend/src/dashboard/components/nativeFilters/utils.ts
@@ -23,6 +23,7 @@ import {
   EXTRA_FORM_DATA_APPEND_KEYS,
   EXTRA_FORM_DATA_OVERRIDE_KEYS,
   ExtraFormData,
+  isFeatureEnabled,
   FeatureFlag,
   Filter,
   getChartMetadataRegistry,
@@ -30,7 +31,6 @@ import {
 } from '@superset-ui/core';
 import { DashboardLayout } from 'src/dashboard/types';
 import extractUrlParams from 'src/dashboard/util/extractUrlParams';
-import { isFeatureEnabled } from 'src/featureFlags';
 import { CHART_TYPE, TAB_TYPE } from '../../util/componentTypes';
 import { DASHBOARD_GRID_ID, DASHBOARD_ROOT_ID } from '../../util/constants';
 import getBootstrapData from '../../../utils/getBootstrapData';
diff --git a/superset-frontend/src/dashboard/util/permissionUtils.test.ts b/superset-frontend/src/dashboard/util/permissionUtils.test.ts
index e5e958013d..b9b0e071b4 100644
--- a/superset-frontend/src/dashboard/util/permissionUtils.test.ts
+++ b/superset-frontend/src/dashboard/util/permissionUtils.test.ts
@@ -16,8 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { FeatureFlag } from '@superset-ui/core';
-import * as featureFlags from 'src/featureFlags';
+import * as uiCore from '@superset-ui/core';
 import {
   UndefinedUser,
   UserWithPermissionsAndRoles,
@@ -91,7 +90,10 @@ const dashboard: Dashboard = {
   roles: [],
 };
 
-let isFeatureEnabledMock: jest.MockInstance<boolean, [feature: FeatureFlag]>;
+let isFeatureEnabledMock: jest.MockInstance<
+  boolean,
+  [feature: uiCore.FeatureFlag]
+>;
 
 describe('canUserEditDashboard', () => {
   it('allows owners to edit', () => {
@@ -160,10 +162,10 @@ test('canUserAccessSqlLab returns true for sqllab role', () => {
 describe('canUserSaveAsDashboard with RBAC feature flag disabled', () => {
   beforeAll(() => {
     isFeatureEnabledMock = jest
-      .spyOn(featureFlags, 'isFeatureEnabled')
+      .spyOn(uiCore, 'isFeatureEnabled')
       .mockImplementation(
-        (featureFlag: FeatureFlag) =>
-          featureFlag !== FeatureFlag.DASHBOARD_RBAC,
+        (featureFlag: uiCore.FeatureFlag) =>
+          featureFlag !== uiCore.FeatureFlag.DASHBOARD_RBAC,
       );
   });
 
@@ -188,10 +190,10 @@ describe('canUserSaveAsDashboard with RBAC feature flag disabled', () => {
 describe('canUserSaveAsDashboard with RBAC feature flag enabled', () => {
   beforeAll(() => {
     isFeatureEnabledMock = jest
-      .spyOn(featureFlags, 'isFeatureEnabled')
+      .spyOn(uiCore, 'isFeatureEnabled')
       .mockImplementation(
-        (featureFlag: FeatureFlag) =>
-          featureFlag === FeatureFlag.DASHBOARD_RBAC,
+        (featureFlag: uiCore.FeatureFlag) =>
+          featureFlag === uiCore.FeatureFlag.DASHBOARD_RBAC,
       );
   });
 
diff --git a/superset-frontend/src/dashboard/util/permissionUtils.ts b/superset-frontend/src/dashboard/util/permissionUtils.ts
index b01484a4e7..23744bde89 100644
--- a/superset-frontend/src/dashboard/util/permissionUtils.ts
+++ b/superset-frontend/src/dashboard/util/permissionUtils.ts
@@ -16,8 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { FeatureFlag } from '@superset-ui/core';
-import { isFeatureEnabled } from 'src/featureFlags';
+import { isFeatureEnabled, FeatureFlag } from '@superset-ui/core';
 import {
   isUserWithPermissionsAndRoles,
   UndefinedUser,
diff --git a/superset-frontend/src/dataMask/actions.ts b/superset-frontend/src/dataMask/actions.ts
index 89e88ce130..7c41703a10 100644
--- a/superset-frontend/src/dataMask/actions.ts
+++ b/superset-frontend/src/dataMask/actions.ts
@@ -18,11 +18,11 @@
  */
 import {
   DataMask,
+  isFeatureEnabled,
   FeatureFlag,
   FilterConfiguration,
   Filters,
 } from '@superset-ui/core';
-import { isFeatureEnabled } from '../featureFlags';
 import { getInitialDataMask } from './reducer';
 
 export const CLEAR_DATA_MASK_STATE = 'CLEAR_DATA_MASK_STATE';
diff --git a/superset-frontend/src/dataMask/reducer.ts b/superset-frontend/src/dataMask/reducer.ts
index ae30d1be78..f2163a54a4 100644
--- a/superset-frontend/src/dataMask/reducer.ts
+++ b/superset-frontend/src/dataMask/reducer.ts
@@ -24,6 +24,7 @@ import {
   DataMask,
   DataMaskStateWithId,
   DataMaskWithId,
+  isFeatureEnabled,
   FeatureFlag,
   Filter,
   FilterConfiguration,
@@ -31,7 +32,6 @@ import {
 } from '@superset-ui/core';
 import { NATIVE_FILTER_PREFIX } from 'src/dashboard/components/nativeFilters/FiltersConfigModal/utils';
 import { HYDRATE_DASHBOARD } from 'src/dashboard/actions/hydrate';
-import { isFeatureEnabled } from 'src/featureFlags';
 import {
   AnyDataMaskAction,
   CLEAR_DATA_MASK_STATE,
diff --git a/superset-frontend/src/explore/components/DataTablesPane/DataTablesPane.tsx b/superset-frontend/src/explore/components/DataTablesPane/DataTablesPane.tsx
index 54f222c717..726f3ea468 100644
--- a/superset-frontend/src/explore/components/DataTablesPane/DataTablesPane.tsx
+++ b/superset-frontend/src/explore/components/DataTablesPane/DataTablesPane.tsx
@@ -23,10 +23,15 @@ import React, {
   useState,
   MouseEvent,
 } from 'react';
-import { FeatureFlag, styled, t, useTheme } from '@superset-ui/core';
+import {
+  isFeatureEnabled,
+  FeatureFlag,
+  styled,
+  t,
+  useTheme,
+} from '@superset-ui/core';
 import Icons from 'src/components/Icons';
 import Tabs from 'src/components/Tabs';
-import { isFeatureEnabled } from 'src/featureFlags';
 import {
   getItem,
   setItem,
diff --git a/superset-frontend/src/explore/components/DatasourcePanel/index.tsx b/superset-frontend/src/explore/components/DatasourcePanel/index.tsx
index efedacd220..1d85c8235f 100644
--- a/superset-frontend/src/explore/components/DatasourcePanel/index.tsx
+++ b/superset-frontend/src/explore/components/DatasourcePanel/index.tsx
@@ -20,6 +20,7 @@ import React, { useEffect, useMemo, useRef, useState } from 'react';
 import {
   css,
   DatasourceType,
+  isFeatureEnabled,
   FeatureFlag,
   Metric,
   QueryFormData,
@@ -37,7 +38,6 @@ import { SaveDatasetModal } from 'src/SqlLab/components/SaveDatasetModal';
 import { getDatasourceAsSaveableDataset } from 'src/utils/datasourceUtils';
 import { Input } from 'src/components/Input';
 import { FAST_DEBOUNCE } from 'src/constants';
-import { isFeatureEnabled } from 'src/featureFlags';
 import { ExploreActions } from 'src/explore/actions/exploreActions';
 import Control from 'src/explore/components/Control';
 import DatasourcePanelDragOption from './DatasourcePanelDragOption';
diff --git a/superset-frontend/src/explore/components/ExploreChartPanel/index.jsx b/superset-frontend/src/explore/components/ExploreChartPanel/index.jsx
index 0dee4d076e..984b389a5c 100644
--- a/superset-frontend/src/explore/components/ExploreChartPanel/index.jsx
+++ b/superset-frontend/src/explore/components/ExploreChartPanel/index.jsx
@@ -23,6 +23,7 @@ import {
   css,
   DatasourceType,
   ensureIsArray,
+  isFeatureEnabled,
   FeatureFlag,
   getChartMetadataRegistry,
   styled,
@@ -32,7 +33,6 @@ import {
 } from '@superset-ui/core';
 import { chartPropShape } from 'src/dashboard/util/propShapes';
 import ChartContainer from 'src/components/Chart/ChartContainer';
-import { isFeatureEnabled } from 'src/featureFlags';
 import {
   getItem,
   setItem,
diff --git a/superset-frontend/src/explore/components/SaveModal.tsx b/superset-frontend/src/explore/components/SaveModal.tsx
index 3142d6b758..86ff27bb42 100644
--- a/superset-frontend/src/explore/components/SaveModal.tsx
+++ b/superset-frontend/src/explore/components/SaveModal.tsx
@@ -19,7 +19,6 @@
 /* eslint camelcase: 0 */
 import React from 'react';
 import { Dispatch } from 'redux';
-import { isFeatureEnabled } from 'src/featureFlags';
 import rison from 'rison';
 import { connect } from 'react-redux';
 import { withRouter, RouteComponentProps } from 'react-router-dom';
@@ -27,6 +26,7 @@ import { InfoTooltipWithTrigger } from '@superset-ui/chart-controls';
 import {
   css,
   DatasourceType,
+  isFeatureEnabled,
   FeatureFlag,
   isDefined,
   styled,
diff --git a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/AdhocFilterEditPopoverSimpleTabContent.test.tsx b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/AdhocFilterEditPopoverSimpleTabContent.test.tsx
index 9ad90167b5..d1e85a30b1 100644
--- a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/AdhocFilterEditPopoverSimpleTabContent.test.tsx
+++ b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/AdhocFilterEditPopoverSimpleTabContent.test.tsx
@@ -34,7 +34,7 @@ import {
 import AdhocMetric from 'src/explore/components/controls/MetricControl/AdhocMetric';
 import { render, screen, act, waitFor } from '@testing-library/react';
 import { supersetTheme, FeatureFlag, ThemeProvider } from '@superset-ui/core';
-import * as featureFlags from 'src/featureFlags';
+import * as uiCore from '@superset-ui/core';
 import userEvent from '@testing-library/user-event';
 import fetchMock from 'fetch-mock';
 
@@ -399,7 +399,7 @@ describe('AdhocFilterEditPopoverSimpleTabContent Advanced data Type Test', () =>
   let isFeatureEnabledMock: any;
   beforeEach(async () => {
     isFeatureEnabledMock = jest
-      .spyOn(featureFlags, 'isFeatureEnabled')
+      .spyOn(uiCore, 'isFeatureEnabled')
       .mockImplementation(
         (featureFlag: FeatureFlag) =>
           featureFlag === FeatureFlag.ENABLE_ADVANCED_DATA_TYPES,
diff --git a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/index.tsx b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/index.tsx
index 482746acf8..8c21bf75a9 100644
--- a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/index.tsx
+++ b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/index.tsx
@@ -20,6 +20,7 @@ import React, { useEffect, useState } from 'react';
 import FormItem from 'src/components/Form/FormItem';
 import { Select } from 'src/components';
 import {
+  isFeatureEnabled,
   FeatureFlag,
   hasGenericChartAxes,
   isDefined,
@@ -43,7 +44,6 @@ import AdhocFilter from 'src/explore/components/controls/FilterControl/AdhocFilt
 import { Tooltip } from 'src/components/Tooltip';
 import { Input } from 'src/components/Input';
 import { optionLabel } from 'src/utils/common';
-import { isFeatureEnabled } from 'src/featureFlags';
 import {
   ColumnMeta,
   Dataset,
diff --git a/superset-frontend/src/explore/components/useExploreAdditionalActionsMenu/index.jsx b/superset-frontend/src/explore/components/useExploreAdditionalActionsMenu/index.jsx
index 7eed888fc1..9dd0bc0ef0 100644
--- a/superset-frontend/src/explore/components/useExploreAdditionalActionsMenu/index.jsx
+++ b/superset-frontend/src/explore/components/useExploreAdditionalActionsMenu/index.jsx
@@ -18,7 +18,14 @@
  */
 import React, { useCallback, useMemo, useState } from 'react';
 import { useSelector } from 'react-redux';
-import { css, FeatureFlag, styled, t, useTheme } from '@superset-ui/core';
+import {
+  css,
+  isFeatureEnabled,
+  FeatureFlag,
+  styled,
+  t,
+  useTheme,
+} from '@superset-ui/core';
 import Icons from 'src/components/Icons';
 import { Menu } from 'src/components/Menu';
 import ModalTrigger from 'src/components/ModalTrigger';
@@ -29,7 +36,6 @@ import downloadAsImage from 'src/utils/downloadAsImage';
 import { getChartPermalink } from 'src/utils/urlUtils';
 import copyTextToClipboard from 'src/utils/copy';
 import HeaderReportDropDown from 'src/components/ReportModal/HeaderReportDropdown';
-import { isFeatureEnabled } from 'src/featureFlags';
 import ViewQueryModal from '../controls/ViewQueryModal';
 import EmbedCodeContent from '../EmbedCodeContent';
 import DashboardsSubMenu from './DashboardsSubMenu';
diff --git a/superset-frontend/src/featureFlags.ts b/superset-frontend/src/featureFlags.ts
deleted file mode 100644
index d7387f2796..0000000000
--- a/superset-frontend/src/featureFlags.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-/**
- * 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.
- */
-import { FeatureFlag, FeatureFlagMap } from '@superset-ui/core';
-
-export function initFeatureFlags(featureFlags?: FeatureFlagMap) {
-  if (!window.featureFlags) {
-    window.featureFlags = featureFlags || {};
-  }
-}
-
-export function isFeatureEnabled(feature: FeatureFlag) {
-  try {
-    return !!window.featureFlags[feature];
-  } catch (error) {
-    // eslint-disable-next-line no-console
-    console.error(`Failed to query feature flag ${feature} (see error below)`);
-    // eslint-disable-next-line no-console
-    console.error(error);
-    return false;
-  }
-}
diff --git a/superset-frontend/src/features/alerts/AlertReportModal.tsx b/superset-frontend/src/features/alerts/AlertReportModal.tsx
index a6c039c38a..497c4d7b76 100644
--- a/superset-frontend/src/features/alerts/AlertReportModal.tsx
+++ b/superset-frontend/src/features/alerts/AlertReportModal.tsx
@@ -25,6 +25,7 @@ import React, {
 } from 'react';
 import {
   css,
+  isFeatureEnabled,
   FeatureFlag,
   styled,
   SupersetClient,
@@ -41,7 +42,6 @@ import Modal from 'src/components/Modal';
 import TimezoneSelector from 'src/components/TimezoneSelector';
 import { Radio } from 'src/components/Radio';
 import { propertyComparator } from 'src/components/Select/utils';
-import { isFeatureEnabled } from 'src/featureFlags';
 import withToasts from 'src/components/MessageToasts/withToasts';
 import Owner from 'src/types/Owner';
 import { AntdCheckbox, AsyncSelect, Select } from 'src/components';
diff --git a/superset-frontend/src/features/charts/ChartCard.tsx b/superset-frontend/src/features/charts/ChartCard.tsx
index 1a80d34aa8..352f34d359 100644
--- a/superset-frontend/src/features/charts/ChartCard.tsx
+++ b/superset-frontend/src/features/charts/ChartCard.tsx
@@ -17,9 +17,8 @@
  * under the License.
  */
 import React from 'react';
-import { FeatureFlag, t, useTheme } from '@superset-ui/core';
+import { isFeatureEnabled, FeatureFlag, t, useTheme } from '@superset-ui/core';
 import { Link, useHistory } from 'react-router-dom';
-import { isFeatureEnabled } from 'src/featureFlags';
 import ConfirmStatusChange from 'src/components/ConfirmStatusChange';
 import Icons from 'src/components/Icons';
 import Chart from 'src/types/Chart';
diff --git a/superset-frontend/src/features/dashboards/DashboardCard.tsx b/superset-frontend/src/features/dashboards/DashboardCard.tsx
index 8d15939515..640648c903 100644
--- a/superset-frontend/src/features/dashboards/DashboardCard.tsx
+++ b/superset-frontend/src/features/dashboards/DashboardCard.tsx
@@ -18,8 +18,7 @@
  */
 import React from 'react';
 import { Link, useHistory } from 'react-router-dom';
-import { FeatureFlag, t, useTheme } from '@superset-ui/core';
-import { isFeatureEnabled } from 'src/featureFlags';
+import { isFeatureEnabled, FeatureFlag, t, useTheme } from '@superset-ui/core';
 import { CardStyles } from 'src/views/CRUD/utils';
 import { AntdDropdown } from 'src/components';
 import { Menu } from 'src/components/Menu';
diff --git a/superset-frontend/src/features/tags/TagCard.tsx b/superset-frontend/src/features/tags/TagCard.tsx
index 869678545c..f9d82f9bce 100644
--- a/superset-frontend/src/features/tags/TagCard.tsx
+++ b/superset-frontend/src/features/tags/TagCard.tsx
@@ -18,9 +18,8 @@
  */
 import React from 'react';
 import { Link } from 'react-router-dom';
-import { FeatureFlag, t, useTheme } from '@superset-ui/core';
+import { isFeatureEnabled, FeatureFlag, t, useTheme } from '@superset-ui/core';
 import { CardStyles } from 'src/views/CRUD/utils';
-import { isFeatureEnabled } from 'src/featureFlags';
 import { AntdDropdown } from 'src/components';
 import { Menu } from 'src/components/Menu';
 import ConfirmStatusChange from 'src/components/ConfirmStatusChange';
diff --git a/superset-frontend/src/middleware/asyncEvent.test.ts b/superset-frontend/src/middleware/asyncEvent.test.ts
index 60ea10f504..c819893580 100644
--- a/superset-frontend/src/middleware/asyncEvent.test.ts
+++ b/superset-frontend/src/middleware/asyncEvent.test.ts
@@ -19,7 +19,7 @@
 import fetchMock from 'fetch-mock';
 import WS from 'jest-websocket-mock';
 import sinon from 'sinon';
-import * as featureFlags from 'src/featureFlags';
+import * as uiCore from '@superset-ui/core';
 import { parseErrorJson } from 'src/utils/getClientErrorObject';
 import * as asyncEvent from 'src/middleware/asyncEvent';
 
@@ -84,7 +84,7 @@ describe('asyncEvent middleware', () => {
   let featureEnabledStub: any;
 
   beforeEach(async () => {
-    featureEnabledStub = sinon.stub(featureFlags, 'isFeatureEnabled');
+    featureEnabledStub = sinon.stub(uiCore, 'isFeatureEnabled');
     featureEnabledStub.withArgs('GLOBAL_ASYNC_QUERIES').returns(true);
   });
 
diff --git a/superset-frontend/src/middleware/asyncEvent.ts b/superset-frontend/src/middleware/asyncEvent.ts
index 5d878cef55..1d5fcb66c5 100644
--- a/superset-frontend/src/middleware/asyncEvent.ts
+++ b/superset-frontend/src/middleware/asyncEvent.ts
@@ -18,6 +18,7 @@
  */
 import {
   ensureIsArray,
+  isFeatureEnabled,
   FeatureFlag,
   makeApi,
   SupersetClient,
@@ -25,7 +26,6 @@ import {
 } from '@superset-ui/core';
 import { SupersetError } from 'src/components/ErrorMessage/types';
 import getBootstrapData from 'src/utils/getBootstrapData';
-import { isFeatureEnabled } from '../featureFlags';
 import {
   getClientErrorObject,
   parseErrorJson,
diff --git a/superset-frontend/src/pages/ChartCreation/index.tsx b/superset-frontend/src/pages/ChartCreation/index.tsx
index 7ff3442c9d..18b8080db7 100644
--- a/superset-frontend/src/pages/ChartCreation/index.tsx
+++ b/superset-frontend/src/pages/ChartCreation/index.tsx
@@ -20,6 +20,7 @@ import React, { ReactNode } from 'react';
 import rison from 'rison';
 import querystring from 'query-string';
 import {
+  isFeatureEnabled,
   FeatureFlag,
   isDefined,
   JsonResponse,
@@ -34,7 +35,6 @@ import Button from 'src/components/Button';
 import { AsyncSelect, Steps } from 'src/components';
 import { Tooltip } from 'src/components/Tooltip';
 import withToasts from 'src/components/MessageToasts/withToasts';
-import { isFeatureEnabled } from 'src/featureFlags';
 
 import VizTypeGallery, {
   MAX_ADVISABLE_VIZ_GALLERY_WIDTH,
diff --git a/superset-frontend/src/pages/ChartList/ChartList.test.jsx b/superset-frontend/src/pages/ChartList/ChartList.test.jsx
index fc216e617d..6169c2a67e 100644
--- a/superset-frontend/src/pages/ChartList/ChartList.test.jsx
+++ b/superset-frontend/src/pages/ChartList/ChartList.test.jsx
@@ -22,7 +22,7 @@ import thunk from 'redux-thunk';
 import configureStore from 'redux-mock-store';
 import { Provider } from 'react-redux';
 import fetchMock from 'fetch-mock';
-import * as featureFlags from 'src/featureFlags';
+import * as uiCore from '@superset-ui/core';
 import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint';
 import { styledMount as mount } from 'spec/helpers/theming';
 import { render, screen, cleanup } from 'spec/helpers/testing-library';
@@ -101,7 +101,7 @@ fetchMock.get('/thumbnail', { body: new Blob(), sendAsJson: false });
 
 describe('ChartList', () => {
   const isFeatureEnabledMock = jest
-    .spyOn(featureFlags, 'isFeatureEnabled')
+    .spyOn(uiCore, 'isFeatureEnabled')
     .mockImplementation(feature => feature === 'LISTVIEWS_DEFAULT_CARD_VIEW');
 
   afterAll(() => {
@@ -199,7 +199,7 @@ describe('RTL', () => {
   let isFeatureEnabledMock;
   beforeEach(async () => {
     isFeatureEnabledMock = jest
-      .spyOn(featureFlags, 'isFeatureEnabled')
+      .spyOn(uiCore, 'isFeatureEnabled')
       .mockImplementation(() => true);
     await renderAndWait();
   });
diff --git a/superset-frontend/src/pages/ChartList/index.tsx b/superset-frontend/src/pages/ChartList/index.tsx
index ef1f9374e4..e94dc426b1 100644
--- a/superset-frontend/src/pages/ChartList/index.tsx
+++ b/superset-frontend/src/pages/ChartList/index.tsx
@@ -18,6 +18,7 @@
  */
 import {
   ensureIsArray,
+  isFeatureEnabled,
   FeatureFlag,
   getChartMetadataRegistry,
   JsonResponse,
@@ -29,7 +30,6 @@ import React, { useState, useMemo, useCallback } from 'react';
 import rison from 'rison';
 import { uniqBy } from 'lodash';
 import moment from 'moment';
-import { isFeatureEnabled } from 'src/featureFlags';
 import {
   createErrorHandler,
   createFetchRelated,
diff --git a/superset-frontend/src/pages/DashboardList/DashboardList.test.jsx b/superset-frontend/src/pages/DashboardList/DashboardList.test.jsx
index bd91faf614..811f51afba 100644
--- a/superset-frontend/src/pages/DashboardList/DashboardList.test.jsx
+++ b/superset-frontend/src/pages/DashboardList/DashboardList.test.jsx
@@ -22,7 +22,7 @@ import thunk from 'redux-thunk';
 import configureStore from 'redux-mock-store';
 import fetchMock from 'fetch-mock';
 import { Provider } from 'react-redux';
-import * as featureFlags from 'src/featureFlags';
+import * as uiCore from '@superset-ui/core';
 
 import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint';
 import { styledMount as mount } from 'spec/helpers/theming';
@@ -98,7 +98,7 @@ fetchMock.get('/thumbnail', { body: new Blob(), sendAsJson: false });
 
 describe('DashboardList', () => {
   const isFeatureEnabledMock = jest
-    .spyOn(featureFlags, 'isFeatureEnabled')
+    .spyOn(uiCore, 'isFeatureEnabled')
     .mockImplementation(feature => feature === 'LISTVIEWS_DEFAULT_CARD_VIEW');
 
   afterAll(() => {
@@ -217,7 +217,7 @@ describe('RTL', () => {
   let isFeatureEnabledMock;
   beforeEach(async () => {
     isFeatureEnabledMock = jest
-      .spyOn(featureFlags, 'isFeatureEnabled')
+      .spyOn(uiCore, 'isFeatureEnabled')
       .mockImplementation(() => true);
     await renderAndWait();
   });
diff --git a/superset-frontend/src/pages/DashboardList/index.tsx b/superset-frontend/src/pages/DashboardList/index.tsx
index 3db775e66e..2b6252309d 100644
--- a/superset-frontend/src/pages/DashboardList/index.tsx
+++ b/superset-frontend/src/pages/DashboardList/index.tsx
@@ -16,11 +16,16 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { FeatureFlag, styled, SupersetClient, t } from '@superset-ui/core';
+import {
+  isFeatureEnabled,
+  FeatureFlag,
+  styled,
+  SupersetClient,
+  t,
+} from '@superset-ui/core';
 import React, { useState, useMemo, useCallback } from 'react';
 import { Link } from 'react-router-dom';
 import rison from 'rison';
-import { isFeatureEnabled } from 'src/featureFlags';
 import {
   createFetchRelated,
   createErrorHandler,
diff --git a/superset-frontend/src/pages/DatabaseList/index.tsx b/superset-frontend/src/pages/DatabaseList/index.tsx
index e2ba4ea9b0..b2c18cecec 100644
--- a/superset-frontend/src/pages/DatabaseList/index.tsx
+++ b/superset-frontend/src/pages/DatabaseList/index.tsx
@@ -17,6 +17,7 @@
  * under the License.
  */
 import {
+  isFeatureEnabled,
   FeatureFlag,
   getExtensionsRegistry,
   styled,
@@ -30,7 +31,6 @@ import { useQueryParams, BooleanParam } from 'use-query-params';
 import { LocalStorageKeys, setItem } from 'src/utils/localStorageHelpers';
 
 import Loading from 'src/components/Loading';
-import { isFeatureEnabled } from 'src/featureFlags';
 import { useListViewResource } from 'src/views/CRUD/hooks';
 import { createErrorHandler, uploadUserPerms } from 'src/views/CRUD/utils';
 import withToasts from 'src/components/MessageToasts/withToasts';
diff --git a/superset-frontend/src/pages/DatasetList/DatasetList.test.tsx b/superset-frontend/src/pages/DatasetList/DatasetList.test.tsx
index 358a5fcfcc..115a861bdd 100644
--- a/superset-frontend/src/pages/DatasetList/DatasetList.test.tsx
+++ b/superset-frontend/src/pages/DatasetList/DatasetList.test.tsx
@@ -26,7 +26,7 @@ import { render, screen, cleanup } from 'spec/helpers/testing-library';
 import { FeatureFlag } from '@superset-ui/core';
 import userEvent from '@testing-library/user-event';
 import { QueryParamProvider } from 'use-query-params';
-import * as featureFlags from 'src/featureFlags';
+import * as uiCore from '@superset-ui/core';
 
 import DatasetList from 'src/pages/DatasetList';
 import ListView from 'src/components/ListView';
@@ -258,7 +258,7 @@ describe('RTL', () => {
   let isFeatureEnabledMock: jest.SpyInstance<boolean, [feature: FeatureFlag]>;
   beforeEach(async () => {
     isFeatureEnabledMock = jest
-      .spyOn(featureFlags, 'isFeatureEnabled')
+      .spyOn(uiCore, 'isFeatureEnabled')
       .mockImplementation(() => true);
     await renderAndWait();
   });
diff --git a/superset-frontend/src/pages/DatasetList/index.tsx b/superset-frontend/src/pages/DatasetList/index.tsx
index 43913a8280..7633edb016 100644
--- a/superset-frontend/src/pages/DatasetList/index.tsx
+++ b/superset-frontend/src/pages/DatasetList/index.tsx
@@ -17,6 +17,7 @@
  * under the License.
  */
 import {
+  isFeatureEnabled,
   FeatureFlag,
   getExtensionsRegistry,
   styled,
@@ -57,7 +58,6 @@ import FacePile from 'src/components/FacePile';
 import CertifiedBadge from 'src/components/CertifiedBadge';
 import InfoTooltip from 'src/components/InfoTooltip';
 import ImportModelsModal from 'src/components/ImportModal/index';
-import { isFeatureEnabled } from 'src/featureFlags';
 import WarningIconWithTooltip from 'src/components/WarningIconWithTooltip';
 import { isUserAdmin } from 'src/dashboard/util/permissionUtils';
 import { GenericLink } from 'src/components/GenericLink/GenericLink';
diff --git a/superset-frontend/src/pages/Home/Home.test.tsx b/superset-frontend/src/pages/Home/Home.test.tsx
index cbf9b00272..9f0b4e3ca1 100644
--- a/superset-frontend/src/pages/Home/Home.test.tsx
+++ b/superset-frontend/src/pages/Home/Home.test.tsx
@@ -23,7 +23,7 @@ import thunk from 'redux-thunk';
 import fetchMock from 'fetch-mock';
 import { act } from 'react-dom/test-utils';
 import configureStore from 'redux-mock-store';
-import * as featureFlags from 'src/featureFlags';
+import * as uiCore from '@superset-ui/core';
 import Welcome from 'src/pages/Home';
 import { ReactWrapper } from 'enzyme';
 import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint';
@@ -211,7 +211,7 @@ describe('Welcome page with toggle switch', () => {
 
   beforeAll(async () => {
     isFeatureEnabledMock = jest
-      .spyOn(featureFlags, 'isFeatureEnabled')
+      .spyOn(uiCore, 'isFeatureEnabled')
       .mockReturnValue(true);
     await act(async () => {
       wrapper = await mountAndWait();
diff --git a/superset-frontend/src/pages/Home/index.tsx b/superset-frontend/src/pages/Home/index.tsx
index 00124eac7c..cfeb4cd982 100644
--- a/superset-frontend/src/pages/Home/index.tsx
+++ b/superset-frontend/src/pages/Home/index.tsx
@@ -18,6 +18,7 @@
  */
 import React, { useEffect, useMemo, useState } from 'react';
 import {
+  isFeatureEnabled,
   FeatureFlag,
   getExtensionsRegistry,
   JsonObject,
@@ -45,7 +46,6 @@ import {
   loadingCardCount,
   mq,
 } from 'src/views/CRUD/utils';
-import { isFeatureEnabled } from 'src/featureFlags';
 import { AntdSwitch } from 'src/components';
 import getBootstrapData from 'src/utils/getBootstrapData';
 import { TableTab } from 'src/views/CRUD/types';
diff --git a/superset-frontend/src/pages/SavedQueryList/SavedQueryList.test.jsx b/superset-frontend/src/pages/SavedQueryList/SavedQueryList.test.jsx
index 5d9b35a4a3..3804010e26 100644
--- a/superset-frontend/src/pages/SavedQueryList/SavedQueryList.test.jsx
+++ b/superset-frontend/src/pages/SavedQueryList/SavedQueryList.test.jsx
@@ -27,7 +27,7 @@ import { render, screen, cleanup, waitFor } from 'spec/helpers/testing-library';
 import userEvent from '@testing-library/user-event';
 import { QueryParamProvider } from 'use-query-params';
 import { act } from 'react-dom/test-utils';
-import * as featureFlags from 'src/featureFlags';
+import * as uiCore from '@superset-ui/core';
 import SavedQueryList from 'src/pages/SavedQueryList';
 import SubMenu from 'src/features/home/SubMenu';
 import ListView from 'src/components/ListView';
@@ -261,7 +261,7 @@ describe('RTL', () => {
   let isFeatureEnabledMock;
   beforeEach(async () => {
     isFeatureEnabledMock = jest
-      .spyOn(featureFlags, 'isFeatureEnabled')
+      .spyOn(uiCore, 'isFeatureEnabled')
       .mockImplementation(() => true);
     await renderAndWait();
   });
diff --git a/superset-frontend/src/pages/SavedQueryList/index.tsx b/superset-frontend/src/pages/SavedQueryList/index.tsx
index a985bd8ea8..f0d89dba27 100644
--- a/superset-frontend/src/pages/SavedQueryList/index.tsx
+++ b/superset-frontend/src/pages/SavedQueryList/index.tsx
@@ -17,7 +17,13 @@
  * under the License.
  */
 
-import { FeatureFlag, styled, SupersetClient, t } from '@superset-ui/core';
+import {
+  isFeatureEnabled,
+  FeatureFlag,
+  styled,
+  SupersetClient,
+  t,
+} from '@superset-ui/core';
 import React, { useState, useMemo, useCallback } from 'react';
 import rison from 'rison';
 import moment from 'moment';
@@ -46,7 +52,6 @@ import { commonMenuData } from 'src/features/home/commonMenuData';
 import { SavedQueryObject } from 'src/views/CRUD/types';
 import copyTextToClipboard from 'src/utils/copy';
 import Tag from 'src/types/TagType';
-import { isFeatureEnabled } from 'src/featureFlags';
 import ImportModelsModal from 'src/components/ImportModal/index';
 import Icons from 'src/components/Icons';
 import { BootstrapUser } from 'src/types/bootstrapTypes';
diff --git a/superset-frontend/src/pages/Tags/index.tsx b/superset-frontend/src/pages/Tags/index.tsx
index 87cf7c787b..bf8577cc58 100644
--- a/superset-frontend/src/pages/Tags/index.tsx
+++ b/superset-frontend/src/pages/Tags/index.tsx
@@ -16,9 +16,8 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { FeatureFlag, t } from '@superset-ui/core';
+import { isFeatureEnabled, FeatureFlag, t } from '@superset-ui/core';
 import React, { useMemo, useCallback } from 'react';
-import { isFeatureEnabled } from 'src/featureFlags';
 import {
   createFetchRelated,
   createErrorHandler,
diff --git a/superset-frontend/src/preamble.ts b/superset-frontend/src/preamble.ts
index d1d84b777c..52fa959905 100644
--- a/superset-frontend/src/preamble.ts
+++ b/superset-frontend/src/preamble.ts
@@ -20,14 +20,18 @@ import { setConfig as setHotLoaderConfig } from 'react-hot-loader';
 import 'abortcontroller-polyfill/dist/abortcontroller-polyfill-only';
 import moment from 'moment';
 // eslint-disable-next-line no-restricted-imports
-import { configure, makeApi, supersetTheme } from '@superset-ui/core';
+import {
+  configure,
+  makeApi,
+  supersetTheme,
+  initFeatureFlags,
+} from '@superset-ui/core';
 import { merge } from 'lodash';
 import setupClient from './setup/setupClient';
 import setupColors from './setup/setupColors';
 import setupFormatters from './setup/setupFormatters';
 import setupDashboardComponents from './setup/setupDashboardComponents';
 import { User } from './types/bootstrapTypes';
-import { initFeatureFlags } from './featureFlags';
 import getBootstrapData from './utils/getBootstrapData';
 
 if (process.env.WEBPACK_MODE === 'development') {
diff --git a/superset-frontend/src/utils/hostNamesConfig.js b/superset-frontend/src/utils/hostNamesConfig.js
index dfbf9b26c9..1d3869a8aa 100644
--- a/superset-frontend/src/utils/hostNamesConfig.js
+++ b/superset-frontend/src/utils/hostNamesConfig.js
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { initFeatureFlags } from 'src/featureFlags';
+import { initFeatureFlags } from '@superset-ui/core';
 import getBootstrapData from './getBootstrapData';
 
 function getDomainsConfig() {
diff --git a/superset-frontend/src/views/routes.test.tsx b/superset-frontend/src/views/routes.test.tsx
index 3b01288bfd..379abefb04 100644
--- a/superset-frontend/src/views/routes.test.tsx
+++ b/superset-frontend/src/views/routes.test.tsx
@@ -19,10 +19,6 @@
 import React from 'react';
 import { isFrontendRoute, routes } from './routes';
 
-jest.mock('src/featureFlags', () => ({
-  ...jest.requireActual<object>('src/featureFlags'),
-  isFeatureEnabled: jest.fn().mockReturnValue(true),
-}));
 jest.mock('src/pages/Home', () => () => <div data-test="mock-home" />);
 
 describe('isFrontendRoute', () => {


[superset] 03/10: fix: Tooltip of area chart shows undefined total (#24916)

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 df92cc2d55ddbaf078f4c0baa8475781f5600cd6
Author: Michael S. Molina <70...@users.noreply.github.com>
AuthorDate: Tue Aug 8 18:13:21 2023 -0300

    fix: Tooltip of area chart shows undefined total (#24916)
    
    (cherry picked from commit ec9e9a46f2f092ce56d3ed5a8a9a3ea0214db88a)
---
 superset-frontend/plugins/legacy-preset-chart-nvd3/src/utils.js | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/utils.js b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/utils.js
index 39b42525af..c3ef5a972e 100644
--- a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/utils.js
+++ b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/utils.js
@@ -191,19 +191,18 @@ export function generateAreaChartTooltipContent(
     '<tr class="tooltip-header"><td></td><td>Category</td><td>Value</td><td>% to total</td></tr>';
   d.series.forEach(series => {
     const key = getFormattedKey(series.key, true);
+    const isTotal = series.key === 'TOTAL';
     let trClass = '';
     if (series.highlight) {
       trClass = 'superset-legacy-chart-nvd3-tr-highlight';
-    } else if (series.key === 'TOTAL') {
+    } else if (isTotal) {
       trClass = 'superset-legacy-chart-nvd3-tr-total';
     }
     tooltip +=
       `<tr class="${trClass}" style="border-color: ${series.color}">` +
-      `<td style="color: ${series.color}">${
-        series.key === 'TOTAL' ? '' : '&#9724;'
-      }</td>` +
+      `<td style="color: ${series.color}">${isTotal ? '' : '&#9724;'}</td>` +
       `<td>${key}</td>` +
-      `<td>${valueFormatter(series?.point?.y)}</td>` +
+      `<td>${valueFormatter(isTotal ? total : series?.point?.y)}</td>` +
       `<td>${((100 * series.value) / total).toFixed(2)}%</td>` +
       '</tr>';
   });


[superset] 01/10: fix(logs): increase json field for logs table (#24911)

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 ed56375d5f6e9a2d3a2d1bd6413b3782a5fcfdc9
Author: Gyuil Han <cn...@gmail.com>
AuthorDate: Wed Aug 9 00:46:27 2023 +0900

    fix(logs): increase json field for logs table (#24911)
    
    (cherry picked from commit eb7c14561e96fc92a493b37bfcaa5aad59b98923)
---
 .../2023-08-08_14-14_2e826adca42c_log_json.py      | 53 ++++++++++++++++++++++
 1 file changed, 53 insertions(+)

diff --git a/superset/migrations/versions/2023-08-08_14-14_2e826adca42c_log_json.py b/superset/migrations/versions/2023-08-08_14-14_2e826adca42c_log_json.py
new file mode 100644
index 0000000000..aa77fa4f88
--- /dev/null
+++ b/superset/migrations/versions/2023-08-08_14-14_2e826adca42c_log_json.py
@@ -0,0 +1,53 @@
+# 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 schema for log
+
+Revision ID: 2e826adca42c
+Revises: 0769ef90fddd
+Create Date: 2023-08-08 14:14:23.381364
+
+"""
+
+
+import sqlalchemy as sa
+from alembic import op
+
+from superset.utils.core import MediumText
+
+# revision identifiers, used by Alembic.
+revision = "2e826adca42c"
+down_revision = "0769ef90fddd"
+
+
+def upgrade():
+    with op.batch_alter_table("logs") as batch_op:
+        batch_op.alter_column(
+            "json",
+            existing_type=sa.Text(),
+            type_=MediumText(),
+            existing_nullable=True,
+        )
+
+
+def downgrade():
+    with op.batch_alter_table("logs") as batch_op:
+        batch_op.alter_column(
+            "json",
+            existing_type=MediumText(),
+            type_=sa.Text(),
+            existing_nullable=True,
+        )


[superset] 06/10: fix: Dashboard aware RBAC "Save as" menu item (#24806)

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 309582516d7e1dfe70c1618d7c91e73a197d8a3a
Author: John Bodley <45...@users.noreply.github.com>
AuthorDate: Wed Aug 9 13:37:52 2023 -0700

    fix: Dashboard aware RBAC "Save as" menu item (#24806)
    
    (cherry picked from commit f6c3f0cbbb820b26ac9dc2f24832d59092a22f53)
---
 _update-notifier-last-checked                      |  0
 superset-frontend/src/dashboard/actions/hydrate.js |  7 +-
 .../HeaderActionsDropdown.test.tsx                 |  4 +-
 .../src/dashboard/components/SaveModal.tsx         |  2 +-
 .../src/dashboard/util/permissionUtils.test.ts     | 91 ++++++++++++++++++----
 .../src/dashboard/util/permissionUtils.ts          | 12 +++
 superset/daos/dashboard.py                         |  7 ++
 superset/dashboards/api.py                         |  6 +-
 .../dashboards/security/security_rbac_tests.py     | 90 ++++++++++++++++++++-
 9 files changed, 197 insertions(+), 22 deletions(-)

diff --git a/_update-notifier-last-checked b/_update-notifier-last-checked
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/superset-frontend/src/dashboard/actions/hydrate.js b/superset-frontend/src/dashboard/actions/hydrate.js
index 68a5e91613..6fd06c8f12 100644
--- a/superset-frontend/src/dashboard/actions/hydrate.js
+++ b/superset-frontend/src/dashboard/actions/hydrate.js
@@ -24,7 +24,10 @@ import { getInitialState as getInitialNativeFilterState } from 'src/dashboard/re
 import { applyDefaultFormData } from 'src/explore/store';
 import { buildActiveFilters } from 'src/dashboard/util/activeDashboardFilters';
 import { findPermission } from 'src/utils/findPermission';
-import { canUserEditDashboard } from 'src/dashboard/util/permissionUtils';
+import {
+  canUserEditDashboard,
+  canUserSaveAsDashboard,
+} from 'src/dashboard/util/permissionUtils';
 import {
   getCrossFiltersConfiguration,
   isCrossFiltersEnabled,
@@ -336,7 +339,7 @@ export const hydrateDashboard =
           metadata,
           userId: user.userId ? String(user.userId) : null, // legacy, please use state.user instead
           dash_edit_perm: canEdit,
-          dash_save_perm: findPermission('can_write', 'Dashboard', roles),
+          dash_save_perm: canUserSaveAsDashboard(dashboard, user),
           dash_share_perm: findPermission(
             'can_share_dashboard',
             'Superset',
diff --git a/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/HeaderActionsDropdown.test.tsx b/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/HeaderActionsDropdown.test.tsx
index e4affa887f..a89bccb480 100644
--- a/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/HeaderActionsDropdown.test.tsx
+++ b/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/HeaderActionsDropdown.test.tsx
@@ -196,7 +196,7 @@ test('should show the share actions', async () => {
   expect(screen.getByText('Share')).toBeInTheDocument();
 });
 
-test('should render the "Save Modal" when user can save', async () => {
+test('should render the "Save as" menu item when user can save', async () => {
   const mockedProps = createProps();
   const canSaveProps = {
     ...mockedProps,
@@ -206,7 +206,7 @@ test('should render the "Save Modal" when user can save', async () => {
   expect(screen.getByText('Save as')).toBeInTheDocument();
 });
 
-test('should NOT render the "Save Modal" menu item when user cannot save', async () => {
+test('should NOT render the "Save as" menu item when user cannot save', async () => {
   const mockedProps = createProps();
   setup(mockedProps);
   expect(screen.queryByText('Save as')).not.toBeInTheDocument();
diff --git a/superset-frontend/src/dashboard/components/SaveModal.tsx b/superset-frontend/src/dashboard/components/SaveModal.tsx
index d024ffc914..fc41798847 100644
--- a/superset-frontend/src/dashboard/components/SaveModal.tsx
+++ b/superset-frontend/src/dashboard/components/SaveModal.tsx
@@ -81,7 +81,7 @@ class SaveModal extends React.PureComponent<SaveModalProps, SaveModalState> {
     super(props);
     this.state = {
       saveType: props.saveType,
-      newDashName: props.dashboardTitle + t('[copy]'),
+      newDashName: `${props.dashboardTitle} ${t('[copy]')}`,
       duplicateSlices: false,
     };
 
diff --git a/superset-frontend/src/dashboard/util/permissionUtils.test.ts b/superset-frontend/src/dashboard/util/permissionUtils.test.ts
index 7ebf0362d2..e5e958013d 100644
--- a/superset-frontend/src/dashboard/util/permissionUtils.test.ts
+++ b/superset-frontend/src/dashboard/util/permissionUtils.test.ts
@@ -16,6 +16,8 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+import { FeatureFlag } from '@superset-ui/core';
+import * as featureFlags from 'src/featureFlags';
 import {
   UndefinedUser,
   UserWithPermissionsAndRoles,
@@ -25,6 +27,7 @@ import Owner from 'src/types/Owner';
 import {
   canUserAccessSqlLab,
   canUserEditDashboard,
+  canUserSaveAsDashboard,
   isUserAdmin,
 } from './permissionUtils';
 
@@ -73,22 +76,24 @@ const sqlLabUser: UserWithPermissionsAndRoles = {
 
 const undefinedUser: UndefinedUser = {};
 
-describe('canUserEditDashboard', () => {
-  const dashboard: Dashboard = {
-    id: 1,
-    dashboard_title: 'Test Dash',
-    url: 'https://dashboard.example.com/1',
-    thumbnail_url: 'https://dashboard.example.com/1/thumbnail.png',
-    published: true,
-    css: null,
-    changed_by_name: 'Test User',
-    changed_by: owner,
-    changed_on: '2021-05-12T16:56:22.116839',
-    charts: [],
-    owners: [owner],
-    roles: [],
-  };
+const dashboard: Dashboard = {
+  id: 1,
+  dashboard_title: 'Test Dash',
+  url: 'https://dashboard.example.com/1',
+  thumbnail_url: 'https://dashboard.example.com/1/thumbnail.png',
+  published: true,
+  css: null,
+  changed_by_name: 'Test User',
+  changed_by: owner,
+  changed_on: '2021-05-12T16:56:22.116839',
+  charts: [],
+  owners: [owner],
+  roles: [],
+};
 
+let isFeatureEnabledMock: jest.MockInstance<boolean, [feature: FeatureFlag]>;
+
+describe('canUserEditDashboard', () => {
   it('allows owners to edit', () => {
     expect(canUserEditDashboard(dashboard, ownerUser)).toEqual(true);
   });
@@ -151,3 +156,59 @@ test('canUserAccessSqlLab returns false for non-sqllab role', () => {
 test('canUserAccessSqlLab returns true for sqllab role', () => {
   expect(canUserAccessSqlLab(sqlLabUser)).toEqual(true);
 });
+
+describe('canUserSaveAsDashboard with RBAC feature flag disabled', () => {
+  beforeAll(() => {
+    isFeatureEnabledMock = jest
+      .spyOn(featureFlags, 'isFeatureEnabled')
+      .mockImplementation(
+        (featureFlag: FeatureFlag) =>
+          featureFlag !== FeatureFlag.DASHBOARD_RBAC,
+      );
+  });
+
+  afterAll(() => {
+    // @ts-ignore
+    isFeatureEnabledMock.restore();
+  });
+
+  it('allows owners', () => {
+    expect(canUserSaveAsDashboard(dashboard, ownerUser)).toEqual(true);
+  });
+
+  it('allows admin users', () => {
+    expect(canUserSaveAsDashboard(dashboard, adminUser)).toEqual(true);
+  });
+
+  it('allows non-owners', () => {
+    expect(canUserSaveAsDashboard(dashboard, outsiderUser)).toEqual(true);
+  });
+});
+
+describe('canUserSaveAsDashboard with RBAC feature flag enabled', () => {
+  beforeAll(() => {
+    isFeatureEnabledMock = jest
+      .spyOn(featureFlags, 'isFeatureEnabled')
+      .mockImplementation(
+        (featureFlag: FeatureFlag) =>
+          featureFlag === FeatureFlag.DASHBOARD_RBAC,
+      );
+  });
+
+  afterAll(() => {
+    // @ts-ignore
+    isFeatureEnabledMock.restore();
+  });
+
+  it('allows owners', () => {
+    expect(canUserSaveAsDashboard(dashboard, ownerUser)).toEqual(true);
+  });
+
+  it('allows admin users', () => {
+    expect(canUserSaveAsDashboard(dashboard, adminUser)).toEqual(true);
+  });
+
+  it('reject non-owners', () => {
+    expect(canUserSaveAsDashboard(dashboard, outsiderUser)).toEqual(false);
+  });
+});
diff --git a/superset-frontend/src/dashboard/util/permissionUtils.ts b/superset-frontend/src/dashboard/util/permissionUtils.ts
index c07f0bb0f8..b01484a4e7 100644
--- a/superset-frontend/src/dashboard/util/permissionUtils.ts
+++ b/superset-frontend/src/dashboard/util/permissionUtils.ts
@@ -16,6 +16,8 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+import { FeatureFlag } from '@superset-ui/core';
+import { isFeatureEnabled } from 'src/featureFlags';
 import {
   isUserWithPermissionsAndRoles,
   UndefinedUser,
@@ -63,3 +65,13 @@ export function canUserAccessSqlLab(
       ))
   );
 }
+
+export const canUserSaveAsDashboard = (
+  dashboard: Dashboard,
+  user?: UserWithPermissionsAndRoles | UndefinedUser | null,
+) =>
+  isUserWithPermissionsAndRoles(user) &&
+  findPermission('can_write', 'Dashboard', user?.roles) &&
+  (!isFeatureEnabled(FeatureFlag.DASHBOARD_RBAC) ||
+    isUserAdmin(user) ||
+    isUserDashboardOwner(dashboard, user));
diff --git a/superset/daos/dashboard.py b/superset/daos/dashboard.py
index 51f90709da..f01d610448 100644
--- a/superset/daos/dashboard.py
+++ b/superset/daos/dashboard.py
@@ -26,10 +26,12 @@ from flask_appbuilder.models.sqla import Model
 from flask_appbuilder.models.sqla.interface import SQLAInterface
 from sqlalchemy.exc import SQLAlchemyError
 
+from superset import is_feature_enabled, security_manager
 from superset.daos.base import BaseDAO
 from superset.daos.exceptions import DAOConfigError, DAOCreateFailedError
 from superset.dashboards.commands.exceptions import (
     DashboardAccessDeniedError,
+    DashboardForbiddenError,
     DashboardNotFoundError,
 )
 from superset.dashboards.filter_sets.consts import (
@@ -321,6 +323,11 @@ class DashboardDAO(BaseDAO[Dashboard]):
     def copy_dashboard(
         cls, original_dash: Dashboard, data: dict[str, Any]
     ) -> Dashboard:
+        if is_feature_enabled("DASHBOARD_RBAC") and not security_manager.is_owner(
+            original_dash
+        ):
+            raise DashboardForbiddenError()
+
         dash = Dashboard()
         dash.owners = [g.user] if g.user else []
         dash.dashboard_title = data["dashboard_title"]
diff --git a/superset/dashboards/api.py b/superset/dashboards/api.py
index 8464c87444..07f5ed6fbc 100644
--- a/superset/dashboards/api.py
+++ b/superset/dashboards/api.py
@@ -1420,7 +1420,11 @@ class DashboardRestApi(BaseSupersetModelRestApi):
         except ValidationError as error:
             return self.response_400(message=error.messages)
 
-        dash = DashboardDAO.copy_dashboard(original_dash, data)
+        try:
+            dash = DashboardDAO.copy_dashboard(original_dash, data)
+        except DashboardForbiddenError:
+            return self.response_403()
+
         return self.response(
             200,
             result={
diff --git a/tests/integration_tests/dashboards/security/security_rbac_tests.py b/tests/integration_tests/dashboards/security/security_rbac_tests.py
index ba464064c3..8b7f2ad1ef 100644
--- a/tests/integration_tests/dashboards/security/security_rbac_tests.py
+++ b/tests/integration_tests/dashboards/security/security_rbac_tests.py
@@ -15,11 +15,16 @@
 # specific language governing permissions and limitations
 # under the License.
 """Unit tests for Superset"""
+import json
 from unittest import mock
+from unittest.mock import patch
 
 import pytest
 
-from superset.utils.core import backend
+from superset.daos.dashboard import DashboardDAO
+from superset.dashboards.commands.exceptions import DashboardForbiddenError
+from superset.utils.core import backend, override_user
+from tests.integration_tests.conftest import with_feature_flags
 from tests.integration_tests.dashboards.dashboard_test_utils import *
 from tests.integration_tests.dashboards.security.base_case import (
     BaseTestDashboardSecurity,
@@ -36,6 +41,10 @@ from tests.integration_tests.fixtures.birth_names_dashboard import (
 )
 from tests.integration_tests.fixtures.public_role import public_role_like_gamma
 from tests.integration_tests.fixtures.query_context import get_query_context
+from tests.integration_tests.fixtures.world_bank_dashboard import (
+    load_world_bank_dashboard_with_slices,
+    load_world_bank_data,
+)
 
 CHART_DATA_URI = "api/v1/chart/data"
 
@@ -431,3 +440,82 @@ class TestDashboardRoleBasedSecurity(BaseTestDashboardSecurity):
         # rollback changes
         db.session.delete(dashboard)
         db.session.commit()
+
+    @with_feature_flags(DASHBOARD_RBAC=True)
+    @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices")
+    def test_copy_dashboard_via_api(self):
+        source = db.session.query(Dashboard).filter_by(slug="world_health").first()
+        source.roles = [self.get_role("Gamma")]
+
+        if not (published := source.published):
+            source.published = True  # Required per the DashboardAccessFilter for RBAC.
+
+        db.session.commit()
+
+        uri = f"api/v1/dashboard/{source.id}/copy/"
+
+        data = {
+            "dashboard_title": "copied dash",
+            "css": "<css>",
+            "duplicate_slices": False,
+            "json_metadata": json.dumps(
+                {
+                    "positions": source.position,
+                    "color_namespace": "Color Namespace Test",
+                    "color_scheme": "Color Scheme Test",
+                }
+            ),
+        }
+
+        self.login(username="gamma")
+        rv = self.client.post(uri, json=data)
+        self.assertEqual(rv.status_code, 403)
+        self.logout()
+
+        self.login(username="admin")
+        rv = self.client.post(uri, json=data)
+        self.assertEqual(rv.status_code, 200)
+        self.logout()
+        response = json.loads(rv.data.decode("utf-8"))
+
+        target = (
+            db.session.query(Dashboard)
+            .filter(Dashboard.id == response["result"]["id"])
+            .one()
+        )
+
+        db.session.delete(target)
+        source.roles = []
+
+        if not published:
+            source.published = False
+
+        db.session.commit()
+
+    @with_feature_flags(DASHBOARD_RBAC=True)
+    @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices")
+    def test_copy_dashboard_via_dao(self):
+        source = db.session.query(Dashboard).filter_by(slug="world_health").first()
+
+        data = {
+            "dashboard_title": "copied dash",
+            "css": "<css>",
+            "duplicate_slices": False,
+            "json_metadata": json.dumps(
+                {
+                    "positions": source.position,
+                    "color_namespace": "Color Namespace Test",
+                    "color_scheme": "Color Scheme Test",
+                }
+            ),
+        }
+
+        with override_user(security_manager.find_user("gamma")):
+            with pytest.raises(DashboardForbiddenError):
+                DashboardDAO.copy_dashboard(source, data)
+
+        with override_user(security_manager.find_user("admin")):
+            target = DashboardDAO.copy_dashboard(source, data)
+            db.session.delete(target)
+
+        db.session.commit()


[superset] 04/10: chore: Refactor dashboard security access (#24804)

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 804cc36080f200a48ac21f7d7187d19002967c74
Author: John Bodley <45...@users.noreply.github.com>
AuthorDate: Wed Aug 9 09:25:58 2023 -0700

    chore: Refactor dashboard security access (#24804)
    
    (cherry picked from commit 5522facdc6f22212eff82ff0d602d413da9d5613)
---
 .../src/components/ErrorMessage/types.ts           |   1 +
 superset/daos/dashboard.py                         |  13 ++-
 superset/errors.py                                 |   2 +-
 superset/models/dashboard.py                       |   9 ++
 superset/security/manager.py                       | 116 +++++++++++----------
 superset/views/core.py                             |  12 ++-
 .../security/guest_token_security_tests.py         |  17 ++-
 7 files changed, 98 insertions(+), 72 deletions(-)

diff --git a/superset-frontend/src/components/ErrorMessage/types.ts b/superset-frontend/src/components/ErrorMessage/types.ts
index 87ef4a1bc4..d3fe5bfdf7 100644
--- a/superset-frontend/src/components/ErrorMessage/types.ts
+++ b/superset-frontend/src/components/ErrorMessage/types.ts
@@ -55,6 +55,7 @@ export const ErrorTypeEnum = {
   DATABASE_SECURITY_ACCESS_ERROR: 'DATABASE_SECURITY_ACCESS_ERROR',
   QUERY_SECURITY_ACCESS_ERROR: 'QUERY_SECURITY_ACCESS_ERROR',
   MISSING_OWNERSHIP_ERROR: 'MISSING_OWNERSHIP_ERROR',
+  DASHBOARD_SECURITY_ACCESS_ERROR: 'DASHBOARD_SECURITY_ACCESS_ERROR',
 
   // Other errors
   BACKEND_TIMEOUT_ERROR: 'BACKEND_TIMEOUT_ERROR',
diff --git a/superset/daos/dashboard.py b/superset/daos/dashboard.py
index 69169e1d02..51f90709da 100644
--- a/superset/daos/dashboard.py
+++ b/superset/daos/dashboard.py
@@ -26,10 +26,12 @@ from flask_appbuilder.models.sqla import Model
 from flask_appbuilder.models.sqla.interface import SQLAInterface
 from sqlalchemy.exc import SQLAlchemyError
 
-from superset import security_manager
 from superset.daos.base import BaseDAO
 from superset.daos.exceptions import DAOConfigError, DAOCreateFailedError
-from superset.dashboards.commands.exceptions import DashboardNotFoundError
+from superset.dashboards.commands.exceptions import (
+    DashboardAccessDeniedError,
+    DashboardNotFoundError,
+)
 from superset.dashboards.filter_sets.consts import (
     DASHBOARD_ID_FIELD,
     DESCRIPTION_FIELD,
@@ -39,6 +41,7 @@ from superset.dashboards.filter_sets.consts import (
     OWNER_TYPE_FIELD,
 )
 from superset.dashboards.filters import DashboardAccessFilter, is_uuid
+from superset.exceptions import SupersetSecurityException
 from superset.extensions import db
 from superset.models.core import FavStar, FavStarClassName
 from superset.models.dashboard import Dashboard, id_or_slug_filter
@@ -77,7 +80,11 @@ class DashboardDAO(BaseDAO[Dashboard]):
             raise DashboardNotFoundError()
 
         # make sure we still have basic access check from security manager
-        security_manager.raise_for_dashboard_access(dashboard)
+        try:
+            dashboard.raise_for_access()
+        except SupersetSecurityException as ex:
+            raise DashboardAccessDeniedError() from ex
+
         return dashboard
 
     @staticmethod
diff --git a/superset/errors.py b/superset/errors.py
index 6661e98183..167ec2d3b4 100644
--- a/superset/errors.py
+++ b/superset/errors.py
@@ -27,7 +27,6 @@ class SupersetErrorType(StrEnum):
     Types of errors that can exist within Superset.
 
     Keep in sync with superset-frontend/src/components/ErrorMessage/types.ts
-    and docs/src/pages/docs/Miscellaneous/issue_codes.mdx
     """
 
     # Frontend errors
@@ -66,6 +65,7 @@ class SupersetErrorType(StrEnum):
     QUERY_SECURITY_ACCESS_ERROR = "QUERY_SECURITY_ACCESS_ERROR"
     MISSING_OWNERSHIP_ERROR = "MISSING_OWNERSHIP_ERROR"
     USER_ACTIVITY_SECURITY_ACCESS_ERROR = "USER_ACTIVITY_SECURITY_ACCESS_ERROR"
+    DASHBOARD_SECURITY_ACCESS_ERROR = "DASHBOARD_SECURITY_ACCESS_ERROR"
 
     # Other errors
     BACKEND_TIMEOUT_ERROR = "BACKEND_TIMEOUT_ERROR"
diff --git a/superset/models/dashboard.py b/superset/models/dashboard.py
index 20f2f7e685..0fecf15a55 100644
--- a/superset/models/dashboard.py
+++ b/superset/models/dashboard.py
@@ -446,6 +446,15 @@ class Dashboard(Model, AuditMixinNullable, ImportExportMixin):
         qry = db.session.query(Dashboard).filter(id_or_slug_filter(id_or_slug))
         return qry.one_or_none()
 
+    def raise_for_access(self) -> None:
+        """
+        Raise an exception if the user cannot access the resource.
+
+        :raises SupersetSecurityException: If the user cannot access the resource
+        """
+
+        security_manager.raise_for_access(dashboard=self)
+
 
 def is_uuid(value: str | int) -> bool:
     try:
diff --git a/superset/security/manager.py b/superset/security/manager.py
index f57e730af0..7db81a8415 100644
--- a/superset/security/manager.py
+++ b/superset/security/manager.py
@@ -412,16 +412,30 @@ class SupersetSecurityManager(  # pylint: disable=too-many-public-methods
         :returns: Whether the user can access the dashboard
         """
 
-        # pylint: disable=import-outside-toplevel
-        from superset.dashboards.commands.exceptions import DashboardAccessDeniedError
-
         try:
-            self.raise_for_dashboard_access(dashboard)
-        except DashboardAccessDeniedError:
+            self.raise_for_access(dashboard=dashboard)
+        except SupersetSecurityException:
             return False
 
         return True
 
+    def get_dashboard_access_error_object(  # pylint: disable=invalid-name
+        self,
+        dashboard: "Dashboard",  # pylint: disable=unused-argument
+    ) -> SupersetError:
+        """
+        Return the error object for the denied Superset dashboard.
+
+        :param dashboard: The denied Superset dashboard
+        :returns: The error object
+        """
+
+        return SupersetError(
+            error_type=SupersetErrorType.DASHBOARD_SECURITY_ACCESS_ERROR,
+            message="You don't have access to this dashboard.",
+            level=ErrorLevel.ERROR,
+        )
+
     @staticmethod
     def get_datasource_access_error_msg(datasource: "BaseDatasource") -> str:
         """
@@ -1761,8 +1775,9 @@ class SupersetSecurityManager(  # pylint: disable=too-many-public-methods
         return []
 
     def raise_for_access(
-        # pylint: disable=too-many-arguments, too-many-locals
+        # pylint: disable=too-many-arguments,too-many-branches,too-many-locals
         self,
+        dashboard: Optional["Dashboard"] = None,
         database: Optional["Database"] = None,
         datasource: Optional["BaseDatasource"] = None,
         query: Optional["Query"] = None,
@@ -1783,6 +1798,7 @@ class SupersetSecurityManager(  # pylint: disable=too-many-public-methods
         """
 
         # pylint: disable=import-outside-toplevel
+        from superset import is_feature_enabled
         from superset.connectors.sqla.models import SqlaTable
         from superset.daos.dashboard import DashboardDAO
         from superset.sql_parse import Table
@@ -1856,6 +1872,45 @@ class SupersetSecurityManager(  # pylint: disable=too-many-public-methods
                     self.get_datasource_access_error_object(datasource)
                 )
 
+        if dashboard:
+            if self.is_guest_user():
+                # Guest user is currently used for embedded dashboards only. If the guest
+                # user doesn't have access to the dashboard, ignore all other checks.
+                if self.has_guest_access(dashboard):
+                    return
+                raise SupersetSecurityException(
+                    self.get_dashboard_access_error_object(dashboard)
+                )
+
+            if self.is_admin() or self.is_owner(dashboard):
+                return
+
+            # RBAC and legacy (datasource inferred) access controls.
+            if is_feature_enabled("DASHBOARD_RBAC") and dashboard.roles:
+                if dashboard.published and {role.id for role in dashboard.roles} & {
+                    role.id for role in self.get_user_roles()
+                }:
+                    return
+            elif (
+                # To understand why we rely on status and give access to draft dashboards
+                # without roles take a look at:
+                #
+                #   - https://github.com/apache/superset/pull/24350#discussion_r1225061550
+                #   - https://github.com/apache/superset/pull/17511#issuecomment-975870169
+                #
+                not dashboard.published
+                or not dashboard.datasources
+                or any(
+                    self.can_access_datasource(datasource)
+                    for datasource in dashboard.datasources
+                )
+            ):
+                return
+
+            raise SupersetSecurityException(
+                self.get_dashboard_access_error_object(dashboard)
+            )
+
     def get_user_by_username(
         self, username: str, session: Session = None
     ) -> Optional[User]:
@@ -1991,55 +2046,6 @@ class SupersetSecurityManager(  # pylint: disable=too-many-public-methods
         guest_rls = self.get_guest_rls_filters_str(datasource)
         return guest_rls + rls_str
 
-    def raise_for_dashboard_access(self, dashboard: "Dashboard") -> None:
-        """
-        Raise an exception if the user cannot access the dashboard.
-
-        This does not check for the required role/permission pairs, it only concerns
-        itself with entity relationships.
-
-        :param dashboard: Dashboard the user wants access to
-        :raises DashboardAccessDeniedError: If the user cannot access the resource
-        """
-
-        # pylint: disable=import-outside-toplevel
-        from superset import is_feature_enabled
-        from superset.dashboards.commands.exceptions import DashboardAccessDeniedError
-
-        if self.is_guest_user():
-            # Guest user is currently used for embedded dashboards only. If the guest user
-            # doesn't have access to the dashboard, ignore all other checks.
-            if self.has_guest_access(dashboard):
-                return
-            raise DashboardAccessDeniedError()
-
-        if self.is_admin() or self.is_owner(dashboard):
-            return
-
-        # RBAC and legacy (datasource inferred) access controls.
-        if is_feature_enabled("DASHBOARD_RBAC") and dashboard.roles:
-            if dashboard.published and {role.id for role in dashboard.roles} & {
-                role.id for role in self.get_user_roles()
-            }:
-                return
-        elif (
-            # To understand why we rely on status and give access to draft dashboards
-            # without roles take a look at:
-            #
-            #   - https://github.com/apache/superset/pull/24350#discussion_r1225061550
-            #   - https://github.com/apache/superset/pull/17511#issuecomment-975870169
-            #
-            not dashboard.published
-            or not dashboard.datasources
-            or any(
-                self.can_access_datasource(datasource)
-                for datasource in dashboard.datasources
-            )
-        ):
-            return
-
-        raise DashboardAccessDeniedError()
-
     @staticmethod
     def _get_current_epoch_time() -> float:
         """This is used so the tests can mock time"""
diff --git a/superset/views/core.py b/superset/views/core.py
index 4508ec70c0..1b50a59e36 100755
--- a/superset/views/core.py
+++ b/superset/views/core.py
@@ -50,12 +50,16 @@ from superset.connectors.sqla.models import SqlaTable
 from superset.daos.chart import ChartDAO
 from superset.daos.database import DatabaseDAO
 from superset.daos.datasource import DatasourceDAO
-from superset.dashboards.commands.exceptions import DashboardAccessDeniedError
 from superset.dashboards.commands.importers.v0 import ImportDashboardsCommand
 from superset.dashboards.permalink.commands.get import GetDashboardPermalinkCommand
 from superset.dashboards.permalink.exceptions import DashboardPermalinkGetFailedError
 from superset.datasets.commands.exceptions import DatasetNotFoundError
-from superset.exceptions import CacheLoadError, DatabaseNotFound, SupersetException
+from superset.exceptions import (
+    CacheLoadError,
+    DatabaseNotFound,
+    SupersetException,
+    SupersetSecurityException,
+)
 from superset.explore.form_data.commands.create import CreateFormDataCommand
 from superset.explore.form_data.commands.get import GetFormDataCommand
 from superset.explore.form_data.commands.parameters import CommandParameters
@@ -866,8 +870,8 @@ class Superset(BaseSupersetView):  # pylint: disable=too-many-public-methods
             abort(404)
 
         try:
-            security_manager.raise_for_dashboard_access(dashboard)
-        except DashboardAccessDeniedError as ex:
+            dashboard.raise_for_access()
+        except SupersetSecurityException as ex:
             return redirect_with_flash(
                 url="/dashboard/list/",
                 message=utils.error_msg_from_exception(ex),
diff --git a/tests/integration_tests/security/guest_token_security_tests.py b/tests/integration_tests/security/guest_token_security_tests.py
index 73dcb1b932..cb6095c0e7 100644
--- a/tests/integration_tests/security/guest_token_security_tests.py
+++ b/tests/integration_tests/security/guest_token_security_tests.py
@@ -22,7 +22,6 @@ from flask import g
 
 from superset import db, security_manager
 from superset.daos.dashboard import EmbeddedDashboardDAO
-from superset.dashboards.commands.exceptions import DashboardAccessDeniedError
 from superset.exceptions import SupersetSecurityException
 from superset.models.dashboard import Dashboard
 from superset.security.guest_token import GuestTokenResourceType
@@ -162,19 +161,19 @@ class TestGuestUserDashboardAccess(SupersetTestCase):
     def test_raise_for_dashboard_access_as_guest(self):
         g.user = self.authorized_guest
 
-        security_manager.raise_for_dashboard_access(self.dash)
+        security_manager.raise_for_access(dashboard=self.dash)
 
-    def test_raise_for_dashboard_access_as_unauthorized_guest(self):
+    def test_raise_for_access_dashboard_as_unauthorized_guest(self):
         g.user = self.unauthorized_guest
 
-        with self.assertRaises(DashboardAccessDeniedError):
-            security_manager.raise_for_dashboard_access(self.dash)
+        with self.assertRaises(SupersetSecurityException):
+            security_manager.raise_for_access(dashboard=self.dash)
 
-    def test_raise_for_dashboard_access_as_guest_no_rbac(self):
+    def test_raise_for_access_dashboard_as_guest_no_rbac(self):
         """
         Test that guest account has no access to other dashboards.
 
-        A bug in the ``raise_for_dashboard_access`` logic allowed the guest user to
+        A bug in the ``raise_for_access`` logic allowed the guest user to
         fetch data from other dashboards, as long as the other dashboard:
 
           - was not embedded AND
@@ -193,8 +192,8 @@ class TestGuestUserDashboardAccess(SupersetTestCase):
         db.session.add(dash)
         db.session.commit()
 
-        with self.assertRaises(DashboardAccessDeniedError):
-            security_manager.raise_for_dashboard_access(dash)
+        with self.assertRaises(SupersetSecurityException):
+            security_manager.raise_for_access(dashboard=dash)
 
         db.session.delete(dash)
         db.session.commit()


[superset] 05/10: chore: Refine native dashboard cleanup logic (#24864)

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 dba72c419718f912371edfe8fa131996f46651c7
Author: John Bodley <45...@users.noreply.github.com>
AuthorDate: Wed Aug 9 10:42:29 2023 -0700

    chore: Refine native dashboard cleanup logic (#24864)
    
    (cherry picked from commit 34586648a50ee5400fcc08dd3accc6d9fe03b142)
---
 superset/cli/native_filters.py                     | 39 +++++++++++-----------
 .../utils/dashboard_filter_scopes_converter.py     | 17 ++++++----
 2 files changed, 30 insertions(+), 26 deletions(-)

diff --git a/superset/cli/native_filters.py b/superset/cli/native_filters.py
index a25724d38d..75df428e38 100644
--- a/superset/cli/native_filters.py
+++ b/superset/cli/native_filters.py
@@ -172,15 +172,16 @@ def upgrade(
                 if (
                     isinstance(value, dict)
                     and value["type"] == "CHART"
-                    and value["meta"]["chartId"] in filter_boxes_by_id
+                    and (meta := value.get("meta"))
+                    and meta["chartId"] in filter_boxes_by_id
                 ):
-                    slc = filter_boxes_by_id[value["meta"]["chartId"]]
+                    slc = filter_boxes_by_id[meta["chartId"]]
                     mapping[key] = key.replace("CHART-", "MARKDOWN-")
 
                     value["id"] = mapping[key]
                     value["type"] = "MARKDOWN"
 
-                    value["meta"]["code"] = dedent(
+                    meta["code"] = dedent(
                         f"""
                         &#9888; The <a href="/superset/slice/{slc.id}/">{slc.slice_name}
                         </a> filter-box chart has been migrated to a native filter.
@@ -192,14 +193,14 @@ def upgrade(
                     )
 
                     # Save the filter-box info for recovery purposes.
-                    value["meta"]["native_filter_migration"] = {
-                        key: value["meta"].pop(key)
+                    meta["native_filter_migration"] = {
+                        key: meta.pop(key)
                         for key in (
                             "chartId",
                             "sliceName",
                             "sliceNameOverride",
                         )
-                        if key in value["meta"]
+                        if key in meta
                     }
 
                     position_json[mapping[key]] = value
@@ -291,13 +292,14 @@ def downgrade(
                 if (
                     isinstance(value, dict)
                     and value["type"] == "MARKDOWN"
-                    and "native_filter_migration" in value["meta"]
+                    and (meta := value.get("meta"))
+                    and "native_filter_migration" in meta
                 ):
-                    value["meta"].update(value["meta"].pop("native_filter_migration"))
-                    slice_ids.add(value["meta"]["chartId"])
+                    meta.update(meta.pop("native_filter_migration"))
+                    slice_ids.add(meta["chartId"])
                     mapping[key] = key.replace("MARKDOWN-", "CHART-")
                     value["id"] = mapping[key]
-                    del value["meta"]["code"]
+                    del meta["code"]
                     value["type"] = "CHART"
                     position_json[mapping[key]] = value
                     del position_json[key]
@@ -368,21 +370,20 @@ def cleanup(
             json_metadata = json.loads(dashboard.json_metadata or "{}")
             position_json = json.loads(dashboard.position_json or "{}")
 
-            if "native_filter_migration" not in json_metadata:
-                click.echo(f"{str(dashboard)} has not been upgraded")
-                continue
-
             # Remove the saved filter configurations.
-            del json_metadata["native_filter_migration"]
-            dashboard.json_metadata = json.dumps(json_metadata)
+            if "native_filter_migration" in json_metadata:
+                del json_metadata["native_filter_migration"]
+                dashboard.json_metadata = json.dumps(json_metadata)
 
             for value in position_json.values():
                 if (
                     isinstance(value, dict)
-                    and "native_filter_migration" in value["meta"]
+                    and value["type"] == "MARKDOWN"
+                    and (meta := value.get("meta"))
+                    and "native_filter_migration" in meta
                 ):
-                    slice_ids.add(value["meta"]["native_filter_migration"]["chartId"])
-                    del value["meta"]["native_filter_migration"]
+                    slice_ids.add(meta["native_filter_migration"]["chartId"])
+                    del meta["native_filter_migration"]
 
             dashboard.json_metadata = json.dumps(json_metadata)
             dashboard.position_json = json.dumps(position_json)
diff --git a/superset/utils/dashboard_filter_scopes_converter.py b/superset/utils/dashboard_filter_scopes_converter.py
index ce89b2a255..38090e22b6 100644
--- a/superset/utils/dashboard_filter_scopes_converter.py
+++ b/superset/utils/dashboard_filter_scopes_converter.py
@@ -298,13 +298,16 @@ def convert_filter_scopes_to_native_filters(  # pylint: disable=invalid-name,too
 
     for filter_box in filter_boxes:
         for value in position_json.values():
-            if (
-                isinstance(value, dict)
-                and value["type"] == "CHART"
-                and value["meta"]["chartId"] == filter_box.id
-                and value["parents"]  # Misnomer as this the the complete ancestry.
-            ):
-                ancestors_by_id[filter_box.id] = set(value["parents"])
+            try:
+                if (
+                    isinstance(value, dict)
+                    and value["type"] == "CHART"
+                    and value["meta"]["chartId"] == filter_box.id
+                    and value["parents"]  # Misnomer as this the the complete ancestry.
+                ):
+                    ancestors_by_id[filter_box.id] = set(value["parents"])
+            except KeyError:
+                pass
 
     # Wire up the hierarchical filters.
     for this in filter_boxes:


[superset] 02/10: fix(explore): double resize triggered (#24886)

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 42451880a8c92ab3980b7cfa181ee5805b61b903
Author: JUST.in DO IT <ju...@airbnb.com>
AuthorDate: Tue Aug 8 10:21:21 2023 -0700

    fix(explore): double resize triggered (#24886)
    
    (cherry picked from commit 340bfd88ae4648cc3fec6edc288040edd219950b)
---
 .../src/dashboard/components/gridComponents/Chart.jsx      | 14 ++++++--------
 1 file changed, 6 insertions(+), 8 deletions(-)

diff --git a/superset-frontend/src/dashboard/components/gridComponents/Chart.jsx b/superset-frontend/src/dashboard/components/gridComponents/Chart.jsx
index 1c4d0dd956..a99061c707 100644
--- a/superset-frontend/src/dashboard/components/gridComponents/Chart.jsx
+++ b/superset-frontend/src/dashboard/components/gridComponents/Chart.jsx
@@ -20,7 +20,7 @@ import cx from 'classnames';
 import React from 'react';
 import PropTypes from 'prop-types';
 import { styled, t, logging } from '@superset-ui/core';
-import { isEqual } from 'lodash';
+import { debounce, isEqual } from 'lodash';
 import { withRouter } from 'react-router-dom';
 
 import { exportChart, mountExploreUrl } from 'src/explore/exploreUtils';
@@ -95,7 +95,7 @@ const defaultProps = {
 
 // we use state + shouldComponentUpdate() logic to prevent perf-wrecking
 // resizing across all slices on a dashboard on every update
-const RESIZE_TIMEOUT = 350;
+const RESIZE_TIMEOUT = 500;
 const SHOULD_UPDATE_ON_PROP_CHANGES = Object.keys(propTypes).filter(
   prop =>
     prop !== 'width' && prop !== 'height' && prop !== 'isComponentVisible',
@@ -142,7 +142,7 @@ class Chart extends React.Component {
     this.exportXLSX = this.exportXLSX.bind(this);
     this.exportFullXLSX = this.exportFullXLSX.bind(this);
     this.forceRefresh = this.forceRefresh.bind(this);
-    this.resize = this.resize.bind(this);
+    this.resize = debounce(this.resize.bind(this), RESIZE_TIMEOUT);
     this.setDescriptionRef = this.setDescriptionRef.bind(this);
     this.setHeaderRef = this.setHeaderRef.bind(this);
     this.getChartHeight = this.getChartHeight.bind(this);
@@ -178,8 +178,7 @@ class Chart extends React.Component {
       }
 
       if (nextProps.isFullSize !== this.props.isFullSize) {
-        clearTimeout(this.resizeTimeout);
-        this.resizeTimeout = setTimeout(this.resize, RESIZE_TIMEOUT);
+        this.resize();
         return false;
       }
 
@@ -189,8 +188,7 @@ class Chart extends React.Component {
         nextProps.width !== this.state.width ||
         nextProps.height !== this.state.height
       ) {
-        clearTimeout(this.resizeTimeout);
-        this.resizeTimeout = setTimeout(this.resize, RESIZE_TIMEOUT);
+        this.resize();
       }
 
       for (let i = 0; i < SHOULD_UPDATE_ON_PROP_CHANGES.length; i += 1) {
@@ -224,7 +222,7 @@ class Chart extends React.Component {
   }
 
   componentWillUnmount() {
-    clearTimeout(this.resizeTimeout);
+    this.resize.cancel();
   }
 
   componentDidUpdate(prevProps) {