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/12/04 21:05:45 UTC

(superset) branch 3.1 updated (c8844bdd5e -> 5bcd3ef17e)

This is an automated email from the ASF dual-hosted git repository.

michaelsmolina pushed a change to branch 3.1
in repository https://gitbox.apache.org/repos/asf/superset.git


    from c8844bdd5e chore: Adds 3.1.0 data to CHANGELOG.md and UPDATING.md
     new f4fd0e19e2 fix: alias column when fetching values (#26120)
     new e382d0dd28 chore(deps): bump pillow deps (#25931)
     new 93319696de fix: remove default secret key from helm (#23916)
     new 2c3bf2895f chore(tags): Allow for lookup via ids vs. name in the API (#25996)
     new fad4616d2f chore: Rename SET_ACTIVE_TABS action, add a new action (#26147)
     new 26e59662fb fix(annotations): time grain column (#26140)
     new 4a4f9983df feat(helm): Add option to deploy extra containers to remaining deployments (#26123)
     new 79d5975028 feat: Adds legacy time support for Waterfall chart (#26136)
     new ceac19fa2f fix: set label on adhoc column should persist (#26154)
     new 77332bfb38 fix(database-import): Support importing a DB connection with a version set (#26116)
     new d0aa34bf79 fix(sqllab): table preview has gone (#25977)
     new 880086c750 fix(Alerts/Reports): allow use of ";" separator in slack recipient entry (#25894)
     new aaa50c4b4a fix: Migration order due to cherry which went astray (#26160)
     new 5ec1edc876 chore: Clean up the examples dashboards (#26158)
     new 5bcd3ef17e chore: harmonize and clean up list views (#25961)

The 15 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:
 helm/superset/Chart.yaml                           |   2 +-
 helm/superset/README.md                            |  11 +-
 helm/superset/README.md.gotmpl                     |   6 +
 helm/superset/templates/_helpers.tpl               |   1 -
 helm/superset/templates/deployment-beat.yaml       |   3 +
 helm/superset/templates/deployment-flower.yaml     |   3 +
 helm/superset/templates/deployment-ws.yaml         |   3 +
 helm/superset/values.yaml                          |   8 +
 setup.py                                           |   2 +-
 .../cypress/e2e/alerts_and_reports/alerts.test.ts  |   7 +-
 .../cypress/e2e/alerts_and_reports/reports.test.ts |   7 +-
 .../cypress/e2e/chart_list/filter.test.ts          |  16 +-
 .../cypress/e2e/chart_list/list.test.ts            |  10 +-
 .../cypress/e2e/dashboard/nativeFilters.test.ts    |   6 +-
 .../cypress/e2e/dashboard/tabs.test.ts             |   3 -
 .../cypress-base/cypress/e2e/dashboard/utils.ts    |   1 -
 .../cypress/e2e/dashboard_list/filter.test.ts      |   6 +-
 .../cypress/e2e/dashboard_list/list.test.ts        |  12 +-
 .../cypress-base/cypress/support/e2e.ts            |   2 +-
 .../src/Waterfall/buildQuery.ts                    |   7 +-
 .../src/Waterfall/controlPanel.tsx                 |   8 +-
 .../plugin-chart-echarts/src/Waterfall/index.ts    |   2 +-
 .../src/Waterfall/transformProps.ts                |   6 +-
 .../src/components/AuditInfo/ModifiedInfo.test.tsx |  42 ++
 .../src/components/AuditInfo/index.tsx             |  30 +
 .../src/components/Chart/chartAction.js            |   9 +-
 .../src/components/Chart/chartActions.test.js      |  67 +++
 .../DatabaseSelector/DatabaseSelector.test.tsx     |   9 +-
 .../src/components/DatabaseSelector/index.tsx      |  10 +-
 .../src/components/Datasource/DatasourceEditor.jsx |   2 +-
 .../src/dashboard/actions/dashboardState.js        |   9 +-
 .../DashboardBuilder/DashboardBuilder.test.tsx     |   6 +-
 .../dashboard/components/PropertiesModal/index.tsx |   2 +-
 .../dashboard/components/gridComponents/Tabs.jsx   |   8 +-
 .../dashboard/containers/DashboardComponent.jsx    |   4 +-
 .../src/dashboard/reducers/dashboardState.js       |   9 +-
 .../src/dashboard/reducers/dashboardState.test.ts  |  22 +-
 .../ColumnSelectPopover.test.tsx                   |  77 +++
 .../DndColumnSelectControl/ColumnSelectPopover.tsx |  39 +-
 .../ColumnSelectPopoverTrigger.tsx                 |  13 +-
 .../src/features/annotations/AnnotationModal.tsx   |   2 +-
 .../src/features/cssTemplates/CssTemplateModal.tsx |   5 +-
 .../src/features/cssTemplates/types.ts             |  11 +-
 .../src/features/tags/TagModal.test.tsx            |   2 +
 superset-frontend/src/features/tags/TagModal.tsx   |   6 +-
 superset-frontend/src/features/tags/tags.ts        |  17 +
 .../src/pages/AlertReportList/index.tsx            |  74 +--
 superset-frontend/src/pages/AllEntities/index.tsx  |  15 +-
 .../src/pages/AnnotationLayerList/index.tsx        |  89 +--
 .../src/pages/AnnotationList/index.tsx             |   2 +-
 superset-frontend/src/pages/ChartList/index.tsx    | 168 +++---
 .../src/pages/CssTemplateList/index.tsx            |  88 +--
 .../src/pages/DashboardList/index.tsx              | 187 +++----
 .../src/pages/DatabaseList/DatabaseList.test.jsx   |   2 +-
 superset-frontend/src/pages/DatabaseList/index.tsx |  80 ++-
 .../src/pages/DatasetList/DatasetList.test.tsx     |  61 +--
 superset-frontend/src/pages/DatasetList/index.tsx  | 109 ++--
 .../src/pages/QueryHistoryList/index.tsx           |   3 +-
 .../RowLevelSecurityList.test.tsx                  |   6 +-
 .../src/pages/RowLevelSecurityList/index.tsx       |  39 +-
 .../src/pages/SavedQueryList/index.tsx             | 136 ++---
 superset-frontend/src/pages/Tags/index.tsx         |  68 +--
 .../utils/{parseCookie.ts => getOwnerName.test.ts} |  18 +-
 .../utils/getOwnerName.ts}                         |  17 +-
 superset-frontend/src/views/CRUD/types.ts          |  11 +-
 superset/annotation_layers/api.py                  |   2 +-
 superset/charts/api.py                             |   2 +-
 superset/css_templates/api.py                      |   6 +-
 superset/daos/tag.py                               |   8 +
 superset/dashboards/api.py                         |   2 +-
 superset/databases/api.py                          |  13 +
 superset/databases/schemas.py                      |   1 +
 superset/datasets/api.py                           |  13 +-
 .../examples/configs/charts/Filter_Segments.yaml   |  68 ---
 .../configs/charts/Filtering_Vaccines.yaml         |  53 --
 .../configs/charts/Vehicle_Sales_Filter.yaml       |  47 --
 .../configs/charts/Video_Game_Sales_Filter.yaml    |  55 --
 .../dashboards/COVID_Vaccine_Dashboard.yaml        | 128 ++---
 .../dashboards/FCC_New_Coder_Survey_2018.yaml      | 608 ++++++++++-----------
 .../configs/dashboards/Sales_Dashboard.yaml        | 300 ++++------
 .../configs/dashboards/Video_Game_Sales.yaml       | 389 ++++++-------
 superset/examples/misc_dashboard.py                | 148 ++---
 superset/examples/world_bank.py                    |  39 +-
 superset/migrations/shared/utils.py                |   8 +-
 ...317970b4400c_added_time_secondary_column_to_.py |  34 +-
 ...2-01_12-03_b7851ee5522f_replay_317970b4400c.py} |  24 +-
 superset/models/helpers.py                         |  10 +-
 superset/queries/saved_queries/api.py              |  12 +-
 superset/reports/api.py                            |  10 +-
 superset/reports/notifications/slack.py            |  11 +-
 superset/row_level_security/api.py                 |   7 +-
 superset/row_level_security/schemas.py             |   2 +
 superset/tags/api.py                               |  17 +-
 superset/utils/screenshots.py                      |   2 +-
 tests/integration_tests/charts/api_tests.py        |   6 +-
 tests/integration_tests/css_templates/api_tests.py |  25 +-
 .../integration_tests/dashboards/commands_tests.py |  20 +-
 tests/integration_tests/dashboards/dao_tests.py    |  54 --
 tests/integration_tests/databases/api_tests.py     |   3 +-
 .../queries/saved_queries/api_tests.py             |  14 +-
 tests/integration_tests/tags/dao_tests.py          |  33 ++
 tests/integration_tests/utils_tests.py             |  44 --
 .../databases/commands/importers/v1/import_test.py |  21 +
 .../notifications/slack_tests.py}                  |  47 +-
 104 files changed, 1881 insertions(+), 2111 deletions(-)
 create mode 100644 superset-frontend/src/components/AuditInfo/ModifiedInfo.test.tsx
 create mode 100644 superset-frontend/src/components/AuditInfo/index.tsx
 create mode 100644 superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopover.test.tsx
 copy superset-frontend/src/utils/{parseCookie.ts => getOwnerName.test.ts} (73%)
 copy superset-frontend/{spec/helpers/ResizeObserver.ts => src/utils/getOwnerName.ts} (81%)
 delete mode 100644 superset/examples/configs/charts/Filter_Segments.yaml
 delete mode 100644 superset/examples/configs/charts/Filtering_Vaccines.yaml
 delete mode 100644 superset/examples/configs/charts/Vehicle_Sales_Filter.yaml
 delete mode 100644 superset/examples/configs/charts/Video_Game_Sales_Filter.yaml
 copy superset/migrations/versions/{2015-11-21_11-18_289ce07647b_add_encrypted_password_field.py => 2023-12-01_12-03_b7851ee5522f_replay_317970b4400c.py} (71%)
 copy tests/unit_tests/{notifications/email_tests.py => reports/notifications/slack_tests.py} (71%)


(superset) 15/15: chore: harmonize and clean up list views (#25961)

Posted by mi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

michaelsmolina pushed a commit to branch 3.1
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 5bcd3ef17eebe67511e2fbd057a522338d3d0851
Author: Ville Brofeldt <33...@users.noreply.github.com>
AuthorDate: Mon Dec 4 11:51:18 2023 -0800

    chore: harmonize and clean up list views (#25961)
    
    (cherry picked from commit 0b477e3f7c08fabdcdb0cb1bca48335685a699bf)
---
 .../cypress/e2e/alerts_and_reports/alerts.test.ts  |   7 +-
 .../cypress/e2e/alerts_and_reports/reports.test.ts |   7 +-
 .../cypress/e2e/chart_list/filter.test.ts          |  16 +-
 .../cypress/e2e/chart_list/list.test.ts            |  10 +-
 .../cypress/e2e/dashboard_list/filter.test.ts      |   6 +-
 .../cypress/e2e/dashboard_list/list.test.ts        |  12 +-
 .../src/components/AuditInfo/ModifiedInfo.test.tsx |  42 +++++
 .../src/components/AuditInfo/index.tsx             |  30 ++++
 .../src/components/Datasource/DatasourceEditor.jsx |   2 +-
 .../dashboard/components/PropertiesModal/index.tsx |   2 +-
 .../src/features/annotations/AnnotationModal.tsx   |   2 +-
 .../src/features/cssTemplates/CssTemplateModal.tsx |   5 +-
 .../src/features/cssTemplates/types.ts             |  11 +-
 .../src/features/tags/TagModal.test.tsx            |   2 +
 .../src/pages/AlertReportList/index.tsx            |  74 ++++----
 superset-frontend/src/pages/AllEntities/index.tsx  |   5 +-
 .../src/pages/AnnotationLayerList/index.tsx        |  89 +++-------
 .../src/pages/AnnotationList/index.tsx             |   2 +-
 superset-frontend/src/pages/ChartList/index.tsx    | 168 +++++++++---------
 .../src/pages/CssTemplateList/index.tsx            |  88 +++-------
 .../src/pages/DashboardList/index.tsx              | 187 ++++++++++-----------
 .../src/pages/DatabaseList/DatabaseList.test.jsx   |   2 +-
 superset-frontend/src/pages/DatabaseList/index.tsx |  80 ++++++---
 .../src/pages/DatasetList/DatasetList.test.tsx     |  61 +++----
 superset-frontend/src/pages/DatasetList/index.tsx  | 109 +++++++-----
 .../src/pages/QueryHistoryList/index.tsx           |   3 +-
 .../RowLevelSecurityList.test.tsx                  |   6 +-
 .../src/pages/RowLevelSecurityList/index.tsx       |  39 ++++-
 .../src/pages/SavedQueryList/index.tsx             | 136 ++++++++-------
 superset-frontend/src/pages/Tags/index.tsx         |  68 ++++----
 .../types.ts => utils/getOwnerName.test.ts}        |  23 ++-
 .../types.ts => utils/getOwnerName.ts}             |  20 +--
 superset-frontend/src/views/CRUD/types.ts          |  11 +-
 superset/annotation_layers/api.py                  |   2 +-
 superset/charts/api.py                             |   2 +-
 superset/css_templates/api.py                      |   6 +-
 superset/dashboards/api.py                         |   2 +-
 superset/databases/api.py                          |  13 ++
 superset/datasets/api.py                           |  13 +-
 superset/queries/saved_queries/api.py              |  12 +-
 superset/reports/api.py                            |  10 +-
 superset/row_level_security/api.py                 |   7 +-
 superset/row_level_security/schemas.py             |   2 +
 superset/tags/api.py                               |   2 +-
 tests/integration_tests/css_templates/api_tests.py |  25 ++-
 tests/integration_tests/databases/api_tests.py     |   1 +
 .../queries/saved_queries/api_tests.py             |  14 +-
 47 files changed, 745 insertions(+), 691 deletions(-)

diff --git a/superset-frontend/cypress-base/cypress/e2e/alerts_and_reports/alerts.test.ts b/superset-frontend/cypress-base/cypress/e2e/alerts_and_reports/alerts.test.ts
index a695541cee..b677507a46 100644
--- a/superset-frontend/cypress-base/cypress/e2e/alerts_and_reports/alerts.test.ts
+++ b/superset-frontend/cypress-base/cypress/e2e/alerts_and_reports/alerts.test.ts
@@ -29,10 +29,9 @@ describe('Alert list view', () => {
     cy.getBySel('sort-header').eq(2).contains('Name');
     cy.getBySel('sort-header').eq(3).contains('Schedule');
     cy.getBySel('sort-header').eq(4).contains('Notification method');
-    cy.getBySel('sort-header').eq(5).contains('Created by');
-    cy.getBySel('sort-header').eq(6).contains('Owners');
-    cy.getBySel('sort-header').eq(7).contains('Modified');
-    cy.getBySel('sort-header').eq(8).contains('Active');
+    cy.getBySel('sort-header').eq(5).contains('Owners');
+    cy.getBySel('sort-header').eq(6).contains('Last modified');
+    cy.getBySel('sort-header').eq(7).contains('Active');
     // TODO Cypress won't recognize the Actions column
     // cy.getBySel('sort-header').eq(9).contains('Actions');
   });
diff --git a/superset-frontend/cypress-base/cypress/e2e/alerts_and_reports/reports.test.ts b/superset-frontend/cypress-base/cypress/e2e/alerts_and_reports/reports.test.ts
index e267d76f6f..a227fa03d7 100644
--- a/superset-frontend/cypress-base/cypress/e2e/alerts_and_reports/reports.test.ts
+++ b/superset-frontend/cypress-base/cypress/e2e/alerts_and_reports/reports.test.ts
@@ -29,10 +29,9 @@ describe('Report list view', () => {
     cy.getBySel('sort-header').eq(2).contains('Name');
     cy.getBySel('sort-header').eq(3).contains('Schedule');
     cy.getBySel('sort-header').eq(4).contains('Notification method');
-    cy.getBySel('sort-header').eq(5).contains('Created by');
-    cy.getBySel('sort-header').eq(6).contains('Owners');
-    cy.getBySel('sort-header').eq(7).contains('Modified');
-    cy.getBySel('sort-header').eq(8).contains('Active');
+    cy.getBySel('sort-header').eq(5).contains('Owners');
+    cy.getBySel('sort-header').eq(6).contains('Last modified');
+    cy.getBySel('sort-header').eq(7).contains('Active');
     // TODO Cypress won't recognize the Actions column
     // cy.getBySel('sort-header').eq(9).contains('Actions');
   });
diff --git a/superset-frontend/cypress-base/cypress/e2e/chart_list/filter.test.ts b/superset-frontend/cypress-base/cypress/e2e/chart_list/filter.test.ts
index acd11669be..00b09e2fb8 100644
--- a/superset-frontend/cypress-base/cypress/e2e/chart_list/filter.test.ts
+++ b/superset-frontend/cypress-base/cypress/e2e/chart_list/filter.test.ts
@@ -35,14 +35,14 @@ describe('Charts filters', () => {
     setFilter('Owner', 'admin user');
   });
 
-  it('should allow filtering by "Created by" correctly', () => {
-    setFilter('Created by', 'alpha user');
-    setFilter('Created by', 'admin user');
+  it('should allow filtering by "Modified by" correctly', () => {
+    setFilter('Modified by', 'alpha user');
+    setFilter('Modified by', 'admin user');
   });
 
-  it('should allow filtering by "Chart type" correctly', () => {
-    setFilter('Chart type', 'Area Chart (legacy)');
-    setFilter('Chart type', 'Bubble Chart');
+  it('should allow filtering by "Type" correctly', () => {
+    setFilter('Type', 'Area Chart (legacy)');
+    setFilter('Type', 'Bubble Chart');
   });
 
   it('should allow filtering by "Dataset" correctly', () => {
@@ -51,7 +51,7 @@ describe('Charts filters', () => {
   });
 
   it('should allow filtering by "Dashboards" correctly', () => {
-    setFilter('Dashboards', 'Unicode Test');
-    setFilter('Dashboards', 'Tabbed Dashboard');
+    setFilter('Dashboard', 'Unicode Test');
+    setFilter('Dashboard', 'Tabbed Dashboard');
   });
 });
diff --git a/superset-frontend/cypress-base/cypress/e2e/chart_list/list.test.ts b/superset-frontend/cypress-base/cypress/e2e/chart_list/list.test.ts
index 6664281abe..44f348edc5 100644
--- a/superset-frontend/cypress-base/cypress/e2e/chart_list/list.test.ts
+++ b/superset-frontend/cypress-base/cypress/e2e/chart_list/list.test.ts
@@ -109,14 +109,12 @@ describe('Charts list', () => {
 
     it('should load rows in list mode', () => {
       cy.getBySel('listview-table').should('be.visible');
-      cy.getBySel('sort-header').eq(1).contains('Chart');
-      cy.getBySel('sort-header').eq(2).contains('Visualization type');
+      cy.getBySel('sort-header').eq(1).contains('Name');
+      cy.getBySel('sort-header').eq(2).contains('Type');
       cy.getBySel('sort-header').eq(3).contains('Dataset');
-      // cy.getBySel('sort-header').eq(4).contains('Dashboards added to');
-      cy.getBySel('sort-header').eq(4).contains('Modified by');
+      cy.getBySel('sort-header').eq(4).contains('Owners');
       cy.getBySel('sort-header').eq(5).contains('Last modified');
-      cy.getBySel('sort-header').eq(6).contains('Created by');
-      cy.getBySel('sort-header').eq(7).contains('Actions');
+      cy.getBySel('sort-header').eq(6).contains('Actions');
     });
 
     it('should sort correctly in list mode', () => {
diff --git a/superset-frontend/cypress-base/cypress/e2e/dashboard_list/filter.test.ts b/superset-frontend/cypress-base/cypress/e2e/dashboard_list/filter.test.ts
index 4654b3b5c2..854ea541c7 100644
--- a/superset-frontend/cypress-base/cypress/e2e/dashboard_list/filter.test.ts
+++ b/superset-frontend/cypress-base/cypress/e2e/dashboard_list/filter.test.ts
@@ -35,9 +35,9 @@ describe('Dashboards filters', () => {
     setFilter('Owner', 'admin user');
   });
 
-  it('should allow filtering by "Created by" correctly', () => {
-    setFilter('Created by', 'alpha user');
-    setFilter('Created by', 'admin user');
+  it('should allow filtering by "Modified by" correctly', () => {
+    setFilter('Modified by', 'alpha user');
+    setFilter('Modified by', 'admin user');
   });
 
   it('should allow filtering by "Status" correctly', () => {
diff --git a/superset-frontend/cypress-base/cypress/e2e/dashboard_list/list.test.ts b/superset-frontend/cypress-base/cypress/e2e/dashboard_list/list.test.ts
index 9bc6eed224..7dfb7cd673 100644
--- a/superset-frontend/cypress-base/cypress/e2e/dashboard_list/list.test.ts
+++ b/superset-frontend/cypress-base/cypress/e2e/dashboard_list/list.test.ts
@@ -54,13 +54,11 @@ describe('Dashboards list', () => {
 
     it('should load rows in list mode', () => {
       cy.getBySel('listview-table').should('be.visible');
-      cy.getBySel('sort-header').eq(1).contains('Title');
-      cy.getBySel('sort-header').eq(2).contains('Modified by');
-      cy.getBySel('sort-header').eq(3).contains('Status');
-      cy.getBySel('sort-header').eq(4).contains('Modified');
-      cy.getBySel('sort-header').eq(5).contains('Created by');
-      cy.getBySel('sort-header').eq(6).contains('Owners');
-      cy.getBySel('sort-header').eq(7).contains('Actions');
+      cy.getBySel('sort-header').eq(1).contains('Name');
+      cy.getBySel('sort-header').eq(2).contains('Status');
+      cy.getBySel('sort-header').eq(3).contains('Owners');
+      cy.getBySel('sort-header').eq(4).contains('Last modified');
+      cy.getBySel('sort-header').eq(5).contains('Actions');
     });
 
     it('should sort correctly in list mode', () => {
diff --git a/superset-frontend/src/components/AuditInfo/ModifiedInfo.test.tsx b/superset-frontend/src/components/AuditInfo/ModifiedInfo.test.tsx
new file mode 100644
index 0000000000..af9d6913d8
--- /dev/null
+++ b/superset-frontend/src/components/AuditInfo/ModifiedInfo.test.tsx
@@ -0,0 +1,42 @@
+import React from 'react';
+import { render, screen, waitFor } from 'spec/helpers/testing-library';
+import '@testing-library/jest-dom';
+import userEvent from '@testing-library/user-event';
+
+import { ModifiedInfo } from '.';
+
+const TEST_DATE = '2023-11-20';
+const USER = {
+  id: 1,
+  first_name: 'Foo',
+  last_name: 'Bar',
+};
+
+test('should render a tooltip when user is provided', async () => {
+  render(<ModifiedInfo user={USER} date={TEST_DATE} />);
+
+  const dateElement = screen.getByTestId('audit-info-date');
+  expect(dateElement).toBeInTheDocument();
+  expect(screen.getByText(TEST_DATE)).toBeInTheDocument();
+  expect(screen.queryByText('Modified by: Foo Bar')).not.toBeInTheDocument();
+  userEvent.hover(dateElement);
+  const tooltip = await screen.findByRole('tooltip');
+  expect(tooltip).toBeInTheDocument();
+  expect(screen.getByText('Modified by: Foo Bar')).toBeInTheDocument();
+});
+
+test('should render only the date if username is not provided', async () => {
+  render(<ModifiedInfo date={TEST_DATE} />);
+
+  const dateElement = screen.getByTestId('audit-info-date');
+  expect(dateElement).toBeInTheDocument();
+  expect(screen.getByText(TEST_DATE)).toBeInTheDocument();
+  userEvent.hover(dateElement);
+  await waitFor(
+    () => {
+      const tooltip = screen.queryByRole('tooltip');
+      expect(tooltip).not.toBeInTheDocument();
+    },
+    { timeout: 1000 },
+  );
+});
diff --git a/superset-frontend/src/components/AuditInfo/index.tsx b/superset-frontend/src/components/AuditInfo/index.tsx
new file mode 100644
index 0000000000..24223a1554
--- /dev/null
+++ b/superset-frontend/src/components/AuditInfo/index.tsx
@@ -0,0 +1,30 @@
+import React from 'react';
+
+import Owner from 'src/types/Owner';
+import { Tooltip } from 'src/components/Tooltip';
+import getOwnerName from 'src/utils/getOwnerName';
+import { t } from '@superset-ui/core';
+
+export type ModifiedInfoProps = {
+  user?: Owner;
+  date: string;
+};
+
+export const ModifiedInfo = ({ user, date }: ModifiedInfoProps) => {
+  const dateSpan = (
+    <span className="no-wrap" data-test="audit-info-date">
+      {date}
+    </span>
+  );
+
+  if (user) {
+    const userName = getOwnerName(user);
+    const title = t('Modified by: %s', userName);
+    return (
+      <Tooltip title={title} placement="bottom">
+        {dateSpan}
+      </Tooltip>
+    );
+  }
+  return dateSpan;
+};
diff --git a/superset-frontend/src/components/Datasource/DatasourceEditor.jsx b/superset-frontend/src/components/Datasource/DatasourceEditor.jsx
index 86b5c22777..751001297a 100644
--- a/superset-frontend/src/components/Datasource/DatasourceEditor.jsx
+++ b/superset-frontend/src/components/Datasource/DatasourceEditor.jsx
@@ -1114,7 +1114,7 @@ class DatasourceEditor extends React.PureComponent {
                     <div css={{ width: 'calc(100% - 34px)', marginTop: -16 }}>
                       <Field
                         fieldKey="table_name"
-                        label={t('Dataset name')}
+                        label={t('Name')}
                         control={
                           <TextControl
                             controlId="table_name"
diff --git a/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx b/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx
index 92d34a4faa..3a1421e380 100644
--- a/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx
+++ b/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx
@@ -681,7 +681,7 @@ const PropertiesModal = ({
         </Row>
         <Row gutter={16}>
           <Col xs={24} md={12}>
-            <FormItem label={t('Title')} name="title">
+            <FormItem label={t('Name')} name="title">
               <Input
                 data-test="dashboard-title-input"
                 type="text"
diff --git a/superset-frontend/src/features/annotations/AnnotationModal.tsx b/superset-frontend/src/features/annotations/AnnotationModal.tsx
index a5c5aa9c31..dd1107dfba 100644
--- a/superset-frontend/src/features/annotations/AnnotationModal.tsx
+++ b/superset-frontend/src/features/annotations/AnnotationModal.tsx
@@ -287,7 +287,7 @@ const AnnotationModal: FunctionComponent<AnnotationModalProps> = ({
       </StyledAnnotationTitle>
       <AnnotationContainer>
         <div className="control-label">
-          {t('Annotation name')}
+          {t('Name')}
           <span className="required">*</span>
         </div>
         <input
diff --git a/superset-frontend/src/features/cssTemplates/CssTemplateModal.tsx b/superset-frontend/src/features/cssTemplates/CssTemplateModal.tsx
index 73bbfe7555..bd3c5b13a6 100644
--- a/superset-frontend/src/features/cssTemplates/CssTemplateModal.tsx
+++ b/superset-frontend/src/features/cssTemplates/CssTemplateModal.tsx
@@ -105,6 +105,9 @@ const CssTemplateModal: FunctionComponent<CssTemplateModalProps> = ({
         const update_id = currentCssTemplate.id;
         delete currentCssTemplate.id;
         delete currentCssTemplate.created_by;
+        delete currentCssTemplate.changed_by;
+        delete currentCssTemplate.changed_on_delta_humanized;
+
         updateResource(update_id, currentCssTemplate).then(response => {
           if (!response) {
             return;
@@ -235,7 +238,7 @@ const CssTemplateModal: FunctionComponent<CssTemplateModalProps> = ({
       </StyledCssTemplateTitle>
       <TemplateContainer>
         <div className="control-label">
-          {t('CSS template name')}
+          {t('Name')}
           <span className="required">*</span>
         </div>
         <input
diff --git a/superset-frontend/src/features/cssTemplates/types.ts b/superset-frontend/src/features/cssTemplates/types.ts
index 1bb5b2e659..5e7e1af97a 100644
--- a/superset-frontend/src/features/cssTemplates/types.ts
+++ b/superset-frontend/src/features/cssTemplates/types.ts
@@ -1,3 +1,5 @@
+import Owner from 'src/types/Owner';
+
 /**
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
@@ -16,17 +18,12 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-type CreatedByUser = {
-  id: number;
-  first_name: string;
-  last_name: string;
-};
-
 export type TemplateObject = {
   id?: number;
   changed_on_delta_humanized?: string;
   created_on?: string;
-  created_by?: CreatedByUser;
+  changed_by?: Owner;
+  created_by?: Owner;
   css?: string;
   template_name: string;
 };
diff --git a/superset-frontend/src/features/tags/TagModal.test.tsx b/superset-frontend/src/features/tags/TagModal.test.tsx
index 5f4fd4e2b9..99b7a3365e 100644
--- a/superset-frontend/src/features/tags/TagModal.test.tsx
+++ b/superset-frontend/src/features/tags/TagModal.test.tsx
@@ -56,10 +56,12 @@ test('renders correctly in edit mode', () => {
     changed_on_delta_humanized: '',
     created_on_delta_humanized: '',
     created_by: {
+      id: 1,
       first_name: 'joe',
       last_name: 'smith',
     },
     changed_by: {
+      id: 2,
       first_name: 'tom',
       last_name: 'brown',
     },
diff --git a/superset-frontend/src/pages/AlertReportList/index.tsx b/superset-frontend/src/pages/AlertReportList/index.tsx
index b0cd0a4622..c6d14d186f 100644
--- a/superset-frontend/src/pages/AlertReportList/index.tsx
+++ b/superset-frontend/src/pages/AlertReportList/index.tsx
@@ -53,6 +53,8 @@ import { isUserAdmin } from 'src/dashboard/util/permissionUtils';
 import Owner from 'src/types/Owner';
 import AlertReportModal from 'src/features/alerts/AlertReportModal';
 import { AlertObject, AlertState } from 'src/features/alerts/types';
+import { ModifiedInfo } from 'src/components/AuditInfo';
+import { QueryObjectColumns } from 'src/views/CRUD/types';
 
 const extensionsRegistry = getExtensionsRegistry();
 
@@ -303,18 +305,6 @@ function AlertList({
         disableSortBy: true,
         size: 'xl',
       },
-      {
-        Cell: ({
-          row: {
-            original: { created_by },
-          },
-        }: any) =>
-          created_by ? `${created_by.first_name} ${created_by.last_name}` : '',
-        Header: t('Created by'),
-        id: 'created_by',
-        disableSortBy: true,
-        size: 'xl',
-      },
       {
         Cell: ({
           row: {
@@ -329,10 +319,13 @@ function AlertList({
       {
         Cell: ({
           row: {
-            original: { changed_on_delta_humanized: changedOn },
+            original: {
+              changed_on_delta_humanized: changedOn,
+              changed_by: changedBy,
+            },
           },
-        }: any) => <span className="no-wrap">{changedOn}</span>,
-        Header: t('Modified'),
+        }: any) => <ModifiedInfo date={changedOn} user={changedBy} />,
+        Header: t('Last modified'),
         accessor: 'changed_on_delta_humanized',
         size: 'xl',
       },
@@ -407,6 +400,10 @@ function AlertList({
         disableSortBy: true,
         size: 'xl',
       },
+      {
+        accessor: QueryObjectColumns.changed_by,
+        hidden: true,
+      },
     ],
     [canDelete, canEdit, isReportEnabled, toggleActive],
   );
@@ -448,6 +445,13 @@ function AlertList({
 
   const filters: Filters = useMemo(
     () => [
+      {
+        Header: t('Name'),
+        key: 'search',
+        id: 'name',
+        input: 'search',
+        operator: FilterOperator.contains,
+      },
       {
         Header: t('Owner'),
         key: 'owner',
@@ -465,23 +469,6 @@ function AlertList({
         ),
         paginate: true,
       },
-      {
-        Header: t('Created by'),
-        key: 'created_by',
-        id: 'created_by',
-        input: 'select',
-        operator: FilterOperator.relationOneMany,
-        unfilteredLabel: 'All',
-        fetchSelects: createFetchRelated(
-          'report',
-          'created_by',
-          createErrorHandler(errMsg =>
-            t('An error occurred while fetching created by values: %s', errMsg),
-          ),
-          user,
-        ),
-        paginate: true,
-      },
       {
         Header: t('Status'),
         key: 'status',
@@ -504,11 +491,24 @@ function AlertList({
         ],
       },
       {
-        Header: t('Search'),
-        key: 'search',
-        id: 'name',
-        input: 'search',
-        operator: FilterOperator.contains,
+        Header: t('Modified by'),
+        key: 'changed_by',
+        id: 'changed_by',
+        input: 'select',
+        operator: FilterOperator.relationOneMany,
+        unfilteredLabel: t('All'),
+        fetchSelects: createFetchRelated(
+          'report',
+          'changed_by',
+          createErrorHandler(errMsg =>
+            t(
+              'An error occurred while fetching dataset datasource values: %s',
+              errMsg,
+            ),
+          ),
+          user,
+        ),
+        paginate: true,
       },
     ],
     [],
diff --git a/superset-frontend/src/pages/AllEntities/index.tsx b/superset-frontend/src/pages/AllEntities/index.tsx
index a1e2c52fe4..b94cab846d 100644
--- a/superset-frontend/src/pages/AllEntities/index.tsx
+++ b/superset-frontend/src/pages/AllEntities/index.tsx
@@ -35,6 +35,7 @@ import TagModal from 'src/features/tags/TagModal';
 import withToasts, { useToasts } from 'src/components/MessageToasts/withToasts';
 import { fetchObjectsByTagIds, fetchSingleTag } from 'src/features/tags/tags';
 import Loading from 'src/components/Loading';
+import getOwnerName from 'src/utils/getOwnerName';
 
 interface TaggedObject {
   id: number;
@@ -132,7 +133,7 @@ function AllEntities() {
 
   const owner: Owner = {
     type: MetadataType.OWNER,
-    createdBy: `${tag?.created_by.first_name} ${tag?.created_by.last_name}`,
+    createdBy: getOwnerName(tag?.created_by),
     createdOn: tag?.created_on_delta_humanized || '',
   };
   items.push(owner);
@@ -140,7 +141,7 @@ function AllEntities() {
   const lastModified: LastModified = {
     type: MetadataType.LAST_MODIFIED,
     value: tag?.changed_on_delta_humanized || '',
-    modifiedBy: `${tag?.changed_by.first_name} ${tag?.changed_by.last_name}`,
+    modifiedBy: getOwnerName(tag?.changed_by),
   };
   items.push(lastModified);
 
diff --git a/superset-frontend/src/pages/AnnotationLayerList/index.tsx b/superset-frontend/src/pages/AnnotationLayerList/index.tsx
index fc909538c0..fff5743b5a 100644
--- a/superset-frontend/src/pages/AnnotationLayerList/index.tsx
+++ b/superset-frontend/src/pages/AnnotationLayerList/index.tsx
@@ -21,7 +21,6 @@ import React, { useMemo, useState } from 'react';
 import rison from 'rison';
 import { t, SupersetClient } from '@superset-ui/core';
 import { Link, useHistory } from 'react-router-dom';
-import moment from 'moment';
 import { useListViewResource } from 'src/views/CRUD/hooks';
 import { createFetchRelated, createErrorHandler } from 'src/views/CRUD/utils';
 import withToasts from 'src/components/MessageToasts/withToasts';
@@ -36,9 +35,10 @@ import DeleteModal from 'src/components/DeleteModal';
 import ConfirmStatusChange from 'src/components/ConfirmStatusChange';
 import AnnotationLayerModal from 'src/features/annotationLayers/AnnotationLayerModal';
 import { AnnotationLayerObject } from 'src/features/annotationLayers/types';
+import { ModifiedInfo } from 'src/components/AuditInfo';
+import { QueryObjectColumns } from 'src/views/CRUD/types';
 
 const PAGE_SIZE = 25;
-const MOMENT_FORMAT = 'MMM DD, YYYY';
 
 interface AnnotationLayersListProps {
   addDangerToast: (msg: string) => void;
@@ -156,65 +156,16 @@ function AnnotationLayersList({
       {
         Cell: ({
           row: {
-            original: { changed_on: changedOn },
+            original: {
+              changed_on_delta_humanized: changedOn,
+              changed_by: changedBy,
+            },
           },
-        }: any) => {
-          const date = new Date(changedOn);
-          const utc = new Date(
-            Date.UTC(
-              date.getFullYear(),
-              date.getMonth(),
-              date.getDate(),
-              date.getHours(),
-              date.getMinutes(),
-              date.getSeconds(),
-              date.getMilliseconds(),
-            ),
-          );
-
-          return moment(utc).format(MOMENT_FORMAT);
-        },
+        }: any) => <ModifiedInfo date={changedOn} user={changedBy} />,
         Header: t('Last modified'),
         accessor: 'changed_on',
         size: 'xl',
       },
-      {
-        Cell: ({
-          row: {
-            original: { created_on: createdOn },
-          },
-        }: any) => {
-          const date = new Date(createdOn);
-          const utc = new Date(
-            Date.UTC(
-              date.getFullYear(),
-              date.getMonth(),
-              date.getDate(),
-              date.getHours(),
-              date.getMinutes(),
-              date.getSeconds(),
-              date.getMilliseconds(),
-            ),
-          );
-
-          return moment(utc).format(MOMENT_FORMAT);
-        },
-        Header: t('Created on'),
-        accessor: 'created_on',
-        size: 'xl',
-      },
-      {
-        accessor: 'created_by',
-        disableSortBy: true,
-        Header: t('Created by'),
-        Cell: ({
-          row: {
-            original: { created_by: createdBy },
-          },
-        }: any) =>
-          createdBy ? `${createdBy.first_name} ${createdBy.last_name}` : '',
-        size: 'xl',
-      },
       {
         Cell: ({ row: { original } }: any) => {
           const handleEdit = () => handleAnnotationLayerEdit(original);
@@ -249,6 +200,10 @@ function AnnotationLayersList({
         hidden: !canEdit && !canDelete,
         size: 'xl',
       },
+      {
+        accessor: QueryObjectColumns.changed_by,
+        hidden: true,
+      },
     ],
     [canDelete, canCreate],
   );
@@ -280,15 +235,22 @@ function AnnotationLayersList({
   const filters: Filters = useMemo(
     () => [
       {
-        Header: t('Created by'),
-        key: 'created_by',
-        id: 'created_by',
+        Header: t('Name'),
+        key: 'search',
+        id: 'name',
+        input: 'search',
+        operator: FilterOperator.contains,
+      },
+      {
+        Header: t('Changed by'),
+        key: 'changed_by',
+        id: 'changed_by',
         input: 'select',
         operator: FilterOperator.relationOneMany,
         unfilteredLabel: t('All'),
         fetchSelects: createFetchRelated(
           'annotation_layer',
-          'created_by',
+          'changed_by',
           createErrorHandler(errMsg =>
             t(
               'An error occurred while fetching dataset datasource values: %s',
@@ -299,13 +261,6 @@ function AnnotationLayersList({
         ),
         paginate: true,
       },
-      {
-        Header: t('Search'),
-        key: 'search',
-        id: 'name',
-        input: 'search',
-        operator: FilterOperator.contains,
-      },
     ],
     [],
   );
diff --git a/superset-frontend/src/pages/AnnotationList/index.tsx b/superset-frontend/src/pages/AnnotationList/index.tsx
index 980a18ba72..e04b48080f 100644
--- a/superset-frontend/src/pages/AnnotationList/index.tsx
+++ b/superset-frontend/src/pages/AnnotationList/index.tsx
@@ -154,7 +154,7 @@ function AnnotationList({
     () => [
       {
         accessor: 'short_descr',
-        Header: t('Label'),
+        Header: t('Name'),
       },
       {
         accessor: 'long_descr',
diff --git a/superset-frontend/src/pages/ChartList/index.tsx b/superset-frontend/src/pages/ChartList/index.tsx
index d13113158e..5ed967d7c1 100644
--- a/superset-frontend/src/pages/ChartList/index.tsx
+++ b/superset-frontend/src/pages/ChartList/index.tsx
@@ -29,7 +29,6 @@ import {
 import React, { useState, useMemo, useCallback } from 'react';
 import rison from 'rison';
 import { uniqBy } from 'lodash';
-import moment from 'moment';
 import { useSelector } from 'react-redux';
 import {
   createErrorHandler,
@@ -69,11 +68,13 @@ import setupPlugins from 'src/setup/setupPlugins';
 import InfoTooltip from 'src/components/InfoTooltip';
 import CertifiedBadge from 'src/components/CertifiedBadge';
 import { GenericLink } from 'src/components/GenericLink/GenericLink';
-import Owner from 'src/types/Owner';
 import { loadTags } from 'src/components/Tags/utils';
+import FacePile from 'src/components/FacePile';
 import ChartCard from 'src/features/charts/ChartCard';
 import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes';
 import { findPermission } from 'src/utils/findPermission';
+import { ModifiedInfo } from 'src/components/AuditInfo';
+import { QueryObjectColumns } from 'src/views/CRUD/types';
 
 const FlexRowContainer = styled.div`
   align-items: center;
@@ -245,10 +246,6 @@ function ChartList(props: ChartListProps) {
     });
     setPreparingExport(true);
   };
-  const changedByName = (lastSavedBy: Owner) =>
-    lastSavedBy?.first_name
-      ? `${lastSavedBy?.first_name} ${lastSavedBy?.last_name}`
-      : null;
 
   function handleBulkChartDelete(chartsToDelete: Chart[]) {
     SupersetClient.delete({
@@ -366,7 +363,7 @@ function ChartList(props: ChartListProps) {
             )}
           </FlexRowContainer>
         ),
-        Header: t('Chart'),
+        Header: t('Name'),
         accessor: 'slice_name',
       },
       {
@@ -375,7 +372,7 @@ function ChartList(props: ChartListProps) {
             original: { viz_type: vizType },
           },
         }: any) => registry.get(vizType)?.name || vizType,
-        Header: t('Visualization type'),
+        Header: t('Type'),
         accessor: 'viz_type',
         size: 'xxl',
       },
@@ -438,44 +435,27 @@ function ChartList(props: ChartListProps) {
       {
         Cell: ({
           row: {
-            original: { last_saved_by: lastSavedBy },
+            original: { owners = [] },
           },
-        }: any) => <>{changedByName(lastSavedBy)}</>,
-        Header: t('Modified by'),
-        accessor: 'last_saved_by.first_name',
+        }: any) => <FacePile users={owners} />,
+        Header: t('Owners'),
+        accessor: 'owners',
+        disableSortBy: true,
         size: 'xl',
       },
       {
         Cell: ({
           row: {
-            original: { last_saved_at: lastSavedAt },
+            original: {
+              changed_on_delta_humanized: changedOn,
+              changed_by: changedBy,
+            },
           },
-        }: any) => (
-          <span className="no-wrap">
-            {lastSavedAt ? moment.utc(lastSavedAt).fromNow() : null}
-          </span>
-        ),
+        }: any) => <ModifiedInfo date={changedOn} user={changedBy} />,
         Header: t('Last modified'),
         accessor: 'last_saved_at',
         size: 'xl',
       },
-      {
-        accessor: 'owners',
-        hidden: true,
-        disableSortBy: true,
-      },
-      {
-        Cell: ({
-          row: {
-            original: { created_by: createdBy },
-          },
-        }: any) =>
-          createdBy ? `${createdBy.first_name} ${createdBy.last_name}` : '',
-        Header: t('Created by'),
-        accessor: 'created_by',
-        disableSortBy: true,
-        size: 'xl',
-      },
       {
         Cell: ({ row: { original } }: any) => {
           const handleDelete = () =>
@@ -563,6 +543,10 @@ function ChartList(props: ChartListProps) {
         disableSortBy: true,
         hidden: !canEdit && !canDelete,
       },
+      {
+        accessor: QueryObjectColumns.changed_by,
+        hidden: true,
+      },
     ],
     [
       userId,
@@ -597,58 +581,14 @@ function ChartList(props: ChartListProps) {
   const filters: Filters = useMemo(() => {
     const filters_list = [
       {
-        Header: t('Search'),
+        Header: t('Name'),
         key: 'search',
         id: 'slice_name',
         input: 'search',
         operator: FilterOperator.chartAllText,
       },
       {
-        Header: t('Owner'),
-        key: 'owner',
-        id: 'owners',
-        input: 'select',
-        operator: FilterOperator.relationManyMany,
-        unfilteredLabel: t('All'),
-        fetchSelects: createFetchRelated(
-          'chart',
-          'owners',
-          createErrorHandler(errMsg =>
-            addDangerToast(
-              t(
-                'An error occurred while fetching chart owners values: %s',
-                errMsg,
-              ),
-            ),
-          ),
-          props.user,
-        ),
-        paginate: true,
-      },
-      {
-        Header: t('Created by'),
-        key: 'created_by',
-        id: 'created_by',
-        input: 'select',
-        operator: FilterOperator.relationOneMany,
-        unfilteredLabel: t('All'),
-        fetchSelects: createFetchRelated(
-          'chart',
-          'created_by',
-          createErrorHandler(errMsg =>
-            addDangerToast(
-              t(
-                'An error occurred while fetching chart created by values: %s',
-                errMsg,
-              ),
-            ),
-          ),
-          props.user,
-        ),
-        paginate: true,
-      },
-      {
-        Header: t('Chart type'),
+        Header: t('Type'),
         key: 'viz_type',
         id: 'viz_type',
         input: 'select',
@@ -683,8 +623,43 @@ function ChartList(props: ChartListProps) {
         fetchSelects: createFetchDatasets,
         paginate: true,
       },
+      ...(isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM) && canReadTag
+        ? [
+            {
+              Header: t('Tag'),
+              key: 'tags',
+              id: 'tags',
+              input: 'select',
+              operator: FilterOperator.chartTags,
+              unfilteredLabel: t('All'),
+              fetchSelects: loadTags,
+            },
+          ]
+        : []),
       {
-        Header: t('Dashboards'),
+        Header: t('Owner'),
+        key: 'owner',
+        id: 'owners',
+        input: 'select',
+        operator: FilterOperator.relationManyMany,
+        unfilteredLabel: t('All'),
+        fetchSelects: createFetchRelated(
+          'chart',
+          'owners',
+          createErrorHandler(errMsg =>
+            addDangerToast(
+              t(
+                'An error occurred while fetching chart owners values: %s',
+                errMsg,
+              ),
+            ),
+          ),
+          props.user,
+        ),
+        paginate: true,
+      },
+      {
+        Header: t('Dashboard'),
         key: 'dashboards',
         id: 'dashboards',
         input: 'select',
@@ -707,18 +682,27 @@ function ChartList(props: ChartListProps) {
           { label: t('No'), value: false },
         ],
       },
-    ] as Filters;
-    if (isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM) && canReadTag) {
-      filters_list.push({
-        Header: t('Tags'),
-        key: 'tags',
-        id: 'tags',
+      {
+        Header: t('Modified by'),
+        key: 'changed_by',
+        id: 'changed_by',
         input: 'select',
-        operator: FilterOperator.chartTags,
+        operator: FilterOperator.relationOneMany,
         unfilteredLabel: t('All'),
-        fetchSelects: loadTags,
-      });
-    }
+        fetchSelects: createFetchRelated(
+          'chart',
+          'changed_by',
+          createErrorHandler(errMsg =>
+            t(
+              'An error occurred while fetching dataset datasource values: %s',
+              errMsg,
+            ),
+          ),
+          props.user,
+        ),
+        paginate: true,
+      },
+    ] as Filters;
     return filters_list;
   }, [addDangerToast, favoritesFilter, props.user]);
 
diff --git a/superset-frontend/src/pages/CssTemplateList/index.tsx b/superset-frontend/src/pages/CssTemplateList/index.tsx
index f777f8e743..b77217b22f 100644
--- a/superset-frontend/src/pages/CssTemplateList/index.tsx
+++ b/superset-frontend/src/pages/CssTemplateList/index.tsx
@@ -21,13 +21,11 @@ import React, { useMemo, useState } from 'react';
 import { t, SupersetClient } from '@superset-ui/core';
 
 import rison from 'rison';
-import moment from 'moment';
 import { useListViewResource } from 'src/views/CRUD/hooks';
-import { createFetchRelated, createErrorHandler } from 'src/views/CRUD/utils';
+import { createErrorHandler, createFetchRelated } from 'src/views/CRUD/utils';
 import withToasts from 'src/components/MessageToasts/withToasts';
 import SubMenu, { SubMenuProps } from 'src/features/home/SubMenu';
 import DeleteModal from 'src/components/DeleteModal';
-import { Tooltip } from 'src/components/Tooltip';
 import ConfirmStatusChange from 'src/components/ConfirmStatusChange';
 import ActionsBar, { ActionProps } from 'src/components/ListView/ActionsBar';
 import ListView, {
@@ -37,6 +35,8 @@ import ListView, {
 } from 'src/components/ListView';
 import CssTemplateModal from 'src/features/cssTemplates/CssTemplateModal';
 import { TemplateObject } from 'src/features/cssTemplates/types';
+import { ModifiedInfo } from 'src/components/AuditInfo';
+import { QueryObjectColumns } from 'src/views/CRUD/types';
 
 const PAGE_SIZE = 25;
 
@@ -138,66 +138,12 @@ function CssTemplatesList({
               changed_by: changedBy,
             },
           },
-        }: any) => {
-          let name = 'null';
-
-          if (changedBy) {
-            name = `${changedBy.first_name} ${changedBy.last_name}`;
-          }
-
-          return (
-            <Tooltip
-              id="allow-run-async-header-tooltip"
-              title={t('Last modified by %s', name)}
-              placement="right"
-            >
-              <span>{changedOn}</span>
-            </Tooltip>
-          );
-        },
+        }: any) => <ModifiedInfo date={changedOn} user={changedBy} />,
         Header: t('Last modified'),
         accessor: 'changed_on_delta_humanized',
         size: 'xl',
         disableSortBy: true,
       },
-      {
-        Cell: ({
-          row: {
-            original: { created_on: createdOn },
-          },
-        }: any) => {
-          const date = new Date(createdOn);
-          const utc = new Date(
-            Date.UTC(
-              date.getFullYear(),
-              date.getMonth(),
-              date.getDate(),
-              date.getHours(),
-              date.getMinutes(),
-              date.getSeconds(),
-              date.getMilliseconds(),
-            ),
-          );
-
-          return moment(utc).fromNow();
-        },
-        Header: t('Created on'),
-        accessor: 'created_on',
-        size: 'xl',
-        disableSortBy: true,
-      },
-      {
-        accessor: 'created_by',
-        disableSortBy: true,
-        Header: t('Created by'),
-        Cell: ({
-          row: {
-            original: { created_by: createdBy },
-          },
-        }: any) =>
-          createdBy ? `${createdBy.first_name} ${createdBy.last_name}` : '',
-        size: 'xl',
-      },
       {
         Cell: ({ row: { original } }: any) => {
           const handleEdit = () => handleCssTemplateEdit(original);
@@ -232,6 +178,10 @@ function CssTemplatesList({
         hidden: !canEdit && !canDelete,
         size: 'xl',
       },
+      {
+        accessor: QueryObjectColumns.changed_by,
+        hidden: true,
+      },
     ],
     [canDelete, canCreate],
   );
@@ -270,15 +220,22 @@ function CssTemplatesList({
   const filters: Filters = useMemo(
     () => [
       {
-        Header: t('Created by'),
-        key: 'created_by',
-        id: 'created_by',
+        Header: t('Name'),
+        key: 'search',
+        id: 'template_name',
+        input: 'search',
+        operator: FilterOperator.contains,
+      },
+      {
+        Header: t('Modified by'),
+        key: 'changed_by',
+        id: 'changed_by',
         input: 'select',
         operator: FilterOperator.relationOneMany,
         unfilteredLabel: t('All'),
         fetchSelects: createFetchRelated(
           'css_template',
-          'created_by',
+          'changed_by',
           createErrorHandler(errMsg =>
             t(
               'An error occurred while fetching dataset datasource values: %s',
@@ -289,13 +246,6 @@ function CssTemplatesList({
         ),
         paginate: true,
       },
-      {
-        Header: t('Search'),
-        key: 'search',
-        id: 'template_name',
-        input: 'search',
-        operator: FilterOperator.contains,
-      },
     ],
     [],
   );
diff --git a/superset-frontend/src/pages/DashboardList/index.tsx b/superset-frontend/src/pages/DashboardList/index.tsx
index 6542d85129..e82b701859 100644
--- a/superset-frontend/src/pages/DashboardList/index.tsx
+++ b/superset-frontend/src/pages/DashboardList/index.tsx
@@ -57,13 +57,17 @@ import { Tooltip } from 'src/components/Tooltip';
 import ImportModelsModal from 'src/components/ImportModal/index';
 
 import Dashboard from 'src/dashboard/containers/Dashboard';
-import { Dashboard as CRUDDashboard } from 'src/views/CRUD/types';
+import {
+  Dashboard as CRUDDashboard,
+  QueryObjectColumns,
+} from 'src/views/CRUD/types';
 import CertifiedBadge from 'src/components/CertifiedBadge';
 import { loadTags } from 'src/components/Tags/utils';
 import DashboardCard from 'src/features/dashboards/DashboardCard';
 import { DashboardStatus } from 'src/features/dashboards/types';
 import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes';
 import { findPermission } from 'src/utils/findPermission';
+import { ModifiedInfo } from 'src/components/AuditInfo';
 
 const PAGE_SIZE = 25;
 const PASSWORDS_NEEDED_MESSAGE = t(
@@ -108,11 +112,7 @@ const Actions = styled.div`
 `;
 
 function DashboardList(props: DashboardListProps) {
-  const {
-    addDangerToast,
-    addSuccessToast,
-    user: { userId },
-  } = props;
+  const { addDangerToast, addSuccessToast, user } = props;
 
   const { roles } = useSelector<any, UserWithPermissionsAndRoles>(
     state => state.user,
@@ -178,7 +178,7 @@ function DashboardList(props: DashboardListProps) {
   };
 
   // TODO: Fix usage of localStorage keying on the user id
-  const userKey = dangerouslyGetItemDoNotUse(userId?.toString(), null);
+  const userKey = dangerouslyGetItemDoNotUse(user?.userId?.toString(), null);
 
   const canCreate = hasPerm('can_write');
   const canEdit = hasPerm('can_write');
@@ -274,7 +274,7 @@ function DashboardList(props: DashboardListProps) {
             original: { id },
           },
         }: any) =>
-          userId && (
+          user?.userId && (
             <FaveStar
               itemId={id}
               saveFaveStar={saveFavoriteStatus}
@@ -285,7 +285,7 @@ function DashboardList(props: DashboardListProps) {
         id: 'id',
         disableSortBy: true,
         size: 'xs',
-        hidden: !userId,
+        hidden: !user?.userId,
       },
       {
         Cell: ({
@@ -310,9 +310,20 @@ function DashboardList(props: DashboardListProps) {
             {dashboardTitle}
           </Link>
         ),
-        Header: t('Title'),
+        Header: t('Name'),
         accessor: 'dashboard_title',
       },
+      {
+        Cell: ({
+          row: {
+            original: { status },
+          },
+        }: any) =>
+          status === DashboardStatus.PUBLISHED ? t('Published') : t('Draft'),
+        Header: t('Status'),
+        accessor: 'published',
+        size: 'xl',
+      },
       {
         Cell: ({
           row: {
@@ -341,55 +352,25 @@ function DashboardList(props: DashboardListProps) {
       {
         Cell: ({
           row: {
-            original: { changed_by_name: changedByName },
-          },
-        }: any) => <>{changedByName}</>,
-        Header: t('Modified by'),
-        accessor: 'changed_by.first_name',
-        size: 'xl',
-      },
-      {
-        Cell: ({
-          row: {
-            original: { status },
-          },
-        }: any) =>
-          status === DashboardStatus.PUBLISHED ? t('Published') : t('Draft'),
-        Header: t('Status'),
-        accessor: 'published',
-        size: 'xl',
-      },
-      {
-        Cell: ({
-          row: {
-            original: { changed_on_delta_humanized: changedOn },
-          },
-        }: any) => <span className="no-wrap">{changedOn}</span>,
-        Header: t('Modified'),
-        accessor: 'changed_on_delta_humanized',
-        size: 'xl',
-      },
-      {
-        Cell: ({
-          row: {
-            original: { created_by: createdBy },
+            original: { owners = [] },
           },
-        }: any) =>
-          createdBy ? `${createdBy.first_name} ${createdBy.last_name}` : '',
-        Header: t('Created by'),
-        accessor: 'created_by',
+        }: any) => <FacePile users={owners} />,
+        Header: t('Owners'),
+        accessor: 'owners',
         disableSortBy: true,
         size: 'xl',
       },
       {
         Cell: ({
           row: {
-            original: { owners = [] },
+            original: {
+              changed_on_delta_humanized: changedOn,
+              changed_by: changedBy,
+            },
           },
-        }: any) => <FacePile users={owners} />,
-        Header: t('Owners'),
-        accessor: 'owners',
-        disableSortBy: true,
+        }: any) => <ModifiedInfo date={changedOn} user={changedBy} />,
+        Header: t('Last modified'),
+        accessor: 'changed_on_delta_humanized',
         size: 'xl',
       },
       {
@@ -475,9 +456,13 @@ function DashboardList(props: DashboardListProps) {
         hidden: !canEdit && !canDelete && !canExport,
         disableSortBy: true,
       },
+      {
+        accessor: QueryObjectColumns.changed_by,
+        hidden: true,
+      },
     ],
     [
-      userId,
+      user?.userId,
       canEdit,
       canDelete,
       canExport,
@@ -509,12 +494,37 @@ function DashboardList(props: DashboardListProps) {
   const filters: Filters = useMemo(() => {
     const filters_list = [
       {
-        Header: t('Search'),
+        Header: t('Name'),
         key: 'search',
         id: 'dashboard_title',
         input: 'search',
         operator: FilterOperator.titleOrSlug,
       },
+      {
+        Header: t('Status'),
+        key: 'published',
+        id: 'published',
+        input: 'select',
+        operator: FilterOperator.equals,
+        unfilteredLabel: t('Any'),
+        selects: [
+          { label: t('Published'), value: true },
+          { label: t('Draft'), value: false },
+        ],
+      },
+      ...(isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM) && canReadTag
+        ? [
+            {
+              Header: t('Tag'),
+              key: 'tags',
+              id: 'tags',
+              input: 'select',
+              operator: FilterOperator.dashboardTags,
+              unfilteredLabel: t('All'),
+              fetchSelects: loadTags,
+            },
+          ]
+        : []),
       {
         Header: t('Owner'),
         key: 'owner',
@@ -537,41 +547,7 @@ function DashboardList(props: DashboardListProps) {
         ),
         paginate: true,
       },
-      {
-        Header: t('Created by'),
-        key: 'created_by',
-        id: 'created_by',
-        input: 'select',
-        operator: FilterOperator.relationOneMany,
-        unfilteredLabel: t('All'),
-        fetchSelects: createFetchRelated(
-          'dashboard',
-          'created_by',
-          createErrorHandler(errMsg =>
-            addDangerToast(
-              t(
-                'An error occurred while fetching dashboard created by values: %s',
-                errMsg,
-              ),
-            ),
-          ),
-          props.user,
-        ),
-        paginate: true,
-      },
-      {
-        Header: t('Status'),
-        key: 'published',
-        id: 'published',
-        input: 'select',
-        operator: FilterOperator.equals,
-        unfilteredLabel: t('Any'),
-        selects: [
-          { label: t('Published'), value: true },
-          { label: t('Draft'), value: false },
-        ],
-      },
-      ...(userId ? [favoritesFilter] : []),
+      ...(user?.userId ? [favoritesFilter] : []),
       {
         Header: t('Certified'),
         key: 'certified',
@@ -585,18 +561,27 @@ function DashboardList(props: DashboardListProps) {
           { label: t('No'), value: false },
         ],
       },
-    ] as Filters;
-    if (isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM) && canReadTag) {
-      filters_list.push({
-        Header: t('Tags'),
-        key: 'tags',
-        id: 'tags',
+      {
+        Header: t('Modified by'),
+        key: 'changed_by',
+        id: 'changed_by',
         input: 'select',
-        operator: FilterOperator.dashboardTags,
+        operator: FilterOperator.relationOneMany,
         unfilteredLabel: t('All'),
-        fetchSelects: loadTags,
-      });
-    }
+        fetchSelects: createFetchRelated(
+          'dashboard',
+          'changed_by',
+          createErrorHandler(errMsg =>
+            t(
+              'An error occurred while fetching dataset datasource values: %s',
+              errMsg,
+            ),
+          ),
+          user,
+        ),
+        paginate: true,
+      },
+    ] as Filters;
     return filters_list;
   }, [addDangerToast, favoritesFilter, props.user]);
 
@@ -632,7 +617,7 @@ function DashboardList(props: DashboardListProps) {
             ? userKey.thumbnails
             : isFeatureEnabled(FeatureFlag.THUMBNAILS)
         }
-        userId={userId}
+        userId={user?.userId}
         loading={loading}
         openDashboardEditModal={openDashboardEditModal}
         saveFavoriteStatus={saveFavoriteStatus}
@@ -646,7 +631,7 @@ function DashboardList(props: DashboardListProps) {
       favoriteStatus,
       hasPerm,
       loading,
-      userId,
+      user?.userId,
       saveFavoriteStatus,
       userKey,
     ],
@@ -743,7 +728,7 @@ function DashboardList(props: DashboardListProps) {
                       addSuccessToast,
                       addDangerToast,
                       undefined,
-                      userId,
+                      user?.userId,
                     );
                     setDashboardToDelete(null);
                   }}
diff --git a/superset-frontend/src/pages/DatabaseList/DatabaseList.test.jsx b/superset-frontend/src/pages/DatabaseList/DatabaseList.test.jsx
index fd989b50d2..b1bfb245d3 100644
--- a/superset-frontend/src/pages/DatabaseList/DatabaseList.test.jsx
+++ b/superset-frontend/src/pages/DatabaseList/DatabaseList.test.jsx
@@ -218,7 +218,7 @@ describe('Admin DatabaseList', () => {
     await waitForComponentToPaint(wrapper);
 
     expect(fetchMock.lastCall()[0]).toMatchInlineSnapshot(
-      `"http://localhost/api/v1/database/?q=(filters:!((col:expose_in_sqllab,opr:eq,value:!t),(col:allow_run_async,opr:eq,value:!f),(col:database_name,opr:ct,value:fooo)),order_column:changed_on_delta_humanized,order_direction:desc,page:0,page_size:25)"`,
+      `"http://localhost/api/v1/database/?q=(filters:!((col:database_name,opr:ct,value:fooo),(col:expose_in_sqllab,opr:eq,value:!t),(col:allow_run_async,opr:eq,value:!f)),order_column:changed_on_delta_humanized,order_direction:desc,page:0,page_size:25)"`,
     );
   });
 
diff --git a/superset-frontend/src/pages/DatabaseList/index.tsx b/superset-frontend/src/pages/DatabaseList/index.tsx
index d2308bd117..8c98392aca 100644
--- a/superset-frontend/src/pages/DatabaseList/index.tsx
+++ b/superset-frontend/src/pages/DatabaseList/index.tsx
@@ -32,7 +32,11 @@ import { LocalStorageKeys, setItem } from 'src/utils/localStorageHelpers';
 
 import Loading from 'src/components/Loading';
 import { useListViewResource } from 'src/views/CRUD/hooks';
-import { createErrorHandler, uploadUserPerms } from 'src/views/CRUD/utils';
+import {
+  createErrorHandler,
+  createFetchRelated,
+  uploadUserPerms,
+} from 'src/views/CRUD/utils';
 import withToasts from 'src/components/MessageToasts/withToasts';
 import SubMenu, { SubMenuProps } from 'src/features/home/SubMenu';
 import DeleteModal from 'src/components/DeleteModal';
@@ -48,6 +52,8 @@ import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes';
 import type { MenuObjectProps } from 'src/types/bootstrapTypes';
 import DatabaseModal from 'src/features/databases/DatabaseModal';
 import { DatabaseObject } from 'src/features/databases/types';
+import { ModifiedInfo } from 'src/components/AuditInfo';
+import { QueryObjectColumns } from 'src/views/CRUD/types';
 
 const extensionsRegistry = getExtensionsRegistry();
 const DatabaseDeleteRelatedExtension = extensionsRegistry.get(
@@ -67,6 +73,11 @@ interface DatabaseDeleteObject extends DatabaseObject {
 interface DatabaseListProps {
   addDangerToast: (msg: string) => void;
   addSuccessToast: (msg: string) => void;
+  user: {
+    userId: string | number;
+    firstName: string;
+    lastName: string;
+  };
 }
 
 const IconCheck = styled(Icons.Check)`
@@ -90,7 +101,11 @@ function BooleanDisplay({ value }: { value: Boolean }) {
   return value ? <IconCheck /> : <IconCancelX />;
 }
 
-function DatabaseList({ addDangerToast, addSuccessToast }: DatabaseListProps) {
+function DatabaseList({
+  addDangerToast,
+  addSuccessToast,
+  user,
+}: DatabaseListProps) {
   const {
     state: {
       loading,
@@ -105,7 +120,7 @@ function DatabaseList({ addDangerToast, addSuccessToast }: DatabaseListProps) {
     t('database'),
     addDangerToast,
   );
-  const user = useSelector<any, UserWithPermissionsAndRoles>(
+  const fullUser = useSelector<any, UserWithPermissionsAndRoles>(
     state => state.user,
   );
   const showDatabaseModal = getUrlParam(URL_PARAMS.showDatabaseModal);
@@ -123,11 +138,11 @@ function DatabaseList({ addDangerToast, addSuccessToast }: DatabaseListProps) {
     null,
   );
   const [allowUploads, setAllowUploads] = useState<boolean>(false);
-  const isAdmin = isUserAdmin(user);
+  const isAdmin = isUserAdmin(fullUser);
   const showUploads = allowUploads || isAdmin;
 
   const [preparingExport, setPreparingExport] = useState<boolean>(false);
-  const { roles } = user;
+  const { roles } = fullUser;
   const {
     CSV_EXTENSIONS,
     COLUMNAR_EXTENSIONS,
@@ -313,7 +328,7 @@ function DatabaseList({ addDangerToast, addSuccessToast }: DatabaseListProps) {
     () => [
       {
         accessor: 'database_name',
-        Header: t('Database'),
+        Header: t('Name'),
       },
       {
         accessor: 'backend',
@@ -380,23 +395,14 @@ function DatabaseList({ addDangerToast, addSuccessToast }: DatabaseListProps) {
         size: 'md',
       },
       {
-        accessor: 'created_by',
-        disableSortBy: true,
-        Header: t('Created by'),
         Cell: ({
           row: {
-            original: { created_by: createdBy },
+            original: {
+              changed_by: changedBy,
+              changed_on_delta_humanized: changedOn,
+            },
           },
-        }: any) =>
-          createdBy ? `${createdBy.first_name} ${createdBy.last_name}` : '',
-        size: 'xl',
-      },
-      {
-        Cell: ({
-          row: {
-            original: { changed_on_delta_humanized: changedOn },
-          },
-        }: any) => changedOn,
+        }: any) => <ModifiedInfo date={changedOn} user={changedBy} />,
         Header: t('Last modified'),
         accessor: 'changed_on_delta_humanized',
         size: 'xl',
@@ -470,12 +476,23 @@ function DatabaseList({ addDangerToast, addSuccessToast }: DatabaseListProps) {
         hidden: !canEdit && !canDelete,
         disableSortBy: true,
       },
+      {
+        accessor: QueryObjectColumns.changed_by,
+        hidden: true,
+      },
     ],
     [canDelete, canEdit, canExport],
   );
 
   const filters: Filters = useMemo(
     () => [
+      {
+        Header: t('Name'),
+        key: 'search',
+        id: 'database_name',
+        input: 'search',
+        operator: FilterOperator.contains,
+      },
       {
         Header: t('Expose in SQL Lab'),
         key: 'expose_in_sql_lab',
@@ -509,11 +526,24 @@ function DatabaseList({ addDangerToast, addSuccessToast }: DatabaseListProps) {
         ],
       },
       {
-        Header: t('Search'),
-        key: 'search',
-        id: 'database_name',
-        input: 'search',
-        operator: FilterOperator.contains,
+        Header: t('Modified by'),
+        key: 'changed_by',
+        id: 'changed_by',
+        input: 'select',
+        operator: FilterOperator.relationOneMany,
+        unfilteredLabel: t('All'),
+        fetchSelects: createFetchRelated(
+          'database',
+          'changed_by',
+          createErrorHandler(errMsg =>
+            t(
+              'An error occurred while fetching dataset datasource values: %s',
+              errMsg,
+            ),
+          ),
+          user,
+        ),
+        paginate: true,
       },
     ],
     [],
diff --git a/superset-frontend/src/pages/DatasetList/DatasetList.test.tsx b/superset-frontend/src/pages/DatasetList/DatasetList.test.tsx
index 916dd0615b..c316001bb4 100644
--- a/superset-frontend/src/pages/DatasetList/DatasetList.test.tsx
+++ b/superset-frontend/src/pages/DatasetList/DatasetList.test.tsx
@@ -285,56 +285,41 @@ describe('RTL', () => {
 });
 
 describe('Prevent unsafe URLs', () => {
+  const columnCount = 8;
+  const exploreUrlIndex = 1;
+  const getTdIndex = (rowNumber: number): number =>
+    rowNumber * columnCount + exploreUrlIndex;
+
   const mockedProps = {};
   let wrapper: any;
 
   it('Check prevent unsafe is on renders relative links', async () => {
-    const tdColumnsNumber = 9;
     useSelectorMock.mockReturnValue(true);
     wrapper = await mountAndWait(mockedProps);
     const tdElements = wrapper.find(ListView).find('td');
-    expect(
-      tdElements
-        .at(0 * tdColumnsNumber + 1)
-        .find('a')
-        .prop('href'),
-    ).toBe('/https://www.google.com?0');
-    expect(
-      tdElements
-        .at(1 * tdColumnsNumber + 1)
-        .find('a')
-        .prop('href'),
-    ).toBe('/https://www.google.com?1');
-    expect(
-      tdElements
-        .at(2 * tdColumnsNumber + 1)
-        .find('a')
-        .prop('href'),
-    ).toBe('/https://www.google.com?2');
+    expect(tdElements.at(getTdIndex(0)).find('a').prop('href')).toBe(
+      '/https://www.google.com?0',
+    );
+    expect(tdElements.at(getTdIndex(1)).find('a').prop('href')).toBe(
+      '/https://www.google.com?1',
+    );
+    expect(tdElements.at(getTdIndex(2)).find('a').prop('href')).toBe(
+      '/https://www.google.com?2',
+    );
   });
 
   it('Check prevent unsafe is off renders absolute links', async () => {
-    const tdColumnsNumber = 9;
     useSelectorMock.mockReturnValue(false);
     wrapper = await mountAndWait(mockedProps);
     const tdElements = wrapper.find(ListView).find('td');
-    expect(
-      tdElements
-        .at(0 * tdColumnsNumber + 1)
-        .find('a')
-        .prop('href'),
-    ).toBe('https://www.google.com?0');
-    expect(
-      tdElements
-        .at(1 * tdColumnsNumber + 1)
-        .find('a')
-        .prop('href'),
-    ).toBe('https://www.google.com?1');
-    expect(
-      tdElements
-        .at(2 * tdColumnsNumber + 1)
-        .find('a')
-        .prop('href'),
-    ).toBe('https://www.google.com?2');
+    expect(tdElements.at(getTdIndex(0)).find('a').prop('href')).toBe(
+      'https://www.google.com?0',
+    );
+    expect(tdElements.at(getTdIndex(1)).find('a').prop('href')).toBe(
+      'https://www.google.com?1',
+    );
+    expect(tdElements.at(getTdIndex(2)).find('a').prop('href')).toBe(
+      'https://www.google.com?2',
+    );
   });
 });
diff --git a/superset-frontend/src/pages/DatasetList/index.tsx b/superset-frontend/src/pages/DatasetList/index.tsx
index d86d7a7b0f..8a39cb0463 100644
--- a/superset-frontend/src/pages/DatasetList/index.tsx
+++ b/superset-frontend/src/pages/DatasetList/index.tsx
@@ -70,6 +70,8 @@ import {
 } from 'src/features/datasets/constants';
 import DuplicateDatasetModal from 'src/features/datasets/DuplicateDatasetModal';
 import { useSelector } from 'react-redux';
+import { ModifiedInfo } from 'src/components/AuditInfo';
+import { QueryObjectColumns } from 'src/views/CRUD/types';
 
 const extensionsRegistry = getExtensionsRegistry();
 const DatasetDeleteRelatedExtension = extensionsRegistry.get(
@@ -380,26 +382,6 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
         accessor: 'schema',
         size: 'lg',
       },
-      {
-        Cell: ({
-          row: {
-            original: { changed_on_delta_humanized: changedOn },
-          },
-        }: any) => <span className="no-wrap">{changedOn}</span>,
-        Header: t('Modified'),
-        accessor: 'changed_on_delta_humanized',
-        size: 'xl',
-      },
-      {
-        Cell: ({
-          row: {
-            original: { changed_by_name: changedByName },
-          },
-        }: any) => changedByName,
-        Header: t('Modified by'),
-        accessor: 'changed_by.first_name',
-        size: 'xl',
-      },
       {
         accessor: 'database',
         disableSortBy: true,
@@ -416,6 +398,19 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
         disableSortBy: true,
         size: 'lg',
       },
+      {
+        Cell: ({
+          row: {
+            original: {
+              changed_on_delta_humanized: changedOn,
+              changed_by: changedBy,
+            },
+          },
+        }: any) => <ModifiedInfo date={changedOn} user={changedBy} />,
+        Header: t('Last modified'),
+        accessor: 'changed_on_delta_humanized',
+        size: 'xl',
+      },
       {
         accessor: 'sql',
         hidden: true,
@@ -515,6 +510,10 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
         hidden: !canEdit && !canDelete && !canDuplicate,
         disableSortBy: true,
       },
+      {
+        accessor: QueryObjectColumns.changed_by,
+        hidden: true,
+      },
     ],
     [canEdit, canDelete, canExport, openDatasetEditModal, canDuplicate, user],
   );
@@ -522,31 +521,23 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
   const filterTypes: Filters = useMemo(
     () => [
       {
-        Header: t('Search'),
+        Header: t('Name'),
         key: 'search',
         id: 'table_name',
         input: 'search',
         operator: FilterOperator.contains,
       },
       {
-        Header: t('Owner'),
-        key: 'owner',
-        id: 'owners',
+        Header: t('Type'),
+        key: 'sql',
+        id: 'sql',
         input: 'select',
-        operator: FilterOperator.relationManyMany,
+        operator: FilterOperator.datasetIsNullOrEmpty,
         unfilteredLabel: 'All',
-        fetchSelects: createFetchRelated(
-          'dataset',
-          'owners',
-          createErrorHandler(errMsg =>
-            t(
-              'An error occurred while fetching dataset owner values: %s',
-              errMsg,
-            ),
-          ),
-          user,
-        ),
-        paginate: true,
+        selects: [
+          { label: t('Virtual'), value: false },
+          { label: t('Physical'), value: true },
+        ],
       },
       {
         Header: t('Database'),
@@ -581,16 +572,24 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
         paginate: true,
       },
       {
-        Header: t('Type'),
-        key: 'sql',
-        id: 'sql',
+        Header: t('Owner'),
+        key: 'owner',
+        id: 'owners',
         input: 'select',
-        operator: FilterOperator.datasetIsNullOrEmpty,
+        operator: FilterOperator.relationManyMany,
         unfilteredLabel: 'All',
-        selects: [
-          { label: t('Virtual'), value: false },
-          { label: t('Physical'), value: true },
-        ],
+        fetchSelects: createFetchRelated(
+          'dataset',
+          'owners',
+          createErrorHandler(errMsg =>
+            t(
+              'An error occurred while fetching dataset owner values: %s',
+              errMsg,
+            ),
+          ),
+          user,
+        ),
+        paginate: true,
       },
       {
         Header: t('Certified'),
@@ -605,6 +604,26 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
           { label: t('No'), value: false },
         ],
       },
+      {
+        Header: t('Modified by'),
+        key: 'changed_by',
+        id: 'changed_by',
+        input: 'select',
+        operator: FilterOperator.relationOneMany,
+        unfilteredLabel: t('All'),
+        fetchSelects: createFetchRelated(
+          'dataset',
+          'changed_by',
+          createErrorHandler(errMsg =>
+            t(
+              'An error occurred while fetching dataset datasource values: %s',
+              errMsg,
+            ),
+          ),
+          user,
+        ),
+        paginate: true,
+      },
     ],
     [user],
   );
diff --git a/superset-frontend/src/pages/QueryHistoryList/index.tsx b/superset-frontend/src/pages/QueryHistoryList/index.tsx
index 77177188e0..94b646d9e4 100644
--- a/superset-frontend/src/pages/QueryHistoryList/index.tsx
+++ b/superset-frontend/src/pages/QueryHistoryList/index.tsx
@@ -53,6 +53,7 @@ import { QueryObject, QueryObjectColumns } from 'src/views/CRUD/types';
 import Icons from 'src/components/Icons';
 import QueryPreviewModal from 'src/features/queries/QueryPreviewModal';
 import { addSuccessToast } from 'src/components/MessageToasts/actions';
+import getOwnerName from 'src/utils/getOwnerName';
 
 const PAGE_SIZE = 25;
 const SQL_PREVIEW_MAX_LINES = 4;
@@ -311,7 +312,7 @@ function QueryList({ addDangerToast }: QueryListProps) {
           row: {
             original: { user },
           },
-        }: any) => (user ? `${user.first_name} ${user.last_name}` : ''),
+        }: any) => getOwnerName(user),
       },
       {
         accessor: QueryObjectColumns.user,
diff --git a/superset-frontend/src/pages/RowLevelSecurityList/RowLevelSecurityList.test.tsx b/superset-frontend/src/pages/RowLevelSecurityList/RowLevelSecurityList.test.tsx
index a4621ed10e..6721f73add 100644
--- a/superset-frontend/src/pages/RowLevelSecurityList/RowLevelSecurityList.test.tsx
+++ b/superset-frontend/src/pages/RowLevelSecurityList/RowLevelSecurityList.test.tsx
@@ -187,8 +187,8 @@ describe('RuleList RTL', () => {
     const searchFilters = screen.queryAllByTestId('filters-search');
     expect(searchFilters).toHaveLength(2);
 
-    const typeFilter = await screen.findByTestId('filters-select');
-    expect(typeFilter).toBeInTheDocument();
+    const typeFilter = screen.queryAllByTestId('filters-select');
+    expect(typeFilter).toHaveLength(2);
   });
 
   it('renders correct list columns', async () => {
@@ -201,7 +201,7 @@ describe('RuleList RTL', () => {
     const fitlerTypeColumn = await within(table).findByText('Filter Type');
     const groupKeyColumn = await within(table).findByText('Group Key');
     const clauseColumn = await within(table).findByText('Clause');
-    const modifiedColumn = await within(table).findByText('Modified');
+    const modifiedColumn = await within(table).findByText('Last modified');
     const actionsColumn = await within(table).findByText('Actions');
 
     expect(nameColumn).toBeInTheDocument();
diff --git a/superset-frontend/src/pages/RowLevelSecurityList/index.tsx b/superset-frontend/src/pages/RowLevelSecurityList/index.tsx
index 3c1e3b8aae..bef42284d0 100644
--- a/superset-frontend/src/pages/RowLevelSecurityList/index.tsx
+++ b/superset-frontend/src/pages/RowLevelSecurityList/index.tsx
@@ -33,7 +33,9 @@ import rison from 'rison';
 import { useListViewResource } from 'src/views/CRUD/hooks';
 import RowLevelSecurityModal from 'src/features/rls/RowLevelSecurityModal';
 import { RLSObject } from 'src/features/rls/types';
-import { createErrorHandler } from 'src/views/CRUD/utils';
+import { createErrorHandler, createFetchRelated } from 'src/views/CRUD/utils';
+import { ModifiedInfo } from 'src/components/AuditInfo';
+import { QueryObjectColumns } from 'src/views/CRUD/types';
 
 const Actions = styled.div`
   color: ${({ theme }) => theme.colors.grayscale.base};
@@ -43,7 +45,7 @@ interface RLSProps {
   addDangerToast: (msg: string) => void;
   addSuccessToast: (msg: string) => void;
   user: {
-    userId?: string | number;
+    userId: string | number;
     firstName: string;
     lastName: string;
   };
@@ -146,10 +148,13 @@ function RowLevelSecurityList(props: RLSProps) {
       {
         Cell: ({
           row: {
-            original: { changed_on_delta_humanized: changedOn },
+            original: {
+              changed_on_delta_humanized: changedOn,
+              changed_by: changedBy,
+            },
           },
-        }: any) => <span className="no-wrap">{changedOn}</span>,
-        Header: t('Modified'),
+        }: any) => <ModifiedInfo date={changedOn} user={changedBy} />,
+        Header: t('Last modified'),
         accessor: 'changed_on_delta_humanized',
         size: 'xl',
       },
@@ -218,6 +223,10 @@ function RowLevelSecurityList(props: RLSProps) {
         hidden: !canEdit && !canWrite && !canExport,
         disableSortBy: true,
       },
+      {
+        accessor: QueryObjectColumns.changed_by,
+        hidden: true,
+      },
     ],
     [
       user.userId,
@@ -270,6 +279,26 @@ function RowLevelSecurityList(props: RLSProps) {
         input: 'search',
         operator: FilterOperator.startsWith,
       },
+      {
+        Header: t('Modified by'),
+        key: 'changed_by',
+        id: 'changed_by',
+        input: 'select',
+        operator: FilterOperator.relationOneMany,
+        unfilteredLabel: t('All'),
+        fetchSelects: createFetchRelated(
+          'rowlevelsecurity',
+          'changed_by',
+          createErrorHandler(errMsg =>
+            t(
+              'An error occurred while fetching dataset datasource values: %s',
+              errMsg,
+            ),
+          ),
+          user,
+        ),
+        paginate: true,
+      },
     ],
     [user],
   );
diff --git a/superset-frontend/src/pages/SavedQueryList/index.tsx b/superset-frontend/src/pages/SavedQueryList/index.tsx
index 3ee62c2ce6..d48ffef8c9 100644
--- a/superset-frontend/src/pages/SavedQueryList/index.tsx
+++ b/superset-frontend/src/pages/SavedQueryList/index.tsx
@@ -18,20 +18,19 @@
  */
 
 import {
-  isFeatureEnabled,
   FeatureFlag,
+  isFeatureEnabled,
   styled,
   SupersetClient,
   t,
 } from '@superset-ui/core';
-import React, { useState, useMemo, useCallback } from 'react';
+import React, { useCallback, useMemo, useState } from 'react';
 import { Link, useHistory } from 'react-router-dom';
 import rison from 'rison';
-import moment from 'moment';
 import {
-  createFetchRelated,
-  createFetchDistinct,
   createErrorHandler,
+  createFetchDistinct,
+  createFetchRelated,
 } from 'src/views/CRUD/utils';
 import { useSelector } from 'react-redux';
 import Popover from 'src/components/Popover';
@@ -39,11 +38,11 @@ import withToasts from 'src/components/MessageToasts/withToasts';
 import { useListViewResource } from 'src/views/CRUD/hooks';
 import ConfirmStatusChange from 'src/components/ConfirmStatusChange';
 import handleResourceExport from 'src/utils/export';
-import SubMenu, { SubMenuProps, ButtonProps } from 'src/features/home/SubMenu';
+import SubMenu, { ButtonProps, SubMenuProps } from 'src/features/home/SubMenu';
 import ListView, {
-  ListViewProps,
-  Filters,
   FilterOperator,
+  Filters,
+  ListViewProps,
 } from 'src/components/ListView';
 import Loading from 'src/components/Loading';
 import DeleteModal from 'src/components/DeleteModal';
@@ -51,15 +50,14 @@ import ActionsBar, { ActionProps } from 'src/components/ListView/ActionsBar';
 import { TagsList } from 'src/components/Tags';
 import { Tooltip } from 'src/components/Tooltip';
 import { commonMenuData } from 'src/features/home/commonMenuData';
-import { SavedQueryObject } from 'src/views/CRUD/types';
+import { QueryObjectColumns, SavedQueryObject } from 'src/views/CRUD/types';
 import copyTextToClipboard from 'src/utils/copy';
 import Tag from 'src/types/TagType';
 import ImportModelsModal from 'src/components/ImportModal/index';
+import { ModifiedInfo } from 'src/components/AuditInfo';
+import { loadTags } from 'src/components/Tags/utils';
 import Icons from 'src/components/Icons';
-import {
-  BootstrapUser,
-  UserWithPermissionsAndRoles,
-} from 'src/types/bootstrapTypes';
+import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes';
 import SavedQueryPreviewModal from 'src/features/queries/SavedQueryPreviewModal';
 import { findPermission } from 'src/utils/findPermission';
 
@@ -80,7 +78,11 @@ const CONFIRM_OVERWRITE_MESSAGE = t(
 interface SavedQueryListProps {
   addDangerToast: (msg: string) => void;
   addSuccessToast: (msg: string) => void;
-  user: BootstrapUser;
+  user: {
+    userId: string | number;
+    firstName: string;
+    lastName: string;
+  };
 }
 
 const StyledTableLabel = styled.div`
@@ -99,6 +101,7 @@ const StyledPopoverItem = styled.div`
 function SavedQueryList({
   addDangerToast,
   addSuccessToast,
+  user,
 }: SavedQueryListProps) {
   const {
     state: {
@@ -348,41 +351,6 @@ function SavedQueryList({
         size: 'xl',
         disableSortBy: true,
       },
-      {
-        Cell: ({
-          row: {
-            original: { created_on: createdOn },
-          },
-        }: any) => {
-          const date = new Date(createdOn);
-          const utc = new Date(
-            Date.UTC(
-              date.getFullYear(),
-              date.getMonth(),
-              date.getDate(),
-              date.getHours(),
-              date.getMinutes(),
-              date.getSeconds(),
-              date.getMilliseconds(),
-            ),
-          );
-
-          return moment(utc).fromNow();
-        },
-        Header: t('Created on'),
-        accessor: 'created_on',
-        size: 'xl',
-      },
-      {
-        Cell: ({
-          row: {
-            original: { changed_on_delta_humanized: changedOn },
-          },
-        }: any) => changedOn,
-        Header: t('Modified'),
-        accessor: 'changed_on_delta_humanized',
-        size: 'xl',
-      },
       {
         Cell: ({
           row: {
@@ -397,6 +365,19 @@ function SavedQueryList({
         disableSortBy: true,
         hidden: !isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM),
       },
+      {
+        Cell: ({
+          row: {
+            original: {
+              changed_by: changedBy,
+              changed_on_delta_humanized: changedOn,
+            },
+          },
+        }: any) => <ModifiedInfo user={changedBy} date={changedOn} />,
+        Header: t('Last modified'),
+        accessor: 'changed_on_delta_humanized',
+        size: 'xl',
+      },
       {
         Cell: ({ row: { original } }: any) => {
           const handlePreview = () => {
@@ -452,12 +433,23 @@ function SavedQueryList({
         id: 'actions',
         disableSortBy: true,
       },
+      {
+        accessor: QueryObjectColumns.changed_by,
+        hidden: true,
+      },
     ],
     [canDelete, canEdit, canExport, copyQueryLink, handleSavedQueryPreview],
   );
 
   const filters: Filters = useMemo(
     () => [
+      {
+        Header: t('Name'),
+        id: 'label',
+        key: 'search',
+        input: 'search',
+        operator: FilterOperator.allText,
+      },
       {
         Header: t('Database'),
         key: 'database',
@@ -497,28 +489,42 @@ function SavedQueryList({
         ),
         paginate: true,
       },
-
+      ...((isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM) && canReadTag
+        ? [
+            {
+              Header: t('Tag'),
+              id: 'tags',
+              key: 'tags',
+              input: 'select',
+              operator: FilterOperator.savedQueryTags,
+              fetchSelects: loadTags,
+            },
+          ]
+        : []) as Filters),
       {
-        Header: t('Search'),
-        id: 'label',
-        key: 'search',
-        input: 'search',
-        operator: FilterOperator.allText,
+        Header: t('Modified by'),
+        key: 'changed_by',
+        id: 'changed_by',
+        input: 'select',
+        operator: FilterOperator.relationOneMany,
+        unfilteredLabel: t('All'),
+        fetchSelects: createFetchRelated(
+          'saved_query',
+          'changed_by',
+          createErrorHandler(errMsg =>
+            t(
+              'An error occurred while fetching dataset datasource values: %s',
+              errMsg,
+            ),
+          ),
+          user,
+        ),
+        paginate: true,
       },
     ],
     [addDangerToast],
   );
 
-  if (isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM) && canReadTag) {
-    filters.push({
-      Header: t('Tags'),
-      id: 'tags',
-      key: 'tags',
-      input: 'search',
-      operator: FilterOperator.savedQueryTags,
-    });
-  }
-
   return (
     <>
       <SubMenu {...menuData} />
diff --git a/superset-frontend/src/pages/Tags/index.tsx b/superset-frontend/src/pages/Tags/index.tsx
index a66d7c7b61..d395ce7cde 100644
--- a/superset-frontend/src/pages/Tags/index.tsx
+++ b/superset-frontend/src/pages/Tags/index.tsx
@@ -19,9 +19,9 @@
 import React, { useMemo, useState } from 'react';
 import { isFeatureEnabled, FeatureFlag, t } from '@superset-ui/core';
 import {
-  createFetchRelated,
-  createErrorHandler,
   Actions,
+  createErrorHandler,
+  createFetchRelated,
 } from 'src/views/CRUD/utils';
 import { useListViewResource, useFavoriteStatus } from 'src/views/CRUD/hooks';
 import ConfirmStatusChange from 'src/components/ConfirmStatusChange';
@@ -35,13 +35,13 @@ import { dangerouslyGetItemDoNotUse } from 'src/utils/localStorageHelpers';
 import withToasts from 'src/components/MessageToasts/withToasts';
 import Icons from 'src/components/Icons';
 import { Tooltip } from 'src/components/Tooltip';
-import FacePile from 'src/components/FacePile';
 import { Link } from 'react-router-dom';
 import { deleteTags } from 'src/features/tags/tags';
 import { Tag as AntdTag } from 'antd';
-import { Tag } from 'src/views/CRUD/types';
+import { QueryObjectColumns, Tag } from 'src/views/CRUD/types';
 import TagModal from 'src/features/tags/TagModal';
 import FaveStar from 'src/components/FaveStar';
+import { ModifiedInfo } from 'src/components/AuditInfo';
 
 const PAGE_SIZE = 25;
 
@@ -56,11 +56,8 @@ interface TagListProps {
 }
 
 function TagList(props: TagListProps) {
-  const {
-    addDangerToast,
-    addSuccessToast,
-    user: { userId },
-  } = props;
+  const { addDangerToast, addSuccessToast, user } = props;
+  const { userId } = user;
 
   const {
     state: {
@@ -162,24 +159,16 @@ function TagList(props: TagListProps) {
       {
         Cell: ({
           row: {
-            original: { changed_on_delta_humanized: changedOn },
+            original: {
+              changed_on_delta_humanized: changedOn,
+              changed_by: changedBy,
+            },
           },
-        }: any) => <span className="no-wrap">{changedOn}</span>,
-        Header: t('Modified'),
+        }: any) => <ModifiedInfo date={changedOn} user={changedBy} />,
+        Header: t('Last modified'),
         accessor: 'changed_on_delta_humanized',
         size: 'xl',
       },
-      {
-        Cell: ({
-          row: {
-            original: { created_by: createdBy },
-          },
-        }: any) => (createdBy ? <FacePile users={[createdBy]} /> : ''),
-        Header: t('Created by'),
-        accessor: 'created_by',
-        disableSortBy: true,
-        size: 'xl',
-      },
       {
         Cell: ({ row: { original } }: any) => {
           const handleEdit = () => handleTagEdit(original);
@@ -238,6 +227,10 @@ function TagList(props: TagListProps) {
         hidden: !canDelete,
         disableSortBy: true,
       },
+      {
+        accessor: QueryObjectColumns.changed_by,
+        hidden: true,
+      },
     ],
     [userId, canDelete, refreshData, addSuccessToast, addDangerToast],
   );
@@ -245,32 +238,31 @@ function TagList(props: TagListProps) {
   const filters: Filters = useMemo(() => {
     const filters_list = [
       {
-        Header: t('Created by'),
-        id: 'created_by',
+        Header: t('Name'),
+        id: 'name',
+        input: 'search',
+        operator: FilterOperator.contains,
+      },
+      {
+        Header: t('Modified by'),
+        key: 'changed_by',
+        id: 'changed_by',
         input: 'select',
         operator: FilterOperator.relationOneMany,
         unfilteredLabel: t('All'),
         fetchSelects: createFetchRelated(
           'tag',
-          'created_by',
+          'changed_by',
           createErrorHandler(errMsg =>
-            addDangerToast(
-              t(
-                'An error occurred while fetching tag created by values: %s',
-                errMsg,
-              ),
+            t(
+              'An error occurred while fetching dataset datasource values: %s',
+              errMsg,
             ),
           ),
-          props.user,
+          user,
         ),
         paginate: true,
       },
-      {
-        Header: t('Search'),
-        id: 'name',
-        input: 'search',
-        operator: FilterOperator.contains,
-      },
     ] as Filters;
     return filters_list;
   }, [addDangerToast, props.user]);
diff --git a/superset-frontend/src/features/cssTemplates/types.ts b/superset-frontend/src/utils/getOwnerName.test.ts
similarity index 73%
copy from superset-frontend/src/features/cssTemplates/types.ts
copy to superset-frontend/src/utils/getOwnerName.test.ts
index 1bb5b2e659..a4a25e57b2 100644
--- a/superset-frontend/src/features/cssTemplates/types.ts
+++ b/superset-frontend/src/utils/getOwnerName.test.ts
@@ -16,17 +16,14 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-type CreatedByUser = {
-  id: number;
-  first_name: string;
-  last_name: string;
-};
+import getOwnerName from './getOwnerName';
 
-export type TemplateObject = {
-  id?: number;
-  changed_on_delta_humanized?: string;
-  created_on?: string;
-  created_by?: CreatedByUser;
-  css?: string;
-  template_name: string;
-};
+test('render owner name correctly', () => {
+  expect(getOwnerName({ id: 1, first_name: 'Foo', last_name: 'Bar' })).toEqual(
+    'Foo Bar',
+  );
+});
+
+test('return empty string for undefined owner', () => {
+  expect(getOwnerName(undefined)).toEqual('');
+});
diff --git a/superset-frontend/src/features/cssTemplates/types.ts b/superset-frontend/src/utils/getOwnerName.ts
similarity index 75%
copy from superset-frontend/src/features/cssTemplates/types.ts
copy to superset-frontend/src/utils/getOwnerName.ts
index 1bb5b2e659..2534c45f2c 100644
--- a/superset-frontend/src/features/cssTemplates/types.ts
+++ b/superset-frontend/src/utils/getOwnerName.ts
@@ -16,17 +16,11 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-type CreatedByUser = {
-  id: number;
-  first_name: string;
-  last_name: string;
-};
+import Owner from 'src/types/Owner';
 
-export type TemplateObject = {
-  id?: number;
-  changed_on_delta_humanized?: string;
-  created_on?: string;
-  created_by?: CreatedByUser;
-  css?: string;
-  template_name: string;
-};
+export default function getOwnerName(owner?: Owner): string {
+  if (!owner) {
+    return '';
+  }
+  return `${owner.first_name} ${owner.last_name}`;
+}
diff --git a/superset-frontend/src/views/CRUD/types.ts b/superset-frontend/src/views/CRUD/types.ts
index 5a53b57696..2fff111b47 100644
--- a/superset-frontend/src/views/CRUD/types.ts
+++ b/superset-frontend/src/views/CRUD/types.ts
@@ -112,6 +112,7 @@ export interface QueryObject {
 export enum QueryObjectColumns {
   id = 'id',
   changed_on = 'changed_on',
+  changed_by = 'changed_by',
   database = 'database',
   database_name = 'database.database_name',
   schema = 'schema',
@@ -138,17 +139,11 @@ export type ImportResourceName =
 
 export interface Tag {
   changed_on_delta_humanized: string;
-  changed_by: {
-    first_name: string;
-    last_name: string;
-  };
+  changed_by: Owner;
   created_on_delta_humanized: string;
   name: string;
   id: number;
-  created_by: {
-    first_name: string;
-    last_name: string;
-  };
+  created_by: Owner;
   description: string;
   type: string;
 }
diff --git a/superset/annotation_layers/api.py b/superset/annotation_layers/api.py
index 886c151a68..5606e944ef 100644
--- a/superset/annotation_layers/api.py
+++ b/superset/annotation_layers/api.py
@@ -99,7 +99,7 @@ class AnnotationLayerRestApi(BaseSupersetModelRestApi):
     ]
 
     search_filters = {"name": [AnnotationLayerAllTextFilter]}
-    allowed_rel_fields = {"created_by"}
+    allowed_rel_fields = {"created_by", "changed_by"}
 
     apispec_parameter_schemas = {
         "get_delete_ids_schema": get_delete_ids_schema,
diff --git a/superset/charts/api.py b/superset/charts/api.py
index ea705f0aa9..191f09c66e 100644
--- a/superset/charts/api.py
+++ b/superset/charts/api.py
@@ -273,7 +273,7 @@ class ChartRestApi(BaseSupersetModelRestApi):
         "created_by": RelatedFieldFilter("first_name", FilterRelatedOwners),
     }
 
-    allowed_rel_fields = {"owners", "created_by"}
+    allowed_rel_fields = {"owners", "created_by", "changed_by"}
 
     @expose("/", methods=("POST",))
     @protect()
diff --git a/superset/css_templates/api.py b/superset/css_templates/api.py
index 25f4d50f30..ac222da66f 100644
--- a/superset/css_templates/api.py
+++ b/superset/css_templates/api.py
@@ -54,6 +54,10 @@ class CssTemplateRestApi(BaseSupersetModelRestApi):
     allow_browser_login = True
 
     show_columns = [
+        "changed_on_delta_humanized",
+        "changed_by.first_name",
+        "changed_by.id",
+        "changed_by.last_name",
         "created_by.first_name",
         "created_by.id",
         "created_by.last_name",
@@ -79,7 +83,7 @@ class CssTemplateRestApi(BaseSupersetModelRestApi):
     order_columns = ["template_name"]
 
     search_filters = {"template_name": [CssTemplateAllTextFilter]}
-    allowed_rel_fields = {"created_by"}
+    allowed_rel_fields = {"created_by", "changed_by"}
 
     apispec_parameter_schemas = {
         "get_delete_ids_schema": get_delete_ids_schema,
diff --git a/superset/dashboards/api.py b/superset/dashboards/api.py
index be773b83c3..cf75a644fb 100644
--- a/superset/dashboards/api.py
+++ b/superset/dashboards/api.py
@@ -261,7 +261,7 @@ class DashboardRestApi(BaseSupersetModelRestApi):
         "roles": RelatedFieldFilter("name", FilterRelatedRoles),
         "created_by": RelatedFieldFilter("first_name", FilterRelatedOwners),
     }
-    allowed_rel_fields = {"owners", "roles", "created_by"}
+    allowed_rel_fields = {"owners", "roles", "created_by", "changed_by"}
 
     openapi_spec_tag = "Dashboards"
     """ Override the name set for this collection of endpoints """
diff --git a/superset/databases/api.py b/superset/databases/api.py
index df69d9ccd7..8de84a16af 100644
--- a/superset/databases/api.py
+++ b/superset/databases/api.py
@@ -111,6 +111,7 @@ class DatabaseRestApi(BaseSupersetModelRestApi):
     include_route_methods = RouteMethod.REST_MODEL_VIEW_CRUD_SET | {
         RouteMethod.EXPORT,
         RouteMethod.IMPORT,
+        RouteMethod.RELATED,
         "tables",
         "table_metadata",
         "table_extra_metadata",
@@ -162,6 +163,8 @@ class DatabaseRestApi(BaseSupersetModelRestApi):
         "backend",
         "changed_on",
         "changed_on_delta_humanized",
+        "changed_by.first_name",
+        "changed_by.last_name",
         "created_by.first_name",
         "created_by.last_name",
         "database_name",
@@ -194,7 +197,17 @@ class DatabaseRestApi(BaseSupersetModelRestApi):
 
     edit_columns = add_columns
 
+    search_columns = [
+        "allow_file_upload",
+        "allow_dml",
+        "allow_run_async",
+        "created_by",
+        "changed_by",
+        "database_name",
+        "expose_in_sqllab",
+    ]
     search_filters = {"allow_file_upload": [DatabaseUploadEnabledFilter]}
+    allowed_rel_fields = {"changed_by", "created_by"}
 
     list_select_columns = list_columns + ["extra", "sqlalchemy_uri", "password"]
     order_columns = [
diff --git a/superset/datasets/api.py b/superset/datasets/api.py
index e256ff99d6..bc4a42e58e 100644
--- a/superset/datasets/api.py
+++ b/superset/datasets/api.py
@@ -247,8 +247,17 @@ class DatasetRestApi(BaseSupersetModelRestApi):
         "sql": [DatasetIsNullOrEmptyFilter],
         "id": [DatasetCertifiedFilter],
     }
-    search_columns = ["id", "database", "owners", "schema", "sql", "table_name"]
-    allowed_rel_fields = {"database", "owners"}
+    search_columns = [
+        "id",
+        "database",
+        "owners",
+        "schema",
+        "sql",
+        "table_name",
+        "created_by",
+        "changed_by",
+    ]
+    allowed_rel_fields = {"database", "owners", "created_by", "changed_by"}
     allowed_distinct_fields = {"schema"}
 
     apispec_parameter_schemas = {
diff --git a/superset/queries/saved_queries/api.py b/superset/queries/saved_queries/api.py
index 25ac520e45..ce283dd6d6 100644
--- a/superset/queries/saved_queries/api.py
+++ b/superset/queries/saved_queries/api.py
@@ -82,7 +82,11 @@ class SavedQueryRestApi(BaseSupersetModelRestApi):
     base_filters = [["id", SavedQueryFilter, lambda: []]]
 
     show_columns = [
+        "changed_on",
         "changed_on_delta_humanized",
+        "changed_by.first_name",
+        "changed_by.id",
+        "changed_by.last_name",
         "created_by.first_name",
         "created_by.id",
         "created_by.last_name",
@@ -97,7 +101,11 @@ class SavedQueryRestApi(BaseSupersetModelRestApi):
         "template_parameters",
     ]
     list_columns = [
+        "changed_on",
         "changed_on_delta_humanized",
+        "changed_by.first_name",
+        "changed_by.id",
+        "changed_by.last_name",
         "created_on",
         "created_by.first_name",
         "created_by.id",
@@ -140,7 +148,7 @@ class SavedQueryRestApi(BaseSupersetModelRestApi):
         "last_run_delta_humanized",
     ]
 
-    search_columns = ["id", "database", "label", "schema", "created_by"]
+    search_columns = ["id", "database", "label", "schema", "created_by", "changed_by"]
     if is_feature_enabled("TAGGING_SYSTEM"):
         search_columns += ["tags"]
     search_filters = {
@@ -161,7 +169,7 @@ class SavedQueryRestApi(BaseSupersetModelRestApi):
         "database": "database_name",
     }
     base_related_field_filters = {"database": [["id", DatabaseFilter, lambda: []]]}
-    allowed_rel_fields = {"database"}
+    allowed_rel_fields = {"database", "changed_by", "created_by"}
     allowed_distinct_fields = {"schema"}
 
     def pre_add(self, item: SavedQuery) -> None:
diff --git a/superset/reports/api.py b/superset/reports/api.py
index ab4f80ae15..8238213fef 100644
--- a/superset/reports/api.py
+++ b/superset/reports/api.py
@@ -198,6 +198,7 @@ class ReportScheduleRestApi(BaseSupersetModelRestApi):
     search_columns = [
         "name",
         "active",
+        "changed_by",
         "created_by",
         "owners",
         "type",
@@ -207,7 +208,14 @@ class ReportScheduleRestApi(BaseSupersetModelRestApi):
         "chart_id",
     ]
     search_filters = {"name": [ReportScheduleAllTextFilter]}
-    allowed_rel_fields = {"owners", "chart", "dashboard", "database", "created_by"}
+    allowed_rel_fields = {
+        "owners",
+        "chart",
+        "dashboard",
+        "database",
+        "created_by",
+        "changed_by",
+    }
 
     base_related_field_filters = {
         "chart": [["id", ChartFilter, lambda: []]],
diff --git a/superset/row_level_security/api.py b/superset/row_level_security/api.py
index e7347f5280..fc505e724f 100644
--- a/superset/row_level_security/api.py
+++ b/superset/row_level_security/api.py
@@ -77,6 +77,9 @@ class RLSRestApi(BaseSupersetModelRestApi):
         "roles.name",
         "clause",
         "changed_on_delta_humanized",
+        "changed_by.first_name",
+        "changed_by.last_name",
+        "changed_by.id",
         "group_key",
     ]
     order_columns = [
@@ -115,6 +118,8 @@ class RLSRestApi(BaseSupersetModelRestApi):
         "roles",
         "group_key",
         "clause",
+        "created_by",
+        "changed_by",
     )
     edit_columns = add_columns
 
@@ -123,7 +128,7 @@ class RLSRestApi(BaseSupersetModelRestApi):
     add_model_schema = RLSPostSchema()
     edit_model_schema = RLSPutSchema()
 
-    allowed_rel_fields = {"tables", "roles"}
+    allowed_rel_fields = {"tables", "roles", "created_by", "changed_by"}
     base_related_field_filters = {
         "tables": [["id", DatasourceFilter, lambda: []]],
         "roles": [["id", BaseFilterRelatedRoles, lambda: []]],
diff --git a/superset/row_level_security/schemas.py b/superset/row_level_security/schemas.py
index 6c8249b875..f02767ec13 100644
--- a/superset/row_level_security/schemas.py
+++ b/superset/row_level_security/schemas.py
@@ -20,6 +20,7 @@ from marshmallow import fields, Schema
 from marshmallow.validate import Length, OneOf
 
 from superset.connectors.sqla.models import RowLevelSecurityFilter
+from superset.dashboards.schemas import UserSchema
 from superset.utils.core import RowLevelSecurityFilterType
 
 id_description = "Unique if of rls filter"
@@ -81,6 +82,7 @@ class RLSListSchema(Schema):
     )
     group_key = fields.String(metadata={"description": "group_key_description"})
     description = fields.String(metadata={"description": "description_description"})
+    changed_by = fields.Nested(UserSchema(exclude=["username"]))
 
 
 class RLSShowSchema(Schema):
diff --git a/superset/tags/api.py b/superset/tags/api.py
index a3c95a5814..c0df921e3e 100644
--- a/superset/tags/api.py
+++ b/superset/tags/api.py
@@ -117,7 +117,7 @@ class TagRestApi(BaseSupersetModelRestApi):
     related_field_filters = {
         "created_by": RelatedFieldFilter("first_name", FilterRelatedOwners),
     }
-    allowed_rel_fields = {"created_by"}
+    allowed_rel_fields = {"created_by", "changed_by"}
 
     add_model_schema = TagPostSchema()
     edit_model_schema = TagPutSchema()
diff --git a/tests/integration_tests/css_templates/api_tests.py b/tests/integration_tests/css_templates/api_tests.py
index b28cca955c..ceb46f553b 100644
--- a/tests/integration_tests/css_templates/api_tests.py
+++ b/tests/integration_tests/css_templates/api_tests.py
@@ -19,6 +19,8 @@
 import json
 import pytest
 import prison
+from datetime import datetime
+from freezegun import freeze_time
 from sqlalchemy.sql import func
 
 import tests.integration_tests.test_app
@@ -189,20 +191,27 @@ class TestCssTemplateApi(SupersetTestCase):
         """
         CSS Template API: Test get CSS Template
         """
-        css_template = (
-            db.session.query(CssTemplate)
-            .filter(CssTemplate.template_name == "template_name1")
-            .one_or_none()
-        )
-        self.login(username="admin")
-        uri = f"api/v1/css_template/{css_template.id}"
-        rv = self.get_assert_metric(uri, "get")
+        with freeze_time(datetime.now()):
+            css_template = (
+                db.session.query(CssTemplate)
+                .filter(CssTemplate.template_name == "template_name1")
+                .one_or_none()
+            )
+            self.login(username="admin")
+            uri = f"api/v1/css_template/{css_template.id}"
+            rv = self.get_assert_metric(uri, "get")
         assert rv.status_code == 200
 
         expected_result = {
             "id": css_template.id,
             "template_name": "template_name1",
             "css": "css1",
+            "changed_by": {
+                "first_name": css_template.created_by.first_name,
+                "id": css_template.created_by.id,
+                "last_name": css_template.created_by.last_name,
+            },
+            "changed_on_delta_humanized": "now",
             "created_by": {
                 "first_name": css_template.created_by.first_name,
                 "id": css_template.created_by.id,
diff --git a/tests/integration_tests/databases/api_tests.py b/tests/integration_tests/databases/api_tests.py
index 496012390e..0bc1f245a1 100644
--- a/tests/integration_tests/databases/api_tests.py
+++ b/tests/integration_tests/databases/api_tests.py
@@ -197,6 +197,7 @@ class TestDatabaseApi(SupersetTestCase):
             "allows_subquery",
             "allows_virtual_table_explore",
             "backend",
+            "changed_by",
             "changed_on",
             "changed_on_delta_humanized",
             "created_by",
diff --git a/tests/integration_tests/queries/saved_queries/api_tests.py b/tests/integration_tests/queries/saved_queries/api_tests.py
index 09929e4d23..c51c0dcbf0 100644
--- a/tests/integration_tests/queries/saved_queries/api_tests.py
+++ b/tests/integration_tests/queries/saved_queries/api_tests.py
@@ -17,6 +17,7 @@
 # isort:skip_file
 """Unit tests for Superset"""
 import json
+from datetime import datetime
 from io import BytesIO
 from typing import Optional
 from zipfile import is_zipfile, ZipFile
@@ -24,6 +25,7 @@ from zipfile import is_zipfile, ZipFile
 import yaml
 import pytest
 import prison
+from freezegun import freeze_time
 from sqlalchemy.sql import func, and_
 
 import tests.integration_tests.test_app
@@ -507,14 +509,17 @@ class TestSavedQueryApi(SupersetTestCase):
             db.session.query(SavedQuery).filter(SavedQuery.label == "label1").all()[0]
         )
         self.login(username="admin")
-        uri = f"api/v1/saved_query/{saved_query.id}"
-        rv = self.get_assert_metric(uri, "get")
-        assert rv.status_code == 200
+        with freeze_time(datetime.now()):
+            uri = f"api/v1/saved_query/{saved_query.id}"
+            rv = self.get_assert_metric(uri, "get")
+            assert rv.status_code == 200
 
         expected_result = {
             "id": saved_query.id,
             "database": {"id": saved_query.database.id, "database_name": "examples"},
             "description": "cool description",
+            "changed_by": None,
+            "changed_on_delta_humanized": "now",
             "created_by": {
                 "first_name": saved_query.created_by.first_name,
                 "id": saved_query.created_by.id,
@@ -527,9 +532,8 @@ class TestSavedQueryApi(SupersetTestCase):
             "template_parameters": None,
         }
         data = json.loads(rv.data.decode("utf-8"))
-        self.assertIn("changed_on_delta_humanized", data["result"])
         for key, value in data["result"].items():
-            if key not in ("changed_on_delta_humanized",):
+            if key != "changed_on":
                 assert value == expected_result[key]
 
     def test_get_saved_query_not_found(self):


(superset) 02/15: chore(deps): bump pillow deps (#25931)

Posted by mi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

michaelsmolina pushed a commit to branch 3.1
in repository https://gitbox.apache.org/repos/asf/superset.git

commit e382d0dd2878cfdbee6dd24b2eb94aab0f12aeda
Author: Gnought <16...@users.noreply.github.com>
AuthorDate: Wed Nov 29 21:59:27 2023 +0800

    chore(deps): bump pillow deps (#25931)
    
    (cherry picked from commit a27a0df1a4933fbe8bced50e80bcb3856cd5db2a)
---
 setup.py                      | 2 +-
 superset/utils/screenshots.py | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/setup.py b/setup.py
index 29df567e04..735154720e 100644
--- a/setup.py
+++ b/setup.py
@@ -201,7 +201,7 @@ setup(
             "thrift>=0.14.1, <1.0.0",
         ],
         "teradata": ["teradatasql>=16.20.0.23"],
-        "thumbnails": ["Pillow>=9.5.0, <10.0.0"],
+        "thumbnails": ["Pillow>=10.0.1, <11"],
         "vertica": ["sqlalchemy-vertica-python>=0.5.9, < 0.6"],
         "netezza": ["nzalchemy>=11.0.2"],
         "starrocks": ["starrocks>=1.0.0"],
diff --git a/superset/utils/screenshots.py b/superset/utils/screenshots.py
index 8609d65038..bf6ed0f9e8 100644
--- a/superset/utils/screenshots.py
+++ b/superset/utils/screenshots.py
@@ -201,7 +201,7 @@ class BaseScreenshot:
             logger.debug("Cropping to: %s*%s", str(img.size[0]), str(desired_width))
             img = img.crop((0, 0, img.size[0], desired_width))
         logger.debug("Resizing to %s", str(thumb_size))
-        img = img.resize(thumb_size, Image.ANTIALIAS)
+        img = img.resize(thumb_size, Image.Resampling.LANCZOS)
         new_img = BytesIO()
         if output != "png":
             img = img.convert("RGB")


(superset) 03/15: fix: remove default secret key from helm (#23916)

Posted by mi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

michaelsmolina pushed a commit to branch 3.1
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 93319696ded850302f3c2093cd6a9408a325c042
Author: Daniel Vaz Gaspar <da...@gmail.com>
AuthorDate: Wed Nov 29 15:48:39 2023 +0000

    fix: remove default secret key from helm (#23916)
    
    (cherry picked from commit 6a5a765689ef2d906784c055fe6007d1799eb33d)
---
 helm/superset/Chart.yaml             | 2 +-
 helm/superset/README.md              | 8 +++++++-
 helm/superset/README.md.gotmpl       | 6 ++++++
 helm/superset/templates/_helpers.tpl | 1 -
 helm/superset/values.yaml            | 2 ++
 5 files changed, 16 insertions(+), 3 deletions(-)

diff --git a/helm/superset/Chart.yaml b/helm/superset/Chart.yaml
index 36d40645df..1f7d974c2b 100644
--- a/helm/superset/Chart.yaml
+++ b/helm/superset/Chart.yaml
@@ -29,7 +29,7 @@ maintainers:
   - name: craig-rueda
     email: craig@craigrueda.com
     url: https://github.com/craig-rueda
-version: 0.10.15
+version: 0.11.0
 dependencies:
   - name: postgresql
     version: 12.1.6
diff --git a/helm/superset/README.md b/helm/superset/README.md
index 1c9bab285e..058ddd615f 100644
--- a/helm/superset/README.md
+++ b/helm/superset/README.md
@@ -23,7 +23,7 @@ NOTE: This file is generated by helm-docs: https://github.com/norwoodj/helm-docs
 
 # superset
 
-![Version: 0.10.15](https://img.shields.io/badge/Version-0.10.15-informational?style=flat-square)
+![Version: 0.11.0](https://img.shields.io/badge/Version-0.11.0-informational?style=flat-square)
 
 Apache Superset is a modern, enterprise-ready business intelligence web application
 
@@ -40,6 +40,12 @@ helm repo add superset http://apache.github.io/superset/
 helm install my-superset superset/superset
 ```
 
+Make sure you set your own `SECRET_KEY` to something unique and secret. This secret key is used by Flask for
+securely signing the session cookie and will be used to encrypt sensitive data on Superset's metadata database.
+It should be a long random bytes or str.
+
+On helm this can be set on `extraSecretEnv.SUPERSET_SECRET_KEY` or `configOverrides.secrets`
+
 ## Requirements
 
 | Repository | Name | Version |
diff --git a/helm/superset/README.md.gotmpl b/helm/superset/README.md.gotmpl
index c17a7e31a7..facb955e31 100644
--- a/helm/superset/README.md.gotmpl
+++ b/helm/superset/README.md.gotmpl
@@ -39,6 +39,12 @@ helm repo add superset http://apache.github.io/superset/
 helm install my-superset superset/superset
 ```
 
+Make sure you set your own `SECRET_KEY` to something unique and secret. This secret key is used by Flask for
+securely signing the session cookie and will be used to encrypt sensitive data on Superset's metadata database.
+It should be a long random bytes or str.
+
+On helm this can be set on `extraSecretEnv.SUPERSET_SECRET_KEY` or `configOverrides.secrets`
+
 {{ template "chart.requirementsSection" . }}
 
 {{ template "chart.valuesSection" . }}
diff --git a/helm/superset/templates/_helpers.tpl b/helm/superset/templates/_helpers.tpl
index 40b769054e..26d68ce603 100644
--- a/helm/superset/templates/_helpers.tpl
+++ b/helm/superset/templates/_helpers.tpl
@@ -82,7 +82,6 @@ DATA_CACHE_CONFIG = CACHE_CONFIG
 
 SQLALCHEMY_DATABASE_URI = f"postgresql+psycopg2://{env('DB_USER')}:{env('DB_PASS')}@{env('DB_HOST')}:{env('DB_PORT')}/{env('DB_NAME')}"
 SQLALCHEMY_TRACK_MODIFICATIONS = True
-SECRET_KEY = env('SECRET_KEY', 'thisISaSECRET_1234')
 
 class CeleryConfig:
   imports  = ("superset.sql_lab", )
diff --git a/helm/superset/values.yaml b/helm/superset/values.yaml
index 67f685bf18..a5b70559d1 100644
--- a/helm/superset/values.yaml
+++ b/helm/superset/values.yaml
@@ -93,6 +93,8 @@ extraSecretEnv: {}
   # # Google API Keys: https://console.cloud.google.com/apis/credentials
   # GOOGLE_KEY: ...
   # GOOGLE_SECRET: ...
+  #   # Generate your own secret key for encryption. Use openssl rand -base64 42 to generate a good key
+  #  SUPERSET_SECRET_KEY: 'CHANGE_ME_TO_A_COMPLEX_RANDOM_SECRET'
 
 # -- Extra files to mount on `/app/pythonpath`
 extraConfigs: {}


(superset) 11/15: fix(sqllab): table preview has gone (#25977)

Posted by mi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

michaelsmolina pushed a commit to branch 3.1
in repository https://gitbox.apache.org/repos/asf/superset.git

commit d0aa34bf7969810b40762266ef75f2d95ce78cd1
Author: JUST.in DO IT <ju...@airbnb.com>
AuthorDate: Fri Dec 1 13:41:31 2023 -0800

    fix(sqllab): table preview has gone (#25977)
    
    (cherry picked from commit cdbbd83705d32e12fbc0a0628e78abb1e98a9404)
---
 .../src/components/DatabaseSelector/DatabaseSelector.test.tsx  |  9 ++++++++-
 superset-frontend/src/components/DatabaseSelector/index.tsx    | 10 +++++++---
 2 files changed, 15 insertions(+), 4 deletions(-)

diff --git a/superset-frontend/src/components/DatabaseSelector/DatabaseSelector.test.tsx b/superset-frontend/src/components/DatabaseSelector/DatabaseSelector.test.tsx
index 7635361d89..874d22ea6b 100644
--- a/superset-frontend/src/components/DatabaseSelector/DatabaseSelector.test.tsx
+++ b/superset-frontend/src/components/DatabaseSelector/DatabaseSelector.test.tsx
@@ -290,7 +290,13 @@ test('Sends the correct db when changing the database', async () => {
 
 test('Sends the correct schema when changing the schema', async () => {
   const props = createProps();
-  render(<DatabaseSelector {...props} />, { useRedux: true, store });
+  const { rerender } = render(<DatabaseSelector {...props} db={null} />, {
+    useRedux: true,
+    store,
+  });
+  await waitFor(() => expect(fetchMock.calls(databaseApiRoute).length).toBe(1));
+  rerender(<DatabaseSelector {...props} />);
+  expect(props.onSchemaChange).toBeCalledTimes(0);
   const select = screen.getByRole('combobox', {
     name: 'Select schema or type to search schemas',
   });
@@ -301,4 +307,5 @@ test('Sends the correct schema when changing the schema', async () => {
   await waitFor(() =>
     expect(props.onSchemaChange).toHaveBeenCalledWith('information_schema'),
   );
+  expect(props.onSchemaChange).toBeCalledTimes(1);
 });
diff --git a/superset-frontend/src/components/DatabaseSelector/index.tsx b/superset-frontend/src/components/DatabaseSelector/index.tsx
index d17489a9c2..7b4afd9af0 100644
--- a/superset-frontend/src/components/DatabaseSelector/index.tsx
+++ b/superset-frontend/src/components/DatabaseSelector/index.tsx
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import React, { ReactNode, useState, useMemo, useEffect } from 'react';
+import React, { ReactNode, useState, useMemo, useEffect, useRef } from 'react';
 import { styled, SupersetClient, t } from '@superset-ui/core';
 import rison from 'rison';
 import { AsyncSelect, Select } from 'src/components';
@@ -133,6 +133,8 @@ export default function DatabaseSelector({
   const [currentSchema, setCurrentSchema] = useState<SchemaOption | undefined>(
     schema ? { label: schema, value: schema, title: schema } : undefined,
   );
+  const schemaRef = useRef(schema);
+  schemaRef.current = schema;
   const { addSuccessToast } = useToasts();
 
   const loadDatabases = useMemo(
@@ -215,7 +217,7 @@ export default function DatabaseSelector({
 
   function changeSchema(schema: SchemaOption | undefined) {
     setCurrentSchema(schema);
-    if (onSchemaChange) {
+    if (onSchemaChange && schema?.value !== schemaRef.current) {
       onSchemaChange(schema?.value);
     }
   }
@@ -229,7 +231,9 @@ export default function DatabaseSelector({
     onSuccess: (schemas, isFetched) => {
       if (schemas.length === 1) {
         changeSchema(schemas[0]);
-      } else if (!schemas.find(schemaOption => schema === schemaOption.value)) {
+      } else if (
+        !schemas.find(schemaOption => schemaRef.current === schemaOption.value)
+      ) {
         changeSchema(undefined);
       }
 


(superset) 05/15: chore: Rename SET_ACTIVE_TABS action, add a new action (#26147)

Posted by mi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

michaelsmolina pushed a commit to branch 3.1
in repository https://gitbox.apache.org/repos/asf/superset.git

commit fad4616d2f8a4a66769f5dbb11ddf93f85f71166
Author: Kamil Gabryjelski <ka...@gmail.com>
AuthorDate: Thu Nov 30 12:27:40 2023 +0100

    chore: Rename SET_ACTIVE_TABS action, add a new action (#26147)
    
    (cherry picked from commit d00c17dde2b80c2deb64ae8f8585cf5c225f3275)
---
 .../src/dashboard/actions/dashboardState.js        |  9 +++++++--
 .../DashboardBuilder/DashboardBuilder.test.tsx     |  6 +++---
 .../dashboard/components/gridComponents/Tabs.jsx   |  8 ++++----
 .../dashboard/containers/DashboardComponent.jsx    |  4 ++--
 .../src/dashboard/reducers/dashboardState.js       |  9 ++++++++-
 .../src/dashboard/reducers/dashboardState.test.ts  | 22 +++++++++++++++++-----
 6 files changed, 41 insertions(+), 17 deletions(-)

diff --git a/superset-frontend/src/dashboard/actions/dashboardState.js b/superset-frontend/src/dashboard/actions/dashboardState.js
index dcf1020e6d..b461275d8c 100644
--- a/superset-frontend/src/dashboard/actions/dashboardState.js
+++ b/superset-frontend/src/dashboard/actions/dashboardState.js
@@ -611,9 +611,14 @@ export function setDirectPathToChild(path) {
   return { type: SET_DIRECT_PATH, path };
 }
 
+export const SET_ACTIVE_TAB = 'SET_ACTIVE_TAB';
+export function setActiveTab(tabId, prevTabId) {
+  return { type: SET_ACTIVE_TAB, tabId, prevTabId };
+}
+
 export const SET_ACTIVE_TABS = 'SET_ACTIVE_TABS';
-export function setActiveTabs(tabId, prevTabId) {
-  return { type: SET_ACTIVE_TABS, tabId, prevTabId };
+export function setActiveTabs(activeTabs) {
+  return { type: SET_ACTIVE_TABS, activeTabs };
 }
 
 export const SET_FOCUSED_FILTER_FIELD = 'SET_FOCUSED_FILTER_FIELD';
diff --git a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.test.tsx b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.test.tsx
index 7c3dd23392..02a3a49971 100644
--- a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.test.tsx
+++ b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.test.tsx
@@ -25,7 +25,7 @@ import DashboardBuilder from 'src/dashboard/components/DashboardBuilder/Dashboar
 import useStoredSidebarWidth from 'src/components/ResizableSidebar/useStoredSidebarWidth';
 import {
   fetchFaveStar,
-  setActiveTabs,
+  setActiveTab,
   setDirectPathToChild,
 } from 'src/dashboard/actions/dashboardState';
 import {
@@ -41,7 +41,7 @@ fetchMock.get('glob:*/csstemplateasyncmodelview/api/read', {});
 jest.mock('src/dashboard/actions/dashboardState', () => ({
   ...jest.requireActual('src/dashboard/actions/dashboardState'),
   fetchFaveStar: jest.fn(),
-  setActiveTabs: jest.fn(),
+  setActiveTab: jest.fn(),
   setDirectPathToChild: jest.fn(),
 }));
 jest.mock('src/components/ResizableSidebar/useStoredSidebarWidth');
@@ -90,7 +90,7 @@ describe('DashboardBuilder', () => {
     favStarStub = (fetchFaveStar as jest.Mock).mockReturnValue({
       type: 'mock-action',
     });
-    activeTabsStub = (setActiveTabs as jest.Mock).mockReturnValue({
+    activeTabsStub = (setActiveTab as jest.Mock).mockReturnValue({
       type: 'mock-action',
     });
     (useStoredSidebarWidth as jest.Mock).mockImplementation(() => [
diff --git a/superset-frontend/src/dashboard/components/gridComponents/Tabs.jsx b/superset-frontend/src/dashboard/components/gridComponents/Tabs.jsx
index 7d9a46b75d..67f4b3c598 100644
--- a/superset-frontend/src/dashboard/components/gridComponents/Tabs.jsx
+++ b/superset-frontend/src/dashboard/components/gridComponents/Tabs.jsx
@@ -51,7 +51,7 @@ const propTypes = {
 
   // actions (from DashboardComponent.jsx)
   logEvent: PropTypes.func.isRequired,
-  setActiveTabs: PropTypes.func,
+  setActiveTab: PropTypes.func,
 
   // grid related
   availableColumnCount: PropTypes.number,
@@ -75,7 +75,7 @@ const defaultProps = {
   columnWidth: 0,
   activeTabs: [],
   directPathToChild: [],
-  setActiveTabs() {},
+  setActiveTab() {},
   onResizeStart() {},
   onResize() {},
   onResizeStop() {},
@@ -125,12 +125,12 @@ export class Tabs extends React.PureComponent {
   }
 
   componentDidMount() {
-    this.props.setActiveTabs(this.state.activeKey);
+    this.props.setActiveTab(this.state.activeKey);
   }
 
   componentDidUpdate(prevProps, prevState) {
     if (prevState.activeKey !== this.state.activeKey) {
-      this.props.setActiveTabs(this.state.activeKey, prevState.activeKey);
+      this.props.setActiveTab(this.state.activeKey, prevState.activeKey);
     }
   }
 
diff --git a/superset-frontend/src/dashboard/containers/DashboardComponent.jsx b/superset-frontend/src/dashboard/containers/DashboardComponent.jsx
index 08b7ed9f82..68478adb07 100644
--- a/superset-frontend/src/dashboard/containers/DashboardComponent.jsx
+++ b/superset-frontend/src/dashboard/containers/DashboardComponent.jsx
@@ -35,7 +35,7 @@ import {
 } from 'src/dashboard/actions/dashboardLayout';
 import {
   setDirectPathToChild,
-  setActiveTabs,
+  setActiveTab,
   setFullSizeChartId,
 } from 'src/dashboard/actions/dashboardState';
 
@@ -109,7 +109,7 @@ function mapDispatchToProps(dispatch) {
       handleComponentDrop,
       setDirectPathToChild,
       setFullSizeChartId,
-      setActiveTabs,
+      setActiveTab,
       logEvent,
     },
     dispatch,
diff --git a/superset-frontend/src/dashboard/reducers/dashboardState.js b/superset-frontend/src/dashboard/reducers/dashboardState.js
index 5d81cd8ac1..015cb9822c 100644
--- a/superset-frontend/src/dashboard/reducers/dashboardState.js
+++ b/superset-frontend/src/dashboard/reducers/dashboardState.js
@@ -37,6 +37,7 @@ import {
   SET_DIRECT_PATH,
   SET_FOCUSED_FILTER_FIELD,
   UNSET_FOCUSED_FILTER_FIELD,
+  SET_ACTIVE_TAB,
   SET_ACTIVE_TABS,
   SET_FULL_SIZE_CHART_ID,
   ON_FILTERS_REFRESH,
@@ -179,7 +180,7 @@ export default function dashboardStateReducer(state = {}, action) {
         directPathLastUpdated: Date.now(),
       };
     },
-    [SET_ACTIVE_TABS]() {
+    [SET_ACTIVE_TAB]() {
       const newActiveTabs = new Set(state.activeTabs);
       newActiveTabs.delete(action.prevTabId);
       newActiveTabs.add(action.tabId);
@@ -188,6 +189,12 @@ export default function dashboardStateReducer(state = {}, action) {
         activeTabs: Array.from(newActiveTabs),
       };
     },
+    [SET_ACTIVE_TABS]() {
+      return {
+        ...state,
+        activeTabs: action.activeTabs,
+      };
+    },
     [SET_OVERRIDE_CONFIRM]() {
       return {
         ...state,
diff --git a/superset-frontend/src/dashboard/reducers/dashboardState.test.ts b/superset-frontend/src/dashboard/reducers/dashboardState.test.ts
index 274b26733c..3a8adc6cbb 100644
--- a/superset-frontend/src/dashboard/reducers/dashboardState.test.ts
+++ b/superset-frontend/src/dashboard/reducers/dashboardState.test.ts
@@ -18,21 +18,33 @@
  */
 
 import dashboardStateReducer from './dashboardState';
-import { setActiveTabs } from '../actions/dashboardState';
+import { setActiveTab, setActiveTabs } from '../actions/dashboardState';
 
 describe('DashboardState reducer', () => {
-  it('SET_ACTIVE_TABS', () => {
+  it('SET_ACTIVE_TAB', () => {
     expect(
-      dashboardStateReducer({ activeTabs: [] }, setActiveTabs('tab1')),
+      dashboardStateReducer({ activeTabs: [] }, setActiveTab('tab1')),
     ).toEqual({ activeTabs: ['tab1'] });
     expect(
-      dashboardStateReducer({ activeTabs: ['tab1'] }, setActiveTabs('tab1')),
+      dashboardStateReducer({ activeTabs: ['tab1'] }, setActiveTab('tab1')),
     ).toEqual({ activeTabs: ['tab1'] });
     expect(
       dashboardStateReducer(
         { activeTabs: ['tab1'] },
-        setActiveTabs('tab2', 'tab1'),
+        setActiveTab('tab2', 'tab1'),
       ),
     ).toEqual({ activeTabs: ['tab2'] });
   });
+
+  it('SET_ACTIVE_TABS', () => {
+    expect(
+      dashboardStateReducer({ activeTabs: [] }, setActiveTabs(['tab1'])),
+    ).toEqual({ activeTabs: ['tab1'] });
+    expect(
+      dashboardStateReducer(
+        { activeTabs: ['tab1', 'tab2'] },
+        setActiveTabs(['tab3', 'tab4']),
+      ),
+    ).toEqual({ activeTabs: ['tab3', 'tab4'] });
+  });
 });


(superset) 12/15: fix(Alerts/Reports): allow use of ";" separator in slack recipient entry (#25894)

Posted by mi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

michaelsmolina pushed a commit to branch 3.1
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 880086c75090c6c1c65617b16991d14767a646be
Author: Ross Mabbett <92...@users.noreply.github.com>
AuthorDate: Fri Dec 1 19:32:08 2023 -0500

    fix(Alerts/Reports): allow use of ";" separator in slack recipient entry (#25894)
    
    Co-authored-by: John Bodley <45...@users.noreply.github.com>
    (cherry picked from commit b7a9c220e14c6e85840568da4bf87be84b246749)
---
 superset/reports/notifications/slack.py            | 11 +++-
 .../reports/notifications/slack_tests.py           | 58 ++++++++++++++++++++++
 2 files changed, 68 insertions(+), 1 deletion(-)

diff --git a/superset/reports/notifications/slack.py b/superset/reports/notifications/slack.py
index a769622b57..fbae398bc5 100644
--- a/superset/reports/notifications/slack.py
+++ b/superset/reports/notifications/slack.py
@@ -44,6 +44,7 @@ from superset.reports.notifications.exceptions import (
     NotificationParamException,
     NotificationUnprocessableException,
 )
+from superset.utils.core import get_email_address_list
 from superset.utils.decorators import statsd_gauge
 
 logger = logging.getLogger(__name__)
@@ -60,7 +61,15 @@ class SlackNotification(BaseNotification):  # pylint: disable=too-few-public-met
     type = ReportRecipientType.SLACK
 
     def _get_channel(self) -> str:
-        return json.loads(self._recipient.recipient_config_json)["target"]
+        """
+        Get the recipient's channel(s).
+        Note Slack SDK uses "channel" to refer to one or more
+        channels. Multiple channels are demarcated by a comma.
+        :returns: The comma separated list of channel(s)
+        """
+        recipient_str = json.loads(self._recipient.recipient_config_json)["target"]
+
+        return ",".join(get_email_address_list(recipient_str))
 
     def _message_template(self, table: str = "") -> str:
         return __(
diff --git a/tests/unit_tests/reports/notifications/slack_tests.py b/tests/unit_tests/reports/notifications/slack_tests.py
new file mode 100644
index 0000000000..0a5e9baa46
--- /dev/null
+++ b/tests/unit_tests/reports/notifications/slack_tests.py
@@ -0,0 +1,58 @@
+# 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 pandas as pd
+
+
+def test_get_channel_with_multi_recipients() -> None:
+    """
+    Test the _get_channel function to ensure it will return a string
+    with recipients separated by commas without interstitial spacing
+    """
+    from superset.reports.models import ReportRecipients, ReportRecipientType
+    from superset.reports.notifications.base import NotificationContent
+    from superset.reports.notifications.slack import SlackNotification
+
+    content = NotificationContent(
+        name="test alert",
+        header_data={
+            "notification_format": "PNG",
+            "notification_type": "Alert",
+            "owners": [1],
+            "notification_source": None,
+            "chart_id": None,
+            "dashboard_id": None,
+        },
+        embedded_data=pd.DataFrame(
+            {
+                "A": [1, 2, 3],
+                "B": [4, 5, 6],
+                "C": ["111", "222", '<a href="http://www.example.com">333</a>'],
+            }
+        ),
+        description='<p>This is <a href="#">a test</a> alert</p><br />',
+    )
+    slack_notification = SlackNotification(
+        recipient=ReportRecipients(
+            type=ReportRecipientType.SLACK,
+            recipient_config_json='{"target": "some_channel; second_channel, third_channel"}',
+        ),
+        content=content,
+    )
+
+    result = slack_notification._get_channel()
+
+    assert result == "some_channel,second_channel,third_channel"


(superset) 13/15: fix: Migration order due to cherry which went astray (#26160)

Posted by mi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

michaelsmolina pushed a commit to branch 3.1
in repository https://gitbox.apache.org/repos/asf/superset.git

commit aaa50c4b4aba30a95154dfb2cc46d6788b3a1ccc
Author: John Bodley <45...@users.noreply.github.com>
AuthorDate: Fri Dec 1 18:29:21 2023 -0800

    fix: Migration order due to cherry which went astray (#26160)
    
    (cherry picked from commit 8644b1a3192ffef3d20357f76cfa1feac20e4147)
---
 superset/migrations/shared/utils.py                |  8 ++--
 ...317970b4400c_added_time_secondary_column_to_.py | 34 +++++++++--------
 ...12-01_12-03_b7851ee5522f_replay_317970b4400c.py | 44 ++++++++++++++++++++++
 3 files changed, 65 insertions(+), 21 deletions(-)

diff --git a/superset/migrations/shared/utils.py b/superset/migrations/shared/utils.py
index 32e7dc1a39..2ae0dfeac1 100644
--- a/superset/migrations/shared/utils.py
+++ b/superset/migrations/shared/utils.py
@@ -43,11 +43,9 @@ def table_has_column(table: str, column: str) -> bool:
     :param column: A column name
     :returns: True iff the column exists in the table
     """
-    config = op.get_context().config
-    engine = engine_from_config(
-        config.get_section(config.config_ini_section), prefix="sqlalchemy."
-    )
-    insp = reflection.Inspector.from_engine(engine)
+
+    insp = inspect(op.get_context().bind)
+
     try:
         return any(col["name"] == column for col in insp.get_columns(table))
     except NoSuchTableError:
diff --git a/superset/migrations/versions/2023-09-06_13-18_317970b4400c_added_time_secondary_column_to_.py b/superset/migrations/versions/2023-09-06_13-18_317970b4400c_added_time_secondary_column_to_.py
index 859a6fe590..4972a86911 100755
--- a/superset/migrations/versions/2023-09-06_13-18_317970b4400c_added_time_secondary_column_to_.py
+++ b/superset/migrations/versions/2023-09-06_13-18_317970b4400c_added_time_secondary_column_to_.py
@@ -32,7 +32,7 @@ from sqlalchemy.ext.declarative import declarative_base
 from sqlalchemy.orm import Session
 
 from superset import db
-from superset.migrations.shared.utils import paginated_update
+from superset.migrations.shared.utils import paginated_update, table_has_column
 
 Base = declarative_base()
 
@@ -45,23 +45,25 @@ class SqlaTable(Base):
 
 
 def upgrade():
-    op.add_column(
-        "tables",
-        sa.Column(
-            "always_filter_main_dttm",
-            sa.Boolean(),
-            nullable=True,
-            default=False,
-            server_default=sa.false(),
-        ),
-    )
+    if not table_has_column("tables", "always_filter_main_dttm"):
+        op.add_column(
+            "tables",
+            sa.Column(
+                "always_filter_main_dttm",
+                sa.Boolean(),
+                nullable=True,
+                default=False,
+                server_default=sa.false(),
+            ),
+        )
 
-    bind = op.get_bind()
-    session = db.Session(bind=bind)
+        bind = op.get_bind()
+        session = db.Session(bind=bind)
 
-    for table in paginated_update(session.query(SqlaTable)):
-        table.always_filter_main_dttm = False
+        for table in paginated_update(session.query(SqlaTable)):
+            table.always_filter_main_dttm = False
 
 
 def downgrade():
-    op.drop_column("tables", "always_filter_main_dttm")
+    if table_has_column("tables", "always_filter_main_dttm"):
+        op.drop_column("tables", "always_filter_main_dttm")
diff --git a/superset/migrations/versions/2023-12-01_12-03_b7851ee5522f_replay_317970b4400c.py b/superset/migrations/versions/2023-12-01_12-03_b7851ee5522f_replay_317970b4400c.py
new file mode 100644
index 0000000000..b4286736f0
--- /dev/null
+++ b/superset/migrations/versions/2023-12-01_12-03_b7851ee5522f_replay_317970b4400c.py
@@ -0,0 +1,44 @@
+# 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.
+"""replay 317970b4400c
+
+Revision ID: b7851ee5522f
+Revises: 4b85906e5b91
+Create Date: 2023-12-01 12:03:27.538945
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = "b7851ee5522f"
+down_revision = "4b85906e5b91"
+
+from importlib import import_module
+
+import sqlalchemy as sa
+from alembic import op
+
+module = import_module(
+    "superset.migrations.versions.2023-09-06_13-18_317970b4400c_added_time_secondary_column_to_"
+)
+
+
+def upgrade():
+    module.upgrade()
+
+
+def downgrade():
+    module.downgrade()


(superset) 14/15: chore: Clean up the examples dashboards (#26158)

Posted by mi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

michaelsmolina pushed a commit to branch 3.1
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 5ec1edc87638d30ad1b13411fd99846984cc5972
Author: Michael S. Molina <70...@users.noreply.github.com>
AuthorDate: Mon Dec 4 16:05:08 2023 -0300

    chore: Clean up the examples dashboards (#26158)
    
    (cherry picked from commit 3ab27c6ec90d4b27e6a7c21783bd3d1f203280a4)
---
 .../cypress/e2e/dashboard/nativeFilters.test.ts    |   6 +-
 .../cypress/e2e/dashboard/tabs.test.ts             |   3 -
 .../cypress-base/cypress/e2e/dashboard/utils.ts    |   1 -
 .../cypress-base/cypress/support/e2e.ts            |   2 +-
 .../examples/configs/charts/Filter_Segments.yaml   |  68 ---
 .../configs/charts/Filtering_Vaccines.yaml         |  53 --
 .../configs/charts/Vehicle_Sales_Filter.yaml       |  47 --
 .../configs/charts/Video_Game_Sales_Filter.yaml    |  55 --
 .../dashboards/COVID_Vaccine_Dashboard.yaml        | 128 ++---
 .../dashboards/FCC_New_Coder_Survey_2018.yaml      | 608 ++++++++++-----------
 .../configs/dashboards/Sales_Dashboard.yaml        | 300 ++++------
 .../configs/dashboards/Video_Game_Sales.yaml       | 389 ++++++-------
 superset/examples/misc_dashboard.py                | 148 ++---
 superset/examples/world_bank.py                    |  39 +-
 tests/integration_tests/charts/api_tests.py        |   6 +-
 .../integration_tests/dashboards/commands_tests.py |  20 +-
 tests/integration_tests/dashboards/dao_tests.py    |  54 --
 tests/integration_tests/databases/api_tests.py     |   2 +-
 tests/integration_tests/utils_tests.py             |  44 --
 19 files changed, 659 insertions(+), 1314 deletions(-)

diff --git a/superset-frontend/cypress-base/cypress/e2e/dashboard/nativeFilters.test.ts b/superset-frontend/cypress-base/cypress/e2e/dashboard/nativeFilters.test.ts
index e8457ba94b..7683d7f878 100644
--- a/superset-frontend/cypress-base/cypress/e2e/dashboard/nativeFilters.test.ts
+++ b/superset-frontend/cypress-base/cypress/e2e/dashboard/nativeFilters.test.ts
@@ -113,7 +113,7 @@ function prepareDashboardFilters(
         },
         type: 'NATIVE_FILTER',
         description: '',
-        chartsInScope: [6],
+        chartsInScope: [5],
         tabsInScope: [],
       });
     });
@@ -150,7 +150,7 @@ function prepareDashboardFilters(
             meta: {
               width: 4,
               height: 50,
-              chartId: 6,
+              chartId: 5,
               sliceName: 'Most Populated Countries',
             },
           },
@@ -414,7 +414,7 @@ describe('Native filters', () => {
       cy.createSampleDashboards([0]);
     });
 
-    it('Verify that default value is respected after revisit', () => {
+    it.only('Verify that default value is respected after revisit', () => {
       prepareDashboardFilters([
         { name: 'country_name', column: 'country_name', datasetId: 2 },
       ]);
diff --git a/superset-frontend/cypress-base/cypress/e2e/dashboard/tabs.test.ts b/superset-frontend/cypress-base/cypress/e2e/dashboard/tabs.test.ts
index 6fc89c1446..ba442e600a 100644
--- a/superset-frontend/cypress-base/cypress/e2e/dashboard/tabs.test.ts
+++ b/superset-frontend/cypress-base/cypress/e2e/dashboard/tabs.test.ts
@@ -25,7 +25,6 @@ import { TABBED_DASHBOARD } from 'cypress/utils/urls';
 import { expandFilterOnLeftPanel } from './utils';
 
 const TREEMAP = { name: 'Treemap', viz: 'treemap_v2' };
-const FILTER_BOX = { name: 'Region Filter', viz: 'filter_box' };
 const LINE_CHART = { name: 'Growth Rate', viz: 'line' };
 const BOX_PLOT = { name: 'Box plot', viz: 'box_plot' };
 const BIG_NUMBER = { name: 'Number of Girls', viz: 'big_number_total' };
@@ -41,7 +40,6 @@ function topLevelTabs() {
 function resetTabs() {
   topLevelTabs();
   cy.get('@top-level-tabs').first().click();
-  waitForChartLoad(FILTER_BOX);
   waitForChartLoad(TREEMAP);
   waitForChartLoad(BIG_NUMBER);
   waitForChartLoad(TABLE);
@@ -96,7 +94,6 @@ describe('Dashboard tabs', () => {
 
   it.skip('should send new queries when tab becomes visible', () => {
     // landing in first tab
-    waitForChartLoad(FILTER_BOX);
     waitForChartLoad(TREEMAP);
 
     getChartAliasBySpec(TREEMAP).then(treemapAlias => {
diff --git a/superset-frontend/cypress-base/cypress/e2e/dashboard/utils.ts b/superset-frontend/cypress-base/cypress/e2e/dashboard/utils.ts
index ca539039cf..c63df51d10 100644
--- a/superset-frontend/cypress-base/cypress/e2e/dashboard/utils.ts
+++ b/superset-frontend/cypress-base/cypress/e2e/dashboard/utils.ts
@@ -23,7 +23,6 @@ import { ChartSpec, waitForChartLoad } from 'cypress/utils';
 export const WORLD_HEALTH_CHARTS = [
   { name: '% Rural', viz: 'world_map' },
   { name: 'Most Populated Countries', viz: 'table' },
-  { name: 'Region Filter', viz: 'filter_box' },
   { name: "World's Population", viz: 'big_number' },
   { name: 'Growth Rate', viz: 'line' },
   { name: 'Rural Breakdown', viz: 'sunburst' },
diff --git a/superset-frontend/cypress-base/cypress/support/e2e.ts b/superset-frontend/cypress-base/cypress/support/e2e.ts
index 6642e0120c..cccc7b2005 100644
--- a/superset-frontend/cypress-base/cypress/support/e2e.ts
+++ b/superset-frontend/cypress-base/cypress/support/e2e.ts
@@ -18,7 +18,7 @@
  */
 import '@cypress/code-coverage/support';
 import '@applitools/eyes-cypress/commands';
-import failOnConsoleError, { Config } from 'cypress-fail-on-console-error';
+import failOnConsoleError from 'cypress-fail-on-console-error';
 
 require('cy-verify-downloads').addCustomCommand();
 
diff --git a/superset/examples/configs/charts/Filter_Segments.yaml b/superset/examples/configs/charts/Filter_Segments.yaml
deleted file mode 100644
index 605e33ca7e..0000000000
--- a/superset/examples/configs/charts/Filter_Segments.yaml
+++ /dev/null
@@ -1,68 +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.
-slice_name: Filter Segments
-viz_type: filter_box
-params:
-  adhoc_filters: []
-  datasource: 42__table
-  date_filter: false
-  filter_configs:
-  - asc: true
-    clearable: true
-    column: ethnic_minority
-    key: -xNBqpfQo
-    label: Ethnic Minority
-    multiple: true
-    searchAllOptions: false
-  - asc: true
-    clearable: true
-    column: gender
-    key: 19VeBGTKf
-    label: Gender
-    multiple: true
-    searchAllOptions: false
-  - asc: true
-    clearable: true
-    column: developer_type
-    key: OWTb4s69T
-    label: Developer Type
-    multiple: true
-    searchAllOptions: false
-  - asc: true
-    clearable: true
-    column: lang_at_home
-    key: Fn-YClyhb
-    label: Language at Home
-    multiple: true
-    searchAllOptions: false
-  - asc: true
-    clearable: true
-    column: country_live
-    key: 2fNskRCLJ
-    label: Country live
-    multiple: true
-    searchAllOptions: false
-  granularity_sqla: time_start
-  queryFields: {}
-  slice_id: 1387
-  time_range: No filter
-  url_params: {}
-  viz_type: filter_box
-cache_timeout: null
-uuid: 6420629a-ce74-2c6b-ef7d-b2e78baa3cfe
-version: 1.0.0
-dataset_uuid: d95a2865-53ce-1f82-a53d-8e3c89331469
diff --git a/superset/examples/configs/charts/Filtering_Vaccines.yaml b/superset/examples/configs/charts/Filtering_Vaccines.yaml
deleted file mode 100644
index e458c5a009..0000000000
--- a/superset/examples/configs/charts/Filtering_Vaccines.yaml
+++ /dev/null
@@ -1,53 +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.
-slice_name: Filtering Vaccines
-viz_type: filter_box
-params:
-  adhoc_filters: []
-  datasource: 69__table
-  date_filter: false
-  filter_configs:
-  - asc: true
-    clearable: true
-    column: country_name
-    key: D00hRxPLE
-    label: Country
-    multiple: true
-    searchAllOptions: false
-  - asc: true
-    clearable: true
-    column: product_category
-    key: jJ7x2cuIc
-    label: Vaccine Approach
-    multiple: true
-    searchAllOptions: false
-  - asc: true
-    clearable: true
-    column: clinical_stage
-    key: EgGwwAUU6
-    label: Clinical Stage
-    multiple: true
-    searchAllOptions: false
-  queryFields: {}
-  slice_id: 3965
-  time_range: No filter
-  url_params: {}
-  viz_type: filter_box
-cache_timeout: null
-uuid: c29381ce-0e99-4cf3-bf0f-5f55d6b94176
-version: 1.0.0
-dataset_uuid: 974b7a1c-22ea-49cb-9214-97b7dbd511e0
diff --git a/superset/examples/configs/charts/Vehicle_Sales_Filter.yaml b/superset/examples/configs/charts/Vehicle_Sales_Filter.yaml
deleted file mode 100644
index 91c8f76bb8..0000000000
--- a/superset/examples/configs/charts/Vehicle_Sales_Filter.yaml
+++ /dev/null
@@ -1,47 +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.
-slice_name: Vehicle Sales Filter
-viz_type: filter_box
-params:
-  adhoc_filters: []
-  datasource: 23__table
-  date_filter: true
-  filter_configs:
-  - asc: true
-    clearable: true
-    column: product_line
-    key: 7oUjq15eQ
-    label: Product Line
-    multiple: true
-    searchAllOptions: false
-  - asc: true
-    clearable: true
-    column: deal_size
-    key: c3hO6Eub8
-    label: Deal Size
-    multiple: true
-    searchAllOptions: false
-  granularity_sqla: order_date
-  queryFields: {}
-  slice_id: 671
-  time_range: '2003-01-01T00:00:00 : 2005-06-01T00:00:00'
-  url_params: {}
-  viz_type: filter_box
-cache_timeout: null
-uuid: a5689df7-98fc-7c51-602c-ebd92dc3ec70
-version: 1.0.0
-dataset_uuid: e8623bb9-5e00-f531-506a-19607f5f8005
diff --git a/superset/examples/configs/charts/Video_Game_Sales_Filter.yaml b/superset/examples/configs/charts/Video_Game_Sales_Filter.yaml
deleted file mode 100644
index 6c76d53e8e..0000000000
--- a/superset/examples/configs/charts/Video_Game_Sales_Filter.yaml
+++ /dev/null
@@ -1,55 +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.
-slice_name: Video Game Sales filter
-viz_type: filter_box
-params:
-  adhoc_filters: []
-  datasource: 21__table
-  date_filter: true
-  filter_configs:
-  - asc: true
-    clearable: true
-    column: platform
-    key: s3ItH9vhG
-    label: Platform
-    multiple: true
-    searchAllOptions: false
-  - asc: true
-    clearable: true
-    column: genre
-    key: 202hDeMsG
-    label: Genre
-    multiple: true
-    searchAllOptions: false
-  - asc: true
-    clearable: true
-    column: publisher
-    key: 5Os6jsJFK
-    label: Publisher
-    multiple: true
-    searchAllOptions: false
-  granularity_sqla: year
-  queryFields: {}
-  time_range: No filter
-  url_params:
-    preselect_filters: '{"1389": {"platform": ["PS", "PS2", "PS3", "PS4"], "genre":
-      null, "__time_range": "No filter"}}'
-  viz_type: filter_box
-cache_timeout: null
-uuid: fd9ce7ec-ae08-4f71-93e0-7c26b132b2e6
-version: 1.0.0
-dataset_uuid: 53d47c0c-c03d-47f0-b9ac-81225f808283
diff --git a/superset/examples/configs/dashboards/COVID_Vaccine_Dashboard.yaml b/superset/examples/configs/dashboards/COVID_Vaccine_Dashboard.yaml
index 363077aebe..1d870880b9 100644
--- a/superset/examples/configs/dashboards/COVID_Vaccine_Dashboard.yaml
+++ b/superset/examples/configs/dashboards/COVID_Vaccine_Dashboard.yaml
@@ -18,6 +18,9 @@ dashboard_title: COVID Vaccine Dashboard
 description: null
 css: ""
 slug: null
+certified_by: ""
+certification_details: ""
+published: true
 uuid: f4065089-110a-41fa-8dd7-9ce98a65e250
 position:
   CHART-63bEuxjDMJ:
@@ -25,32 +28,32 @@ position:
     id: CHART-63bEuxjDMJ
     meta:
       chartId: 3961
-      height: 72
+      height: 60
       sliceName: Vaccine Candidates per Country
       sliceNameOverride: Map of Vaccine Candidates
       uuid: ddc91df6-fb40-4826-bdca-16b85af1c024
-      width: 12
+      width: 8
     parents:
       - ROOT_ID
       - TABS-wUKya7eQ0Z
       - TAB-BCIJF4NvgQ
-      - ROW-zvw7luvEL
+      - ROW-xSeNAspgw
     type: CHART
   CHART-F-fkth0Dnv:
     children: []
     id: CHART-F-fkth0Dnv
     meta:
       chartId: 3960
-      height: 60
+      height: 82
       sliceName: Vaccine Candidates per Country
       sliceNameOverride: Treemap of Vaccine Candidates per Country
       uuid: e2f5a8a7-feb0-4f79-bc6b-01fe55b98b3c
-      width: 8
+      width: 4
     parents:
       - ROOT_ID
       - TABS-wUKya7eQ0Z
       - TAB-BCIJF4NvgQ
-      - ROW-xSeNAspgw
+      - ROW-dieUdkeUw
     type: CHART
   CHART-RjD_ygqtwH:
     children: []
@@ -66,7 +69,7 @@ position:
       - ROOT_ID
       - TABS-wUKya7eQ0Z
       - TAB-BCIJF4NvgQ
-      - ROW-zvw7luvEL
+      - ROW-zhOlQLQnB
     type: CHART
   CHART-aGfmWtliqA:
     children: []
@@ -81,17 +84,17 @@ position:
       - ROOT_ID
       - TABS-wUKya7eQ0Z
       - TAB-BCIJF4NvgQ
-      - ROW-zvw7luvEL
+      - ROW-zhOlQLQnB
     type: CHART
-  CHART-j4hUvP5dDD:
+  CHART-dCUpAcPsji:
     children: []
-    id: CHART-j4hUvP5dDD
+    id: CHART-dCUpAcPsji
     meta:
-      chartId: 3962
+      chartId: 3963
       height: 82
-      sliceName: Vaccine Candidates per Approach & Stage
-      sliceNameOverride: Heatmap of Approaches & Clinical Stages
-      uuid: 0c953c84-0c9a-418d-be9f-2894d2a2cee0
+      sliceName: Vaccine Candidates per Country & Stage
+      sliceNameOverride: Heatmap of Countries & Clinical Stages
+      uuid: cd111331-d286-4258-9020-c7949a109ed2
       width: 4
     parents:
       - ROOT_ID
@@ -99,37 +102,37 @@ position:
       - TAB-BCIJF4NvgQ
       - ROW-dieUdkeUw
     type: CHART
-  CHART-dCUpAcPsji:
+  CHART-fYo7IyvKZQ:
     children: []
-    id: CHART-dCUpAcPsji
+    id: CHART-fYo7IyvKZQ
     meta:
-      chartId: 3963
-      height: 72
+      chartId: 3964
+      height: 60
       sliceName: Vaccine Candidates per Country & Stage
-      sliceNameOverride: Heatmap of Countries & Clinical Stages
-      uuid: cd111331-d286-4258-9020-c7949a109ed2
+      sliceNameOverride: Sunburst of Country & Clinical Stages
+      uuid: f69c556f-15fe-4a82-a8bb-69d5b6954123
       width: 4
     parents:
       - ROOT_ID
       - TABS-wUKya7eQ0Z
       - TAB-BCIJF4NvgQ
-      - ROW-dieUdkeUw
+      - ROW-xSeNAspgw
     type: CHART
-  CHART-eirDduqb1A:
+  CHART-j4hUvP5dDD:
     children: []
-    id: CHART-eirDduqb1A
+    id: CHART-j4hUvP5dDD
     meta:
-      chartId: 3965
-      height: 60
-      sliceName: Filtering Vaccines
-      sliceNameOverride: Filter Box of Vaccines
-      uuid: c29381ce-0e99-4cf3-bf0f-5f55d6b94176
+      chartId: 3962
+      height: 82
+      sliceName: Vaccine Candidates per Approach & Stage
+      sliceNameOverride: Heatmap of Approaches & Clinical Stages
+      uuid: 0c953c84-0c9a-418d-be9f-2894d2a2cee0
       width: 4
     parents:
       - ROOT_ID
       - TABS-wUKya7eQ0Z
       - TAB-BCIJF4NvgQ
-      - ROW-xSeNAspgw
+      - ROW-dieUdkeUw
     type: CHART
   DASHBOARD_VERSION_KEY: v2
   GRID_ID:
@@ -189,46 +192,17 @@ position:
       - TAB-BCIJF4NvgQ
       - ROW-zhOlQLQnB
     type: MARKDOWN
-  CHART-fYo7IyvKZQ:
-    children: []
-    id: CHART-fYo7IyvKZQ
-    meta:
-      chartId: 3964
-      height: 72
-      sliceName: Vaccine Candidates per Country & Stage
-      sliceNameOverride: Sunburst of Country & Clinical Stages
-      uuid: f69c556f-15fe-4a82-a8bb-69d5b6954123
-      width: 4
-    parents:
-      - ROOT_ID
-      - TABS-wUKya7eQ0Z
-      - TAB-BCIJF4NvgQ
-      - ROW-dieUdkeUw
-    type: CHART
   ROOT_ID:
     children:
       - TABS-wUKya7eQ0Z
     id: ROOT_ID
     type: ROOT
-  ROW-zhOlQLQnB:
-    children:
-      - MARKDOWN-VjQQ5SFj5v
-      - CHART-RjD_ygqtwH
-      - CHART-aGfmWtliqA
-    id: ROW-zhOlQLQnB
-    meta:
-      "0": ROOT_ID
-      background: BACKGROUND_TRANSPARENT
-    parents:
-      - ROOT_ID
-      - TABS-wUKya7eQ0Z
-      - TAB-BCIJF4NvgQ
-    type: ROW
-  ROW-xSeNAspgw:
+  ROW-dieUdkeUw:
     children:
-      - CHART-eirDduqb1A
       - CHART-F-fkth0Dnv
-    id: ROW-xSeNAspgw
+      - CHART-dCUpAcPsji
+      - CHART-j4hUvP5dDD
+    id: ROW-dieUdkeUw
     meta:
       "0": ROOT_ID
       background: BACKGROUND_TRANSPARENT
@@ -237,12 +211,11 @@ position:
       - TABS-wUKya7eQ0Z
       - TAB-BCIJF4NvgQ
     type: ROW
-  ROW-dieUdkeUw:
+  ROW-xSeNAspgw:
     children:
-      - CHART-dCUpAcPsji
+      - CHART-63bEuxjDMJ
       - CHART-fYo7IyvKZQ
-      - CHART-j4hUvP5dDD
-    id: ROW-dieUdkeUw
+    id: ROW-xSeNAspgw
     meta:
       "0": ROOT_ID
       background: BACKGROUND_TRANSPARENT
@@ -251,10 +224,12 @@ position:
       - TABS-wUKya7eQ0Z
       - TAB-BCIJF4NvgQ
     type: ROW
-  ROW-zvw7luvEL:
+  ROW-zhOlQLQnB:
     children:
-      - CHART-63bEuxjDMJ
-    id: ROW-zvw7luvEL
+      - MARKDOWN-VjQQ5SFj5v
+      - CHART-RjD_ygqtwH
+      - CHART-aGfmWtliqA
+    id: ROW-zhOlQLQnB
     meta:
       "0": ROOT_ID
       background: BACKGROUND_TRANSPARENT
@@ -267,7 +242,6 @@ position:
     children:
       - ROW-zhOlQLQnB
       - ROW-xSeNAspgw
-      - ROW-zvw7luvEL
       - ROW-dieUdkeUw
     id: TAB-BCIJF4NvgQ
     meta:
@@ -316,18 +290,4 @@ metadata:
     Unknown: "#EFA1AA"
     Live attenuated virus: "#FDE380"
     COUNT(*): "#D1C6BC"
-  filter_scopes:
-    "3965":
-      country_name:
-        scope:
-          - ROOT_ID
-        immune: []
-      product_category:
-        scope:
-          - ROOT_ID
-        immune: []
-      clinical_stage:
-        scope:
-          - ROOT_ID
-        immune: []
 version: 1.0.0
diff --git a/superset/examples/configs/dashboards/FCC_New_Coder_Survey_2018.yaml b/superset/examples/configs/dashboards/FCC_New_Coder_Survey_2018.yaml
index 2e97e6b576..b1508daff0 100644
--- a/superset/examples/configs/dashboards/FCC_New_Coder_Survey_2018.yaml
+++ b/superset/examples/configs/dashboards/FCC_New_Coder_Survey_2018.yaml
@@ -16,8 +16,11 @@
 # under the License.
 dashboard_title: FCC New Coder Survey 2018
 description: null
-css: ''
+css: ""
 slug: null
+certified_by: ""
+certification_details: ""
+published: true
 uuid: 5b12b583-8204-08e9-392c-422209c29787
 position:
   CHART--0GPGmD-pO:
@@ -25,17 +28,17 @@ position:
     id: CHART--0GPGmD-pO
     meta:
       chartId: 1361
-      height: 48
-      sliceName: 'Current Developers: Is this your first development job?'
+      height: 56
+      sliceName: "Current Developers: Is this your first development job?"
       sliceNameOverride: Is this your first development job?
       uuid: bfe5a8e6-146f-ef59-5e6c-13d519b236a8
       width: 2
     parents:
-    - ROOT_ID
-    - GRID_ID
-    - TABS-L-d9eyOE-b
-    - TAB-l_9I0aNYZ
-    - ROW-b7USYEngT
+      - ROOT_ID
+      - GRID_ID
+      - TABS-L-d9eyOE-b
+      - TAB-l_9I0aNYZ
+      - ROW-b7USYEngT
     type: CHART
   CHART--w_Br1tPP3:
     children: []
@@ -47,27 +50,27 @@ position:
       uuid: a6dd2d5a-2cdc-c8ec-f30c-85920f4f8a65
       width: 3
     parents:
-    - ROOT_ID
-    - GRID_ID
-    - TABS-L-d9eyOE-b
-    - TAB-YT6eNksV-
-    - ROW-DR80aHJA2c
+      - ROOT_ID
+      - GRID_ID
+      - TABS-L-d9eyOE-b
+      - TAB-YT6eNksV-
+      - ROW-DR80aHJA2c
     type: CHART
   CHART-0-zzTwBINh:
     children: []
     id: CHART-0-zzTwBINh
     meta:
       chartId: 3631
-      height: 49
+      height: 55
       sliceName: Last Year Income Distribution
       uuid: a2ec5256-94b4-43c4-b8c7-b83f70c5d4df
       width: 3
     parents:
-    - ROOT_ID
-    - GRID_ID
-    - TABS-L-d9eyOE-b
-    - TAB-l_9I0aNYZ
-    - ROW-b7USYEngT
+      - ROOT_ID
+      - GRID_ID
+      - TABS-L-d9eyOE-b
+      - TAB-l_9I0aNYZ
+      - ROW-b7USYEngT
     type: CHART
   CHART-37fu7fO6Z0:
     children: []
@@ -79,11 +82,11 @@ position:
       uuid: 02f546ae-1bf4-bd26-8bc2-14b9279c8a62
       width: 7
     parents:
-    - ROOT_ID
-    - GRID_ID
-    - TABS-L-d9eyOE-b
-    - TAB-l_9I0aNYZ
-    - ROW-kNjtGVFpp
+      - ROOT_ID
+      - GRID_ID
+      - TABS-L-d9eyOE-b
+      - TAB-l_9I0aNYZ
+      - ROW-kNjtGVFpp
     type: CHART
   CHART-5QwNlSbXYU:
     children: []
@@ -95,11 +98,11 @@ position:
       uuid: 097c05c9-2dd2-481d-813d-d6c0c12b4a3d
       width: 5
     parents:
-    - ROOT_ID
-    - GRID_ID
-    - TABS-L-d9eyOE-b
-    - TAB-l_9I0aNYZ
-    - ROW-kNjtGVFpp
+      - ROOT_ID
+      - GRID_ID
+      - TABS-L-d9eyOE-b
+      - TAB-l_9I0aNYZ
+      - ROW-kNjtGVFpp
     type: CHART
   CHART-FKuVqq4kaA:
     children: []
@@ -112,11 +115,11 @@ position:
       uuid: e6b09c28-98cf-785f-4caf-320fd4fca802
       width: 3
     parents:
-    - ROOT_ID
-    - GRID_ID
-    - TABS-L-d9eyOE-b
-    - TAB-YT6eNksV-
-    - ROW-DR80aHJA2c
+      - ROOT_ID
+      - GRID_ID
+      - TABS-L-d9eyOE-b
+      - TAB-YT6eNksV-
+      - ROW-DR80aHJA2c
     type: CHART
   CHART-JnpdZOhVer:
     children: []
@@ -124,16 +127,16 @@ position:
     meta:
       chartId: 1369
       height: 50
-      sliceName: "\U0001F393 Highest degree held"
+      sliceName: Highest degree held
       uuid: 9f7d2b9c-6b3a-69f9-f03e-d3a141514639
       width: 2
     parents:
-    - ROOT_ID
-    - GRID_ID
-    - TABS-L-d9eyOE-b
-    - TAB-YT6eNksV-
-    - ROW--BIzjz9F0
-    - COLUMN-IEKAo_QJlz
+      - ROOT_ID
+      - GRID_ID
+      - TABS-L-d9eyOE-b
+      - TAB-YT6eNksV-
+      - ROW--BIzjz9F0
+      - COLUMN-IEKAo_QJlz
     type: CHART
   CHART-LjfhrUkEef:
     children: []
@@ -145,11 +148,11 @@ position:
       uuid: 067c4a1e-ae03-4c0c-8e2a-d2c0f4bf43c3
       width: 5
     parents:
-    - ROOT_ID
-    - GRID_ID
-    - TABS-L-d9eyOE-b
-    - TAB-l_9I0aNYZ
-    - ROW-s3l4os7YY
+      - ROOT_ID
+      - GRID_ID
+      - TABS-L-d9eyOE-b
+      - TAB-l_9I0aNYZ
+      - ROW-s3l4os7YY
     type: CHART
   CHART-Q3pbwsH3id:
     children: []
@@ -162,27 +165,27 @@ position:
       uuid: def07750-b5c0-0b69-6228-cb2330916166
       width: 3
     parents:
-    - ROOT_ID
-    - GRID_ID
-    - TABS-L-d9eyOE-b
-    - TAB-AsMaxdYL_t
-    - ROW-mOvr_xWm1
+      - ROOT_ID
+      - GRID_ID
+      - TABS-L-d9eyOE-b
+      - TAB-AsMaxdYL_t
+      - ROW-mOvr_xWm1
     type: CHART
   CHART-QVql08s5Bv:
     children: []
     id: CHART-QVql08s5Bv
     meta:
       chartId: 3632
-      height: 50
+      height: 56
       sliceName: First Time Developer?
       uuid: edc75073-8f33-4123-a28d-cd6dfb33cade
       width: 3
     parents:
-    - ROOT_ID
-    - GRID_ID
-    - TABS-L-d9eyOE-b
-    - TAB-l_9I0aNYZ
-    - ROW-b7USYEngT
+      - ROOT_ID
+      - GRID_ID
+      - TABS-L-d9eyOE-b
+      - TAB-l_9I0aNYZ
+      - ROW-b7USYEngT
     type: CHART
   CHART-UtSaz4pfV6:
     children: []
@@ -194,12 +197,12 @@ position:
       uuid: 5f1ea868-604e-f69d-a241-5daa83ff33be
       width: 3
     parents:
-    - ROOT_ID
-    - GRID_ID
-    - TABS-L-d9eyOE-b
-    - TAB-AsMaxdYL_t
-    - ROW-UsW-_RPAb
-    - COLUMN-OJ5spdMmNh
+      - ROOT_ID
+      - GRID_ID
+      - TABS-L-d9eyOE-b
+      - TAB-AsMaxdYL_t
+      - ROW-UsW-_RPAb
+      - COLUMN-OJ5spdMmNh
     type: CHART
   CHART-VvFbGxi3X_:
     children: []
@@ -211,12 +214,12 @@ position:
       uuid: 03a74c97-52fc-cf87-233c-d4275f8c550c
       width: 3
     parents:
-    - ROOT_ID
-    - GRID_ID
-    - TABS-L-d9eyOE-b
-    - TAB-AsMaxdYL_t
-    - ROW-UsW-_RPAb
-    - COLUMN-OJ5spdMmNh
+      - ROOT_ID
+      - GRID_ID
+      - TABS-L-d9eyOE-b
+      - TAB-AsMaxdYL_t
+      - ROW-UsW-_RPAb
+      - COLUMN-OJ5spdMmNh
     type: CHART
   CHART-XHncHuS5pZ:
     children: []
@@ -229,11 +232,11 @@ position:
       uuid: a0e5329f-224e-6fc8-efd2-d37d0f546ee8
       width: 2
     parents:
-    - ROOT_ID
-    - GRID_ID
-    - TABS-L-d9eyOE-b
-    - TAB-YT6eNksV-
-    - ROW-DR80aHJA2c
+      - ROOT_ID
+      - GRID_ID
+      - TABS-L-d9eyOE-b
+      - TAB-YT6eNksV-
+      - ROW-DR80aHJA2c
     type: CHART
   CHART-YSzS5GOOLf:
     children: []
@@ -245,11 +248,11 @@ position:
       uuid: 4880e4f4-b701-4be0-86f3-e7e89432e83b
       width: 3
     parents:
-    - ROOT_ID
-    - GRID_ID
-    - TABS-L-d9eyOE-b
-    - TAB-AsMaxdYL_t
-    - ROW-mOvr_xWm1
+      - ROOT_ID
+      - GRID_ID
+      - TABS-L-d9eyOE-b
+      - TAB-AsMaxdYL_t
+      - ROW-mOvr_xWm1
     type: CHART
   CHART-ZECnzPz8Bi:
     children: []
@@ -261,44 +264,27 @@ position:
       uuid: 5596e0f6-78a9-465d-8325-7139c794a06a
       width: 7
     parents:
-    - ROOT_ID
-    - GRID_ID
-    - TABS-L-d9eyOE-b
-    - TAB-l_9I0aNYZ
-    - ROW-s3l4os7YY
+      - ROOT_ID
+      - GRID_ID
+      - TABS-L-d9eyOE-b
+      - TAB-l_9I0aNYZ
+      - ROW-s3l4os7YY
     type: CHART
   CHART-aytwlT4GAq:
     children: []
     id: CHART-aytwlT4GAq
     meta:
       chartId: 1384
-      height: 50
+      height: 30
       sliceName: Breakdown of Developer Type
       uuid: b8386be8-f44e-6535-378c-2aa2ba461286
-      width: 4
-    parents:
-    - ROOT_ID
-    - GRID_ID
-    - TABS-L-d9eyOE-b
-    - TAB-AsMaxdYL_t
-    - ROW-y-GwJPgxLr
-    type: CHART
-  CHART-d6vjW6rC6V:
-    children: []
-    id: CHART-d6vjW6rC6V
-    meta:
-      chartId: 1387
-      height: 54
-      sliceName: Filter Segments
-      sliceNameOverride: Filter By
-      uuid: 6420629a-ce74-2c6b-ef7d-b2e78baa3cfe
-      width: 5
+      width: 6
     parents:
-    - ROOT_ID
-    - GRID_ID
-    - TABS-L-d9eyOE-b
-    - TAB-AsMaxdYL_t
-    - ROW-y-GwJPgxLr
+      - ROOT_ID
+      - GRID_ID
+      - TABS-L-d9eyOE-b
+      - TAB-AsMaxdYL_t
+      - ROW-y-GwJPgxLr
     type: CHART
   CHART-fLpTSAHpAO:
     children: []
@@ -310,11 +296,11 @@ position:
       uuid: 2ba66056-a756-d6a3-aaec-0c243fb7062e
       width: 9
     parents:
-    - ROOT_ID
-    - GRID_ID
-    - TABS-L-d9eyOE-b
-    - TAB-AsMaxdYL_t
-    - ROW-UsW-_RPAb
+      - ROOT_ID
+      - GRID_ID
+      - TABS-L-d9eyOE-b
+      - TAB-AsMaxdYL_t
+      - ROW-UsW-_RPAb
     type: CHART
   CHART-lQVSAw0Or3:
     children: []
@@ -327,11 +313,11 @@ position:
       uuid: cb8998ab-9f93-4f0f-4e4b-3bfe4b0dea9d
       width: 4
     parents:
-    - ROOT_ID
-    - GRID_ID
-    - TABS-L-d9eyOE-b
-    - TAB-YT6eNksV-
-    - ROW--BIzjz9F0
+      - ROOT_ID
+      - GRID_ID
+      - TABS-L-d9eyOE-b
+      - TAB-YT6eNksV-
+      - ROW--BIzjz9F0
     type: CHART
   CHART-o-JPAWMZK-:
     children: []
@@ -343,11 +329,11 @@ position:
       uuid: 0f6b447c-828c-e71c-87ac-211bc412b214
       width: 3
     parents:
-    - ROOT_ID
-    - GRID_ID
-    - TABS-L-d9eyOE-b
-    - TAB-AsMaxdYL_t
-    - ROW-mOvr_xWm1
+      - ROOT_ID
+      - GRID_ID
+      - TABS-L-d9eyOE-b
+      - TAB-AsMaxdYL_t
+      - ROW-mOvr_xWm1
     type: CHART
   CHART-v22McUFMtx:
     children: []
@@ -360,12 +346,12 @@ position:
       uuid: 6d0ceb30-2008-d19c-d285-cf77dc764433
       width: 4
     parents:
-    - ROOT_ID
-    - GRID_ID
-    - TABS-L-d9eyOE-b
-    - TAB-YT6eNksV-
-    - ROW--BIzjz9F0
-    - COLUMN-IEKAo_QJlz
+      - ROOT_ID
+      - GRID_ID
+      - TABS-L-d9eyOE-b
+      - TAB-YT6eNksV-
+      - ROW--BIzjz9F0
+      - COLUMN-IEKAo_QJlz
     type: CHART
   CHART-wxWVtlajRF:
     children: []
@@ -377,49 +363,49 @@ position:
       uuid: bff88053-ccc4-92f2-d6f5-de83e950e8cd
       width: 4
     parents:
-    - ROOT_ID
-    - GRID_ID
-    - TABS-L-d9eyOE-b
-    - TAB-YT6eNksV-
-    - ROW--BIzjz9F0
+      - ROOT_ID
+      - GRID_ID
+      - TABS-L-d9eyOE-b
+      - TAB-YT6eNksV-
+      - ROW--BIzjz9F0
     type: CHART
   COLUMN-IEKAo_QJlz:
     children:
-    - CHART-JnpdZOhVer
-    - CHART-v22McUFMtx
+      - CHART-JnpdZOhVer
+      - CHART-v22McUFMtx
     id: COLUMN-IEKAo_QJlz
     meta:
       background: BACKGROUND_TRANSPARENT
       width: 4
     parents:
-    - ROOT_ID
-    - GRID_ID
-    - TABS-L-d9eyOE-b
-    - TAB-YT6eNksV-
-    - ROW--BIzjz9F0
+      - ROOT_ID
+      - GRID_ID
+      - TABS-L-d9eyOE-b
+      - TAB-YT6eNksV-
+      - ROW--BIzjz9F0
     type: COLUMN
   COLUMN-OJ5spdMmNh:
     children:
-    - CHART-VvFbGxi3X_
-    - CHART-UtSaz4pfV6
+      - CHART-VvFbGxi3X_
+      - CHART-UtSaz4pfV6
     id: COLUMN-OJ5spdMmNh
     meta:
       background: BACKGROUND_TRANSPARENT
       width: 3
     parents:
-    - ROOT_ID
-    - GRID_ID
-    - TABS-L-d9eyOE-b
-    - TAB-AsMaxdYL_t
-    - ROW-UsW-_RPAb
+      - ROOT_ID
+      - GRID_ID
+      - TABS-L-d9eyOE-b
+      - TAB-AsMaxdYL_t
+      - ROW-UsW-_RPAb
     type: COLUMN
   DASHBOARD_VERSION_KEY: v2
   GRID_ID:
     children:
-    - TABS-L-d9eyOE-b
+      - TABS-L-d9eyOE-b
     id: GRID_ID
     parents:
-    - ROOT_ID
+      - ROOT_ID
     type: GRID
   HEADER_ID:
     id: HEADER_ID
@@ -453,21 +439,21 @@ position:
       height: 50
       width: 4
     parents:
-    - ROOT_ID
-    - GRID_ID
-    - TABS-L-d9eyOE-b
-    - TAB-YT6eNksV-
-    - ROW-DR80aHJA2c
+      - ROOT_ID
+      - GRID_ID
+      - TABS-L-d9eyOE-b
+      - TAB-YT6eNksV-
+      - ROW-DR80aHJA2c
     type: MARKDOWN
   MARKDOWN-NQmSPDOtpl:
     children: []
     id: MARKDOWN-NQmSPDOtpl
     meta:
-      code: '# Current Developers
+      code: "# Current Developers
 
 
-        While majority of the students on FCC are Aspiring developers, there''s a
-        nontrivial minority that''s there to continue leveling up their skills (17%
+        While majority of the students on FCC are Aspiring developers, there's a
+        nontrivial minority that's there to continue leveling up their skills (17%
         of the survey respondents).
 
 
@@ -480,28 +466,28 @@ position:
         - The proportion of developers whose current job is their first developer
         job
 
-        - Distribution of last year''s income
+        - Distribution of last year's income
 
         - The geographic distribution of these developers
 
         - The overlap between commute time and if their current job is their first
         developer job
 
-        - Potential link between highest degree earned and last year''s income'
-      height: 50
+        - Potential link between highest degree earned and last year's income"
+      height: 56
       width: 4
     parents:
-    - ROOT_ID
-    - GRID_ID
-    - TABS-L-d9eyOE-b
-    - TAB-l_9I0aNYZ
-    - ROW-b7USYEngT
+      - ROOT_ID
+      - GRID_ID
+      - TABS-L-d9eyOE-b
+      - TAB-l_9I0aNYZ
+      - ROW-b7USYEngT
     type: MARKDOWN
   MARKDOWN-__u6CsUyfh:
     children: []
     id: MARKDOWN-__u6CsUyfh
     meta:
-      code: '## FreeCodeCamp New Coder Survey 2018
+      code: "## FreeCodeCamp New Coder Survey 2018
 
 
         Every year, FCC surveys its user base (mostly budding software developers)
@@ -513,21 +499,22 @@ position:
 
         - [Dataset](https://github.com/freeCodeCamp/2018-new-coder-survey)
 
-        - [FCC Blog Post](https://www.freecodecamp.org/news/we-asked-20-000-people-who-they-are-and-how-theyre-learning-to-code-fff5d668969/)'
-      height: 45
-      width: 3
-    parents:
-    - ROOT_ID
-    - GRID_ID
-    - TABS-L-d9eyOE-b
-    - TAB-AsMaxdYL_t
-    - ROW-y-GwJPgxLr
+        - [FCC Blog Post](https://www.freecodecamp.org/news/we-asked-20-000-people-who-they-are-and-how-theyre-learning-to-code-fff5d668969/)"
+      height: 30
+      width: 6
+    parents:
+      - ROOT_ID
+      - GRID_ID
+      - TABS-L-d9eyOE-b
+      - TAB-AsMaxdYL_t
+      - ROW-y-GwJPgxLr
     type: MARKDOWN
   MARKDOWN-zc2mWxZeox:
     children: []
     id: MARKDOWN-zc2mWxZeox
     meta:
-      code: "# Demographics\n\nFreeCodeCamp is a completely-online community of people\
+      code:
+        "# Demographics\n\nFreeCodeCamp is a completely-online community of people\
         \ learning to code and consists of aspiring & current developers from all\
         \ over the world. That doesn't necessarily mean that access to these types\
         \ of opportunities are evenly distributed. \n\nThe following charts can begin\
@@ -537,243 +524,220 @@ position:
       height: 52
       width: 3
     parents:
-    - ROOT_ID
-    - GRID_ID
-    - TABS-L-d9eyOE-b
-    - TAB-AsMaxdYL_t
-    - ROW-mOvr_xWm1
+      - ROOT_ID
+      - GRID_ID
+      - TABS-L-d9eyOE-b
+      - TAB-AsMaxdYL_t
+      - ROW-mOvr_xWm1
     type: MARKDOWN
   ROOT_ID:
     children:
-    - GRID_ID
+      - GRID_ID
     id: ROOT_ID
     type: ROOT
   ROW--BIzjz9F0:
     children:
-    - COLUMN-IEKAo_QJlz
-    - CHART-lQVSAw0Or3
-    - CHART-wxWVtlajRF
+      - COLUMN-IEKAo_QJlz
+      - CHART-lQVSAw0Or3
+      - CHART-wxWVtlajRF
     id: ROW--BIzjz9F0
     meta:
       background: BACKGROUND_TRANSPARENT
     parents:
-    - ROOT_ID
-    - GRID_ID
-    - TABS-L-d9eyOE-b
-    - TAB-YT6eNksV-
+      - ROOT_ID
+      - GRID_ID
+      - TABS-L-d9eyOE-b
+      - TAB-YT6eNksV-
     type: ROW
   ROW-DR80aHJA2c:
     children:
-    - MARKDOWN-BUmyHM2s0x
-    - CHART-XHncHuS5pZ
-    - CHART--w_Br1tPP3
-    - CHART-FKuVqq4kaA
+      - MARKDOWN-BUmyHM2s0x
+      - CHART-XHncHuS5pZ
+      - CHART--w_Br1tPP3
+      - CHART-FKuVqq4kaA
     id: ROW-DR80aHJA2c
     meta:
       background: BACKGROUND_TRANSPARENT
     parents:
-    - ROOT_ID
-    - GRID_ID
-    - TABS-L-d9eyOE-b
-    - TAB-YT6eNksV-
+      - ROOT_ID
+      - GRID_ID
+      - TABS-L-d9eyOE-b
+      - TAB-YT6eNksV-
     type: ROW
   ROW-UsW-_RPAb:
     children:
-    - COLUMN-OJ5spdMmNh
-    - CHART-fLpTSAHpAO
+      - COLUMN-OJ5spdMmNh
+      - CHART-fLpTSAHpAO
     id: ROW-UsW-_RPAb
     meta:
       background: BACKGROUND_TRANSPARENT
     parents:
-    - ROOT_ID
-    - GRID_ID
-    - TABS-L-d9eyOE-b
-    - TAB-AsMaxdYL_t
+      - ROOT_ID
+      - GRID_ID
+      - TABS-L-d9eyOE-b
+      - TAB-AsMaxdYL_t
     type: ROW
   ROW-b7USYEngT:
     children:
-    - MARKDOWN-NQmSPDOtpl
-    - CHART--0GPGmD-pO
-    - CHART-QVql08s5Bv
-    - CHART-0-zzTwBINh
+      - MARKDOWN-NQmSPDOtpl
+      - CHART--0GPGmD-pO
+      - CHART-QVql08s5Bv
+      - CHART-0-zzTwBINh
     id: ROW-b7USYEngT
     meta:
       background: BACKGROUND_TRANSPARENT
     parents:
-    - ROOT_ID
-    - GRID_ID
-    - TABS-L-d9eyOE-b
-    - TAB-l_9I0aNYZ
+      - ROOT_ID
+      - GRID_ID
+      - TABS-L-d9eyOE-b
+      - TAB-l_9I0aNYZ
     type: ROW
   ROW-kNjtGVFpp:
     children:
-    - CHART-5QwNlSbXYU
-    - CHART-37fu7fO6Z0
+      - CHART-5QwNlSbXYU
+      - CHART-37fu7fO6Z0
     id: ROW-kNjtGVFpp
     meta:
       background: BACKGROUND_TRANSPARENT
     parents:
-    - ROOT_ID
-    - GRID_ID
-    - TABS-L-d9eyOE-b
-    - TAB-l_9I0aNYZ
+      - ROOT_ID
+      - GRID_ID
+      - TABS-L-d9eyOE-b
+      - TAB-l_9I0aNYZ
     type: ROW
   ROW-mOvr_xWm1:
     children:
-    - MARKDOWN-zc2mWxZeox
-    - CHART-Q3pbwsH3id
-    - CHART-o-JPAWMZK-
-    - CHART-YSzS5GOOLf
+      - MARKDOWN-zc2mWxZeox
+      - CHART-Q3pbwsH3id
+      - CHART-o-JPAWMZK-
+      - CHART-YSzS5GOOLf
     id: ROW-mOvr_xWm1
     meta:
       background: BACKGROUND_TRANSPARENT
     parents:
-    - ROOT_ID
-    - GRID_ID
-    - TABS-L-d9eyOE-b
-    - TAB-AsMaxdYL_t
+      - ROOT_ID
+      - GRID_ID
+      - TABS-L-d9eyOE-b
+      - TAB-AsMaxdYL_t
     type: ROW
   ROW-s3l4os7YY:
     children:
-    - CHART-LjfhrUkEef
-    - CHART-ZECnzPz8Bi
+      - CHART-LjfhrUkEef
+      - CHART-ZECnzPz8Bi
     id: ROW-s3l4os7YY
     meta:
       background: BACKGROUND_TRANSPARENT
     parents:
-    - ROOT_ID
-    - GRID_ID
-    - TABS-L-d9eyOE-b
-    - TAB-l_9I0aNYZ
+      - ROOT_ID
+      - GRID_ID
+      - TABS-L-d9eyOE-b
+      - TAB-l_9I0aNYZ
     type: ROW
   ROW-y-GwJPgxLr:
     children:
-    - MARKDOWN-__u6CsUyfh
-    - CHART-aytwlT4GAq
-    - CHART-d6vjW6rC6V
+      - MARKDOWN-__u6CsUyfh
+      - CHART-aytwlT4GAq
     id: ROW-y-GwJPgxLr
     meta:
       background: BACKGROUND_TRANSPARENT
     parents:
-    - ROOT_ID
-    - GRID_ID
-    - TABS-L-d9eyOE-b
-    - TAB-AsMaxdYL_t
+      - ROOT_ID
+      - GRID_ID
+      - TABS-L-d9eyOE-b
+      - TAB-AsMaxdYL_t
     type: ROW
   TAB-AsMaxdYL_t:
     children:
-    - ROW-y-GwJPgxLr
-    - ROW-mOvr_xWm1
-    - ROW-UsW-_RPAb
+      - ROW-y-GwJPgxLr
+      - ROW-mOvr_xWm1
+      - ROW-UsW-_RPAb
     id: TAB-AsMaxdYL_t
     meta:
       text: Overview
     parents:
-    - ROOT_ID
-    - GRID_ID
-    - TABS-L-d9eyOE-b
+      - ROOT_ID
+      - GRID_ID
+      - TABS-L-d9eyOE-b
     type: TAB
   TAB-YT6eNksV-:
     children:
-    - ROW-DR80aHJA2c
-    - ROW--BIzjz9F0
+      - ROW-DR80aHJA2c
+      - ROW--BIzjz9F0
     id: TAB-YT6eNksV-
     meta:
       text: "\U0001F680 Aspiring Developers"
     parents:
-    - ROOT_ID
-    - GRID_ID
-    - TABS-L-d9eyOE-b
+      - ROOT_ID
+      - GRID_ID
+      - TABS-L-d9eyOE-b
     type: TAB
   TAB-l_9I0aNYZ:
     children:
-    - ROW-b7USYEngT
-    - ROW-kNjtGVFpp
-    - ROW-s3l4os7YY
+      - ROW-b7USYEngT
+      - ROW-kNjtGVFpp
+      - ROW-s3l4os7YY
     id: TAB-l_9I0aNYZ
     meta:
       text: "\U0001F4BB Current Developers"
     parents:
-    - ROOT_ID
-    - GRID_ID
-    - TABS-L-d9eyOE-b
+      - ROOT_ID
+      - GRID_ID
+      - TABS-L-d9eyOE-b
     type: TAB
   TABS-L-d9eyOE-b:
     children:
-    - TAB-AsMaxdYL_t
-    - TAB-YT6eNksV-
-    - TAB-l_9I0aNYZ
+      - TAB-AsMaxdYL_t
+      - TAB-YT6eNksV-
+      - TAB-l_9I0aNYZ
     id: TABS-L-d9eyOE-b
     meta: {}
     parents:
-    - ROOT_ID
-    - GRID_ID
+      - ROOT_ID
+      - GRID_ID
     type: TABS
 metadata:
   timed_refresh_immune_slices: []
   expanded_slices: {}
   refresh_frequency: 0
-  default_filters: '{}'
+  default_filters: "{}"
   color_scheme: supersetColors
-  filter_scopes:
-    '1387':
-      ethnic_minority:
-        scope:
-        - TAB-AsMaxdYL_t
-        immune: []
-      gender:
-        scope:
-        - ROOT_ID
-        immune: []
-      developer_type:
-        scope:
-        - ROOT_ID
-        immune: []
-      lang_at_home:
-        scope:
-        - ROOT_ID
-        immune: []
-      country_live:
-        scope:
-        - ROOT_ID
-        immune: []
   label_colors:
-    '0': '#FCC700'
-    '1': '#A868B7'
-    '15': '#3CCCCB'
-    '30': '#A38F79'
-    '45': '#8FD3E4'
-    age: '#1FA8C9'
-    Yes,: '#1FA8C9'
-    Female: '#454E7C'
-    Prefer: '#5AC189'
-    No,: '#FF7F44'
-    Male: '#666666'
-    Prefer not to say: '#E04355'
-    Ph.D.: '#FCC700'
-    associate's degree: '#A868B7'
-    bachelor's degree: '#3CCCCB'
-    high school diploma or equivalent (GED): '#A38F79'
-    master's degree (non-professional): '#8FD3E4'
-    no high school (secondary school): '#A1A6BD'
-    professional degree (MBA, MD, JD, etc.): '#ACE1C4'
-    some college credit, no degree: '#FEC0A1'
-    some high school: '#B2B2B2'
-    trade, technical, or vocational training: '#EFA1AA'
-    No, not an ethnic minority: '#1FA8C9'
-    Yes, an ethnic minority: '#454E7C'
-    <NULL>: '#5AC189'
-    'Yes': '#FF7F44'
-    'No': '#666666'
-    last_yr_income: '#E04355'
-    More: '#A1A6BD'
-    Less: '#ACE1C4'
-    I: '#FEC0A1'
-    expected_earn: '#B2B2B2'
-    'Yes: Willing To': '#EFA1AA'
-    'No: Not Willing to': '#FDE380'
-    No Answer: '#D3B3DA'
-    In an Office (with Other Developers): '#9EE5E5'
-    No Preference: '#D1C6BC'
-    From Home: '#1FA8C9'
+    "0": "#FCC700"
+    "1": "#A868B7"
+    "15": "#3CCCCB"
+    "30": "#A38F79"
+    "45": "#8FD3E4"
+    age: "#1FA8C9"
+    Yes,: "#1FA8C9"
+    Female: "#454E7C"
+    Prefer: "#5AC189"
+    No,: "#FF7F44"
+    Male: "#666666"
+    Prefer not to say: "#E04355"
+    Ph.D.: "#FCC700"
+    associate's degree: "#A868B7"
+    bachelor's degree: "#3CCCCB"
+    high school diploma or equivalent (GED): "#A38F79"
+    master's degree (non-professional): "#8FD3E4"
+    no high school (secondary school): "#A1A6BD"
+    professional degree (MBA, MD, JD, etc.): "#ACE1C4"
+    some college credit, no degree: "#FEC0A1"
+    some high school: "#B2B2B2"
+    trade, technical, or vocational training: "#EFA1AA"
+    No, not an ethnic minority: "#1FA8C9"
+    Yes, an ethnic minority: "#454E7C"
+    <NULL>: "#5AC189"
+    "Yes": "#FF7F44"
+    "No": "#666666"
+    last_yr_income: "#E04355"
+    More: "#A1A6BD"
+    Less: "#ACE1C4"
+    I: "#FEC0A1"
+    expected_earn: "#B2B2B2"
+    "Yes: Willing To": "#EFA1AA"
+    "No: Not Willing to": "#FDE380"
+    No Answer: "#D3B3DA"
+    In an Office (with Other Developers): "#9EE5E5"
+    No Preference: "#D1C6BC"
+    From Home: "#1FA8C9"
 version: 1.0.0
diff --git a/superset/examples/configs/dashboards/Sales_Dashboard.yaml b/superset/examples/configs/dashboards/Sales_Dashboard.yaml
index 3efea3af25..439b763d0c 100644
--- a/superset/examples/configs/dashboards/Sales_Dashboard.yaml
+++ b/superset/examples/configs/dashboards/Sales_Dashboard.yaml
@@ -16,8 +16,11 @@
 # under the License.
 dashboard_title: Sales Dashboard
 description: null
-css: ''
+css: ""
 slug: null
+certified_by: ""
+certification_details: ""
+published: true
 uuid: 04f79081-fb49-7bac-7f14-cc76cd2ad93b
 position:
   CHART-1NOOLm5YPs:
@@ -31,26 +34,26 @@ position:
       uuid: c3d643cd-fd6f-4659-a5b7-59402487a8d0
       width: 2
     parents:
-    - ROOT_ID
-    - TABS-e5Ruro0cjP
-    - TAB-d-E0Zc1cTH
-    - ROW-Tyv02UA_6W
-    - COLUMN-8Rp54B6ikC
+      - ROOT_ID
+      - TABS-e5Ruro0cjP
+      - TAB-d-E0Zc1cTH
+      - ROW-Tyv02UA_6W
+      - COLUMN-8Rp54B6ikC
     type: CHART
   CHART-AYpv8gFi_q:
     children: []
     id: CHART-AYpv8gFi_q
     meta:
       chartId: 2810
-      height: 91
+      height: 70
       sliceName: Number of Deals (for each Combination)
       uuid: bd20fc69-dd51-46c1-99b5-09e37a434bf1
-      width: 3
+      width: 6
     parents:
-    - ROOT_ID
-    - TABS-e5Ruro0cjP
-    - TAB-4fthLQmdX
-    - ROW-0l1WcDzW3
+      - ROOT_ID
+      - TABS-e5Ruro0cjP
+      - TAB-4fthLQmdX
+      - ROW-0l1WcDzW3
     type: CHART
   CHART-KKT9BsnUst:
     children: []
@@ -63,90 +66,74 @@ position:
       uuid: db9609e4-9b78-4a32-87a7-4d9e19d51cd8
       width: 7
     parents:
-    - ROOT_ID
-    - TABS-e5Ruro0cjP
-    - TAB-d-E0Zc1cTH
-    - ROW-oAtmu5grZ
+      - ROOT_ID
+      - TABS-e5Ruro0cjP
+      - TAB-d-E0Zc1cTH
+      - ROW-oAtmu5grZ
     type: CHART
   CHART-OJ9aWDmn1q:
     children: []
     id: CHART-OJ9aWDmn1q
     meta:
       chartId: 2808
-      height: 91
+      height: 70
       sliceName: Proportion of Revenue by Product Line
       sliceNameOverride: Proportion of Monthly Revenue by Product Line
       uuid: 08aff161-f60c-4cb3-a225-dc9b1140d2e3
       width: 6
     parents:
-    - ROOT_ID
-    - TABS-e5Ruro0cjP
-    - TAB-4fthLQmdX
-    - ROW-0l1WcDzW3
+      - ROOT_ID
+      - TABS-e5Ruro0cjP
+      - TAB-4fthLQmdX
+      - ROW-0l1WcDzW3
     type: CHART
   CHART-YFg-9wHE7s:
     children: []
     id: CHART-YFg-9wHE7s
     meta:
       chartId: 2811
-      height: 63
+      height: 49
       sliceName: Seasonality of Revenue (per Product Line)
       uuid: cf0da099-b3ab-4d94-ab62-cf353ac3c611
       width: 6
     parents:
-    - ROOT_ID
-    - TABS-e5Ruro0cjP
-    - TAB-4fthLQmdX
-    - ROW-E7MDSGfnm
+      - ROOT_ID
+      - TABS-e5Ruro0cjP
+      - TAB-4fthLQmdX
+      - ROW-E7MDSGfnm
     type: CHART
   CHART-_LMKI0D3tj:
     children: []
     id: CHART-_LMKI0D3tj
     meta:
       chartId: 2809
-      height: 62
-      sliceName: Revenue by Deal SIze
+      height: 49
+      sliceName: Revenue by Deal Size
       sliceNameOverride: Monthly Revenue by Deal SIze
       uuid: f065a533-2e13-42b9-bd19-801a21700dff
       width: 6
     parents:
-    - ROOT_ID
-    - TABS-e5Ruro0cjP
-    - TAB-4fthLQmdX
-    - ROW-E7MDSGfnm
+      - ROOT_ID
+      - TABS-e5Ruro0cjP
+      - TAB-4fthLQmdX
+      - ROW-E7MDSGfnm
     type: CHART
   CHART-id4RGv80N-:
     children: []
     id: CHART-id4RGv80N-
     meta:
       chartId: 2807
-      height: 40
+      height: 59
       sliceName: Total Items Sold (By Product Line)
       sliceNameOverride: Total Products Sold (By Product Line)
       uuid: b8b7ca30-6291-44b0-bc64-ba42e2892b86
       width: 2
     parents:
-    - ROOT_ID
-    - TABS-e5Ruro0cjP
-    - TAB-d-E0Zc1cTH
-    - ROW-oAtmu5grZ
-    - COLUMN-G6_2DvG8aK
-    type: CHART
-  CHART-iyvXMcqHt9:
-    children: []
-    id: CHART-iyvXMcqHt9
-    meta:
-      chartId: 671
-      height: 39
-      sliceName: Filter
-      uuid: a5689df7-98fc-7c51-602c-ebd92dc3ec70
-      width: 2
-    parents:
-    - ROOT_ID
-    - TABS-e5Ruro0cjP
-    - TAB-4fthLQmdX
-    - ROW-0l1WcDzW3
-    - COLUMN-jlNWyWCfTC
+      - ROOT_ID
+      - TABS-e5Ruro0cjP
+      - TAB-d-E0Zc1cTH
+      - ROW-oAtmu5grZ
+      - COLUMN-G6_2DvG8aK
     type: CHART
   CHART-j24u8ve41b:
     children: []
@@ -159,10 +146,10 @@ position:
       uuid: 09c497e0-f442-1121-c9e7-671e37750424
       width: 3
     parents:
-    - ROOT_ID
-    - TABS-e5Ruro0cjP
-    - TAB-d-E0Zc1cTH
-    - ROW-oAtmu5grZ
+      - ROOT_ID
+      - TABS-e5Ruro0cjP
+      - TAB-d-E0Zc1cTH
+      - ROW-oAtmu5grZ
     type: CHART
   CHART-lFanAaYKBK:
     children: []
@@ -174,11 +161,11 @@ position:
       uuid: 7b12a243-88e0-4dc5-ac33-9a840bb0ac5a
       width: 3
     parents:
-    - ROOT_ID
-    - TABS-e5Ruro0cjP
-    - TAB-d-E0Zc1cTH
-    - ROW-Tyv02UA_6W
-    - COLUMN-8Rp54B6ikC
+      - ROOT_ID
+      - TABS-e5Ruro0cjP
+      - TAB-d-E0Zc1cTH
+      - ROW-Tyv02UA_6W
+      - COLUMN-8Rp54B6ikC
     type: CHART
   CHART-vomBOiI7U9:
     children: []
@@ -191,58 +178,44 @@ position:
       uuid: 692aca26-a526-85db-c94c-411c91cc1077
       width: 7
     parents:
-    - ROOT_ID
-    - TABS-e5Ruro0cjP
-    - TAB-d-E0Zc1cTH
-    - ROW-Tyv02UA_6W
+      - ROOT_ID
+      - TABS-e5Ruro0cjP
+      - TAB-d-E0Zc1cTH
+      - ROW-Tyv02UA_6W
     type: CHART
   COLUMN-8Rp54B6ikC:
     children:
-    - CHART-lFanAaYKBK
-    - CHART-1NOOLm5YPs
+      - CHART-lFanAaYKBK
+      - CHART-1NOOLm5YPs
     id: COLUMN-8Rp54B6ikC
     meta:
       background: BACKGROUND_TRANSPARENT
       width: 2
     parents:
-    - ROOT_ID
-    - TABS-e5Ruro0cjP
-    - TAB-d-E0Zc1cTH
-    - ROW-Tyv02UA_6W
+      - ROOT_ID
+      - TABS-e5Ruro0cjP
+      - TAB-d-E0Zc1cTH
+      - ROW-Tyv02UA_6W
     type: COLUMN
   COLUMN-G6_2DvG8aK:
     children:
-    - CHART-id4RGv80N-
+      - CHART-id4RGv80N-
     id: COLUMN-G6_2DvG8aK
     meta:
       background: BACKGROUND_TRANSPARENT
       width: 2
     parents:
-    - ROOT_ID
-    - TABS-e5Ruro0cjP
-    - TAB-d-E0Zc1cTH
-    - ROW-oAtmu5grZ
-    type: COLUMN
-  COLUMN-jlNWyWCfTC:
-    children:
-    - MARKDOWN-HrzsMmvGQo
-    - CHART-iyvXMcqHt9
-    id: COLUMN-jlNWyWCfTC
-    meta:
-      background: BACKGROUND_TRANSPARENT
-      width: 3
-    parents:
-    - ROOT_ID
-    - TABS-e5Ruro0cjP
-    - TAB-4fthLQmdX
-    - ROW-0l1WcDzW3
+      - ROOT_ID
+      - TABS-e5Ruro0cjP
+      - TAB-d-E0Zc1cTH
+      - ROW-oAtmu5grZ
     type: COLUMN
   DASHBOARD_VERSION_KEY: v2
   GRID_ID:
     children: []
     id: GRID_ID
     parents:
-    - ROOT_ID
+      - ROOT_ID
     type: GRID
   HEADER_ID:
     id: HEADER_ID
@@ -253,7 +226,8 @@ position:
     children: []
     id: MARKDOWN--AtDSWnapE
     meta:
-      code: "# \U0001F697 Vehicle Sales Dashboard \U0001F3CD\n\nThis example dashboard\
+      code:
+        "# \U0001F697 Vehicle Sales Dashboard \U0001F3CD\n\nThis example dashboard\
         \ provides insight into the business operations of vehicle seller. The dataset\
         \ powering this dashboard can be found [here on Kaggle](https://www.kaggle.com/kyanyoga/sample-sales-data).\n\
         \n### Timeline\n\nThe dataset contains data on all orders from the 2003 and\
@@ -265,151 +239,113 @@ position:
       height: 53
       width: 3
     parents:
-    - ROOT_ID
-    - TABS-e5Ruro0cjP
-    - TAB-d-E0Zc1cTH
-    - ROW-Tyv02UA_6W
-    type: MARKDOWN
-  MARKDOWN-HrzsMmvGQo:
-    children: []
-    id: MARKDOWN-HrzsMmvGQo
-    meta:
-      code: "# \U0001F50D Filter Box\n\nDashboard filters are a powerful way to enable\
-        \ teams to dive deeper into their business operations data. This filter box\
-        \ helps focus the charts along the following variables:\n\n- Time Range: Focus\
-        \ in on a specific time period (e.g. a holiday or quarter)\n- Product Line:\
-        \ Choose 1 or more product lines to see relevant sales data\n- Deal Size:\
-        \ Zoom in on small, medium, and / or large sales deals\n\nThe filter box below\
-        \ \U0001F447 is configured to only apply to the charts in this tab (**Exploratory**).\
-        \ You can customize the charts that this filter box applies to by:\n\n- entering\
-        \ Edit mode in this dashboard\n- selecting the `...` in the top right corner\n\
-        - selecting the **Set filter mapping** button"
-      height: 50
-      width: 3
-    parents:
-    - ROOT_ID
-    - TABS-e5Ruro0cjP
-    - TAB-4fthLQmdX
-    - ROW-0l1WcDzW3
-    - COLUMN-jlNWyWCfTC
+      - ROOT_ID
+      - TABS-e5Ruro0cjP
+      - TAB-d-E0Zc1cTH
+      - ROW-Tyv02UA_6W
     type: MARKDOWN
   ROOT_ID:
     children:
-    - TABS-e5Ruro0cjP
+      - TABS-e5Ruro0cjP
     id: ROOT_ID
     type: ROOT
   ROW-0l1WcDzW3:
     children:
-    - COLUMN-jlNWyWCfTC
-    - CHART-OJ9aWDmn1q
-    - CHART-AYpv8gFi_q
+      - CHART-OJ9aWDmn1q
+      - CHART-AYpv8gFi_q
     id: ROW-0l1WcDzW3
     meta:
       background: BACKGROUND_TRANSPARENT
     parents:
-    - ROOT_ID
-    - TABS-e5Ruro0cjP
-    - TAB-4fthLQmdX
+      - ROOT_ID
+      - TABS-e5Ruro0cjP
+      - TAB-4fthLQmdX
     type: ROW
   ROW-E7MDSGfnm:
     children:
-    - CHART-YFg-9wHE7s
-    - CHART-_LMKI0D3tj
+      - CHART-YFg-9wHE7s
+      - CHART-_LMKI0D3tj
     id: ROW-E7MDSGfnm
     meta:
       background: BACKGROUND_TRANSPARENT
     parents:
-    - ROOT_ID
-    - TABS-e5Ruro0cjP
-    - TAB-4fthLQmdX
+      - ROOT_ID
+      - TABS-e5Ruro0cjP
+      - TAB-4fthLQmdX
     type: ROW
   ROW-Tyv02UA_6W:
     children:
-    - COLUMN-8Rp54B6ikC
-    - CHART-vomBOiI7U9
-    - MARKDOWN--AtDSWnapE
+      - COLUMN-8Rp54B6ikC
+      - CHART-vomBOiI7U9
+      - MARKDOWN--AtDSWnapE
     id: ROW-Tyv02UA_6W
     meta:
       background: BACKGROUND_TRANSPARENT
     parents:
-    - ROOT_ID
-    - TABS-e5Ruro0cjP
-    - TAB-d-E0Zc1cTH
+      - ROOT_ID
+      - TABS-e5Ruro0cjP
+      - TAB-d-E0Zc1cTH
     type: ROW
   ROW-oAtmu5grZ:
     children:
-    - COLUMN-G6_2DvG8aK
-    - CHART-KKT9BsnUst
-    - CHART-j24u8ve41b
+      - COLUMN-G6_2DvG8aK
+      - CHART-KKT9BsnUst
+      - CHART-j24u8ve41b
     id: ROW-oAtmu5grZ
     meta:
       background: BACKGROUND_TRANSPARENT
     parents:
-    - ROOT_ID
-    - TABS-e5Ruro0cjP
-    - TAB-d-E0Zc1cTH
+      - ROOT_ID
+      - TABS-e5Ruro0cjP
+      - TAB-d-E0Zc1cTH
     type: ROW
   TAB-4fthLQmdX:
     children:
-    - ROW-0l1WcDzW3
-    - ROW-E7MDSGfnm
+      - ROW-0l1WcDzW3
+      - ROW-E7MDSGfnm
     id: TAB-4fthLQmdX
     meta:
       text: "\U0001F9ED Exploratory"
     parents:
-    - ROOT_ID
-    - TABS-e5Ruro0cjP
+      - ROOT_ID
+      - TABS-e5Ruro0cjP
     type: TAB
   TAB-d-E0Zc1cTH:
     children:
-    - ROW-Tyv02UA_6W
-    - ROW-oAtmu5grZ
+      - ROW-Tyv02UA_6W
+      - ROW-oAtmu5grZ
     id: TAB-d-E0Zc1cTH
     meta:
       text: "\U0001F3AF Sales Overview"
     parents:
-    - ROOT_ID
-    - TABS-e5Ruro0cjP
+      - ROOT_ID
+      - TABS-e5Ruro0cjP
     type: TAB
   TABS-e5Ruro0cjP:
     children:
-    - TAB-d-E0Zc1cTH
-    - TAB-4fthLQmdX
+      - TAB-d-E0Zc1cTH
+      - TAB-4fthLQmdX
     id: TABS-e5Ruro0cjP
     meta: {}
     parents:
-    - ROOT_ID
+      - ROOT_ID
     type: TABS
 metadata:
   timed_refresh_immune_slices: []
   expanded_slices: {}
   refresh_frequency: 0
-  default_filters: '{"671": {"__time_range": "No filter"}}'
-  filter_scopes:
-    "671":
-      product_line:
-        scope:
-        - TAB-4fthLQmdX
-        immune: []
-      deal_size:
-        scope:
-        - ROOT_ID
-        immune: []
-      __time_range:
-        scope:
-        - ROOT_ID
-        immune: []
+  default_filters: "{}"
   color_scheme: supersetColors
   label_colors:
-    Medium: '#1FA8C9'
-    Small: '#454E7C'
-    Large: '#5AC189'
-    SUM(SALES): '#1FA8C9'
-    Classic Cars: '#454E7C'
-    Vintage Cars: '#5AC189'
-    Motorcycles: '#FF7F44'
-    Trucks and Buses: '#666666'
-    Planes: '#E04355'
-    Ships: '#FCC700'
-    Trains: '#A868B7'
+    Medium: "#1FA8C9"
+    Small: "#454E7C"
+    Large: "#5AC189"
+    SUM(SALES): "#1FA8C9"
+    Classic Cars: "#454E7C"
+    Vintage Cars: "#5AC189"
+    Motorcycles: "#FF7F44"
+    Trucks and Buses: "#666666"
+    Planes: "#E04355"
+    Ships: "#FCC700"
+    Trains: "#A868B7"
 version: 1.0.0
diff --git a/superset/examples/configs/dashboards/Video_Game_Sales.yaml b/superset/examples/configs/dashboards/Video_Game_Sales.yaml
index 958d32b069..2edaad2d1a 100644
--- a/superset/examples/configs/dashboards/Video_Game_Sales.yaml
+++ b/superset/examples/configs/dashboards/Video_Game_Sales.yaml
@@ -16,39 +16,27 @@
 # under the License.
 dashboard_title: Video Game Sales
 description: null
-css: ''
+css: ""
 slug: null
+certified_by: ""
+certification_details: ""
+published: true
 uuid: c7bc10f4-6a2d-7569-caae-bbc91864ee11
 position:
-  CHART-1L7NIcXvVN:
-    children: []
-    id: CHART-1L7NIcXvVN
-    meta:
-      chartId: 3544
-      height: 79
-      sliceName: Games per Genre over time
-      uuid: 0f8976aa-7bb4-40c7-860b-64445a51aaaf
-      width: 6
-    parents:
-    - ROOT_ID
-    - TABS-97PVJa11D_
-    - TAB-2_QXp8aNq
-    - ROW-fjg6YQBkH
-    type: CHART
   CHART-7mKdnU7OUJ:
     children: []
     id: CHART-7mKdnU7OUJ
     meta:
       chartId: 3545
-      height: 80
+      height: 55
       sliceName: Games per Genre
       uuid: 0499bdec-0837-44f3-ae8a-8c670de81afd
-      width: 3
+      width: 8
     parents:
-    - ROOT_ID
-    - TABS-97PVJa11D_
-    - TAB-2_QXp8aNq
-    - ROW-yP9SB89PZ
+      - ROOT_ID
+      - TABS-97PVJa11D_
+      - TAB-2_QXp8aNq
+      - ROW-yP9SB89PZ
     type: CHART
   CHART-8OG3UJX-Tn:
     children: []
@@ -56,15 +44,15 @@ position:
     meta:
       chartId: 661
       height: 54
-      sliceName: '# of Games That Hit 100k in Sales By Release Year'
-      sliceNameOverride: 'Top 10 Consoles, by # of Hit Games'
+      sliceName: "# of Games That Hit 100k in Sales By Release Year"
+      sliceNameOverride: "Top 10 Consoles, by # of Hit Games"
       uuid: 2b69887b-23e3-b46d-d38c-8ea11856c555
       width: 6
     parents:
-    - ROOT_ID
-    - TABS-97PVJa11D_
-    - TAB-lg-5ymUDgm
-    - ROW-7kAf1blYU
+      - ROOT_ID
+      - TABS-97PVJa11D_
+      - TAB-lg-5ymUDgm
+      - ROW-7kAf1blYU
     type: CHART
   CHART-W02beJK7ms:
     children: []
@@ -77,10 +65,10 @@ position:
       uuid: d20b7324-3b80-24d4-37e2-3bd583b66713
       width: 3
     parents:
-    - ROOT_ID
-    - TABS-97PVJa11D_
-    - TAB-lg-5ymUDgm
-    - ROW-7kAf1blYU
+      - ROOT_ID
+      - TABS-97PVJa11D_
+      - TAB-lg-5ymUDgm
+      - ROW-7kAf1blYU
     type: CHART
   CHART-XFag0yZdLk:
     children: []
@@ -93,10 +81,10 @@ position:
       uuid: 1810975a-f6d4-07c3-495c-c3b535d01f21
       width: 3
     parents:
-    - ROOT_ID
-    - TABS-97PVJa11D_
-    - TAB-lg-5ymUDgm
-    - ROW-7kAf1blYU
+      - ROOT_ID
+      - TABS-97PVJa11D_
+      - TAB-lg-5ymUDgm
+      - ROW-7kAf1blYU
     type: CHART
   CHART-XRvRfsMsaQ:
     children: []
@@ -104,14 +92,14 @@ position:
     meta:
       chartId: 3546
       height: 62
-      sliceName: 'Top 10 Games: Proportion of Sales in Markets'
+      sliceName: "Top 10 Games: Proportion of Sales in Markets"
       uuid: a40879d5-653a-42fe-9314-bbe88ad26e92
       width: 6
     parents:
-    - ROOT_ID
-    - TABS-97PVJa11D_
-    - TAB-lg-5ymUDgm
-    - ROW-NuR8GFQTO
+      - ROOT_ID
+      - TABS-97PVJa11D_
+      - TAB-lg-5ymUDgm
+      - ROW-NuR8GFQTO
     type: CHART
   CHART-XVIYTeubZh:
     children: []
@@ -121,12 +109,12 @@ position:
       height: 80
       sliceName: Games
       uuid: 2a5e562b-ab37-1b9b-1de3-1be4335c8e83
-      width: 5
+      width: 6
     parents:
-    - ROOT_ID
-    - TABS-97PVJa11D_
-    - TAB-2_QXp8aNq
-    - ROW-yP9SB89PZ
+      - ROOT_ID
+      - TABS-97PVJa11D_
+      - TAB-2_QXp8aNq
+      - ROW-yP9SB89PZ
     type: CHART
   CHART-_sx22yawJO:
     children: []
@@ -138,78 +126,45 @@ position:
       uuid: 326fc7e5-b7f1-448e-8a6f-80d0e7ce0b64
       width: 6
     parents:
-    - ROOT_ID
-    - TABS-97PVJa11D_
-    - TAB-lg-5ymUDgm
-    - ROW-NuR8GFQTO
+      - ROOT_ID
+      - TABS-97PVJa11D_
+      - TAB-lg-5ymUDgm
+      - ROW-NuR8GFQTO
     type: CHART
   CHART-nYns6xr4Ft:
     children: []
     id: CHART-nYns6xr4Ft
     meta:
       chartId: 3548
-      height: 79
+      height: 80
       sliceName: Total Sales per Market (Grouped by Genre)
       uuid: d8bf948e-46fd-4380-9f9c-a950c34bcc92
       width: 6
     parents:
-    - ROOT_ID
-    - TABS-97PVJa11D_
-    - TAB-2_QXp8aNq
-    - ROW-fjg6YQBkH
-    type: CHART
-  CHART-uP9GF0z0rT:
-    children: []
-    id: CHART-uP9GF0z0rT
-    meta:
-      chartId: 3547
-      height: 45
-      sliceName: Filter
-      uuid: fd9ce7ec-ae08-4f71-93e0-7c26b132b2e6
-      width: 4
-    parents:
-    - ROOT_ID
-    - TABS-97PVJa11D_
-    - TAB-2_QXp8aNq
-    - ROW-yP9SB89PZ
-    - COLUMN-F53B1OSMcz
-    type: CHART
-  CHART-wt6ZO8jRXZ:
-    children: []
-    id: CHART-wt6ZO8jRXZ
-    meta:
-      chartId: 659
-      height: 72
-      sliceName: Rise & Fall of Video Game Consoles
-      sliceNameOverride: Global Sales per Console
-      uuid: 83b0e2d0-d38b-d980-ed8e-e1c9846361b6
-      width: 12
-    parents:
-    - ROOT_ID
-    - TABS-97PVJa11D_
-    - TAB-lg-5ymUDgm
-    - ROW-XT1DsNA_V
+      - ROOT_ID
+      - TABS-97PVJa11D_
+      - TAB-2_QXp8aNq
+      - ROW-fjg6YQBkH
     type: CHART
   COLUMN-F53B1OSMcz:
     children:
-    - MARKDOWN-7K5cBNy7qu
-    - CHART-uP9GF0z0rT
+      - MARKDOWN-7K5cBNy7qu
     id: COLUMN-F53B1OSMcz
     meta:
       background: BACKGROUND_TRANSPARENT
       width: 4
     parents:
-    - ROOT_ID
-    - TABS-97PVJa11D_
-    - TAB-2_QXp8aNq
-    - ROW-yP9SB89PZ
+      - ROOT_ID
+      - TABS-97PVJa11D_
+      - TAB-2_QXp8aNq
+      - ROW-yP9SB89PZ
     type: COLUMN
   DASHBOARD_VERSION_KEY: v2
   GRID_ID:
     children: []
     id: GRID_ID
     parents:
-    - ROOT_ID
+      - ROOT_ID
     type: GRID
   HEADER_ID:
     id: HEADER_ID
@@ -220,224 +175,194 @@ position:
     children: []
     id: MARKDOWN-7K5cBNy7qu
     meta:
-      code: "# \U0001F93F Explore Trends\n\nDive into data on popular video games\
+      code:
+        "# \U0001F93F Explore Trends\n\nDive into data on popular video games\
         \ using the following dimensions:\n\n- Year\n- Platform\n- Publisher\n- Genre\n\
         \nTo use the **Filter Games** box below, select values for each dimension\
         \ you want to zoom in on and then click **Apply**. \n\nThe filter criteria\
         \ you set in this Filter-box will apply to *all* charts in this tab."
-      height: 33
+      height: 55
       width: 4
     parents:
-    - ROOT_ID
-    - TABS-97PVJa11D_
-    - TAB-2_QXp8aNq
-    - ROW-yP9SB89PZ
-    - COLUMN-F53B1OSMcz
+      - ROOT_ID
+      - TABS-97PVJa11D_
+      - TAB-2_QXp8aNq
+      - ROW-yP9SB89PZ
+      - COLUMN-F53B1OSMcz
     type: MARKDOWN
   MARKDOWN-JOZKOjVc3a:
     children: []
     id: MARKDOWN-JOZKOjVc3a
     meta:
-      code: "## \U0001F3AEVideo Game Sales\n\nThis dashboard visualizes sales & platform\
+      code:
+        "## \U0001F3AEVideo Game Sales\n\nThis dashboard visualizes sales & platform\
         \ data on video games that sold more than 100k copies. The data was last updated\
         \ in early 2017.\n\n[Original dataset](https://www.kaggle.com/gregorut/videogamesales)"
       height: 18
       width: 12
     parents:
-    - ROOT_ID
-    - TABS-97PVJa11D_
-    - TAB-lg-5ymUDgm
-    - ROW-0F99WDC-sz
+      - ROOT_ID
+      - TABS-97PVJa11D_
+      - TAB-lg-5ymUDgm
+      - ROW-0F99WDC-sz
     type: MARKDOWN
   ROOT_ID:
     children:
-    - TABS-97PVJa11D_
+      - TABS-97PVJa11D_
     id: ROOT_ID
     type: ROOT
   ROW-0F99WDC-sz:
     children:
-    - MARKDOWN-JOZKOjVc3a
+      - MARKDOWN-JOZKOjVc3a
     id: ROW-0F99WDC-sz
     meta:
       background: BACKGROUND_TRANSPARENT
     parents:
-    - ROOT_ID
-    - TABS-97PVJa11D_
-    - TAB-lg-5ymUDgm
+      - ROOT_ID
+      - TABS-97PVJa11D_
+      - TAB-lg-5ymUDgm
     type: ROW
   ROW-7kAf1blYU:
     children:
-    - CHART-W02beJK7ms
-    - CHART-XFag0yZdLk
-    - CHART-8OG3UJX-Tn
+      - CHART-W02beJK7ms
+      - CHART-XFag0yZdLk
+      - CHART-8OG3UJX-Tn
     id: ROW-7kAf1blYU
     meta:
-      '0': ROOT_ID
+      "0": ROOT_ID
       background: BACKGROUND_TRANSPARENT
     parents:
-    - ROOT_ID
-    - TABS-97PVJa11D_
-    - TAB-lg-5ymUDgm
+      - ROOT_ID
+      - TABS-97PVJa11D_
+      - TAB-lg-5ymUDgm
     type: ROW
   ROW-NuR8GFQTO:
     children:
-    - CHART-_sx22yawJO
-    - CHART-XRvRfsMsaQ
+      - CHART-_sx22yawJO
+      - CHART-XRvRfsMsaQ
     id: ROW-NuR8GFQTO
     meta:
-      '0': ROOT_ID
-      '1': TABS-97PVJa11D_
-      background: BACKGROUND_TRANSPARENT
-    parents:
-    - ROOT_ID
-    - TABS-97PVJa11D_
-    - TAB-lg-5ymUDgm
-    type: ROW
-  ROW-XT1DsNA_V:
-    children:
-    - CHART-wt6ZO8jRXZ
-    id: ROW-XT1DsNA_V
-    meta:
+      "0": ROOT_ID
+      "1": TABS-97PVJa11D_
       background: BACKGROUND_TRANSPARENT
     parents:
-    - ROOT_ID
-    - TABS-97PVJa11D_
-    - TAB-lg-5ymUDgm
+      - ROOT_ID
+      - TABS-97PVJa11D_
+      - TAB-lg-5ymUDgm
     type: ROW
   ROW-fjg6YQBkH:
     children:
-    - CHART-1L7NIcXvVN
-    - CHART-nYns6xr4Ft
+      - CHART-nYns6xr4Ft
+      - CHART-XVIYTeubZh
     id: ROW-fjg6YQBkH
     meta:
       background: BACKGROUND_TRANSPARENT
     parents:
-    - ROOT_ID
-    - TABS-97PVJa11D_
-    - TAB-2_QXp8aNq
+      - ROOT_ID
+      - TABS-97PVJa11D_
+      - TAB-2_QXp8aNq
     type: ROW
   ROW-yP9SB89PZ:
     children:
-    - COLUMN-F53B1OSMcz
-    - CHART-XVIYTeubZh
-    - CHART-7mKdnU7OUJ
+      - COLUMN-F53B1OSMcz
+      - CHART-7mKdnU7OUJ
     id: ROW-yP9SB89PZ
     meta:
       background: BACKGROUND_TRANSPARENT
     parents:
-    - ROOT_ID
-    - TABS-97PVJa11D_
-    - TAB-2_QXp8aNq
+      - ROOT_ID
+      - TABS-97PVJa11D_
+      - TAB-2_QXp8aNq
     type: ROW
   TAB-2_QXp8aNq:
     children:
-    - ROW-yP9SB89PZ
-    - ROW-fjg6YQBkH
+      - ROW-yP9SB89PZ
+      - ROW-fjg6YQBkH
     id: TAB-2_QXp8aNq
     meta:
       text: "\U0001F93F Explore Trends"
     parents:
-    - ROOT_ID
-    - TABS-97PVJa11D_
+      - ROOT_ID
+      - TABS-97PVJa11D_
     type: TAB
   TAB-lg-5ymUDgm:
     children:
-    - ROW-0F99WDC-sz
-    - ROW-XT1DsNA_V
-    - ROW-7kAf1blYU
-    - ROW-NuR8GFQTO
+      - ROW-0F99WDC-sz
+      - ROW-7kAf1blYU
+      - ROW-NuR8GFQTO
     id: TAB-lg-5ymUDgm
     meta:
       text: Overview
     parents:
-    - ROOT_ID
-    - TABS-97PVJa11D_
+      - ROOT_ID
+      - TABS-97PVJa11D_
     type: TAB
   TABS-97PVJa11D_:
     children:
-    - TAB-lg-5ymUDgm
-    - TAB-2_QXp8aNq
+      - TAB-lg-5ymUDgm
+      - TAB-2_QXp8aNq
     id: TABS-97PVJa11D_
     meta: {}
     parents:
-    - ROOT_ID
+      - ROOT_ID
     type: TABS
 metadata:
   timed_refresh_immune_slices: []
   expanded_slices: {}
   refresh_frequency: 0
-  default_filters: '{"3547": {"platform": ["PS", "PS2", "PS3", "XB", "X360"], "__time_range":
-    "No filter"}}'
+  default_filters: "{}"
   color_scheme: supersetColors
-  filter_scopes:
-    "3547":
-      platform:
-        scope:
-        - TAB-2_QXp8aNq
-        immune: []
-      genre:
-        scope:
-        - ROOT_ID
-        immune: []
-      publisher:
-        scope:
-        - ROOT_ID
-        immune: []
-      __time_range:
-        scope:
-        - ROOT_ID
-        immune: []
   label_colors:
-    '0': '#1FA8C9'
-    '1': '#454E7C'
-    '2600': '#666666'
-    Europe: '#5AC189'
-    Japan: '#FF7F44'
-    North America: '#666666'
-    Other: '#E04355'
-    PS2: '#FCC700'
-    X360: '#A868B7'
-    PS3: '#3CCCCB'
-    Wii: '#A38F79'
-    DS: '#8FD3E4'
-    PS: '#A1A6BD'
-    GBA: '#ACE1C4'
-    PSP: '#FEC0A1'
-    PS4: '#B2B2B2'
-    PC: '#EFA1AA'
-    GB: '#FDE380'
-    XB: '#D3B3DA'
-    NES: '#9EE5E5'
-    3DS: '#D1C6BC'
-    N64: '#1FA8C9'
-    SNES: '#454E7C'
-    GC: '#5AC189'
-    XOne: '#FF7F44'
-    WiiU: '#E04355'
-    PSV: '#FCC700'
-    SAT: '#A868B7'
-    GEN: '#3CCCCB'
-    DC: '#A38F79'
-    SCD: '#8FD3E4'
-    NG: '#A1A6BD'
-    WS: '#ACE1C4'
-    TG16: '#FEC0A1'
-    3DO: '#B2B2B2'
-    GG: '#EFA1AA'
-    PCFX: '#FDE380'
-    Nintendo: '#D3B3DA'
-    Take-Two Interactive: '#9EE5E5'
-    Microsoft Game Studios: '#D1C6BC'
-    Action: '#1FA8C9'
-    Adventure: '#454E7C'
-    Fighting: '#5AC189'
-    Misc: '#FF7F44'
-    Platform: '#666666'
-    Puzzle: '#E04355'
-    Racing: '#FCC700'
-    Role-Playing: '#A868B7'
-    Shooter: '#3CCCCB'
-    Simulation: '#A38F79'
-    Sports: '#8FD3E4'
-    Strategy: '#A1A6BD'
+    "0": "#1FA8C9"
+    "1": "#454E7C"
+    "2600": "#666666"
+    Europe: "#5AC189"
+    Japan: "#FF7F44"
+    North America: "#666666"
+    Other: "#E04355"
+    PS2: "#FCC700"
+    X360: "#A868B7"
+    PS3: "#3CCCCB"
+    Wii: "#A38F79"
+    DS: "#8FD3E4"
+    PS: "#A1A6BD"
+    GBA: "#ACE1C4"
+    PSP: "#FEC0A1"
+    PS4: "#B2B2B2"
+    PC: "#EFA1AA"
+    GB: "#FDE380"
+    XB: "#D3B3DA"
+    NES: "#9EE5E5"
+    3DS: "#D1C6BC"
+    N64: "#1FA8C9"
+    SNES: "#454E7C"
+    GC: "#5AC189"
+    XOne: "#FF7F44"
+    WiiU: "#E04355"
+    PSV: "#FCC700"
+    SAT: "#A868B7"
+    GEN: "#3CCCCB"
+    DC: "#A38F79"
+    SCD: "#8FD3E4"
+    NG: "#A1A6BD"
+    WS: "#ACE1C4"
+    TG16: "#FEC0A1"
+    3DO: "#B2B2B2"
+    GG: "#EFA1AA"
+    PCFX: "#FDE380"
+    Nintendo: "#D3B3DA"
+    Take-Two Interactive: "#9EE5E5"
+    Microsoft Game Studios: "#D1C6BC"
+    Action: "#1FA8C9"
+    Adventure: "#454E7C"
+    Fighting: "#5AC189"
+    Misc: "#FF7F44"
+    Platform: "#666666"
+    Puzzle: "#E04355"
+    Racing: "#FCC700"
+    Role-Playing: "#A868B7"
+    Shooter: "#3CCCCB"
+    Simulation: "#A38F79"
+    Sports: "#8FD3E4"
+    Strategy: "#A1A6BD"
 version: 1.0.0
diff --git a/superset/examples/misc_dashboard.py b/superset/examples/misc_dashboard.py
index 6336a78820..aa8d037495 100644
--- a/superset/examples/misc_dashboard.py
+++ b/superset/examples/misc_dashboard.py
@@ -38,37 +38,22 @@ def load_misc_dashboard() -> None:
     js = textwrap.dedent(
         """\
 {
-    "CHART-BkeVbh8ANQ": {
-        "children": [],
-        "id": "CHART-BkeVbh8ANQ",
-        "meta": {
-            "chartId": 4004,
-            "height": 34,
-            "sliceName": "Multi Line",
-            "width": 8
-        },
-        "type": "CHART"
-    },
-    "CHART-H1HYNzEANX": {
-        "children": [],
-        "id": "CHART-H1HYNzEANX",
-        "meta": {
-            "chartId": 3940,
-            "height": 50,
-            "sliceName": "Energy Sankey",
-            "width": 6
-        },
-        "type": "CHART"
-    },
     "CHART-HJOYVMV0E7": {
         "children": [],
         "id": "CHART-HJOYVMV0E7",
         "meta": {
             "chartId": 3969,
-            "height": 63,
+            "height": 69,
             "sliceName": "Mapbox Long/Lat",
-            "width": 6
+            "uuid": "164efe31-295b-4408-aaa6-2f4bfb58a212",
+            "width": 4
         },
+        "parents": [
+            "ROOT_ID",
+            "GRID_ID",
+            "ROW-S1MK4M4A4X",
+            "COLUMN-ByUFVf40EQ"
+        ],
         "type": "CHART"
     },
     "CHART-S1WYNz4AVX": {
@@ -76,32 +61,16 @@ def load_misc_dashboard() -> None:
         "id": "CHART-S1WYNz4AVX",
         "meta": {
             "chartId": 3989,
-            "height": 25,
+            "height": 69,
             "sliceName": "Parallel Coordinates",
+            "uuid": "e84f7e74-031a-47bb-9f80-ae0694dcca48",
             "width": 4
         },
-        "type": "CHART"
-    },
-    "CHART-r19KVMNCE7": {
-        "children": [],
-        "id": "CHART-r19KVMNCE7",
-        "meta": {
-            "chartId": 3971,
-            "height": 34,
-            "sliceName": "Calendar Heatmap multiformat 0",
-            "width": 4
-        },
-        "type": "CHART"
-    },
-    "CHART-rJ4K4GV04Q": {
-        "children": [],
-        "id": "CHART-rJ4K4GV04Q",
-        "meta": {
-            "chartId": 3941,
-            "height": 63,
-            "sliceName": "Energy Force Layout",
-            "width": 6
-        },
+        "parents": [
+            "ROOT_ID",
+            "GRID_ID",
+            "ROW-SytNzNA4X"
+        ],
         "type": "CHART"
     },
     "CHART-rkgF4G4A4X": {
@@ -109,54 +78,27 @@ def load_misc_dashboard() -> None:
         "id": "CHART-rkgF4G4A4X",
         "meta": {
             "chartId": 3970,
-            "height": 25,
+            "height": 69,
             "sliceName": "Birth in France by department in 2016",
-            "width": 8
-        },
-        "type": "CHART"
-    },
-    "CHART-rywK4GVR4X": {
-        "children": [],
-        "id": "CHART-rywK4GVR4X",
-        "meta": {
-            "chartId": 3942,
-            "height": 50,
-            "sliceName": "Heatmap",
-            "width": 6
-        },
-        "type": "CHART"
-    },
-    "COLUMN-ByUFVf40EQ": {
-        "children": [
-            "CHART-rywK4GVR4X",
-            "CHART-HJOYVMV0E7"
-        ],
-        "id": "COLUMN-ByUFVf40EQ",
-        "meta": {
-            "background": "BACKGROUND_TRANSPARENT",
-            "width": 6
+            "uuid": "54583ae9-c99a-42b5-a906-7ee2adfe1fb1",
+            "width": 4
         },
-        "type": "COLUMN"
-    },
-    "COLUMN-rkmYVGN04Q": {
-        "children": [
-            "CHART-rJ4K4GV04Q",
-            "CHART-H1HYNzEANX"
+        "parents": [
+            "ROOT_ID",
+            "GRID_ID",
+            "ROW-SytNzNA4X"
         ],
-        "id": "COLUMN-rkmYVGN04Q",
-        "meta": {
-            "background": "BACKGROUND_TRANSPARENT",
-            "width": 6
-        },
-        "type": "COLUMN"
+        "type": "CHART"
     },
+    "DASHBOARD_VERSION_KEY": "v2",
     "GRID_ID": {
         "children": [
-            "ROW-SytNzNA4X",
-            "ROW-S1MK4M4A4X",
-            "ROW-HkFFEzVRVm"
+            "ROW-SytNzNA4X"
         ],
         "id": "GRID_ID",
+        "parents": [
+            "ROOT_ID"
+        ],
         "type": "GRID"
     },
     "HEADER_ID": {
@@ -173,40 +115,22 @@ def load_misc_dashboard() -> None:
         "id": "ROOT_ID",
         "type": "ROOT"
     },
-    "ROW-HkFFEzVRVm": {
-        "children": [
-            "CHART-r19KVMNCE7",
-            "CHART-BkeVbh8ANQ"
-        ],
-        "id": "ROW-HkFFEzVRVm",
-        "meta": {
-            "background": "BACKGROUND_TRANSPARENT"
-        },
-        "type": "ROW"
-    },
-    "ROW-S1MK4M4A4X": {
-        "children": [
-            "COLUMN-rkmYVGN04Q",
-            "COLUMN-ByUFVf40EQ"
-        ],
-        "id": "ROW-S1MK4M4A4X",
-        "meta": {
-            "background": "BACKGROUND_TRANSPARENT"
-        },
-        "type": "ROW"
-    },
     "ROW-SytNzNA4X": {
         "children": [
             "CHART-rkgF4G4A4X",
-            "CHART-S1WYNz4AVX"
+            "CHART-S1WYNz4AVX",
+            "CHART-HJOYVMV0E7"
         ],
         "id": "ROW-SytNzNA4X",
         "meta": {
             "background": "BACKGROUND_TRANSPARENT"
         },
+        "parents": [
+            "ROOT_ID",
+            "GRID_ID"
+        ],
         "type": "ROW"
-    },
-    "DASHBOARD_VERSION_KEY": "v2"
+    }
 }
     """
     )
diff --git a/superset/examples/world_bank.py b/superset/examples/world_bank.py
index 5513a52d64..1541e3e472 100644
--- a/superset/examples/world_bank.py
+++ b/superset/examples/world_bank.py
@@ -167,35 +167,6 @@ def create_slices(tbl: BaseDatasource) -> list[Slice]:
     }
 
     return [
-        Slice(
-            slice_name="Region Filter",
-            viz_type="filter_box",
-            datasource_type=DatasourceType.TABLE,
-            datasource_id=tbl.id,
-            params=get_slice_json(
-                defaults,
-                viz_type="filter_box",
-                date_filter=False,
-                filter_configs=[
-                    {
-                        "asc": False,
-                        "clearable": True,
-                        "column": "region",
-                        "key": "2s98dfu",
-                        "metric": "sum__SP_POP_TOTL",
-                        "multiple": False,
-                    },
-                    {
-                        "asc": False,
-                        "clearable": True,
-                        "key": "li3j2lk",
-                        "column": "country_name",
-                        "metric": "sum__SP_POP_TOTL",
-                        "multiple": True,
-                    },
-                ],
-            ),
-        ),
         Slice(
             slice_name="World's Population",
             viz_type="big_number",
@@ -372,18 +343,12 @@ def create_slices(tbl: BaseDatasource) -> list[Slice]:
 
 
 dashboard_positions = {
-    "CHART-36bfc934": {
-        "children": [],
-        "id": "CHART-36bfc934",
-        "meta": {"chartId": 40, "height": 25, "sliceName": "Region Filter", "width": 2},
-        "type": "CHART",
-    },
     "CHART-37982887": {
         "children": [],
         "id": "CHART-37982887",
         "meta": {
             "chartId": 41,
-            "height": 25,
+            "height": 52,
             "sliceName": "World's Population",
             "width": 2,
         },
@@ -464,7 +429,7 @@ dashboard_positions = {
         "type": "COLUMN",
     },
     "COLUMN-fe3914b8": {
-        "children": ["CHART-36bfc934", "CHART-37982887"],
+        "children": ["CHART-37982887"],
         "id": "COLUMN-fe3914b8",
         "meta": {"background": "BACKGROUND_TRANSPARENT", "width": 2},
         "type": "COLUMN",
diff --git a/tests/integration_tests/charts/api_tests.py b/tests/integration_tests/charts/api_tests.py
index fe489a0f36..69888104fa 100644
--- a/tests/integration_tests/charts/api_tests.py
+++ b/tests/integration_tests/charts/api_tests.py
@@ -981,7 +981,7 @@ class TestChartApi(SupersetTestCase, ApiOwnersTestCaseMixin, InsertChartMixin):
         rv = self.get_assert_metric(uri, "get_list")
         self.assertEqual(rv.status_code, 200)
         data = json.loads(rv.data.decode("utf-8"))
-        self.assertEqual(data["count"], 34)
+        self.assertEqual(data["count"], 33)
 
     @pytest.mark.usefixtures("load_energy_table_with_slice", "add_dashboard_to_chart")
     def test_get_charts_dashboards(self):
@@ -1447,7 +1447,7 @@ class TestChartApi(SupersetTestCase, ApiOwnersTestCaseMixin, InsertChartMixin):
         """
         Chart API: Test get charts filter
         """
-        # Assuming we have 34 sample charts
+        # Assuming we have 33 sample charts
         self.login(username="admin")
         arguments = {"page_size": 10, "page": 0}
         uri = f"api/v1/chart/?q={prison.dumps(arguments)}"
@@ -1461,7 +1461,7 @@ class TestChartApi(SupersetTestCase, ApiOwnersTestCaseMixin, InsertChartMixin):
         rv = self.get_assert_metric(uri, "get_list")
         self.assertEqual(rv.status_code, 200)
         data = json.loads(rv.data.decode("utf-8"))
-        self.assertEqual(len(data["result"]), 4)
+        self.assertEqual(len(data["result"]), 3)
 
     def test_get_charts_no_data_access(self):
         """
diff --git a/tests/integration_tests/dashboards/commands_tests.py b/tests/integration_tests/dashboards/commands_tests.py
index cfc992b95f..175a8a3198 100644
--- a/tests/integration_tests/dashboards/commands_tests.py
+++ b/tests/integration_tests/dashboards/commands_tests.py
@@ -97,17 +97,11 @@ class TestExportDashboardsCommand(SupersetTestCase):
             "published": False,
             "uuid": str(example_dashboard.uuid),
             "position": {
-                "CHART-36bfc934": {
-                    "children": [],
-                    "id": "CHART-36bfc934",
-                    "meta": {"height": 25, "sliceName": "Region Filter", "width": 2},
-                    "type": "CHART",
-                },
                 "CHART-37982887": {
                     "children": [],
                     "id": "CHART-37982887",
                     "meta": {
-                        "height": 25,
+                        "height": 52,
                         "sliceName": "World's Population",
                         "width": 2,
                     },
@@ -180,7 +174,7 @@ class TestExportDashboardsCommand(SupersetTestCase):
                     "type": "COLUMN",
                 },
                 "COLUMN-fe3914b8": {
-                    "children": ["CHART-36bfc934", "CHART-37982887"],
+                    "children": ["CHART-37982887"],
                     "id": "COLUMN-fe3914b8",
                     "meta": {"background": "BACKGROUND_TRANSPARENT", "width": 2},
                     "type": "COLUMN",
@@ -299,7 +293,9 @@ class TestExportDashboardsCommand(SupersetTestCase):
         mock_suffix.side_effect = (str(i) for i in itertools.count(1))
 
         position = get_default_position("example")
-        chart_1 = db.session.query(Slice).filter_by(slice_name="Region Filter").one()
+        chart_1 = (
+            db.session.query(Slice).filter_by(slice_name="World's Population").one()
+        )
         new_position = append_charts(position, {chart_1})
         assert new_position == {
             "DASHBOARD_VERSION_KEY": "v2",
@@ -328,7 +324,7 @@ class TestExportDashboardsCommand(SupersetTestCase):
                 "meta": {
                     "chartId": chart_1.id,
                     "height": 50,
-                    "sliceName": "Region Filter",
+                    "sliceName": "World's Population",
                     "uuid": str(chart_1.uuid),
                     "width": 4,
                 },
@@ -375,7 +371,7 @@ class TestExportDashboardsCommand(SupersetTestCase):
                 "meta": {
                     "chartId": chart_1.id,
                     "height": 50,
-                    "sliceName": "Region Filter",
+                    "sliceName": "World's Population",
                     "uuid": str(chart_1.uuid),
                     "width": 4,
                 },
@@ -406,7 +402,7 @@ class TestExportDashboardsCommand(SupersetTestCase):
                 "meta": {
                     "chartId": chart_1.id,
                     "height": 50,
-                    "sliceName": "Region Filter",
+                    "sliceName": "World's Population",
                     "uuid": str(chart_1.uuid),
                     "width": 4,
                 },
diff --git a/tests/integration_tests/dashboards/dao_tests.py b/tests/integration_tests/dashboards/dao_tests.py
index 8f73e5c2f8..65fc9e32dd 100644
--- a/tests/integration_tests/dashboards/dao_tests.py
+++ b/tests/integration_tests/dashboards/dao_tests.py
@@ -33,60 +33,6 @@ from tests.integration_tests.fixtures.world_bank_dashboard import (
 
 
 class TestDashboardDAO(SupersetTestCase):
-    @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices")
-    def test_set_dash_metadata(self):
-        dash: Dashboard = (
-            db.session.query(Dashboard).filter_by(slug="world_health").first()
-        )
-        data = dash.data
-        positions = data["position_json"]
-        data.update({"positions": positions})
-        original_data = copy.deepcopy(data)
-
-        # add filter scopes
-        filter_slice = next(slc for slc in dash.slices if slc.viz_type == "filter_box")
-        immune_slices = [slc for slc in dash.slices if slc != filter_slice]
-        filter_scopes = {
-            str(filter_slice.id): {
-                "region": {
-                    "scope": ["ROOT_ID"],
-                    "immune": [slc.id for slc in immune_slices],
-                }
-            }
-        }
-        data.update({"filter_scopes": json.dumps(filter_scopes)})
-        DashboardDAO.set_dash_metadata(dash, data)
-        updated_metadata = json.loads(dash.json_metadata)
-        self.assertEqual(updated_metadata["filter_scopes"], filter_scopes)
-
-        # remove a slice and change slice ids (as copy slices)
-        removed_slice = immune_slices.pop()
-        removed_components = [
-            key
-            for (key, value) in positions.items()
-            if isinstance(value, dict)
-            and value.get("type") == "CHART"
-            and value["meta"]["chartId"] == removed_slice.id
-        ]
-        for component_id in removed_components:
-            del positions[component_id]
-
-        data.update({"positions": positions})
-        DashboardDAO.set_dash_metadata(dash, data)
-        updated_metadata = json.loads(dash.json_metadata)
-        expected_filter_scopes = {
-            str(filter_slice.id): {
-                "region": {
-                    "scope": ["ROOT_ID"],
-                    "immune": [slc.id for slc in immune_slices],
-                }
-            }
-        }
-        self.assertEqual(updated_metadata["filter_scopes"], expected_filter_scopes)
-
-        # reset dash to original data
-        DashboardDAO.set_dash_metadata(dash, original_data)
-
     @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices")
     @patch("superset.utils.core.g")
     @patch("superset.security.manager.g")
diff --git a/tests/integration_tests/databases/api_tests.py b/tests/integration_tests/databases/api_tests.py
index 6e6f6e15b8..496012390e 100644
--- a/tests/integration_tests/databases/api_tests.py
+++ b/tests/integration_tests/databases/api_tests.py
@@ -2074,7 +2074,7 @@ class TestDatabaseApi(SupersetTestCase):
         rv = self.get_assert_metric(uri, "related_objects")
         self.assertEqual(rv.status_code, 200)
         response = json.loads(rv.data.decode("utf-8"))
-        self.assertEqual(response["charts"]["count"], 34)
+        self.assertEqual(response["charts"]["count"], 33)
         self.assertEqual(response["dashboards"]["count"], 3)
 
     def test_get_database_related_objects_not_found(self):
diff --git a/tests/integration_tests/utils_tests.py b/tests/integration_tests/utils_tests.py
index 405040e3d0..ddd0b0caf4 100644
--- a/tests/integration_tests/utils_tests.py
+++ b/tests/integration_tests/utils_tests.py
@@ -744,50 +744,6 @@ class TestUtils(SupersetTestCase):
         self.assertListEqual(as_list([123]), [123])
         self.assertListEqual(as_list("foo"), ["foo"])
 
-    @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices")
-    def test_build_extra_filters(self):
-        world_health = db.session.query(Dashboard).filter_by(slug="world_health").one()
-        layout = json.loads(world_health.position_json)
-        filter_ = db.session.query(Slice).filter_by(slice_name="Region Filter").one()
-        world = db.session.query(Slice).filter_by(slice_name="World's Population").one()
-        box_plot = db.session.query(Slice).filter_by(slice_name="Box plot").one()
-        treemap = db.session.query(Slice).filter_by(slice_name="Treemap").one()
-
-        filter_scopes = {
-            str(filter_.id): {
-                "region": {"scope": ["ROOT_ID"], "immune": [treemap.id]},
-                "country_name": {
-                    "scope": ["ROOT_ID"],
-                    "immune": [treemap.id, box_plot.id],
-                },
-            }
-        }
-
-        default_filters = {
-            str(filter_.id): {
-                "region": ["North America"],
-                "country_name": ["United States"],
-            }
-        }
-
-        # immune to all filters
-        assert (
-            build_extra_filters(layout, filter_scopes, default_filters, treemap.id)
-            == []
-        )
-
-        # in scope
-        assert build_extra_filters(
-            layout, filter_scopes, default_filters, world.id
-        ) == [
-            {"col": "region", "op": "==", "val": "North America"},
-            {"col": "country_name", "op": "in", "val": ["United States"]},
-        ]
-
-        assert build_extra_filters(
-            layout, filter_scopes, default_filters, box_plot.id
-        ) == [{"col": "region", "op": "==", "val": "North America"}]
-
     def test_merge_extra_filters_with_no_extras(self):
         form_data = {
             "time_range": "Last 10 days",


(superset) 04/15: chore(tags): Allow for lookup via ids vs. name in the API (#25996)

Posted by mi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

michaelsmolina pushed a commit to branch 3.1
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 2c3bf2895fdc5fc0001bd8466cbb81bc76f349ca
Author: Hugh A. Miles II <hu...@gmail.com>
AuthorDate: Wed Nov 29 13:59:59 2023 -0500

    chore(tags): Allow for lookup via ids vs. name in the API (#25996)
    
    (cherry picked from commit ee308fbc6459273bd6234de03c418e9d1dcbc3a7)
---
 superset-frontend/src/features/tags/TagModal.tsx  |  6 ++---
 superset-frontend/src/features/tags/tags.ts       | 17 ++++++++++++
 superset-frontend/src/pages/AllEntities/index.tsx | 10 ++++---
 superset/daos/tag.py                              |  8 ++++++
 superset/tags/api.py                              | 15 ++++++++---
 tests/integration_tests/tags/dao_tests.py         | 33 +++++++++++++++++++++++
 6 files changed, 80 insertions(+), 9 deletions(-)

diff --git a/superset-frontend/src/features/tags/TagModal.tsx b/superset-frontend/src/features/tags/TagModal.tsx
index a0ac8636a5..5057c8441d 100644
--- a/superset-frontend/src/features/tags/TagModal.tsx
+++ b/superset-frontend/src/features/tags/TagModal.tsx
@@ -26,7 +26,7 @@ import { Input } from 'antd';
 import { Divider } from 'src/components';
 import Button from 'src/components/Button';
 import { Tag } from 'src/views/CRUD/types';
-import { fetchObjects } from 'src/features/tags/tags';
+import { fetchObjectsByTagIds } from 'src/features/tags/tags';
 
 const StyledModalBody = styled.div`
   .ant-select-dropdown {
@@ -115,8 +115,8 @@ const TagModal: React.FC<TagModalProps> = ({
     };
     clearResources();
     if (isEditMode) {
-      fetchObjects(
-        { tags: editTag.name, types: null },
+      fetchObjectsByTagIds(
+        { tagIds: [editTag.id], types: null },
         (data: Tag[]) => {
           data.forEach(updateResourceOptions);
           setDashboardsToTag(resourceMap[TaggableResources.Dashboard]);
diff --git a/superset-frontend/src/features/tags/tags.ts b/superset-frontend/src/features/tags/tags.ts
index 45c4e88fc5..db172681cb 100644
--- a/superset-frontend/src/features/tags/tags.ts
+++ b/superset-frontend/src/features/tags/tags.ts
@@ -194,3 +194,20 @@ export function fetchObjects(
     .then(({ json }) => callback(json.result))
     .catch(response => error(response));
 }
+
+export function fetchObjectsByTagIds(
+  {
+    tagIds = [],
+    types,
+  }: { tagIds: number[] | undefined; types: string | null },
+  callback: (json: JsonObject) => void,
+  error: (response: Response) => void,
+) {
+  let url = `/api/v1/tag/get_objects/?tagIds=${tagIds}`;
+  if (types) {
+    url += `&types=${types}`;
+  }
+  SupersetClient.get({ endpoint: url })
+    .then(({ json }) => callback(json.result))
+    .catch(response => error(response));
+}
diff --git a/superset-frontend/src/pages/AllEntities/index.tsx b/superset-frontend/src/pages/AllEntities/index.tsx
index ca815795d6..a1e2c52fe4 100644
--- a/superset-frontend/src/pages/AllEntities/index.tsx
+++ b/superset-frontend/src/pages/AllEntities/index.tsx
@@ -33,7 +33,7 @@ import { PageHeaderWithActions } from 'src/components/PageHeaderWithActions';
 import { Tag } from 'src/views/CRUD/types';
 import TagModal from 'src/features/tags/TagModal';
 import withToasts, { useToasts } from 'src/components/MessageToasts/withToasts';
-import { fetchObjects, fetchSingleTag } from 'src/features/tags/tags';
+import { fetchObjectsByTagIds, fetchSingleTag } from 'src/features/tags/tags';
 import Loading from 'src/components/Loading';
 
 interface TaggedObject {
@@ -146,8 +146,12 @@ function AllEntities() {
 
   const fetchTaggedObjects = () => {
     setLoading(true);
-    fetchObjects(
-      { tags: tag?.name || '', types: null },
+    if (!tag) {
+      addDangerToast('Error tag object is not referenced!');
+      return;
+    }
+    fetchObjectsByTagIds(
+      { tagIds: [tag?.id] || '', types: null },
       (data: TaggedObject[]) => {
         const objects = { dashboard: [], chart: [], query: [] };
         data.forEach(function (object) {
diff --git a/superset/daos/tag.py b/superset/daos/tag.py
index fbc9aa229e..60362bfbbd 100644
--- a/superset/daos/tag.py
+++ b/superset/daos/tag.py
@@ -167,6 +167,14 @@ class TagDAO(BaseDAO[Tag]):
             .first()
         )
 
+    @staticmethod
+    def get_tagged_objects_by_tag_id(
+        tag_ids: Optional[list[int]], obj_types: Optional[list[str]] = None
+    ) -> list[dict[str, Any]]:
+        tags = db.session.query(Tag).filter(Tag.id.in_(tag_ids)).all()
+        tag_names = [tag.name for tag in tags]
+        return TagDAO.get_tagged_objects_for_tags(tag_names, obj_types)
+
     @staticmethod
     def get_tagged_objects_for_tags(
         tags: Optional[list[str]] = None, obj_types: Optional[list[str]] = None
diff --git a/superset/tags/api.py b/superset/tags/api.py
index a4fc185f29..a3c95a5814 100644
--- a/superset/tags/api.py
+++ b/superset/tags/api.py
@@ -584,12 +584,21 @@ class TagRestApi(BaseSupersetModelRestApi):
             500:
               $ref: '#/components/responses/500'
         """
+        tag_ids = [
+            tag_id for tag_id in request.args.get("tagIds", "").split(",") if tag_id
+        ]
         tags = [tag for tag in request.args.get("tags", "").split(",") if tag]
         # filter types
         types = [type_ for type_ in request.args.get("types", "").split(",") if type_]
 
         try:
-            tagged_objects = TagDAO.get_tagged_objects_for_tags(tags, types)
+            if tag_ids:
+                # priotize using ids for lookups vs. names mainly using this
+                # for backward compatibility
+                tagged_objects = TagDAO.get_tagged_objects_by_tag_id(tag_ids, types)
+            else:
+                tagged_objects = TagDAO.get_tagged_objects_for_tags(tags, types)
+
             result = [
                 self.object_entity_response_schema.dump(tagged_object)
                 for tagged_object in tagged_objects
@@ -609,11 +618,11 @@ class TagRestApi(BaseSupersetModelRestApi):
         log_to_statsd=False,
     )
     def favorite_status(self, **kwargs: Any) -> Response:
-        """Favorite Stars for Dashboards
+        """Favorite Stars for Tags
         ---
         get:
           description: >-
-            Check favorited dashboards for current user
+            Get favorited tags for current user
           parameters:
           - in: query
             name: q
diff --git a/tests/integration_tests/tags/dao_tests.py b/tests/integration_tests/tags/dao_tests.py
index ea4b3ba783..272ba43ed3 100644
--- a/tests/integration_tests/tags/dao_tests.py
+++ b/tests/integration_tests/tags/dao_tests.py
@@ -207,6 +207,39 @@ class TestTagsDAO(SupersetTestCase):
         tagged_objects = TagDAO.get_tagged_objects_for_tags(obj_types=["chart"])
         assert len(tagged_objects) == num_charts
 
+    @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices")
+    @pytest.mark.usefixtures("with_tagging_system_feature")
+    @pytest.mark.usefixtures("create_tags")
+    # test get objects from tag
+    def test_get_objects_from_tag_with_id(self):
+        # create tagged objects
+        dashboard = (
+            db.session.query(Dashboard)
+            .filter(Dashboard.dashboard_title == "World Bank's Data")
+            .first()
+        )
+        dashboard_id = dashboard.id
+        tag_1 = db.session.query(Tag).filter_by(name="example_tag_1").one()
+        tag_2 = db.session.query(Tag).filter_by(name="example_tag_2").one()
+        tag_ids = [tag_1.id, tag_2.id]
+        self.insert_tagged_object(
+            object_id=dashboard_id, object_type=ObjectType.dashboard, tag_id=tag_1.id
+        )
+        # get objects
+        tagged_objects = TagDAO.get_tagged_objects_by_tag_id(tag_ids)
+        assert len(tagged_objects) == 1
+
+        # test get objects from tag with type
+        tagged_objects = TagDAO.get_tagged_objects_by_tag_id(
+            tag_ids, obj_types=["dashboard", "chart"]
+        )
+        assert len(tagged_objects) == 1
+
+        tagged_objects = TagDAO.get_tagged_objects_by_tag_id(
+            tag_ids, obj_types=["chart"]
+        )
+        assert len(tagged_objects) == 0
+
     @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices")
     @pytest.mark.usefixtures("with_tagging_system_feature")
     @pytest.mark.usefixtures("create_tagged_objects")


(superset) 09/15: fix: set label on adhoc column should persist (#26154)

Posted by mi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

michaelsmolina pushed a commit to branch 3.1
in repository https://gitbox.apache.org/repos/asf/superset.git

commit ceac19fa2fb623756a938c069fabc184f78d9e91
Author: Beto Dealmeida <ro...@dealmeida.net>
AuthorDate: Fri Dec 1 08:40:52 2023 -0500

    fix: set label on adhoc column should persist (#26154)
    
    (cherry picked from commit b2ea97a98484e18eee760b7a2914926143918231)
---
 .../ColumnSelectPopover.test.tsx                   | 77 ++++++++++++++++++++++
 .../DndColumnSelectControl/ColumnSelectPopover.tsx | 39 +++++++++--
 .../ColumnSelectPopoverTrigger.tsx                 | 13 ++--
 3 files changed, 121 insertions(+), 8 deletions(-)

diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopover.test.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopover.test.tsx
new file mode 100644
index 0000000000..e7ff7cd9a7
--- /dev/null
+++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopover.test.tsx
@@ -0,0 +1,77 @@
+/**
+ * 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 React from 'react';
+import { render, fireEvent } from '@testing-library/react';
+import '@testing-library/jest-dom/extend-expect';
+import { Provider } from 'react-redux';
+import configureMockStore from 'redux-mock-store';
+import thunk from 'redux-thunk';
+import { supersetTheme, ThemeProvider } from '@superset-ui/core';
+import ColumnSelectPopover from 'src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopover';
+
+const middlewares = [thunk];
+const mockStore = configureMockStore(middlewares);
+
+describe('ColumnSelectPopover - onTabChange function', () => {
+  it('updates adhocColumn when switching to sqlExpression tab with custom label', () => {
+    const mockColumns = [{ column_name: 'year' }];
+    const mockOnClose = jest.fn();
+    const mockOnChange = jest.fn();
+    const mockGetCurrentTab = jest.fn();
+    const mockSetDatasetModal = jest.fn();
+    const mockSetLabel = jest.fn();
+
+    const store = mockStore({ explore: { datasource: { type: 'table' } } });
+
+    const { container, getByText } = render(
+      <Provider store={store}>
+        <ThemeProvider theme={supersetTheme}>
+          <ColumnSelectPopover
+            columns={mockColumns}
+            editedColumn={mockColumns[0]}
+            getCurrentTab={mockGetCurrentTab}
+            hasCustomLabel
+            isTemporal
+            label="Custom Label"
+            onChange={mockOnChange}
+            onClose={mockOnClose}
+            setDatasetModal={mockSetDatasetModal}
+            setLabel={mockSetLabel}
+          />
+        </ThemeProvider>
+      </Provider>,
+    );
+
+    const sqlExpressionTab = container.querySelector(
+      '#adhoc-metric-edit-tabs-tab-sqlExpression',
+    );
+    expect(sqlExpressionTab).not.toBeNull();
+    fireEvent.click(sqlExpressionTab!);
+    expect(mockGetCurrentTab).toHaveBeenCalledWith('sqlExpression');
+
+    const saveButton = getByText('Save');
+    fireEvent.click(saveButton);
+    expect(mockOnChange).toHaveBeenCalledWith({
+      label: 'Custom Label',
+      sqlExpression: 'year',
+      expressionType: 'SQL',
+    });
+  });
+});
diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopover.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopover.tsx
index 4806e5394a..96abf36484 100644
--- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopover.tsx
+++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopover.tsx
@@ -68,6 +68,7 @@ interface ColumnSelectPopoverProps {
   editedColumn?: ColumnMeta | AdhocColumn;
   onChange: (column: ColumnMeta | AdhocColumn) => void;
   onClose: () => void;
+  hasCustomLabel: boolean;
   setLabel: (title: string) => void;
   getCurrentTab: (tab: string) => void;
   label: string;
@@ -93,13 +94,14 @@ const getInitialColumnValues = (
 const ColumnSelectPopover = ({
   columns,
   editedColumn,
+  getCurrentTab,
+  hasCustomLabel,
+  isTemporal,
+  label,
   onChange,
   onClose,
   setDatasetModal,
   setLabel,
-  getCurrentTab,
-  label,
-  isTemporal,
 }: ColumnSelectPopoverProps) => {
   const datasourceType = useSelector<ExplorePageState, string | undefined>(
     state => state.explore.datasource.type,
@@ -117,6 +119,7 @@ const ColumnSelectPopover = ({
   const [selectedSimpleColumn, setSelectedSimpleColumn] = useState<
     ColumnMeta | undefined
   >(initialSimpleColumn);
+  const [selectedTab, setSelectedTab] = useState<string | null>(null);
 
   const [resizeButton, width, height] = useResizeButton(
     POPOVER_INITIAL_WIDTH,
@@ -188,7 +191,34 @@ const ColumnSelectPopover = ({
 
   useEffect(() => {
     getCurrentTab(defaultActiveTabKey);
-  }, [defaultActiveTabKey, getCurrentTab]);
+    setSelectedTab(defaultActiveTabKey);
+  }, [defaultActiveTabKey, getCurrentTab, setSelectedTab]);
+
+  useEffect(() => {
+    /* if the adhoc column is not set (because it was never edited) but the
+     * tab is selected and the label has changed, then we need to set the
+     * adhoc column manually */
+    if (
+      adhocColumn === undefined &&
+      selectedTab === 'sqlExpression' &&
+      hasCustomLabel
+    ) {
+      const sqlExpression =
+        selectedSimpleColumn?.column_name ||
+        selectedCalculatedColumn?.expression ||
+        '';
+      setAdhocColumn({ label, sqlExpression, expressionType: 'SQL' });
+    }
+  }, [
+    adhocColumn,
+    defaultActiveTabKey,
+    hasCustomLabel,
+    getCurrentTab,
+    label,
+    selectedCalculatedColumn,
+    selectedSimpleColumn,
+    selectedTab,
+  ]);
 
   const onSave = useCallback(() => {
     if (adhocColumn && adhocColumn.label !== label) {
@@ -225,6 +255,7 @@ const ColumnSelectPopover = ({
   const onTabChange = useCallback(
     tab => {
       getCurrentTab(tab);
+      setSelectedTab(tab);
       // @ts-ignore
       sqlEditorRef.current?.editor.focus();
     },
diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopoverTrigger.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopoverTrigger.tsx
index 4340317f04..341d91e616 100644
--- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopoverTrigger.tsx
+++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopoverTrigger.tsx
@@ -103,6 +103,7 @@ const ColumnSelectPopoverTrigger = ({
           setDatasetModal={setDatasetModal}
           onClose={handleClosePopover}
           onChange={onColumnEdit}
+          hasCustomLabel={hasCustomLabel}
           label={popoverLabel}
           setLabel={setPopoverLabel}
           getCurrentTab={getCurrentTab}
@@ -114,6 +115,7 @@ const ColumnSelectPopoverTrigger = ({
       columns,
       editedColumn,
       getCurrentTab,
+      hasCustomLabel,
       handleClosePopover,
       isTemporal,
       onColumnEdit,
@@ -121,10 +123,13 @@ const ColumnSelectPopoverTrigger = ({
     ],
   );
 
-  const onLabelChange = useCallback((e: any) => {
-    setPopoverLabel(e.target.value);
-    setHasCustomLabel(true);
-  }, []);
+  const onLabelChange = useCallback(
+    (e: any) => {
+      setPopoverLabel(e.target.value);
+      setHasCustomLabel(true);
+    },
+    [setPopoverLabel, setHasCustomLabel],
+  );
 
   const popoverTitle = useMemo(
     () => (


(superset) 07/15: feat(helm): Add option to deploy extra containers to remaining deployments (#26123)

Posted by mi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

michaelsmolina pushed a commit to branch 3.1
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 4a4f9983dfe9d0301f749f8b6e144a11dae7bab7
Author: Tom Murphy <to...@limepay.com.au>
AuthorDate: Fri Dec 1 09:39:15 2023 +1100

    feat(helm): Add option to deploy extra containers to remaining deployments (#26123)
    
    (cherry picked from commit 4f004048054776c3074dfa5b4d5fd684cced47f1)
---
 helm/superset/Chart.yaml                       | 2 +-
 helm/superset/README.md                        | 5 ++++-
 helm/superset/templates/deployment-beat.yaml   | 3 +++
 helm/superset/templates/deployment-flower.yaml | 3 +++
 helm/superset/templates/deployment-ws.yaml     | 3 +++
 helm/superset/values.yaml                      | 6 ++++++
 6 files changed, 20 insertions(+), 2 deletions(-)

diff --git a/helm/superset/Chart.yaml b/helm/superset/Chart.yaml
index 1f7d974c2b..459392f8a1 100644
--- a/helm/superset/Chart.yaml
+++ b/helm/superset/Chart.yaml
@@ -29,7 +29,7 @@ maintainers:
   - name: craig-rueda
     email: craig@craigrueda.com
     url: https://github.com/craig-rueda
-version: 0.11.0
+version: 0.11.1
 dependencies:
   - name: postgresql
     version: 12.1.6
diff --git a/helm/superset/README.md b/helm/superset/README.md
index 058ddd615f..886623e3f8 100644
--- a/helm/superset/README.md
+++ b/helm/superset/README.md
@@ -23,7 +23,7 @@ NOTE: This file is generated by helm-docs: https://github.com/norwoodj/helm-docs
 
 # superset
 
-![Version: 0.11.0](https://img.shields.io/badge/Version-0.11.0-informational?style=flat-square)
+![Version: 0.11.1](https://img.shields.io/badge/Version-0.11.1-informational?style=flat-square)
 
 Apache Superset is a modern, enterprise-ready business intelligence web application
 
@@ -130,6 +130,7 @@ On helm this can be set on `extraSecretEnv.SUPERSET_SECRET_KEY` or `configOverri
 | supersetCeleryBeat.containerSecurityContext | object | `{}` |  |
 | supersetCeleryBeat.deploymentAnnotations | object | `{}` | Annotations to be added to supersetCeleryBeat deployment |
 | supersetCeleryBeat.enabled | bool | `false` | This is only required if you intend to use alerts and reports |
+| supersetCeleryBeat.extraContainers | list | `[]` | Launch additional containers into supersetCeleryBeat pods |
 | supersetCeleryBeat.forceReload | bool | `false` | If true, forces deployment to reload on each upgrade |
 | supersetCeleryBeat.initContainers | list | a container waiting for postgres | List of init containers |
 | supersetCeleryBeat.podAnnotations | object | `{}` | Annotations to be added to supersetCeleryBeat pods |
@@ -142,6 +143,7 @@ On helm this can be set on `extraSecretEnv.SUPERSET_SECRET_KEY` or `configOverri
 | supersetCeleryFlower.containerSecurityContext | object | `{}` |  |
 | supersetCeleryFlower.deploymentAnnotations | object | `{}` | Annotations to be added to supersetCeleryFlower deployment |
 | supersetCeleryFlower.enabled | bool | `false` | Enables a Celery flower deployment (management UI to monitor celery jobs) WARNING: on superset 1.x, this requires a Superset image that has `flower<1.0.0` installed (which is NOT the case of the default images) flower>=1.0.0 requires Celery 5+ which Superset 1.5 does not support |
+| supersetCeleryFlower.extraContainers | list | `[]` | Launch additional containers into supersetCeleryFlower pods |
 | supersetCeleryFlower.initContainers | list | a container waiting for postgres and redis | List of init containers |
 | supersetCeleryFlower.livenessProbe.failureThreshold | int | `3` |  |
 | supersetCeleryFlower.livenessProbe.httpGet.path | string | `"/api/workers"` |  |
@@ -229,6 +231,7 @@ On helm this can be set on `extraSecretEnv.SUPERSET_SECRET_KEY` or `configOverri
 | supersetWebsockets.containerSecurityContext | object | `{}` |  |
 | supersetWebsockets.deploymentAnnotations | object | `{}` |  |
 | supersetWebsockets.enabled | bool | `false` | This is only required if you intend to use `GLOBAL_ASYNC_QUERIES` in `ws` mode see https://github.com/apache/superset/blob/master/CONTRIBUTING.md#async-chart-queries |
+| supersetWebsockets.extraContainers | list | `[]` | Launch additional containers into supersetWebsockets pods |
 | supersetWebsockets.image.pullPolicy | string | `"IfNotPresent"` |  |
 | supersetWebsockets.image.repository | string | `"oneacrefund/superset-websocket"` | There is no official image (yet), this one is community-supported |
 | supersetWebsockets.image.tag | string | `"latest"` |  |
diff --git a/helm/superset/templates/deployment-beat.yaml b/helm/superset/templates/deployment-beat.yaml
index eab9a6f3eb..30d1eff61a 100644
--- a/helm/superset/templates/deployment-beat.yaml
+++ b/helm/superset/templates/deployment-beat.yaml
@@ -120,6 +120,9 @@ spec:
             {{- else }}
               {{- toYaml .Values.resources | nindent 12 }}
             {{- end }}
+        {{- if .Values.supersetCeleryBeat.extraContainers }}
+            {{- toYaml .Values.supersetCeleryBeat.extraContainers | nindent 8 }}
+        {{- end }}
       {{- with .Values.nodeSelector }}
       nodeSelector: {{- toYaml . | nindent 8 }}
       {{- end }}
diff --git a/helm/superset/templates/deployment-flower.yaml b/helm/superset/templates/deployment-flower.yaml
index 2213ffa353..e4b05a17e9 100644
--- a/helm/superset/templates/deployment-flower.yaml
+++ b/helm/superset/templates/deployment-flower.yaml
@@ -115,6 +115,9 @@ spec:
             {{- else }}
               {{- toYaml .Values.resources | nindent 12 }}
             {{- end }}
+        {{- if .Values.supersetCeleryFlower.extraContainers }}
+            {{- toYaml .Values.supersetCeleryFlower.extraContainers | nindent 8 }}
+        {{- end }}
       {{- with .Values.nodeSelector }}
       nodeSelector: {{- toYaml . | nindent 8 }}
       {{- end }}
diff --git a/helm/superset/templates/deployment-ws.yaml b/helm/superset/templates/deployment-ws.yaml
index 6bc9faac67..7612900b07 100644
--- a/helm/superset/templates/deployment-ws.yaml
+++ b/helm/superset/templates/deployment-ws.yaml
@@ -114,6 +114,9 @@ spec:
           {{- if .Values.supersetWebsockets.livenessProbe }}
           livenessProbe: {{- .Values.supersetWebsockets.livenessProbe | toYaml | nindent 12 }}
           {{- end }}
+        {{- if .Values.supersetWebsockets.extraContainers }}
+            {{- toYaml .Values.supersetWebsockets.extraContainers | nindent 8 }}
+        {{- end }}
       {{- with .Values.nodeSelector }}
       nodeSelector: {{- toYaml . | nindent 8 }}
       {{- end }}
diff --git a/helm/superset/values.yaml b/helm/superset/values.yaml
index a5b70559d1..26d4547420 100644
--- a/helm/superset/values.yaml
+++ b/helm/superset/values.yaml
@@ -443,6 +443,8 @@ supersetCeleryBeat:
         - /bin/sh
         - -c
         - dockerize -wait "tcp://$DB_HOST:$DB_PORT" -wait "tcp://$REDIS_HOST:$REDIS_PORT" -timeout 120s
+  # -- Launch additional containers into supersetCeleryBeat pods
+  extraContainers: []
   # -- Annotations to be added to supersetCeleryBeat deployment
   deploymentAnnotations: {}
   # -- Affinity to be added to supersetCeleryBeat deployment
@@ -524,6 +526,8 @@ supersetCeleryFlower:
         - /bin/sh
         - -c
         - dockerize -wait "tcp://$DB_HOST:$DB_PORT" -wait "tcp://$REDIS_HOST:$REDIS_PORT" -timeout 120s
+  # -- Launch additional containers into supersetCeleryFlower pods
+  extraContainers: []
   # -- Annotations to be added to supersetCeleryFlower deployment
   deploymentAnnotations: {}
   # -- Affinity to be added to supersetCeleryFlower deployment
@@ -590,6 +594,8 @@ supersetWebsockets:
       http: nil
   command: []
   resources: {}
+  # -- Launch additional containers into supersetWebsockets pods
+  extraContainers: []
   deploymentAnnotations: {}
   # -- Affinity to be added to supersetWebsockets deployment
   affinity: {}


(superset) 08/15: feat: Adds legacy time support for Waterfall chart (#26136)

Posted by mi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

michaelsmolina pushed a commit to branch 3.1
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 79d59750286eb9cac9fbfa2b066ae203cf11d2eb
Author: Michael S. Molina <70...@users.noreply.github.com>
AuthorDate: Fri Dec 1 10:04:53 2023 -0300

    feat: Adds legacy time support for Waterfall chart (#26136)
    
    (cherry picked from commit f405ba033e04e2694f869738163d33e1d3991297)
---
 .../plugins/plugin-chart-echarts/src/Waterfall/buildQuery.ts      | 7 +++----
 .../plugins/plugin-chart-echarts/src/Waterfall/controlPanel.tsx   | 8 +++++---
 .../plugins/plugin-chart-echarts/src/Waterfall/index.ts           | 2 +-
 .../plugins/plugin-chart-echarts/src/Waterfall/transformProps.ts  | 6 +++++-
 4 files changed, 14 insertions(+), 9 deletions(-)

diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/buildQuery.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/buildQuery.ts
index e47effb3c2..deb3571938 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/buildQuery.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/buildQuery.ts
@@ -19,15 +19,14 @@
 import {
   buildQueryContext,
   ensureIsArray,
-  getXAxisColumn,
-  isXAxisSet,
   QueryFormData,
 } from '@superset-ui/core';
 
 export default function buildQuery(formData: QueryFormData) {
+  const { x_axis, granularity_sqla, groupby } = formData;
   const columns = [
-    ...(isXAxisSet(formData) ? ensureIsArray(getXAxisColumn(formData)) : []),
-    ...ensureIsArray(formData.groupby),
+    ...ensureIsArray(x_axis || granularity_sqla),
+    ...ensureIsArray(groupby),
   ];
   return buildQueryContext(formData, baseQueryObject => [
     {
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/controlPanel.tsx
index 7a71dd4fcb..d07e5175e6 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/controlPanel.tsx
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/controlPanel.tsx
@@ -17,25 +17,27 @@
  * under the License.
  */
 import React from 'react';
-import { t } from '@superset-ui/core';
+import { hasGenericChartAxes, t } from '@superset-ui/core';
 import {
   ControlPanelConfig,
   ControlSubSectionHeader,
   D3_TIME_FORMAT_DOCS,
   DEFAULT_TIME_FORMAT,
   formatSelectOptions,
+  sections,
   sharedControls,
 } from '@superset-ui/chart-controls';
 import { showValueControl } from '../controls';
 
 const config: ControlPanelConfig = {
   controlPanelSections: [
+    sections.genericTime,
     {
       label: t('Query'),
       expanded: true,
       controlSetRows: [
-        ['x_axis'],
-        ['time_grain_sqla'],
+        [hasGenericChartAxes ? 'x_axis' : null],
+        [hasGenericChartAxes ? 'time_grain_sqla' : null],
         ['groupby'],
         ['metric'],
         ['adhoc_filters'],
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/index.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/index.ts
index c0d7a11067..b8c66fabb1 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/index.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/index.ts
@@ -61,7 +61,7 @@ export default class EchartsWaterfallChartPlugin extends ChartPlugin<
           { url: example3 },
         ],
         name: t('Waterfall Chart'),
-        tags: [t('Categorical'), t('Comparison'), t('ECharts')],
+        tags: [t('Categorical'), t('Comparison'), t('ECharts'), t('Popular')],
         thumbnail,
       }),
       transformProps,
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/transformProps.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/transformProps.ts
index 7b5faed1b2..84fbbf6cb9 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/transformProps.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/transformProps.ts
@@ -185,6 +185,7 @@ export default function transformProps(
   const { setDataMask = () => {}, onContextMenu, onLegendStateChanged } = hooks;
   const {
     currencyFormat,
+    granularitySqla = '',
     groupby,
     increaseColor,
     decreaseColor,
@@ -213,7 +214,10 @@ export default function transformProps(
   const breakdownName = isAdhocColumn(breakdownColumn)
     ? breakdownColumn.label!
     : breakdownColumn;
-  const xAxisName = isAdhocColumn(xAxis) ? xAxis.label! : xAxis;
+  const xAxisColumn = xAxis || granularitySqla;
+  const xAxisName = isAdhocColumn(xAxisColumn)
+    ? xAxisColumn.label!
+    : xAxisColumn;
   const metricLabel = getMetricLabel(metric);
 
   const transformedData = transformer({


(superset) 01/15: fix: alias column when fetching values (#26120)

Posted by mi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

michaelsmolina pushed a commit to branch 3.1
in repository https://gitbox.apache.org/repos/asf/superset.git

commit f4fd0e19e26a7545597da49cc9e1616fdc747433
Author: Beto Dealmeida <ro...@dealmeida.net>
AuthorDate: Tue Nov 28 15:11:20 2023 -0500

    fix: alias column when fetching values (#26120)
    
    (cherry picked from commit 7223633da600fb3973834cb24d977f194a76f328)
---
 superset/models/helpers.py | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/superset/models/helpers.py b/superset/models/helpers.py
index fc947ff577..df3dd93488 100644
--- a/superset/models/helpers.py
+++ b/superset/models/helpers.py
@@ -1352,7 +1352,13 @@ class ExploreMixin:  # pylint: disable=too-many-public-methods
         tbl, cte = self.get_from_clause(tp)
 
         qry = (
-            sa.select([target_col.get_sqla_col(template_processor=tp)])
+            sa.select(
+                # The alias (label) here is important because some dialects will
+                # automatically add a random alias to the projection because of the
+                # call to DISTINCT; others will uppercase the column names. This
+                # gives us a deterministic column name in the dataframe.
+                [target_col.get_sqla_col(template_processor=tp).label("column_values")]
+            )
             .select_from(tbl)
             .distinct()
         )
@@ -1368,7 +1374,7 @@ class ExploreMixin:  # pylint: disable=too-many-public-methods
             sql = self.mutate_query_from_config(sql)
 
             df = pd.read_sql_query(sql=sql, con=engine)
-            return df[denormalized_col_name].to_list()
+            return df["column_values"].to_list()
 
     def get_timestamp_expression(
         self,


(superset) 06/15: fix(annotations): time grain column (#26140)

Posted by mi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

michaelsmolina pushed a commit to branch 3.1
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 26e59662fb56b7a604a2c54f289b53a568410c4b
Author: Beto Dealmeida <ro...@dealmeida.net>
AuthorDate: Thu Nov 30 11:13:12 2023 -0500

    fix(annotations): time grain column (#26140)
    
    (cherry picked from commit cff473f825825a419eb544d56960ce3a8a541592)
---
 .../src/components/Chart/chartAction.js            |  9 ++-
 .../src/components/Chart/chartActions.test.js      | 67 ++++++++++++++++++++++
 2 files changed, 73 insertions(+), 3 deletions(-)

diff --git a/superset-frontend/src/components/Chart/chartAction.js b/superset-frontend/src/components/Chart/chartAction.js
index 42aa3fc5b1..8cd3785ae5 100644
--- a/superset-frontend/src/components/Chart/chartAction.js
+++ b/superset-frontend/src/components/Chart/chartAction.js
@@ -269,9 +269,12 @@ export function runAnnotationQuery({
       return Promise.resolve();
     }
 
-    const granularity = fd.time_grain_sqla || fd.granularity;
-    fd.time_grain_sqla = granularity;
-    fd.granularity = granularity;
+    // In the original formData the `granularity` attribute represents the time grain (eg
+    // `P1D`), but in the request payload it corresponds to the name of the column where
+    // the time grain should be applied (eg, `Date`), so we need to move things around.
+    fd.time_grain_sqla = fd.time_grain_sqla || fd.granularity;
+    fd.granularity = fd.granularity_sqla;
+
     const overridesKeys = Object.keys(annotation.overrides);
     if (overridesKeys.includes('since') || overridesKeys.includes('until')) {
       annotation.overrides = {
diff --git a/superset-frontend/src/components/Chart/chartActions.test.js b/superset-frontend/src/components/Chart/chartActions.test.js
index b44ca7c8d7..b3a6fed9f5 100644
--- a/superset-frontend/src/components/Chart/chartActions.test.js
+++ b/superset-frontend/src/components/Chart/chartActions.test.js
@@ -21,6 +21,7 @@ import fetchMock from 'fetch-mock';
 import sinon from 'sinon';
 
 import * as chartlib from '@superset-ui/core';
+import { SupersetClient } from '@superset-ui/core';
 import { LOG_EVENT } from 'src/logger/actions';
 import * as exploreUtils from 'src/explore/exploreUtils';
 import * as actions from 'src/components/Chart/chartAction';
@@ -233,4 +234,70 @@ describe('chart actions', () => {
       expect(json.result[0].value.toString()).toEqual(expectedBigNumber);
     });
   });
+
+  describe('runAnnotationQuery', () => {
+    const mockDispatch = jest.fn();
+    const mockGetState = () => ({
+      charts: {
+        chartKey: {
+          latestQueryFormData: {
+            time_grain_sqla: 'P1D',
+            granularity_sqla: 'Date',
+          },
+        },
+      },
+    });
+
+    beforeEach(() => {
+      jest.clearAllMocks();
+    });
+
+    it('should dispatch annotationQueryStarted and annotationQuerySuccess on successful query', async () => {
+      const annotation = {
+        name: 'Holidays',
+        annotationType: 'EVENT',
+        sourceType: 'NATIVE',
+        color: null,
+        opacity: '',
+        style: 'solid',
+        width: 1,
+        showMarkers: false,
+        hideLine: false,
+        value: 1,
+        overrides: {
+          time_range: null,
+        },
+        show: true,
+        showLabel: false,
+        titleColumn: '',
+        descriptionColumns: [],
+        timeColumn: '',
+        intervalEndColumn: '',
+      };
+      const key = undefined;
+
+      const postSpy = jest.spyOn(SupersetClient, 'post');
+      postSpy.mockImplementation(() =>
+        Promise.resolve({ json: { result: [] } }),
+      );
+      const buildV1ChartDataPayloadSpy = jest.spyOn(
+        exploreUtils,
+        'buildV1ChartDataPayload',
+      );
+
+      const queryFunc = actions.runAnnotationQuery({ annotation, key });
+      await queryFunc(mockDispatch, mockGetState);
+
+      expect(buildV1ChartDataPayloadSpy).toHaveBeenCalledWith({
+        formData: {
+          granularity: 'Date',
+          granularity_sqla: 'Date',
+          time_grain_sqla: 'P1D',
+        },
+        force: false,
+        resultFormat: 'json',
+        resultType: 'full',
+      });
+    });
+  });
 });


(superset) 10/15: fix(database-import): Support importing a DB connection with a version set (#26116)

Posted by mi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

michaelsmolina pushed a commit to branch 3.1
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 77332bfb3891ce401d56522e7bb563b90081ba11
Author: Vitor Avila <96...@users.noreply.github.com>
AuthorDate: Fri Dec 1 18:20:27 2023 -0300

    fix(database-import): Support importing a DB connection with a version set (#26116)
    
    (cherry picked from commit c033ca959d391cca8f0e5a34e563ca4297ce2848)
---
 superset/databases/schemas.py                       |  1 +
 .../databases/commands/importers/v1/import_test.py  | 21 +++++++++++++++++++++
 2 files changed, 22 insertions(+)

diff --git a/superset/databases/schemas.py b/superset/databases/schemas.py
index ac4d0e127d..b56c98c5d6 100644
--- a/superset/databases/schemas.py
+++ b/superset/databases/schemas.py
@@ -750,6 +750,7 @@ class ImportV1DatabaseExtraSchema(Schema):
     allows_virtual_table_explore = fields.Boolean(required=False)
     cancel_query_on_windows_unload = fields.Boolean(required=False)
     disable_data_preview = fields.Boolean(required=False)
+    version = fields.String(required=False, allow_none=True)
 
 
 class ImportV1DatabaseSchema(Schema):
diff --git a/tests/unit_tests/databases/commands/importers/v1/import_test.py b/tests/unit_tests/databases/commands/importers/v1/import_test.py
index dcd093a9cf..5fb4d12ce5 100644
--- a/tests/unit_tests/databases/commands/importers/v1/import_test.py
+++ b/tests/unit_tests/databases/commands/importers/v1/import_test.py
@@ -17,6 +17,7 @@
 # pylint: disable=unused-argument, import-outside-toplevel, invalid-name
 
 import copy
+import json
 
 import pytest
 from pytest_mock import MockFixture
@@ -142,3 +143,23 @@ def test_import_database_without_permission(
         str(excinfo.value)
         == "Database doesn't exist and user doesn't have permission to create databases"
     )
+
+
+def test_import_database_with_version(mocker: MockFixture, session: Session) -> None:
+    """
+    Test importing a database with a version set.
+    """
+    from superset import security_manager
+    from superset.commands.database.importers.v1.utils import import_database
+    from superset.models.core import Database
+    from tests.integration_tests.fixtures.importexport import database_config
+
+    mocker.patch.object(security_manager, "can_access", return_value=True)
+
+    engine = session.get_bind()
+    Database.metadata.create_all(engine)  # pylint: disable=no-member
+
+    config = copy.deepcopy(database_config)
+    config["extra"]["version"] = "1.1.1"
+    database = import_database(session, config)
+    assert json.loads(database.extra)["version"] == "1.1.1"