You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by vi...@apache.org on 2022/04/04 08:12:00 UTC

[superset] tag 1.5.0rc1 created (now 96901d6c46)

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

villebro pushed a change to tag 1.5.0rc1
in repository https://gitbox.apache.org/repos/asf/superset.git


      at 96901d6c46 (commit)
This tag includes the following new commits:

     new 038d114b07 perf: improve perf in SIP-68 migration (#19416)
     new 625555ac7e chore: attempt to force docs deploy (#19424)
     new 7aba89c486 fix: add missing init on python pkg key_value (#19428)
     new bfa203aee0 fix: missing init on importexport pkg (#19435)
     new 0c78522bfe chore: Disables no literal colors for the theme configuration (#19437)
     new a7ee677154 docs: add 1.4.2 changelog entries (#19411)
     new 795ed3c719 feat: Embedded dashboard configuration (#19364)
     new 3c09690ed2 fix: can't save dataset modal (#19452)
     new e93d64d58e fix(dashboard): make to show the correct owned objects (#19372)
     new 91bf9bd68b chore: Removes hard-coded colors from legacy-preset-chart-nvd3 (#19443)
     new 99c8f9bd13 chore: Removes plugin-chart-pivot-table hard-coded colors (#19439)
     new 70d800dc27 refactor: Removes the CSS files from the Calendar Heatmap plugin (#19436)
     new a96ff005fd refactor: Removes the CSS files from the Heatmap plugin (#19417)
     new e4cbbdc653 fix(drill): specify an SA URL parm of `impersonation_target` for drill+sadrill (#19252)
     new 840be9972f feat: improve adhoc SQL validation (#19454)
     new af0dc75207 feat(sql lab): enable ACE editor search in SQL editors (#19328)
     new 3eece91378 test(native filter): add new test for dependent filter (#19392)
     new 3c627bdc61 fix: request samples with default row limit (#19456)
     new 3ff9cdeb65 fix: can't sync temporal flag on virtual table (#19366)
     new 90f49e261e fix: flaky test for dashboard changed on (#19483)
     new f44ed063e8 chore(plugin-chart-echarts): upgrade echarts 5.3.2 (#19481)
     new dba4610f9b fix(dashboard list): do not show favorite star for anonymous users  #18210 (#19409)
     new 96901d6c46 update changelog, updating, version

The 23 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.



[superset] 11/23: chore: Removes plugin-chart-pivot-table hard-coded colors (#19439)

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

villebro pushed a commit to tag 1.5.0rc1
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 99c8f9bd1319f1a245dbc323cecfc3137458c167
Author: Michael S. Molina <70...@users.noreply.github.com>
AuthorDate: Thu Mar 31 13:30:36 2022 -0300

    chore: Removes plugin-chart-pivot-table hard-coded colors (#19439)
    
    (cherry picked from commit 31095a523d5bb379fef5ef4cdf5738031857179b)
---
 .../plugins/plugin-chart-pivot-table/src/react-pivottable/Styles.js | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/superset-frontend/plugins/plugin-chart-pivot-table/src/react-pivottable/Styles.js b/superset-frontend/plugins/plugin-chart-pivot-table/src/react-pivottable/Styles.js
index 06bfdd3b0c..7ff2c0e526 100644
--- a/superset-frontend/plugins/plugin-chart-pivot-table/src/react-pivottable/Styles.js
+++ b/superset-frontend/plugins/plugin-chart-pivot-table/src/react-pivottable/Styles.js
@@ -84,7 +84,7 @@ export const Styles = styled.div`
     }
 
     table.pvtTable tr th.active {
-      background-color: #d9dbe4;
+      background-color: ${theme.colors.primary.light3};
     }
 
     table.pvtTable .pvtTotalLabel {
@@ -97,7 +97,7 @@ export const Styles = styled.div`
     }
 
     table.pvtTable tbody tr td {
-      color: #2a3f5f;
+      color: ${theme.colors.primary.dark2};
       padding: ${theme.gridUnit}px;
       background-color: ${theme.colors.grayscale.light5};
       border-top: 1px solid ${theme.colors.grayscale.light2};
@@ -133,7 +133,7 @@ export const Styles = styled.div`
     }
 
     .hoverable:hover {
-      background-color: #eceef2;
+      background-color: ${theme.colors.primary.light4};
       cursor: pointer;
     }
   `}


[superset] 05/23: chore: Disables no literal colors for the theme configuration (#19437)

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

villebro pushed a commit to tag 1.5.0rc1
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 0c78522bfe9d5a08f09a48fae9de55e9bd83c9ff
Author: Michael S. Molina <70...@users.noreply.github.com>
AuthorDate: Wed Mar 30 11:57:21 2022 -0300

    chore: Disables no literal colors for the theme configuration (#19437)
    
    (cherry picked from commit 193744842bd168ca0ded47ee9d32a9e9fed2cc95)
---
 superset-frontend/.eslintrc.js | 1 +
 1 file changed, 1 insertion(+)

diff --git a/superset-frontend/.eslintrc.js b/superset-frontend/.eslintrc.js
index 8decc32ee7..facd3431f4 100644
--- a/superset-frontend/.eslintrc.js
+++ b/superset-frontend/.eslintrc.js
@@ -194,6 +194,7 @@ module.exports = {
         'fixtures.*',
         'cypress-base/cypress/**/*',
         'Stories.tsx',
+        'packages/superset-ui-core/src/style/index.tsx',
       ],
       rules: {
         'theme-colors/no-literal-colors': 0,


[superset] 17/23: test(native filter): add new test for dependent filter (#19392)

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

villebro pushed a commit to tag 1.5.0rc1
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 3eece91378cf45755d472d4a95c1b47a6fb47f8a
Author: Jinghuayao <81...@users.noreply.github.com>
AuthorDate: Fri Apr 1 01:06:48 2022 -0700

    test(native filter): add new test for dependent filter (#19392)
    
    * add new test for dependent filter
    
    (cherry picked from commit 2a75e4c3c37ec6dddab5ecdec8908a0f18ed391d)
---
 docker/docker-init.sh                              |   2 +-
 .../integration/dashboard/dashboard.helper.ts      | 120 +++++++++++++++++++++
 .../integration/dashboard/nativeFilters.test.ts    |  97 ++++++++++++-----
 .../cypress-base/cypress/support/index.d.ts        |  14 +++
 .../cypress-base/cypress/support/index.ts          |  86 +++++++++++++++
 superset-frontend/package-lock.json                |   2 +-
 6 files changed, 292 insertions(+), 29 deletions(-)

diff --git a/docker/docker-init.sh b/docker/docker-init.sh
index d5ead50398..c928c1ba50 100755
--- a/docker/docker-init.sh
+++ b/docker/docker-init.sh
@@ -41,7 +41,7 @@ ADMIN_PASSWORD="admin"
 # If Cypress run – overwrite the password for admin and export env variables
 if [ "$CYPRESS_CONFIG" == "true" ]; then
     ADMIN_PASSWORD="general"
-    export SUPERSET_CONFIG=tests.superset_test_config
+    export SUPERSET_CONFIG=tests.integration_tests.superset_test_config
     export SUPERSET_TESTENV=true
     export ENABLE_REACT_CRUD_VIEWS=true
     export SUPERSET__SQLALCHEMY_DATABASE_URI=postgresql+psycopg2://superset:superset@db:5432/superset
diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/dashboard.helper.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/dashboard.helper.ts
index 1458fc7d59..9822f70359 100644
--- a/superset-frontend/cypress-base/cypress/integration/dashboard/dashboard.helper.ts
+++ b/superset-frontend/cypress-base/cypress/integration/dashboard/dashboard.helper.ts
@@ -1,4 +1,9 @@
 import { getChartAlias, Slice } from 'cypress/utils/vizPlugins';
+import {
+  dashboardView,
+  editDashboardView,
+  nativeFilters,
+} from 'cypress/support/directories';
 
 /**
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -25,7 +30,13 @@ export const testItems = {
   dashboard: 'Cypress Sales Dashboard',
   dataset: 'Vehicle Sales',
   chart: 'Cypress chart',
+  newChart: 'New Cypress Chart',
+  createdDashboard: 'New Dashboard',
   defaultNameDashboard: '[ untitled dashboard ]',
+  newDashboardTitle: `Test dashboard [NEW TEST]`,
+  bulkFirstNameDashboard: 'First Dash',
+  bulkSecondNameDashboard: 'Second Dash',
+  worldBanksDataCopy: `World Bank's Data [copy]`,
 };
 
 export const CHECK_DASHBOARD_FAVORITE_ENDPOINT =
@@ -133,3 +144,112 @@ export function resize(selector: string) {
     },
   };
 }
+
+export function cleanUp() {
+  cy.deleteDashboardByName(testItems.dashboard);
+  cy.deleteDashboardByName(testItems.defaultNameDashboard);
+  cy.deleteDashboardByName('');
+  cy.deleteDashboardByName(testItems.newDashboardTitle);
+  cy.deleteDashboardByName(testItems.bulkFirstNameDashboard);
+  cy.deleteDashboardByName(testItems.bulkSecondNameDashboard);
+  cy.deleteDashboardByName(testItems.createdDashboard);
+  cy.deleteDashboardByName(testItems.worldBanksDataCopy);
+  cy.deleteChartByName(testItems.chart);
+  cy.deleteChartByName(testItems.newChart);
+}
+
+/** ************************************************************************
+ * Clicks on new filter button
+ * @returns {None}
+ * @summary helper for adding new filter
+ ************************************************************************* */
+export function clickOnAddFilterInModal() {
+  return cy
+    .get(nativeFilters.addFilterButton.button)
+    .first()
+    .click()
+    .then(() => {
+      cy.get(nativeFilters.addFilterButton.dropdownItem)
+        .contains('Filter')
+        .click({ force: true });
+    });
+}
+
+/** ************************************************************************
+ * Fills value native filter form with basic information
+ * @param {string} name name for filter
+ * @param {string} dataset which dataset should be used
+ * @param {string} filterColumn which column should be used
+ * @returns {None}
+ * @summary helper for filling value native filter form
+ ************************************************************************* */
+export function fillValueNativeFilterForm(
+  name: string,
+  dataset: string,
+  filterColumn: string,
+) {
+  cy.get(nativeFilters.modal.container)
+    .find(nativeFilters.filtersPanel.filterName)
+    .last()
+    .click({ scrollBehavior: false })
+    .type(name, { scrollBehavior: false });
+  cy.get(nativeFilters.modal.container)
+    .find(nativeFilters.filtersPanel.datasetName)
+    .last()
+    .click({ scrollBehavior: false })
+    .type(`${dataset}{enter}`, { scrollBehavior: false });
+  cy.get(nativeFilters.silentLoading).should('not.exist');
+  cy.get(nativeFilters.filtersPanel.filterInfoInput)
+    .last()
+    .should('be.visible')
+    .click({ force: true });
+  cy.get(nativeFilters.filtersPanel.filterInfoInput).last().type(filterColumn);
+  cy.get(nativeFilters.filtersPanel.inputDropdown)
+    .should('be.visible', { timeout: 20000 })
+    .last()
+    .click();
+}
+/** ************************************************************************
+ * Get native filter placeholder e.g 9 options
+ * @param {number} index which input it fills
+ * @returns cy object for assertions
+ * @summary helper for getting placeholder value
+ ************************************************************************* */
+export function getNativeFilterPlaceholderWithIndex(index: number) {
+  return cy.get(nativeFilters.filtersPanel.columnEmptyInput).eq(index);
+}
+
+/** ************************************************************************
+ * Apply native filter value from dashboard view
+ * @param {number} index which input it fills
+ * @param {string} value what is filter value
+ * @returns {null}
+ * @summary put value to nth native filter input in view
+ ************************************************************************* */
+export function applyNativeFilterValueWithIndex(index: number, value: string) {
+  cy.get(nativeFilters.filterFromDashboardView.filterValueInput)
+    .eq(index)
+    .parent()
+    .should('be.visible', { timeout: 10000 })
+    .type(`${value}{enter}`);
+  // click the title to dismiss shown options
+  cy.get(nativeFilters.filterFromDashboardView.filterName).eq(index).click();
+}
+
+/** ************************************************************************
+ * Fills parent filter input
+ * @param {number} index which input it fills
+ * @param {string} value on which filter it depends on
+ * @returns {null}
+ * @summary takes first or second input and modify the depends on filter value
+ ************************************************************************* */
+export function addParentFilterWithValue(index: number, value: string) {
+  return cy
+    .get(nativeFilters.filterConfigurationSections.displayedSection)
+    .within(() => {
+      cy.get('input[aria-label="Limit type"]')
+        .eq(index)
+        .click({ force: true })
+        .type(`${value}{enter}`, { delay: 30, force: true });
+    });
+}
diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilters.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilters.test.ts
index 92a86f6369..6fab297a1d 100644
--- a/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilters.test.ts
+++ b/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilters.test.ts
@@ -22,7 +22,17 @@ import {
   nativeFilters,
   exploreView,
 } from 'cypress/support/directories';
-import { testItems } from './dashboard.helper';
+import {
+  cleanUp,
+  testItems,
+  WORLD_HEALTH_CHARTS,
+  waitForChartLoad,
+  clickOnAddFilterInModal,
+  fillValueNativeFilterForm,
+  getNativeFilterPlaceholderWithIndex,
+  addParentFilterWithValue,
+  applyNativeFilterValueWithIndex,
+} from './dashboard.helper';
 import { DASHBOARD_LIST } from '../dashboard_list/dashboard_list.helper';
 import { CHART_LIST } from '../chart_list/chart_list.helper';
 import { FORM_DATA_DEFAULTS } from '../explore/visualizations/shared.helper';
@@ -39,21 +49,27 @@ const milliseconds = new Date().getTime();
 const dashboard = `Test Dashboard${milliseconds}`;
 
 describe('Nativefilters Sanity test', () => {
-  before(() => {
+  beforeEach(() => {
     cy.login();
+    cleanUp();
     cy.intercept('/api/v1/dashboard/?q=**').as('dashboardsList');
     cy.intercept('POST', '**/copy_dash/*').as('copy');
     cy.intercept('/api/v1/dashboard/*').as('dashboard');
-    cy.request(
-      'api/v1/dashboard/?q=(order_column:changed_on_delta_humanized,order_direction:desc,page:0,page_size:100)',
-    ).then(xhr => {
-      const dashboards = xhr.body.result;
+    cy.intercept('GET', '**/api/v1/dataset/**').as('datasetLoad');
+    cy.intercept('**/api/v1/dashboard/?q=**').as('dashboardsList');
+    cy.visit('dashboard/list/');
+    cy.contains('Actions');
+    cy.wait('@dashboardsList').then(xhr => {
+      const dashboards = xhr.response?.body.result;
+      /* eslint-disable no-unused-expressions */
+      expect(dashboards).not.to.be.undefined;
       const worldBankDashboard = dashboards.find(
         (d: { dashboard_title: string }) =>
           d.dashboard_title === "World Bank's Data",
       );
       cy.visit(worldBankDashboard.url);
     });
+    WORLD_HEALTH_CHARTS.forEach(waitForChartLoad);
     cy.get(dashboardView.threeDotsMenuIcon).should('be.visible').click();
     cy.get(dashboardView.saveAsMenuOption).should('be.visible').click();
     cy.get(dashboardView.saveModal.dashboardNameInput)
@@ -65,19 +81,10 @@ describe('Nativefilters Sanity test', () => {
       .its('response.statusCode')
       .should('eq', 200);
   });
-  beforeEach(() => {
-    cy.login();
-    cy.request(
-      'api/v1/dashboard/?q=(order_column:changed_on_delta_humanized,order_direction:desc,page:0,page_size:100)',
-    ).then(xhr => {
-      const dashboards = xhr.body.result;
-      const testDashboard = dashboards.find(
-        (d: { dashboard_title: string }) =>
-          d.dashboard_title === testItems.dashboard,
-      );
-      cy.visit(testDashboard.url);
-    });
+  afterEach(() => {
+    cleanUp();
   });
+
   it('User can expand / retract native filter sidebar on a dashboard', () => {
     cy.get(nativeFilters.createFilterButton).should('not.exist');
     cy.get(nativeFilters.filterFromDashboardView.expand)
@@ -390,15 +397,6 @@ describe('Nativefilters Sanity test', () => {
     cy.get('.line').within(() => {
       cy.contains('United States').should('be.visible');
     });
-
-    // clean up the default setting
-    cy.get(nativeFilters.filterFromDashboardView.expand).click({ force: true });
-    cy.get(nativeFilters.filterFromDashboardView.createFilterButton).click();
-    cy.contains('Filter has default value').click();
-    cy.get(nativeFilters.modal.footer)
-      .find(nativeFilters.modal.saveButton)
-      .should('be.visible')
-      .click({ force: true });
   });
 
   it('User can create a time grain filter', () => {
@@ -566,6 +564,51 @@ describe('Nativefilters Sanity test', () => {
       .should('be.visible', { timeout: 40000 })
       .contains('country_name');
   });
+
+  it('User can create parent filters using "Values are dependent on other filters"', () => {
+    cy.get(nativeFilters.filterFromDashboardView.expand)
+      .should('be.visible')
+      .click({ force: true });
+    cy.get(nativeFilters.filterFromDashboardView.createFilterButton).click();
+    // Create parent filter 'region'.
+    fillValueNativeFilterForm('region', 'wb_health_population', 'region');
+    // Create filter 'country_name' depend on region filter.
+    clickOnAddFilterInModal();
+    fillValueNativeFilterForm(
+      'country_name',
+      'wb_health_population',
+      'country_name',
+    );
+    cy.get(nativeFilters.filterConfigurationSections.displayedSection).within(
+      () => {
+        cy.contains('Values are dependent on other filters')
+          .should('be.visible')
+          .click();
+      },
+    );
+    addParentFilterWithValue(0, 'region');
+    cy.wait(1000);
+    cy.get(nativeFilters.modal.footer)
+      .contains('Save')
+      .should('be.visible')
+      .click();
+    // Validate both filter in dashboard view.
+    WORLD_HEALTH_CHARTS.forEach(waitForChartLoad);
+    ['region', 'country_name'].forEach(it => {
+      cy.get(nativeFilters.filterFromDashboardView.filterName)
+        .contains(it)
+        .should('be.visible');
+    });
+    getNativeFilterPlaceholderWithIndex(1)
+      .invoke('text')
+      .should('equal', '214 options', { timeout: 20000 });
+    // apply first filter value and validate 2nd filter is depden on 1st filter.
+    applyNativeFilterValueWithIndex(0, 'East Asia & Pacific');
+
+    getNativeFilterPlaceholderWithIndex(0).should('have.text', '36 options', {
+      timeout: 20000,
+    });
+  });
 });
 
 xdescribe('Nativefilters', () => {
diff --git a/superset-frontend/cypress-base/cypress/support/index.d.ts b/superset-frontend/cypress-base/cypress/support/index.d.ts
index fdacf3232b..eca68a7ced 100644
--- a/superset-frontend/cypress-base/cypress/support/index.d.ts
+++ b/superset-frontend/cypress-base/cypress/support/index.d.ts
@@ -47,6 +47,20 @@ declare namespace Cypress {
       querySubstring?: string | RegExp;
       chartSelector?: JQuery.Selector;
     }): cy;
+
+    /**
+     * Get
+     */
+    getDashboards(): cy;
+    getCharts(): cy;
+
+    /**
+     * Delete
+     */
+    deleteDashboard(id: number): cy;
+    deleteDashboardByName(name: string): cy;
+    deleteChartByName(name: string): cy;
+    deleteChart(id: number): cy;
   }
 }
 
diff --git a/superset-frontend/cypress-base/cypress/support/index.ts b/superset-frontend/cypress-base/cypress/support/index.ts
index e22f69975e..905d00df9f 100644
--- a/superset-frontend/cypress-base/cypress/support/index.ts
+++ b/superset-frontend/cypress-base/cypress/support/index.ts
@@ -19,6 +19,7 @@
 import '@cypress/code-coverage/support';
 
 const BASE_EXPLORE_URL = '/superset/explore/?form_data=';
+const TokenName = Cypress.env('TOKEN_NAME');
 
 /* eslint-disable consistent-return */
 Cypress.on('uncaught:exception', err => {
@@ -102,3 +103,88 @@ Cypress.Commands.add(
     return cy;
   },
 );
+
+Cypress.Commands.add('deleteDashboardByName', (name: string) =>
+  cy.getDashboards().then((dashboards: any) => {
+    dashboards?.forEach((element: any) => {
+      if (element.dashboard_title === name) {
+        const elementId = element.id;
+        cy.deleteDashboard(elementId);
+      }
+    });
+  }),
+);
+
+Cypress.Commands.add('deleteDashboard', (id: number) =>
+  cy
+    .request({
+      method: 'DELETE',
+      url: `api/v1/dashboard/${id}`,
+      headers: {
+        Cookie: `csrf_access_token=${window.localStorage.getItem(
+          'access_token',
+        )}`,
+        'Content-Type': 'application/json',
+        Authorization: `Bearer ${TokenName}`,
+        'X-CSRFToken': `${window.localStorage.getItem('access_token')}`,
+        Referer: `${Cypress.config().baseUrl}/`,
+      },
+    })
+    .then(resp => resp),
+);
+
+Cypress.Commands.add('getDashboards', () =>
+  cy
+    .request({
+      method: 'GET',
+      url: `api/v1/dashboard/`,
+      headers: {
+        'Content-Type': 'application/json',
+        Authorization: `Bearer ${TokenName}`,
+      },
+    })
+    .then(resp => resp.body.result),
+);
+
+Cypress.Commands.add('deleteChart', (id: number) =>
+  cy
+    .request({
+      method: 'DELETE',
+      url: `api/v1/chart/${id}`,
+      headers: {
+        Cookie: `csrf_access_token=${window.localStorage.getItem(
+          'access_token',
+        )}`,
+        'Content-Type': 'application/json',
+        Authorization: `Bearer ${TokenName}`,
+        'X-CSRFToken': `${window.localStorage.getItem('access_token')}`,
+        Referer: `${Cypress.config().baseUrl}/`,
+      },
+      failOnStatusCode: false,
+    })
+    .then(resp => resp),
+);
+
+Cypress.Commands.add('getCharts', () =>
+  cy
+    .request({
+      method: 'GET',
+      url: `api/v1/chart/`,
+      headers: {
+        'Content-Type': 'application/json',
+        Authorization: `Bearer ${TokenName}`,
+      },
+    })
+    .then(resp => resp.body.result),
+);
+
+Cypress.Commands.add('deleteChartByName', (name: string) =>
+  cy.getCharts().then((slices: any) => {
+    slices?.forEach((element: any) => {
+      if (element.slice_name === name) {
+        const elementId = element.id;
+        cy.deleteChart(elementId);
+      }
+    });
+  }),
+);
diff --git a/superset-frontend/package-lock.json b/superset-frontend/package-lock.json
index 92ee874f67..d9d49996a2 100644
--- a/superset-frontend/package-lock.json
+++ b/superset-frontend/package-lock.json
@@ -276,7 +276,7 @@
       },
       "engines": {
         "node": "^16.9.1",
-        "npm": "^7.5.4"
+        "npm": "^7.5.4 || ^8.1.2"
       }
     },
     "buildtools/eslint-plugin-theme-colors": {


[superset] 10/23: chore: Removes hard-coded colors from legacy-preset-chart-nvd3 (#19443)

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

villebro pushed a commit to tag 1.5.0rc1
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 91bf9bd68b9c402ef68e538f86f5d9283f281688
Author: Michael S. Molina <70...@users.noreply.github.com>
AuthorDate: Thu Mar 31 13:29:54 2022 -0300

    chore: Removes hard-coded colors from legacy-preset-chart-nvd3 (#19443)
    
    (cherry picked from commit 47abad867355f45bc369d6e7e59f3b7f1f9d02ca)
---
 .../plugins/legacy-preset-chart-nvd3/src/ReactNVD3.jsx       | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/ReactNVD3.jsx b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/ReactNVD3.jsx
index 507cc60fe6..9a9962e8aa 100644
--- a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/ReactNVD3.jsx
+++ b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/ReactNVD3.jsx
@@ -63,7 +63,7 @@ export default styled(NVD3)`
     }
     .nvtooltip tr.highlight td {
       font-weight: ${({ theme }) => theme.typography.weights.bold};
-      font-size: 15px !important;
+      font-size: ${({ theme }) => theme.typography.sizes.m}px !important;
     }
     text.nv-axislabel {
       font-size: ${({ theme }) => theme.typography.sizes.m} !important;
@@ -160,15 +160,15 @@ export default styled(NVD3)`
   .d3-tip.nv-event-annotation-layer-NATIVE {
     width: 200px;
     border-radius: 2px;
-    background-color: #484848;
+    background-color: ${({ theme }) => theme.colors.grayscale.base};
     fill-opacity: 0.6;
-    margin: 8px;
-    padding: 8px;
-    color: #fff;
+    margin: ${({ theme }) => theme.gridUnit * 2}px;
+    padding: ${({ theme }) => theme.gridUnit * 2}px;
+    color: ${({ theme }) => theme.colors.grayscale.light5};
     &:after {
       content: '\\25BC';
       font-size: ${({ theme }) => theme.typography.sizes.m};
-      color: #484848;
+      color: ${({ theme }) => theme.colors.grayscale.base};
       position: absolute;
       bottom: -14px;
       left: 94px;


[superset] 21/23: chore(plugin-chart-echarts): upgrade echarts 5.3.2 (#19481)

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

villebro pushed a commit to tag 1.5.0rc1
in repository https://gitbox.apache.org/repos/asf/superset.git

commit f44ed063e8013afd119cdc45133c81101a2f74b2
Author: Ville Brofeldt <33...@users.noreply.github.com>
AuthorDate: Fri Apr 1 13:05:03 2022 +0300

    chore(plugin-chart-echarts): upgrade echarts 5.3.2 (#19481)
    
    (cherry picked from commit 11bf0d09cb854b07492947bb8d02dde55e0b8d04)
---
 superset-frontend/package-lock.json                | 29 ++++++++++------------
 .../plugins/plugin-chart-echarts/package.json      |  2 +-
 2 files changed, 14 insertions(+), 17 deletions(-)

diff --git a/superset-frontend/package-lock.json b/superset-frontend/package-lock.json
index d9d49996a2..f9d2a88471 100644
--- a/superset-frontend/package-lock.json
+++ b/superset-frontend/package-lock.json
@@ -32152,9 +32152,9 @@
       }
     },
     "node_modules/echarts": {
-      "version": "5.3.1",
-      "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.3.1.tgz",
-      "integrity": "sha512-nWdlbgX3OVY0hpqncSvp0gDt1FRSKWn7lsWEH+PHmfCuvE0QmSw17pczQvm8AvawnLEkmf1Cts7YwQJZNC0AEQ==",
+      "version": "5.3.2",
+      "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.3.2.tgz",
+      "integrity": "sha512-LWCt7ohOKdJqyiBJ0OGBmE9szLdfA9sGcsMEi+GGoc6+Xo75C+BkcT/6NNGRHAWtnQl2fNow05AQjznpap28TQ==",
       "dependencies": {
         "tslib": "2.3.0",
         "zrender": "5.3.1"
@@ -59037,6 +59037,7 @@
         "prop-types": "^15.6.2"
       },
       "peerDependencies": {
+        "@emotion/react": "^11.4.1",
         "@superset-ui/chart-controls": "*",
         "@superset-ui/core": "*",
         "react": "^16.13.1"
@@ -59125,8 +59126,10 @@
         "prop-types": "^15.6.2"
       },
       "peerDependencies": {
+        "@emotion/react": "^11.4.1",
         "@superset-ui/chart-controls": "*",
-        "@superset-ui/core": "*"
+        "@superset-ui/core": "*",
+        "react": "^16.13.1"
       }
     },
     "plugins/legacy-plugin-chart-histogram": {
@@ -59545,7 +59548,7 @@
       "license": "Apache-2.0",
       "dependencies": {
         "d3-array": "^1.2.0",
-        "echarts": "^5.3.1",
+        "echarts": "^5.3.2",
         "lodash": "^4.17.15",
         "moment": "^2.26.0"
       },
@@ -59791,9 +59794,6 @@
       "version": "1.0.0",
       "dev": true,
       "license": "Apache-2.0",
-      "dependencies": {
-        "lodash": "^4.17.21"
-      },
       "engines": {
         "node": "^16.9.1",
         "npm": "^7.5.4"
@@ -76940,7 +76940,7 @@
       "version": "file:plugins/plugin-chart-echarts",
       "requires": {
         "d3-array": "^1.2.0",
-        "echarts": "^5.3.1",
+        "echarts": "^5.3.2",
         "lodash": "^4.17.15",
         "moment": "^2.26.0"
       }
@@ -85147,9 +85147,9 @@
       }
     },
     "echarts": {
-      "version": "5.3.1",
-      "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.3.1.tgz",
-      "integrity": "sha512-nWdlbgX3OVY0hpqncSvp0gDt1FRSKWn7lsWEH+PHmfCuvE0QmSw17pczQvm8AvawnLEkmf1Cts7YwQJZNC0AEQ==",
+      "version": "5.3.2",
+      "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.3.2.tgz",
+      "integrity": "sha512-LWCt7ohOKdJqyiBJ0OGBmE9szLdfA9sGcsMEi+GGoc6+Xo75C+BkcT/6NNGRHAWtnQl2fNow05AQjznpap28TQ==",
       "requires": {
         "tslib": "2.3.0",
         "zrender": "5.3.1"
@@ -86477,10 +86477,7 @@
       }
     },
     "eslint-plugin-theme-colors": {
-      "version": "file:tools/eslint-plugin-theme-colors",
-      "requires": {
-        "lodash": "^4.17.21"
-      }
+      "version": "file:tools/eslint-plugin-theme-colors"
     },
     "eslint-scope": {
       "version": "5.1.1",
diff --git a/superset-frontend/plugins/plugin-chart-echarts/package.json b/superset-frontend/plugins/plugin-chart-echarts/package.json
index 8d1e559258..0c5cbcf595 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/package.json
+++ b/superset-frontend/plugins/plugin-chart-echarts/package.json
@@ -27,7 +27,7 @@
   },
   "dependencies": {
     "d3-array": "^1.2.0",
-    "echarts": "^5.3.1",
+    "echarts": "^5.3.2",
     "lodash": "^4.17.15",
     "moment": "^2.26.0"
   },


[superset] 07/23: feat: Embedded dashboard configuration (#19364)

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

villebro pushed a commit to tag 1.5.0rc1
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 795ed3c7195227e259209ef07e3d893db2e9c4ba
Author: David Aaron Suddjian <18...@users.noreply.github.com>
AuthorDate: Wed Mar 30 12:34:05 2022 -0700

    feat: Embedded dashboard configuration (#19364)
    
    * embedded dashboard model
    
    * embedded dashboard endpoints
    
    * DRY up using the with_dashboard decorator elsewhere
    
    * wip
    
    * check feature flags and permissions
    
    * wip
    
    * sdk
    
    * urls
    
    * dao option for id column
    
    * got it working
    
    * Update superset/embedded/view.py
    
    * use the curator check
    
    * put back old endpoint, for now
    
    * allow access by either embedded.uuid or dashboard.id
    
    * keep the old endpoint around, for the time being
    
    * openapi
    
    * lint
    
    * lint
    
    * lint
    
    * test stuff
    
    * lint, test
    
    * typo
    
    * Update superset-frontend/src/embedded/index.tsx
    
    * Update superset-frontend/src/embedded/index.tsx
    
    * fix tests
    
    * bump sdk
    
    (cherry picked from commit 8e29ec5a6685867ffc035d20999c54c2abe36fb1)
---
 superset-embedded-sdk/package.json                 |   2 +-
 superset-embedded-sdk/src/index.ts                 |   2 +-
 .../components/DashboardEmbedControls.tsx          | 228 +++++++++++++++++++++
 .../Header/HeaderActionsDropdown/index.jsx         |  14 ++
 .../src/dashboard/components/Header/index.jsx      |  23 +++
 .../src/dashboard/containers/DashboardPage.tsx     |   8 +-
 .../src/dashboard/containers/DashboardRoute.tsx    |  28 +++
 superset-frontend/src/dashboard/types.ts           |   6 +
 superset-frontend/src/embedded/index.tsx           |  28 +--
 .../src/hooks/apiResources/dashboards.ts           |   5 +-
 superset-frontend/src/preamble.ts                  |   3 +
 superset-frontend/src/views/routes.tsx             |   6 +-
 superset/common/query_context_processor.py         |   2 +-
 superset/dao/base.py                               |  28 ++-
 superset/dashboards/api.py                         | 217 ++++++++++++++++++--
 superset/dashboards/commands/export.py             |   7 +-
 superset/dashboards/filters.py                     |  26 ++-
 superset/dashboards/schemas.py                     |  12 ++
 superset/databases/dao.py                          |   3 +-
 superset/embedded/__init__.py                      |  16 ++
 superset/embedded/dao.py                           |  53 +++++
 superset/embedded/view.py                          |  80 ++++++++
 superset/initialization/__init__.py                |   2 +
 superset/models/dashboard.py                       |   5 +
 superset/models/embedded_dashboard.py              |  57 ++++++
 superset/security/manager.py                       |  30 ++-
 superset/sqllab/command.py                         |   2 +-
 superset/views/dashboard/views.py                  |   1 +
 tests/integration_tests/dashboards/api_tests.py    |  64 +++++-
 tests/integration_tests/embedded/__init__.py       |  16 ++
 tests/integration_tests/embedded/dao_tests.py      |  51 +++++
 .../security/guest_token_security_tests.py         | 108 +++++-----
 tests/integration_tests/security_tests.py          |   2 +
 33 files changed, 1015 insertions(+), 120 deletions(-)

diff --git a/superset-embedded-sdk/package.json b/superset-embedded-sdk/package.json
index 88642e7232..49debb4ad3 100644
--- a/superset-embedded-sdk/package.json
+++ b/superset-embedded-sdk/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@superset-ui/embedded-sdk",
-  "version": "0.1.0-alpha.6",
+  "version": "0.1.0-alpha.7",
   "description": "SDK for embedding resources from Superset into your own application",
   "access": "public",
   "keywords": [
diff --git a/superset-embedded-sdk/src/index.ts b/superset-embedded-sdk/src/index.ts
index 34932bd625..32b02641e0 100644
--- a/superset-embedded-sdk/src/index.ts
+++ b/superset-embedded-sdk/src/index.ts
@@ -131,7 +131,7 @@ export async function embedDashboard({
         resolve(new Switchboard({ port: ourPort, name: 'superset-embedded-sdk', debug }));
       });
 
-      iframe.src = `${supersetDomain}/dashboard/${id}/embedded${dashboardConfig}`;
+      iframe.src = `${supersetDomain}/embedded/${id}${dashboardConfig}`;
       mountPoint.replaceChildren(iframe);
       log('placed the iframe')
     });
diff --git a/superset-frontend/src/dashboard/components/DashboardEmbedControls.tsx b/superset-frontend/src/dashboard/components/DashboardEmbedControls.tsx
new file mode 100644
index 0000000000..d20cc24600
--- /dev/null
+++ b/superset-frontend/src/dashboard/components/DashboardEmbedControls.tsx
@@ -0,0 +1,228 @@
+/**
+ * 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, { useCallback, useEffect, useState } from 'react';
+import { makeApi, styled, SupersetApiError, t } from '@superset-ui/core';
+import { InfoTooltipWithTrigger } from '@superset-ui/chart-controls';
+import Modal from 'src/components/Modal';
+import Loading from 'src/components/Loading';
+import Button from 'src/components/Button';
+import { Input } from 'src/components/Input';
+import { useToasts } from 'src/components/MessageToasts/withToasts';
+import { FormItem } from 'src/components/Form';
+import { EmbeddedDashboard } from '../types';
+
+type Props = {
+  dashboardId: string;
+  show: boolean;
+  onHide: () => void;
+};
+
+type EmbeddedApiPayload = { allowed_domains: string[] };
+
+const stringToList = (stringyList: string): string[] =>
+  stringyList.split(/(?:\s|,)+/).filter(x => x);
+
+const ButtonRow = styled.div`
+  display: flex;
+  flex-direction: row;
+  justify-content: flex-end;
+`;
+
+export const DashboardEmbedControls = ({ dashboardId, onHide }: Props) => {
+  const { addInfoToast, addDangerToast } = useToasts();
+  const [ready, setReady] = useState(true); // whether we have initialized yet
+  const [loading, setLoading] = useState(false); // whether we are currently doing an async thing
+  const [embedded, setEmbedded] = useState<EmbeddedDashboard | null>(null); // the embedded dashboard config
+  const [allowedDomains, setAllowedDomains] = useState<string>('');
+
+  const endpoint = `/api/v1/dashboard/${dashboardId}/embedded`;
+  // whether saveable changes have been made to the config
+  const isDirty =
+    !embedded ||
+    stringToList(allowedDomains).join() !== embedded.allowed_domains.join();
+
+  const enableEmbedded = useCallback(() => {
+    setLoading(true);
+    makeApi<EmbeddedApiPayload, { result: EmbeddedDashboard }>({
+      method: 'POST',
+      endpoint,
+    })({
+      allowed_domains: stringToList(allowedDomains),
+    })
+      .then(
+        ({ result }) => {
+          setEmbedded(result);
+          setAllowedDomains(result.allowed_domains.join(', '));
+          addInfoToast(t('Changes saved.'));
+        },
+        err => {
+          console.error(err);
+          addDangerToast(
+            t(
+              t('Sorry, something went wrong. The changes could not be saved.'),
+            ),
+          );
+        },
+      )
+      .finally(() => {
+        setLoading(false);
+      });
+  }, [endpoint, allowedDomains]);
+
+  const disableEmbedded = useCallback(() => {
+    Modal.confirm({
+      title: t('Disable embedding?'),
+      content: t('This will remove your current embed configuration.'),
+      okType: 'danger',
+      onOk: () => {
+        setLoading(true);
+        makeApi<{}>({ method: 'DELETE', endpoint })({})
+          .then(
+            () => {
+              setEmbedded(null);
+              setAllowedDomains('');
+              addInfoToast(t('Embedding deactivated.'));
+              onHide();
+            },
+            err => {
+              console.error(err);
+              addDangerToast(
+                t(
+                  'Sorry, something went wrong. Embedding could not be deactivated.',
+                ),
+              );
+            },
+          )
+          .finally(() => {
+            setLoading(false);
+          });
+      },
+    });
+  }, [endpoint]);
+
+  useEffect(() => {
+    setReady(false);
+    makeApi<{}, { result: EmbeddedDashboard }>({
+      method: 'GET',
+      endpoint,
+    })({})
+      .catch(err => {
+        if ((err as SupersetApiError).status === 404) {
+          // 404 just means the dashboard isn't currently embedded
+          return { result: null };
+        }
+        throw err;
+      })
+      .then(({ result }) => {
+        setReady(true);
+        setEmbedded(result);
+        setAllowedDomains(result ? result.allowed_domains.join(', ') : '');
+      });
+  }, [dashboardId]);
+
+  if (!ready) {
+    return <Loading />;
+  }
+
+  return (
+    <>
+      <p>
+        {embedded ? (
+          <>
+            {t(
+              'This dashboard is ready to embed. In your application, pass the following id to the SDK:',
+            )}
+            <br />
+            <code>{embedded.uuid}</code>
+          </>
+        ) : (
+          t(
+            'Configure this dashboard to embed it into an external web application.',
+          )
+        )}
+      </p>
+      <p>
+        {t('For further instructions, consult the')}{' '}
+        <a
+          href="https://www.npmjs.com/package/@superset-ui/embedded-sdk"
+          target="_blank"
+          rel="noreferrer"
+        >
+          {t('Superset Embedded SDK documentation.')}
+        </a>
+      </p>
+      <h3>Settings</h3>
+      <FormItem>
+        <label htmlFor="allowed-domains">
+          {t('Allowed Domains (comma separated)')}{' '}
+          <InfoTooltipWithTrigger
+            tooltip={t(
+              'A list of domain names that can embed this dashboard. Leaving this field empty will allow embedding from any domain.',
+            )}
+          />
+        </label>
+        <Input
+          name="allowed-domains"
+          value={allowedDomains}
+          placeholder="superset.example.com"
+          onChange={event => setAllowedDomains(event.target.value)}
+        />
+      </FormItem>
+      <ButtonRow>
+        {embedded ? (
+          <>
+            <Button
+              onClick={disableEmbedded}
+              buttonStyle="secondary"
+              loading={loading}
+            >
+              {t('Deactivate')}
+            </Button>
+            <Button
+              onClick={enableEmbedded}
+              buttonStyle="primary"
+              disabled={!isDirty}
+              loading={loading}
+            >
+              {t('Save changes')}
+            </Button>
+          </>
+        ) : (
+          <Button
+            onClick={enableEmbedded}
+            buttonStyle="primary"
+            loading={loading}
+          >
+            {t('Enable embedding')}
+          </Button>
+        )}
+      </ButtonRow>
+    </>
+  );
+};
+
+export const DashboardEmbedModal = (props: Props) => {
+  const { show, onHide } = props;
+
+  return (
+    <Modal show={show} onHide={onHide} title={t('Embed')} hideFooter>
+      <DashboardEmbedControls {...props} />
+    </Modal>
+  );
+};
diff --git a/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/index.jsx b/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/index.jsx
index 9375c684af..619e10ea22 100644
--- a/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/index.jsx
+++ b/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/index.jsx
@@ -59,11 +59,13 @@ const propTypes = {
   userCanEdit: PropTypes.bool.isRequired,
   userCanShare: PropTypes.bool.isRequired,
   userCanSave: PropTypes.bool.isRequired,
+  userCanCurate: PropTypes.bool.isRequired,
   isLoading: PropTypes.bool.isRequired,
   layout: PropTypes.object.isRequired,
   expandedSlices: PropTypes.object.isRequired,
   onSave: PropTypes.func.isRequired,
   showPropertiesModal: PropTypes.func.isRequired,
+  manageEmbedded: PropTypes.func.isRequired,
   refreshLimit: PropTypes.number,
   refreshWarning: PropTypes.string,
   lastModifiedTime: PropTypes.number.isRequired,
@@ -88,6 +90,7 @@ const MENU_KEYS = {
   EDIT_CSS: 'edit-css',
   DOWNLOAD_AS_IMAGE: 'download-as-image',
   TOGGLE_FULLSCREEN: 'toggle-fullscreen',
+  MANAGE_EMBEDDED: 'manage-embedded',
 };
 
 const DropdownButton = styled.div`
@@ -182,6 +185,10 @@ class HeaderActionsDropdown extends React.PureComponent {
         window.location.replace(url);
         break;
       }
+      case MENU_KEYS.MANAGE_EMBEDDED: {
+        this.props.manageEmbedded();
+        break;
+      }
       default:
         break;
     }
@@ -204,6 +211,7 @@ class HeaderActionsDropdown extends React.PureComponent {
       userCanEdit,
       userCanShare,
       userCanSave,
+      userCanCurate,
       isLoading,
       refreshLimit,
       refreshWarning,
@@ -313,6 +321,12 @@ class HeaderActionsDropdown extends React.PureComponent {
           </Menu.Item>
         )}
 
+        {!editMode && userCanCurate && (
+          <Menu.Item key={MENU_KEYS.MANAGE_EMBEDDED}>
+            {t('Embed dashboard')}
+          </Menu.Item>
+        )}
+
         {!editMode && (
           <Menu.Item key={MENU_KEYS.DOWNLOAD_AS_IMAGE}>
             {t('Download as image')}
diff --git a/superset-frontend/src/dashboard/components/Header/index.jsx b/superset-frontend/src/dashboard/components/Header/index.jsx
index 7fd1afc82e..ad668daf79 100644
--- a/superset-frontend/src/dashboard/components/Header/index.jsx
+++ b/superset-frontend/src/dashboard/components/Header/index.jsx
@@ -52,7 +52,9 @@ import setPeriodicRunner, {
   stopPeriodicRender,
 } from 'src/dashboard/util/setPeriodicRunner';
 import { options as PeriodicRefreshOptions } from 'src/dashboard/components/RefreshIntervalModal';
+import findPermission from 'src/dashboard/util/findPermission';
 import { FILTER_BOX_MIGRATION_STATES } from 'src/explore/constants';
+import { DashboardEmbedModal } from '../DashboardEmbedControls';
 
 const propTypes = {
   addSuccessToast: PropTypes.func.isRequired,
@@ -420,6 +422,14 @@ class Header extends React.PureComponent {
     this.setState({ showingReportModal: false });
   }
 
+  showEmbedModal = () => {
+    this.setState({ showingEmbedModal: true });
+  };
+
+  hideEmbedModal = () => {
+    this.setState({ showingEmbedModal: false });
+  };
+
   renderReportModal() {
     const attachedReportExists = !!Object.keys(this.props.reports).length;
     return attachedReportExists ? (
@@ -498,6 +508,9 @@ class Header extends React.PureComponent {
     const userCanSaveAs =
       dashboardInfo.dash_save_perm &&
       filterboxMigrationState !== FILTER_BOX_MIGRATION_STATES.REVIEWING;
+    const userCanCurate =
+      isFeatureEnabled(FeatureFlag.EMBEDDED_SUPERSET) &&
+      findPermission('can_set_embedded', 'Dashboard', user.roles);
     const shouldShowReport = !editMode && this.canAddReports();
     const refreshLimit =
       dashboardInfo.common?.conf?.SUPERSET_DASHBOARD_PERIODICAL_REFRESH_LIMIT;
@@ -658,6 +671,14 @@ class Header extends React.PureComponent {
             />
           )}
 
+          {userCanCurate && (
+            <DashboardEmbedModal
+              show={this.state.showingEmbedModal}
+              onHide={this.hideEmbedModal}
+              dashboardId={dashboardInfo.id}
+            />
+          )}
+
           <HeaderActionsDropdown
             addSuccessToast={this.props.addSuccessToast}
             addDangerToast={this.props.addDangerToast}
@@ -683,8 +704,10 @@ class Header extends React.PureComponent {
             userCanEdit={userCanEdit}
             userCanShare={userCanShare}
             userCanSave={userCanSaveAs}
+            userCanCurate={userCanCurate}
             isLoading={isLoading}
             showPropertiesModal={this.showPropertiesModal}
+            manageEmbedded={this.showEmbedModal}
             refreshLimit={refreshLimit}
             refreshWarning={refreshWarning}
             lastModifiedTime={lastModifiedTime}
diff --git a/superset-frontend/src/dashboard/containers/DashboardPage.tsx b/superset-frontend/src/dashboard/containers/DashboardPage.tsx
index be0ba91772..97a4dc2283 100644
--- a/superset-frontend/src/dashboard/containers/DashboardPage.tsx
+++ b/superset-frontend/src/dashboard/containers/DashboardPage.tsx
@@ -27,7 +27,6 @@ import {
 } from '@superset-ui/core';
 import { useDispatch, useSelector } from 'react-redux';
 import { Global } from '@emotion/react';
-import { useParams } from 'react-router-dom';
 import { useToasts } from 'src/components/MessageToasts/withToasts';
 import Loading from 'src/components/Loading';
 import FilterBoxMigrationModal from 'src/dashboard/components/FilterBoxMigrationModal';
@@ -79,14 +78,17 @@ const DashboardContainer = React.lazy(
 
 const originalDocumentTitle = document.title;
 
-const DashboardPage: FC = () => {
+type PageProps = {
+  idOrSlug: string;
+};
+
+export const DashboardPage: FC<PageProps> = ({ idOrSlug }: PageProps) => {
   const dispatch = useDispatch();
   const theme = useTheme();
   const user = useSelector<any, UserWithPermissionsAndRoles>(
     state => state.user,
   );
   const { addDangerToast } = useToasts();
-  const { idOrSlug } = useParams<{ idOrSlug: string }>();
   const { result: dashboard, error: dashboardApiError } =
     useDashboard(idOrSlug);
   const { result: charts, error: chartsApiError } =
diff --git a/superset-frontend/src/dashboard/containers/DashboardRoute.tsx b/superset-frontend/src/dashboard/containers/DashboardRoute.tsx
new file mode 100644
index 0000000000..a382a28d45
--- /dev/null
+++ b/superset-frontend/src/dashboard/containers/DashboardRoute.tsx
@@ -0,0 +1,28 @@
+/**
+ * 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, { FC } from 'react';
+import { useParams } from 'react-router-dom';
+import { DashboardPage } from './DashboardPage';
+
+const DashboardRoute: FC = () => {
+  const { idOrSlug } = useParams<{ idOrSlug: string }>();
+  return <DashboardPage idOrSlug={idOrSlug} />;
+};
+
+export default DashboardRoute;
diff --git a/superset-frontend/src/dashboard/types.ts b/superset-frontend/src/dashboard/types.ts
index dffbd9fbe0..c0b312d434 100644
--- a/superset-frontend/src/dashboard/types.ts
+++ b/superset-frontend/src/dashboard/types.ts
@@ -152,3 +152,9 @@ export type DashboardPermalinkValue = {
     hash: string;
   };
 };
+
+export type EmbeddedDashboard = {
+  uuid: string;
+  dashboard_id: string;
+  allowed_domains: string[];
+};
diff --git a/superset-frontend/src/embedded/index.tsx b/superset-frontend/src/embedded/index.tsx
index 3cd89a88be..afea2fd8bb 100644
--- a/superset-frontend/src/embedded/index.tsx
+++ b/superset-frontend/src/embedded/index.tsx
@@ -45,18 +45,22 @@ const LazyDashboardPage = lazy(
     ),
 );
 
+const EmbeddedRoute = () => (
+  <Suspense fallback={<Loading />}>
+    <RootContextProviders>
+      <ErrorBoundary>
+        <LazyDashboardPage idOrSlug={bootstrapData.embedded!.dashboard_id} />
+      </ErrorBoundary>
+      <ToastContainer position="top" />
+    </RootContextProviders>
+  </Suspense>
+);
+
 const EmbeddedApp = () => (
   <Router>
-    <Route path="/dashboard/:idOrSlug/embedded">
-      <Suspense fallback={<Loading />}>
-        <RootContextProviders>
-          <ErrorBoundary>
-            <LazyDashboardPage />
-          </ErrorBoundary>
-          <ToastContainer position="top" />
-        </RootContextProviders>
-      </Suspense>
-    </Route>
+    {/* todo (embedded) remove this line after uuids are deployed */}
+    <Route path="/dashboard/:idOrSlug/embedded/" component={EmbeddedRoute} />
+    <Route path="/embedded/:uuid/" component={EmbeddedRoute} />
   </Router>
 );
 
@@ -64,9 +68,9 @@ const appMountPoint = document.getElementById('app')!;
 
 const MESSAGE_TYPE = '__embedded_comms__';
 
-if (!window.parent) {
+if (!window.parent || window.parent === window) {
   appMountPoint.innerHTML =
-    'This page is intended to be embedded in an iframe, but no window.parent was found.';
+    'This page is intended to be embedded in an iframe, but it looks like that is not the case.';
 }
 
 // if the page is embedded in an origin that hasn't
diff --git a/superset-frontend/src/hooks/apiResources/dashboards.ts b/superset-frontend/src/hooks/apiResources/dashboards.ts
index b5b59d4ef4..9f512d5b15 100644
--- a/superset-frontend/src/hooks/apiResources/dashboards.ts
+++ b/superset-frontend/src/hooks/apiResources/dashboards.ts
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-import { Dashboard, Datasource } from 'src/dashboard/types';
+import { Dashboard, Datasource, EmbeddedDashboard } from 'src/dashboard/types';
 import { Chart } from 'src/types/Chart';
 import { useApiV1Resource, useTransformedResource } from './apiResources';
 
@@ -42,3 +42,6 @@ export const useDashboardCharts = (idOrSlug: string | number) =>
 // that are necessary for rendering the given dashboard
 export const useDashboardDatasets = (idOrSlug: string | number) =>
   useApiV1Resource<Datasource[]>(`/api/v1/dashboard/${idOrSlug}/datasets`);
+
+export const useEmbeddedDashboard = (idOrSlug: string | number) =>
+  useApiV1Resource<EmbeddedDashboard>(`/api/v1/dashboard/${idOrSlug}/embedded`);
diff --git a/superset-frontend/src/preamble.ts b/superset-frontend/src/preamble.ts
index 7bd6dbe29b..8d89104bf2 100644
--- a/superset-frontend/src/preamble.ts
+++ b/superset-frontend/src/preamble.ts
@@ -37,6 +37,9 @@ export let bootstrapData: {
   user?: User | undefined;
   common?: any;
   config?: any;
+  embedded?: {
+    dashboard_id: string;
+  };
 } = {};
 // Configure translation
 if (typeof window !== 'undefined') {
diff --git a/superset-frontend/src/views/routes.tsx b/superset-frontend/src/views/routes.tsx
index ba92d46b61..e7b189291f 100644
--- a/superset-frontend/src/views/routes.tsx
+++ b/superset-frontend/src/views/routes.tsx
@@ -58,10 +58,10 @@ const DashboardList = lazy(
       /* webpackChunkName: "DashboardList" */ 'src/views/CRUD/dashboard/DashboardList'
     ),
 );
-const DashboardPage = lazy(
+const DashboardRoute = lazy(
   () =>
     import(
-      /* webpackChunkName: "DashboardPage" */ 'src/dashboard/containers/DashboardPage'
+      /* webpackChunkName: "DashboardRoute" */ 'src/dashboard/containers/DashboardRoute'
     ),
 );
 const DatabaseList = lazy(
@@ -113,7 +113,7 @@ export const routes: Routes = [
   },
   {
     path: '/superset/dashboard/:idOrSlug/',
-    Component: DashboardPage,
+    Component: DashboardRoute,
   },
   {
     path: '/chart/list/',
diff --git a/superset/common/query_context_processor.py b/superset/common/query_context_processor.py
index 202088c853..c87e878fdd 100644
--- a/superset/common/query_context_processor.py
+++ b/superset/common/query_context_processor.py
@@ -471,9 +471,9 @@ class QueryContextProcessor:
         annotation_layer: Dict[str, Any], force: bool
     ) -> Dict[str, Any]:
         chart = ChartDAO.find_by_id(annotation_layer["value"])
-        form_data = chart.form_data.copy()
         if not chart:
             raise QueryObjectValidationError(_("The chart does not exist"))
+        form_data = chart.form_data.copy()
         try:
             viz_obj = get_viz(
                 datasource_type=chart.datasource.type,
diff --git a/superset/dao/base.py b/superset/dao/base.py
index ebd6a89088..607967e304 100644
--- a/superset/dao/base.py
+++ b/superset/dao/base.py
@@ -15,12 +15,12 @@
 # specific language governing permissions and limitations
 # under the License.
 # pylint: disable=isinstance-second-argument-not-valid-type
-from typing import Any, Dict, List, Optional, Type
+from typing import Any, Dict, List, Optional, Type, Union
 
 from flask_appbuilder.models.filters import BaseFilter
 from flask_appbuilder.models.sqla import Model
 from flask_appbuilder.models.sqla.interface import SQLAInterface
-from sqlalchemy.exc import SQLAlchemyError
+from sqlalchemy.exc import SQLAlchemyError, StatementError
 from sqlalchemy.orm import Session
 
 from superset.dao.exceptions import (
@@ -46,9 +46,12 @@ class BaseDAO:
     """
     Child classes can register base filtering to be aplied to all filter methods
     """
+    id_column_name = "id"
 
     @classmethod
-    def find_by_id(cls, model_id: int, session: Session = None) -> Model:
+    def find_by_id(
+        cls, model_id: Union[str, int], session: Session = None
+    ) -> Optional[Model]:
         """
         Find a model by id, if defined applies `base_filter`
         """
@@ -57,23 +60,28 @@ class BaseDAO:
         if cls.base_filter:
             data_model = SQLAInterface(cls.model_cls, session)
             query = cls.base_filter(  # pylint: disable=not-callable
-                "id", data_model
+                cls.id_column_name, data_model
             ).apply(query, None)
-        return query.filter_by(id=model_id).one_or_none()
+        id_filter = {cls.id_column_name: model_id}
+        try:
+            return query.filter_by(**id_filter).one_or_none()
+        except StatementError:
+            # can happen if int is passed instead of a string or similar
+            return None
 
     @classmethod
-    def find_by_ids(cls, model_ids: List[int]) -> List[Model]:
+    def find_by_ids(cls, model_ids: Union[List[str], List[int]]) -> List[Model]:
         """
         Find a List of models by a list of ids, if defined applies `base_filter`
         """
-        id_col = getattr(cls.model_cls, "id", None)
+        id_col = getattr(cls.model_cls, cls.id_column_name, None)
         if id_col is None:
             return []
         query = db.session.query(cls.model_cls).filter(id_col.in_(model_ids))
         if cls.base_filter:
             data_model = SQLAInterface(cls.model_cls, db.session)
             query = cls.base_filter(  # pylint: disable=not-callable
-                "id", data_model
+                cls.id_column_name, data_model
             ).apply(query, None)
         return query.all()
 
@@ -86,7 +94,7 @@ class BaseDAO:
         if cls.base_filter:
             data_model = SQLAInterface(cls.model_cls, db.session)
             query = cls.base_filter(  # pylint: disable=not-callable
-                "id", data_model
+                cls.id_column_name, data_model
             ).apply(query, None)
         return query.all()
 
@@ -99,7 +107,7 @@ class BaseDAO:
         if cls.base_filter:
             data_model = SQLAInterface(cls.model_cls, db.session)
             query = cls.base_filter(  # pylint: disable=not-callable
-                "id", data_model
+                cls.id_column_name, data_model
             ).apply(query, None)
         return query.filter_by(**filter_by).one_or_none()
 
diff --git a/superset/dashboards/api.py b/superset/dashboards/api.py
index 5e761cdd1f..d97b5f78e3 100644
--- a/superset/dashboards/api.py
+++ b/superset/dashboards/api.py
@@ -15,14 +15,16 @@
 # specific language governing permissions and limitations
 # under the License.
 # pylint: disable=too-many-lines
+import functools
 import json
 import logging
 from datetime import datetime
 from io import BytesIO
-from typing import Any, Optional
+from typing import Any, Callable, Optional
 from zipfile import is_zipfile, ZipFile
 
 from flask import g, make_response, redirect, request, Response, send_file, url_for
+from flask_appbuilder import permission_name
 from flask_appbuilder.api import expose, protect, rison, safe
 from flask_appbuilder.hooks import before_request
 from flask_appbuilder.models.sqla.interface import SQLAInterface
@@ -65,6 +67,8 @@ from superset.dashboards.schemas import (
     DashboardGetResponseSchema,
     DashboardPostSchema,
     DashboardPutSchema,
+    EmbeddedDashboardConfigSchema,
+    EmbeddedDashboardResponseSchema,
     get_delete_ids_schema,
     get_export_ids_schema,
     get_fav_star_ids_schema,
@@ -72,8 +76,10 @@ from superset.dashboards.schemas import (
     openapi_spec_methods_override,
     thumbnail_query_schema,
 )
+from superset.embedded.dao import EmbeddedDAO
 from superset.extensions import event_logger
 from superset.models.dashboard import Dashboard
+from superset.models.embedded_dashboard import EmbeddedDashboard
 from superset.tasks.thumbnails import cache_dashboard_thumbnail
 from superset.utils.cache import etag_cache
 from superset.utils.screenshots import DashboardScreenshot
@@ -91,6 +97,27 @@ from superset.views.filters import FilterRelatedOwners
 logger = logging.getLogger(__name__)
 
 
+def with_dashboard(
+    f: Callable[[BaseSupersetModelRestApi, Dashboard], Response]
+) -> Callable[[BaseSupersetModelRestApi, str], Response]:
+    """
+    A decorator that looks up the dashboard by id or slug and passes it to the api.
+    Route must include an <id_or_slug> parameter.
+    Responds with 403 or 404 without calling the route, if necessary.
+    """
+
+    def wraps(self: BaseSupersetModelRestApi, id_or_slug: str) -> Response:
+        try:
+            dash = DashboardDAO.get_by_id_or_slug(id_or_slug)
+            return f(self, dash)
+        except DashboardAccessDeniedError:
+            return self.response_403()
+        except DashboardNotFoundError:
+            return self.response_404()
+
+    return functools.update_wrapper(wraps, f)
+
+
 class DashboardRestApi(BaseSupersetModelRestApi):
     datamodel = SQLAInterface(Dashboard)
 
@@ -108,6 +135,9 @@ class DashboardRestApi(BaseSupersetModelRestApi):
         "favorite_status",
         "get_charts",
         "get_datasets",
+        "get_embedded",
+        "set_embedded",
+        "delete_embedded",
         "thumbnail",
     }
     resource_name = "dashboard"
@@ -193,6 +223,8 @@ class DashboardRestApi(BaseSupersetModelRestApi):
     chart_entity_response_schema = ChartEntityResponseSchema()
     dashboard_get_response_schema = DashboardGetResponseSchema()
     dashboard_dataset_schema = DashboardDatasetSchema()
+    embedded_response_schema = EmbeddedDashboardResponseSchema()
+    embedded_config_schema = EmbeddedDashboardConfigSchema()
 
     base_filters = [["id", DashboardAccessFilter, lambda: []]]
 
@@ -215,6 +247,7 @@ class DashboardRestApi(BaseSupersetModelRestApi):
         DashboardGetResponseSchema,
         DashboardDatasetSchema,
         GetFavStarIdsSchema,
+        EmbeddedDashboardResponseSchema,
     )
     apispec_parameter_schemas = {
         "get_delete_ids_schema": get_delete_ids_schema,
@@ -248,9 +281,11 @@ class DashboardRestApi(BaseSupersetModelRestApi):
     @statsd_metrics
     @event_logger.log_this_with_context(
         action=lambda self, *args, **kwargs: f"{self.__class__.__name__}.get",
-        log_to_statsd=False,  # pylint: disable=arguments-renamed
+        log_to_statsd=False,
     )
-    def get(self, id_or_slug: str) -> Response:
+    @with_dashboard
+    # pylint: disable=arguments-renamed, arguments-differ
+    def get(self, dash: Dashboard) -> Response:
         """Gets a dashboard
         ---
         get:
@@ -283,15 +318,8 @@ class DashboardRestApi(BaseSupersetModelRestApi):
             404:
               $ref: '#/components/responses/404'
         """
-        # pylint: disable=arguments-differ
-        try:
-            dash = DashboardDAO.get_by_id_or_slug(id_or_slug)
-            result = self.dashboard_get_response_schema.dump(dash)
-            return self.response(200, result=result)
-        except DashboardAccessDeniedError:
-            return self.response_403()
-        except DashboardNotFoundError:
-            return self.response_404()
+        result = self.dashboard_get_response_schema.dump(dash)
+        return self.response(200, result=result)
 
     @etag_cache(
         get_last_modified=lambda _self, id_or_slug: DashboardDAO.get_dashboard_and_datasets_changed_on(  # pylint: disable=line-too-long,useless-suppression
@@ -1001,3 +1029,168 @@ class DashboardRestApi(BaseSupersetModelRestApi):
         )
         command.run()
         return self.response(200, message="OK")
+
+    @expose("/<id_or_slug>/embedded", methods=["GET"])
+    @protect()
+    @safe
+    @permission_name("read")
+    @statsd_metrics
+    @event_logger.log_this_with_context(
+        action=lambda self, *args, **kwargs: f"{self.__class__.__name__}.get_embedded",
+        log_to_statsd=False,
+    )
+    @with_dashboard
+    def get_embedded(self, dashboard: Dashboard) -> Response:
+        """Response
+        Returns the dashboard's embedded configuration
+        ---
+        get:
+          description: >-
+            Returns the dashboard's embedded configuration
+          parameters:
+          - in: path
+            schema:
+              type: string
+            name: id_or_slug
+            description: The dashboard id or slug
+          responses:
+            200:
+              description: Result contains the embedded dashboard config
+              content:
+                application/json:
+                  schema:
+                    type: object
+                    properties:
+                      result:
+                        $ref: '#/components/schemas/EmbeddedDashboardResponseSchema'
+            401:
+              $ref: '#/components/responses/401'
+            500:
+              $ref: '#/components/responses/500'
+        """
+        if not dashboard.embedded:
+            return self.response(404)
+        embedded: EmbeddedDashboard = dashboard.embedded[0]
+        result = self.embedded_response_schema.dump(embedded)
+        return self.response(200, result=result)
+
+    @expose("/<id_or_slug>/embedded", methods=["POST", "PUT"])
+    @protect()
+    @safe
+    @statsd_metrics
+    @event_logger.log_this_with_context(
+        action=lambda self, *args, **kwargs: f"{self.__class__.__name__}.set_embedded",
+        log_to_statsd=False,
+    )
+    @with_dashboard
+    def set_embedded(self, dashboard: Dashboard) -> Response:
+        """Response
+        Sets a dashboard's embedded configuration.
+        ---
+        post:
+          description: >-
+            Sets a dashboard's embedded configuration.
+          parameters:
+          - in: path
+            schema:
+              type: string
+            name: id_or_slug
+            description: The dashboard id or slug
+          requestBody:
+            description: The embedded configuration to set
+            required: true
+            content:
+              application/json:
+                schema: EmbeddedDashboardConfigSchema
+          responses:
+            200:
+              description: Successfully set the configuration
+              content:
+                application/json:
+                  schema:
+                    type: object
+                    properties:
+                      result:
+                        $ref: '#/components/schemas/EmbeddedDashboardResponseSchema'
+            401:
+              $ref: '#/components/responses/401'
+            500:
+              $ref: '#/components/responses/500'
+        put:
+          description: >-
+            Sets a dashboard's embedded configuration.
+          parameters:
+          - in: path
+            schema:
+              type: string
+            name: id_or_slug
+            description: The dashboard id or slug
+          requestBody:
+            description: The embedded configuration to set
+            required: true
+            content:
+              application/json:
+                schema: EmbeddedDashboardConfigSchema
+          responses:
+            200:
+              description: Successfully set the configuration
+              content:
+                application/json:
+                  schema:
+                    type: object
+                    properties:
+                      result:
+                        $ref: '#/components/schemas/EmbeddedDashboardResponseSchema'
+            401:
+              $ref: '#/components/responses/401'
+            500:
+              $ref: '#/components/responses/500'
+        """
+        try:
+            body = self.embedded_config_schema.load(request.json)
+            embedded = EmbeddedDAO.upsert(dashboard, body["allowed_domains"])
+            result = self.embedded_response_schema.dump(embedded)
+            return self.response(200, result=result)
+        except ValidationError as error:
+            return self.response_400(message=error.messages)
+
+    @expose("/<id_or_slug>/embedded", methods=["DELETE"])
+    @protect()
+    @safe
+    @permission_name("set_embedded")
+    @statsd_metrics
+    @event_logger.log_this_with_context(
+        action=lambda self, *args, **kwargs: f"{self.__class__.__name__}.delete_embedded",
+        log_to_statsd=False,
+    )
+    @with_dashboard
+    def delete_embedded(self, dashboard: Dashboard) -> Response:
+        """Response
+        Removes a dashboard's embedded configuration.
+        ---
+        delete:
+          description: >-
+            Removes a dashboard's embedded configuration.
+          parameters:
+          - in: path
+            schema:
+              type: string
+            name: id_or_slug
+            description: The dashboard id or slug
+          responses:
+            200:
+              description: Successfully removed the configuration
+              content:
+                application/json:
+                  schema:
+                    type: object
+                    properties:
+                      message:
+                        type: string
+            401:
+              $ref: '#/components/responses/401'
+            500:
+              $ref: '#/components/responses/500'
+        """
+        dashboard.embedded = []
+        return self.response(200, message="OK")
diff --git a/superset/dashboards/commands/export.py b/superset/dashboards/commands/export.py
index 87408bab37..c7aa8b6e5c 100644
--- a/superset/dashboards/commands/export.py
+++ b/superset/dashboards/commands/export.py
@@ -140,9 +140,10 @@ class ExportDashboardsCommand(ExportModelsCommand):
                 dataset_id = target.pop("datasetId", None)
                 if dataset_id is not None:
                     dataset = DatasetDAO.find_by_id(dataset_id)
-                    target["datasetUuid"] = str(dataset.uuid)
-                    if export_related:
-                        yield from ExportDatasetsCommand([dataset_id]).run()
+                    if dataset:
+                        target["datasetUuid"] = str(dataset.uuid)
+                        if export_related:
+                            yield from ExportDatasetsCommand([dataset_id]).run()
 
         # the mapping between dashboard -> charts is inferred from the position
         # attribute, so if it's not present we need to add a default config
diff --git a/superset/dashboards/filters.py b/superset/dashboards/filters.py
index 5f79392e71..52a945ca41 100644
--- a/superset/dashboards/filters.py
+++ b/superset/dashboards/filters.py
@@ -14,7 +14,8 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-from typing import Any, Optional
+import uuid
+from typing import Any, Optional, Union
 
 from flask import g
 from flask_appbuilder.security.sqla.models import Role
@@ -25,6 +26,7 @@ from sqlalchemy.orm.query import Query
 from superset import db, is_feature_enabled, security_manager
 from superset.models.core import FavStar
 from superset.models.dashboard import Dashboard
+from superset.models.embedded_dashboard import EmbeddedDashboard
 from superset.models.slice import Slice
 from superset.security.guest_token import GuestTokenResourceType, GuestUser
 from superset.views.base import BaseFilter, is_user_admin
@@ -59,6 +61,14 @@ class DashboardFavoriteFilter(  # pylint: disable=too-few-public-methods
     model = Dashboard
 
 
+def is_uuid(value: Union[str, int]) -> bool:
+    try:
+        uuid.UUID(str(value))
+        return True
+    except ValueError:
+        return False
+
+
 class DashboardAccessFilter(BaseFilter):  # pylint: disable=too-few-public-methods
     """
     List dashboards with the following criteria:
@@ -133,14 +143,24 @@ class DashboardAccessFilter(BaseFilter):  # pylint: disable=too-few-public-metho
         if is_feature_enabled("EMBEDDED_SUPERSET") and security_manager.is_guest_user(
             g.user
         ):
+
             guest_user: GuestUser = g.user
             embedded_dashboard_ids = [
                 r["id"]
                 for r in guest_user.resources
                 if r["type"] == GuestTokenResourceType.DASHBOARD.value
             ]
-            if len(embedded_dashboard_ids) != 0:
-                feature_flagged_filters.append(Dashboard.id.in_(embedded_dashboard_ids))
+
+            # TODO (embedded): only use uuid filter once uuids are rolled out
+            condition = (
+                Dashboard.embedded.any(
+                    EmbeddedDashboard.uuid.in_(embedded_dashboard_ids)
+                )
+                if any(is_uuid(id_) for id_ in embedded_dashboard_ids)
+                else Dashboard.id.in_(embedded_dashboard_ids)
+            )
+
+            feature_flagged_filters.append(condition)
 
         query = query.filter(
             or_(
diff --git a/superset/dashboards/schemas.py b/superset/dashboards/schemas.py
index 9b668df721..d91879f0d8 100644
--- a/superset/dashboards/schemas.py
+++ b/superset/dashboards/schemas.py
@@ -309,3 +309,15 @@ class ImportV1DashboardSchema(Schema):
     version = fields.String(required=True)
     is_managed_externally = fields.Boolean(allow_none=True, default=False)
     external_url = fields.String(allow_none=True)
+
+
+class EmbeddedDashboardConfigSchema(Schema):
+    allowed_domains = fields.List(fields.String(), required=True)
+
+
+class EmbeddedDashboardResponseSchema(Schema):
+    uuid = fields.String()
+    allowed_domains = fields.List(fields.String())
+    dashboard_id = fields.String()
+    changed_on = fields.DateTime()
+    changed_by = fields.Nested(UserSchema)
diff --git a/superset/databases/dao.py b/superset/databases/dao.py
index d8813dc8eb..5e47772cfc 100644
--- a/superset/databases/dao.py
+++ b/superset/databases/dao.py
@@ -68,7 +68,8 @@ class DatabaseDAO(BaseDAO):
 
     @classmethod
     def get_related_objects(cls, database_id: int) -> Dict[str, Any]:
-        datasets = cls.find_by_id(database_id).tables
+        database: Any = cls.find_by_id(database_id)
+        datasets = database.tables
         dataset_ids = [dataset.id for dataset in datasets]
 
         charts = (
diff --git a/superset/embedded/__init__.py b/superset/embedded/__init__.py
new file mode 100644
index 0000000000..13a83393a9
--- /dev/null
+++ b/superset/embedded/__init__.py
@@ -0,0 +1,16 @@
+# 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.
diff --git a/superset/embedded/dao.py b/superset/embedded/dao.py
new file mode 100644
index 0000000000..957a7242a7
--- /dev/null
+++ b/superset/embedded/dao.py
@@ -0,0 +1,53 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+import logging
+from typing import Any, Dict, List
+
+from superset.dao.base import BaseDAO
+from superset.extensions import db
+from superset.models.dashboard import Dashboard
+from superset.models.embedded_dashboard import EmbeddedDashboard
+
+logger = logging.getLogger(__name__)
+
+
+class EmbeddedDAO(BaseDAO):
+    model_cls = EmbeddedDashboard
+    # There isn't really a regular scenario where we would rather get Embedded by id
+    id_column_name = "uuid"
+
+    @staticmethod
+    def upsert(dashboard: Dashboard, allowed_domains: List[str]) -> EmbeddedDashboard:
+        """
+        Sets up a dashboard to be embeddable.
+        Upsert is used to preserve the embedded_dashboard uuid across updates.
+        """
+        embedded: EmbeddedDashboard = (
+            dashboard.embedded[0] if dashboard.embedded else EmbeddedDashboard()
+        )
+        embedded.allow_domain_list = ",".join(allowed_domains)
+        dashboard.embedded = [embedded]
+        db.session.commit()
+        return embedded
+
+    @classmethod
+    def create(cls, properties: Dict[str, Any], commit: bool = True) -> Any:
+        """
+        Use EmbeddedDAO.upsert() instead.
+        At least, until we are ok with more than one embedded instance per dashboard.
+        """
+        raise NotImplementedError("Use EmbeddedDAO.upsert() instead.")
diff --git a/superset/embedded/view.py b/superset/embedded/view.py
new file mode 100644
index 0000000000..487850b728
--- /dev/null
+++ b/superset/embedded/view.py
@@ -0,0 +1,80 @@
+# 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 json
+from typing import Callable
+
+from flask import abort
+from flask_appbuilder import expose
+from flask_login import AnonymousUserMixin, LoginManager
+
+from superset import event_logger, is_feature_enabled, security_manager
+from superset.embedded.dao import EmbeddedDAO
+from superset.superset_typing import FlaskResponse
+from superset.utils import core as utils
+from superset.views.base import BaseSupersetView, common_bootstrap_payload
+
+
+class EmbeddedView(BaseSupersetView):
+    """The views for embedded resources to be rendered in an iframe"""
+
+    route_base = "/embedded"
+
+    @expose("/<uuid>")
+    @event_logger.log_this_with_extra_payload
+    def embedded(
+        self,
+        uuid: str,
+        add_extra_log_payload: Callable[..., None] = lambda **kwargs: None,
+    ) -> FlaskResponse:
+        """
+        Server side rendering for the embedded dashboard page
+        :param uuid: identifier for embedded dashboard
+        :param add_extra_log_payload: added by `log_this_with_manual_updates`, set a
+            default value to appease pylint
+        """
+        if not is_feature_enabled("EMBEDDED_SUPERSET"):
+            abort(404)
+
+        embedded = EmbeddedDAO.find_by_id(uuid)
+        if not embedded:
+            abort(404)
+
+        # Log in as an anonymous user, just for this view.
+        # This view needs to be visible to all users,
+        # and building the page fails if g.user and/or ctx.user aren't present.
+        login_manager: LoginManager = security_manager.lm
+        login_manager.reload_user(AnonymousUserMixin())
+
+        add_extra_log_payload(
+            embedded_dashboard_id=uuid,
+            dashboard_version="v2",
+        )
+
+        bootstrap_data = {
+            "common": common_bootstrap_payload(),
+            "embedded": {
+                "dashboard_id": embedded.dashboard_id,
+            },
+        }
+
+        return self.render_template(
+            "superset/spa.html",
+            entry="embedded",
+            bootstrap_data=json.dumps(
+                bootstrap_data, default=utils.pessimistic_json_iso_dttm_ser
+            ),
+        )
diff --git a/superset/initialization/__init__.py b/superset/initialization/__init__.py
index f6ffd3ec3a..349990a0cd 100644
--- a/superset/initialization/__init__.py
+++ b/superset/initialization/__init__.py
@@ -141,6 +141,7 @@ class SupersetAppInitializer:  # pylint: disable=too-many-public-methods
         from superset.datasets.api import DatasetRestApi
         from superset.datasets.columns.api import DatasetColumnsRestApi
         from superset.datasets.metrics.api import DatasetMetricRestApi
+        from superset.embedded.view import EmbeddedView
         from superset.explore.form_data.api import ExploreFormDataRestApi
         from superset.explore.permalink.api import ExplorePermalinkRestApi
         from superset.importexport.api import ImportExportRestApi
@@ -302,6 +303,7 @@ class SupersetAppInitializer:  # pylint: disable=too-many-public-methods
         appbuilder.add_view_no_menu(Dashboard)
         appbuilder.add_view_no_menu(DashboardModelViewAsync)
         appbuilder.add_view_no_menu(Datasource)
+        appbuilder.add_view_no_menu(EmbeddedView)
         appbuilder.add_view_no_menu(KV)
         appbuilder.add_view_no_menu(R)
         appbuilder.add_view_no_menu(SavedQueryView)
diff --git a/superset/models/dashboard.py b/superset/models/dashboard.py
index 7a8710af0d..50619829c2 100644
--- a/superset/models/dashboard.py
+++ b/superset/models/dashboard.py
@@ -152,6 +152,11 @@ class Dashboard(Model, AuditMixinNullable, ImportExportMixin):
     is_managed_externally = Column(Boolean, nullable=False, default=False)
     external_url = Column(Text, nullable=True)
     roles = relationship(security_manager.role_model, secondary=DashboardRoles)
+    embedded = relationship(
+        "EmbeddedDashboard",
+        back_populates="dashboard",
+        cascade="all, delete-orphan",
+    )
     _filter_sets = relationship(
         "FilterSet", back_populates="dashboard", cascade="all, delete"
     )
diff --git a/superset/models/embedded_dashboard.py b/superset/models/embedded_dashboard.py
new file mode 100644
index 0000000000..7718bc886f
--- /dev/null
+++ b/superset/models/embedded_dashboard.py
@@ -0,0 +1,57 @@
+# 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 uuid
+from typing import List
+
+from flask_appbuilder import Model
+from sqlalchemy import Column, ForeignKey, Integer, Text
+from sqlalchemy.orm import relationship
+from sqlalchemy_utils import UUIDType
+
+from superset.models.helpers import AuditMixinNullable
+
+
+class EmbeddedDashboard(Model, AuditMixinNullable):
+    """
+    A configuration of embedding for a dashboard.
+    Currently, the only embeddable resource is the Dashboard.
+    If we add new embeddable resource types, this model should probably be renamed.
+
+    References the dashboard, and contains a config for embedding that dashboard.
+
+    This data model allows multiple configurations for a given dashboard,
+    but at this time the API only allows setting one.
+    """
+
+    __tablename__ = "embedded_dashboards"
+
+    uuid = Column(UUIDType(binary=True), default=uuid.uuid4, primary_key=True)
+    allow_domain_list = Column(Text)  # reference the `allowed_domains` property instead
+    dashboard_id = Column(Integer, ForeignKey("dashboards.id"), nullable=False)
+    dashboard = relationship(
+        "Dashboard",
+        back_populates="embedded",
+        foreign_keys=[dashboard_id],
+    )
+
+    @property
+    def allowed_domains(self) -> List[str]:
+        """
+        A list of domains which are allowed to embed the dashboard.
+        An empty list means any domain can embed.
+        """
+        return self.allow_domain_list.split(",") if self.allow_domain_list else []
diff --git a/superset/security/manager.py b/superset/security/manager.py
index 9f13bf6134..2ad8baa5e1 100644
--- a/superset/security/manager.py
+++ b/superset/security/manager.py
@@ -189,6 +189,7 @@ class SupersetSecurityManager(  # pylint: disable=too-many-public-methods
         "can_update_role",
         "all_query_access",
         "can_grant_guest_token",
+        "can_set_embedded",
     }
 
     READ_ONLY_PERMISSION = {
@@ -1274,10 +1275,8 @@ class SupersetSecurityManager(  # pylint: disable=too-many-public-methods
                 for dashboard_role in dashboard.roles
             )
 
-        if self.is_guest_user():
-            can_access = self.has_guest_access(
-                GuestTokenResourceType.DASHBOARD, dashboard.id
-            )
+        if self.is_guest_user() and dashboard.embedded:
+            can_access = self.has_guest_access(dashboard)
         else:
             can_access = (
                 is_user_admin()
@@ -1416,15 +1415,26 @@ class SupersetSecurityManager(  # pylint: disable=too-many-public-methods
             return g.user
         return None
 
-    def has_guest_access(
-        self, resource_type: GuestTokenResourceType, resource_id: Union[str, int]
-    ) -> bool:
+    def has_guest_access(self, dashboard: "Dashboard") -> bool:
         user = self.get_current_guest_user_if_guest()
         if not user:
             return False
 
-        strid = str(resource_id)
-        for resource in user.resources:
-            if resource["type"] == resource_type.value and str(resource["id"]) == strid:
+        dashboards = [
+            r
+            for r in user.resources
+            if r["type"] == GuestTokenResourceType.DASHBOARD.value
+        ]
+
+        # TODO (embedded): remove this check once uuids are rolled out
+        for resource in dashboards:
+            if str(resource["id"]) == str(dashboard.id):
+                return True
+
+        if not dashboard.embedded:
+            return False
+
+        for resource in dashboards:
+            if str(resource["id"]) == str(dashboard.embedded[0].uuid):
                 return True
         return False
diff --git a/superset/sqllab/command.py b/superset/sqllab/command.py
index bdc570b603..ff50e18eda 100644
--- a/superset/sqllab/command.py
+++ b/superset/sqllab/command.py
@@ -138,7 +138,7 @@ class ExecuteSqlCommand(BaseCommand):
             raise ex
 
     def _get_the_query_db(self) -> Database:
-        mydb = self._database_dao.find_by_id(self._execution_context.database_id)
+        mydb: Any = self._database_dao.find_by_id(self._execution_context.database_id)
         self._validate_query_db(mydb)
         return mydb
 
diff --git a/superset/views/dashboard/views.py b/superset/views/dashboard/views.py
index 84a25c2cea..57d9b78092 100644
--- a/superset/views/dashboard/views.py
+++ b/superset/views/dashboard/views.py
@@ -164,6 +164,7 @@ class Dashboard(BaseSupersetView):
 
         bootstrap_data = {
             "common": common_bootstrap_payload(),
+            "embedded": {"dashboard_id": dashboard_id_or_slug},
         }
 
         return self.render_template(
diff --git a/tests/integration_tests/dashboards/api_tests.py b/tests/integration_tests/dashboards/api_tests.py
index 4c8a207438..b6029676a9 100644
--- a/tests/integration_tests/dashboards/api_tests.py
+++ b/tests/integration_tests/dashboards/api_tests.py
@@ -388,7 +388,14 @@ class TestDashboardApi(SupersetTestCase, ApiOwnersTestCaseMixin, InsertChartMixi
         rv = self.get_assert_metric(uri, "info")
         data = json.loads(rv.data.decode("utf-8"))
         assert rv.status_code == 200
-        assert set(data["permissions"]) == {"can_read", "can_write", "can_export"}
+        assert set(data["permissions"]) == {
+            "can_read",
+            "can_write",
+            "can_export",
+            "can_get_embedded",
+            "can_delete_embedded",
+            "can_set_embedded",
+        }
 
     @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices")
     def test_get_dashboard_not_found(self):
@@ -1722,3 +1729,58 @@ class TestDashboardApi(SupersetTestCase, ApiOwnersTestCaseMixin, InsertChartMixi
 
         response_roles = [result["text"] for result in response["result"]]
         assert "Alpha" in response_roles
+
+    @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices")
+    def test_embedded_dashboards(self):
+        self.login(username="admin")
+        uri = "api/v1/dashboard/world_health/embedded"
+
+        # initial get should return 404
+        resp = self.get_assert_metric(uri, "get_embedded")
+        self.assertEqual(resp.status_code, 404)
+
+        # post succeeds and returns value
+        allowed_domains = ["test.example", "embedded.example"]
+        resp = self.post_assert_metric(
+            uri,
+            {"allowed_domains": allowed_domains},
+            "set_embedded",
+        )
+        self.assertEqual(resp.status_code, 200)
+        result = json.loads(resp.data.decode("utf-8"))["result"]
+        self.assertIsNotNone(result["uuid"])
+        self.assertNotEqual(result["uuid"], "")
+        self.assertEqual(result["allowed_domains"], allowed_domains)
+
+        # get returns value
+        resp = self.get_assert_metric(uri, "get_embedded")
+        self.assertEqual(resp.status_code, 200)
+        result = json.loads(resp.data.decode("utf-8"))["result"]
+        self.assertIsNotNone(result["uuid"])
+        self.assertNotEqual(result["uuid"], "")
+        self.assertEqual(result["allowed_domains"], allowed_domains)
+
+        # save uuid for later
+        original_uuid = result["uuid"]
+
+        # put succeeds and returns value
+        resp = self.post_assert_metric(uri, {"allowed_domains": []}, "set_embedded")
+        self.assertEqual(resp.status_code, 200)
+        self.assertIsNotNone(result["uuid"])
+        self.assertNotEqual(result["uuid"], "")
+        self.assertEqual(result["allowed_domains"], allowed_domains)
+
+        # get returns changed value
+        resp = self.get_assert_metric(uri, "get_embedded")
+        self.assertEqual(resp.status_code, 200)
+        result = json.loads(resp.data.decode("utf-8"))["result"]
+        self.assertEqual(result["uuid"], original_uuid)
+        self.assertEqual(result["allowed_domains"], [])
+
+        # delete succeeds
+        resp = self.delete_assert_metric(uri, "delete_embedded")
+        self.assertEqual(resp.status_code, 200)
+
+        # get returns 404
+        resp = self.get_assert_metric(uri, "get_embedded")
+        self.assertEqual(resp.status_code, 404)
diff --git a/tests/integration_tests/embedded/__init__.py b/tests/integration_tests/embedded/__init__.py
new file mode 100644
index 0000000000..13a83393a9
--- /dev/null
+++ b/tests/integration_tests/embedded/__init__.py
@@ -0,0 +1,16 @@
+# 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.
diff --git a/tests/integration_tests/embedded/dao_tests.py b/tests/integration_tests/embedded/dao_tests.py
new file mode 100644
index 0000000000..8160144a25
--- /dev/null
+++ b/tests/integration_tests/embedded/dao_tests.py
@@ -0,0 +1,51 @@
+# 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.
+# isort:skip_file
+import pytest
+
+import tests.integration_tests.test_app  # pylint: disable=unused-import
+from superset import db
+from superset.embedded.dao import EmbeddedDAO
+from superset.models.dashboard import Dashboard
+from tests.integration_tests.base_tests import SupersetTestCase
+from tests.integration_tests.fixtures.world_bank_dashboard import (
+    load_world_bank_dashboard_with_slices,
+    load_world_bank_data,
+)
+
+
+class TestEmbeddedDAO(SupersetTestCase):
+    @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices")
+    def test_upsert(self):
+        dash = db.session.query(Dashboard).filter_by(slug="world_health").first()
+        assert not dash.embedded
+        EmbeddedDAO.upsert(dash, ["test.example.com"])
+        assert dash.embedded
+        self.assertEqual(dash.embedded[0].allowed_domains, ["test.example.com"])
+        original_uuid = dash.embedded[0].uuid
+        self.assertIsNotNone(original_uuid)
+        EmbeddedDAO.upsert(dash, [])
+        self.assertEqual(dash.embedded[0].allowed_domains, [])
+        self.assertEqual(dash.embedded[0].uuid, original_uuid)
+
+    @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices")
+    def test_get_by_uuid(self):
+        dash = db.session.query(Dashboard).filter_by(slug="world_health").first()
+        uuid = str(EmbeddedDAO.upsert(dash, ["test.example.com"]).uuid)
+        db.session.expire_all()
+        embedded = EmbeddedDAO.find_by_id(uuid)
+        self.assertIsNotNone(embedded)
diff --git a/tests/integration_tests/security/guest_token_security_tests.py b/tests/integration_tests/security/guest_token_security_tests.py
index e4d55d9747..78bd8bde86 100644
--- a/tests/integration_tests/security/guest_token_security_tests.py
+++ b/tests/integration_tests/security/guest_token_security_tests.py
@@ -22,6 +22,7 @@ from flask import g
 
 from superset import db, security_manager
 from superset.dashboards.commands.exceptions import DashboardAccessDeniedError
+from superset.embedded.dao import EmbeddedDAO
 from superset.exceptions import SupersetSecurityException
 from superset.models.dashboard import Dashboard
 from superset.security.guest_token import GuestTokenResourceType
@@ -38,14 +39,9 @@ from tests.integration_tests.fixtures.birth_names_dashboard import (
     EMBEDDED_SUPERSET=True,
 )
 class TestGuestUserSecurity(SupersetTestCase):
-    # This test doesn't use a dashboard fixture, the next test does.
-    # That way tests are faster.
-
-    resource_id = 42
-
     def authorized_guest(self):
         return security_manager.get_guest_user_from_token(
-            {"user": {}, "resources": [{"type": "dashboard", "id": self.resource_id}]}
+            {"user": {}, "resources": [{"type": "dashboard", "id": "some-uuid"}]}
         )
 
     def test_is_guest_user__regular_user(self):
@@ -83,88 +79,86 @@ class TestGuestUserSecurity(SupersetTestCase):
         guest_user = security_manager.get_current_guest_user_if_guest()
         self.assertEqual(guest_user, g.user)
 
+    def test_get_guest_user_roles_explicit(self):
+        guest = self.authorized_guest()
+        roles = security_manager.get_user_roles(guest)
+        self.assertEqual(guest.roles, roles)
+
+    def test_get_guest_user_roles_implicit(self):
+        guest = self.authorized_guest()
+        g.user = guest
+
+        roles = security_manager.get_user_roles()
+        self.assertEqual(guest.roles, roles)
+
+
+@mock.patch.dict(
+    "superset.extensions.feature_flag_manager._feature_flags",
+    EMBEDDED_SUPERSET=True,
+)
+@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
+class TestGuestUserDashboardAccess(SupersetTestCase):
+    def setUp(self) -> None:
+        self.dash = db.session.query(Dashboard).filter_by(slug="births").first()
+        self.embedded = EmbeddedDAO.upsert(self.dash, [])
+        self.authorized_guest = security_manager.get_guest_user_from_token(
+            {
+                "user": {},
+                "resources": [{"type": "dashboard", "id": str(self.embedded.uuid)}],
+            }
+        )
+        self.unauthorized_guest = security_manager.get_guest_user_from_token(
+            {
+                "user": {},
+                "resources": [
+                    {"type": "dashboard", "id": "06383667-3e02-4e5e-843f-44e9c5896b6c"}
+                ],
+            }
+        )
+
     def test_has_guest_access__regular_user(self):
         g.user = security_manager.find_user("admin")
-        has_guest_access = security_manager.has_guest_access(
-            GuestTokenResourceType.DASHBOARD, self.resource_id
-        )
+        has_guest_access = security_manager.has_guest_access(self.dash)
         self.assertFalse(has_guest_access)
 
     def test_has_guest_access__anonymous_user(self):
         g.user = security_manager.get_anonymous_user()
-        has_guest_access = security_manager.has_guest_access(
-            GuestTokenResourceType.DASHBOARD, self.resource_id
-        )
+        has_guest_access = security_manager.has_guest_access(self.dash)
         self.assertFalse(has_guest_access)
 
     def test_has_guest_access__authorized_guest_user(self):
-        g.user = self.authorized_guest()
-        has_guest_access = security_manager.has_guest_access(
-            GuestTokenResourceType.DASHBOARD, self.resource_id
-        )
+        g.user = self.authorized_guest
+        has_guest_access = security_manager.has_guest_access(self.dash)
         self.assertTrue(has_guest_access)
 
     def test_has_guest_access__authorized_guest_user__non_zero_resource_index(self):
-        guest = self.authorized_guest()
+        # set up a user who has authorized access, plus another resource
+        guest = self.authorized_guest
         guest.resources = [
-            {"type": "dashboard", "id": self.resource_id - 1}
+            {"type": "dashboard", "id": "not-a-real-id"}
         ] + guest.resources
         g.user = guest
 
-        has_guest_access = security_manager.has_guest_access(
-            GuestTokenResourceType.DASHBOARD, self.resource_id
-        )
+        has_guest_access = security_manager.has_guest_access(self.dash)
         self.assertTrue(has_guest_access)
 
     def test_has_guest_access__unauthorized_guest_user__different_resource_id(self):
         g.user = security_manager.get_guest_user_from_token(
             {
                 "user": {},
-                "resources": [{"type": "dashboard", "id": self.resource_id - 1}],
+                "resources": [{"type": "dashboard", "id": "not-a-real-id"}],
             }
         )
-        has_guest_access = security_manager.has_guest_access(
-            GuestTokenResourceType.DASHBOARD, self.resource_id
-        )
+        has_guest_access = security_manager.has_guest_access(self.dash)
         self.assertFalse(has_guest_access)
 
     def test_has_guest_access__unauthorized_guest_user__different_resource_type(self):
         g.user = security_manager.get_guest_user_from_token(
-            {"user": {}, "resources": [{"type": "dirt", "id": self.resource_id}]}
-        )
-        has_guest_access = security_manager.has_guest_access(
-            GuestTokenResourceType.DASHBOARD, self.resource_id
+            {"user": {}, "resources": [{"type": "dirt", "id": self.embedded.uuid}]}
         )
+        has_guest_access = security_manager.has_guest_access(self.dash)
         self.assertFalse(has_guest_access)
 
-    def test_get_guest_user_roles_explicit(self):
-        guest = self.authorized_guest()
-        roles = security_manager.get_user_roles(guest)
-        self.assertEqual(guest.roles, roles)
-
-    def test_get_guest_user_roles_implicit(self):
-        guest = self.authorized_guest()
-        g.user = guest
-
-        roles = security_manager.get_user_roles()
-        self.assertEqual(guest.roles, roles)
-
-
-@mock.patch.dict(
-    "superset.extensions.feature_flag_manager._feature_flags",
-    EMBEDDED_SUPERSET=True,
-)
-@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
-class TestGuestUserDashboardAccess(SupersetTestCase):
-    def setUp(self) -> None:
-        self.dash = db.session.query(Dashboard).filter_by(slug="births").first()
-        self.authorized_guest = security_manager.get_guest_user_from_token(
-            {"user": {}, "resources": [{"type": "dashboard", "id": self.dash.id}]}
-        )
-        self.unauthorized_guest = security_manager.get_guest_user_from_token(
-            {"user": {}, "resources": [{"type": "dashboard", "id": self.dash.id + 1}]}
-        )
-
     def test_chart_raise_for_access_as_guest(self):
         chart = self.dash.slices[0]
         g.user = self.authorized_guest
diff --git a/tests/integration_tests/security_tests.py b/tests/integration_tests/security_tests.py
index d54e400817..48017f69d1 100644
--- a/tests/integration_tests/security_tests.py
+++ b/tests/integration_tests/security_tests.py
@@ -906,7 +906,9 @@ class TestRolePermission(SupersetTestCase):
             ["AuthDBView", "login"],
             ["AuthDBView", "logout"],
             ["CurrentUserRestApi", "get_me"],
+            # TODO (embedded) remove Dashboard:embedded after uuids have been shipped
             ["Dashboard", "embedded"],
+            ["EmbeddedView", "embedded"],
             ["R", "index"],
             ["Superset", "log"],
             ["Superset", "theme"],


[superset] 06/23: docs: add 1.4.2 changelog entries (#19411)

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

villebro pushed a commit to tag 1.5.0rc1
in repository https://gitbox.apache.org/repos/asf/superset.git

commit a7ee67715475c060766febacd3139a858ef6b937
Author: Ville Brofeldt <33...@users.noreply.github.com>
AuthorDate: Wed Mar 30 19:31:51 2022 +0300

    docs: add 1.4.2 changelog entries (#19411)
    
    (cherry picked from commit a4c261d72c3f2c26d443cb202fb79d168fab6c93)
---
 CHANGELOG.md | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 34657f0ba6..9aa311f32a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,16 @@ specific language governing permissions and limitations
 under the License.
 -->
 ## Change Log
+
+### 1.4.2 (Sat Mar 19 00:08:06 2022 +0200)
+**Features**
+- [#19248](https://github.com/apache/superset/pull/19248) feat: add support for comments in adhoc clauses (@villebro)
+- [#18214](https://github.com/apache/superset/pull/18214) feat(docker-compose): add TAG option (@villebro)
+
+**Fixes**
+- [#17641](https://github.com/apache/superset/pull/17641) fix(sqla): make text clause escaping optional (@villebro)
+- [#18566](https://github.com/apache/superset/pull/18566) fix(plugin-chart-echarts): area chart opacity bug (@villebro)
+
 ### 1.4.1
 **Database Migrations**
 


[superset] 18/23: fix: request samples with default row limit (#19456)

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

villebro pushed a commit to tag 1.5.0rc1
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 3c627bdc618a736304606ff5f6bb6d532a7254f2
Author: Ville Brofeldt <33...@users.noreply.github.com>
AuthorDate: Fri Apr 1 11:32:36 2022 +0300

    fix: request samples with default row limit (#19456)
    
    * fix: request samples with default row limit
    
    * lodashLint
    
    * fix cypress test
    
    (cherry picked from commit d684ad073d63953d803cf05b4b208484f1fe7e51)
---
 .../cypress/integration/explore/control.test.ts    |  2 +-
 .../src/explore/exploreUtils/index.js              | 10 ++++++++-
 superset/common/query_actions.py                   | 26 ++++++++++++----------
 superset/common/query_object.py                    |  4 ++--
 4 files changed, 26 insertions(+), 16 deletions(-)

diff --git a/superset-frontend/cypress-base/cypress/integration/explore/control.test.ts b/superset-frontend/cypress-base/cypress/integration/explore/control.test.ts
index 804fffafa2..f8254ac9bb 100644
--- a/superset-frontend/cypress-base/cypress/integration/explore/control.test.ts
+++ b/superset-frontend/cypress-base/cypress/integration/explore/control.test.ts
@@ -129,7 +129,7 @@ describe('Test datatable', () => {
   it('Datapane loads view samples', () => {
     cy.get('[data-test="data-tab"]').click();
     cy.contains('View samples').click();
-    cy.get('[data-test="row-count-label"]').contains('10k rows retrieved');
+    cy.get('[data-test="row-count-label"]').contains('1k rows retrieved');
     cy.get('.ant-empty-description').should('not.exist');
   });
 });
diff --git a/superset-frontend/src/explore/exploreUtils/index.js b/superset-frontend/src/explore/exploreUtils/index.js
index 679902243b..79bcb1a362 100644
--- a/superset-frontend/src/explore/exploreUtils/index.js
+++ b/superset-frontend/src/explore/exploreUtils/index.js
@@ -26,6 +26,7 @@ import {
   getChartBuildQueryRegistry,
   getChartMetadataRegistry,
 } from '@superset-ui/core';
+import { omit } from 'lodash';
 import { availableDomains } from 'src/utils/hostNamesConfig';
 import { safeStringify } from 'src/utils/safeStringify';
 import { URL_PARAMS } from 'src/constants';
@@ -215,7 +216,7 @@ export const buildV1ChartDataPayload = ({
           ...baseQueryObject,
         },
       ]));
-  return buildQuery(
+  const payload = buildQuery(
     {
       ...formData,
       force,
@@ -229,6 +230,13 @@ export const buildV1ChartDataPayload = ({
       },
     },
   );
+  if (resultType === 'samples') {
+    // remove row limit and offset to fall back to defaults
+    payload.queries = payload.queries.map(query =>
+      omit(query, ['row_limit', 'row_offset']),
+    );
+  }
+  return payload;
 };
 
 export const getLegacyEndpointType = ({ resultType, resultFormat }) =>
diff --git a/superset/common/query_actions.py b/superset/common/query_actions.py
index 2b85125b0e..5899757d52 100644
--- a/superset/common/query_actions.py
+++ b/superset/common/query_actions.py
@@ -14,6 +14,8 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
+from __future__ import annotations
+
 import copy
 from typing import Any, Callable, cast, Dict, List, Optional, TYPE_CHECKING
 
@@ -41,13 +43,13 @@ config = app.config
 
 
 def _get_datasource(
-    query_context: "QueryContext", query_obj: "QueryObject"
+    query_context: QueryContext, query_obj: QueryObject
 ) -> BaseDatasource:
     return query_obj.datasource or query_context.datasource
 
 
 def _get_columns(
-    query_context: "QueryContext", query_obj: "QueryObject", _: bool
+    query_context: QueryContext, query_obj: QueryObject, _: bool
 ) -> Dict[str, Any]:
     datasource = _get_datasource(query_context, query_obj)
     return {
@@ -63,7 +65,7 @@ def _get_columns(
 
 
 def _get_timegrains(
-    query_context: "QueryContext", query_obj: "QueryObject", _: bool
+    query_context: QueryContext, query_obj: QueryObject, _: bool
 ) -> Dict[str, Any]:
     datasource = _get_datasource(query_context, query_obj)
     return {
@@ -79,8 +81,8 @@ def _get_timegrains(
 
 
 def _get_query(
-    query_context: "QueryContext",
-    query_obj: "QueryObject",
+    query_context: QueryContext,
+    query_obj: QueryObject,
     _: bool,
 ) -> Dict[str, Any]:
     datasource = _get_datasource(query_context, query_obj)
@@ -93,8 +95,8 @@ def _get_query(
 
 
 def _get_full(
-    query_context: "QueryContext",
-    query_obj: "QueryObject",
+    query_context: QueryContext,
+    query_obj: QueryObject,
     force_cached: Optional[bool] = False,
 ) -> Dict[str, Any]:
     datasource = _get_datasource(query_context, query_obj)
@@ -140,7 +142,7 @@ def _get_full(
 
 
 def _get_samples(
-    query_context: "QueryContext", query_obj: "QueryObject", force_cached: bool = False
+    query_context: QueryContext, query_obj: QueryObject, force_cached: bool = False
 ) -> Dict[str, Any]:
     datasource = _get_datasource(query_context, query_obj)
     query_obj = copy.copy(query_obj)
@@ -155,14 +157,14 @@ def _get_samples(
 
 
 def _get_results(
-    query_context: "QueryContext", query_obj: "QueryObject", force_cached: bool = False
+    query_context: QueryContext, query_obj: QueryObject, force_cached: bool = False
 ) -> Dict[str, Any]:
     payload = _get_full(query_context, query_obj, force_cached)
     return payload
 
 
 _result_type_functions: Dict[
-    ChartDataResultType, Callable[["QueryContext", "QueryObject", bool], Dict[str, Any]]
+    ChartDataResultType, Callable[[QueryContext, QueryObject, bool], Dict[str, Any]]
 ] = {
     ChartDataResultType.COLUMNS: _get_columns,
     ChartDataResultType.TIMEGRAINS: _get_timegrains,
@@ -179,8 +181,8 @@ _result_type_functions: Dict[
 
 def get_query_results(
     result_type: ChartDataResultType,
-    query_context: "QueryContext",
-    query_obj: "QueryObject",
+    query_context: QueryContext,
+    query_obj: QueryObject,
     force_cached: bool,
 ) -> Dict[str, Any]:
     """
diff --git a/superset/common/query_object.py b/superset/common/query_object.py
index 78a76fc3cd..40d37041b9 100644
--- a/superset/common/query_object.py
+++ b/superset/common/query_object.py
@@ -100,7 +100,7 @@ class QueryObject:  # pylint: disable=too-many-instance-attributes
     orderby: List[OrderBy]
     post_processing: List[Dict[str, Any]]
     result_type: Optional[ChartDataResultType]
-    row_limit: int
+    row_limit: Optional[int]
     row_offset: int
     series_columns: List[Column]
     series_limit: int
@@ -127,7 +127,7 @@ class QueryObject:  # pylint: disable=too-many-instance-attributes
         order_desc: bool = True,
         orderby: Optional[List[OrderBy]] = None,
         post_processing: Optional[List[Optional[Dict[str, Any]]]] = None,
-        row_limit: int,
+        row_limit: Optional[int],
         row_offset: Optional[int] = None,
         series_columns: Optional[List[Column]] = None,
         series_limit: int = 0,


[superset] 04/23: fix: missing init on importexport pkg (#19435)

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

villebro pushed a commit to tag 1.5.0rc1
in repository https://gitbox.apache.org/repos/asf/superset.git

commit bfa203aee0d16564c6147b832b315c7287af711a
Author: Daniel Vaz Gaspar <da...@gmail.com>
AuthorDate: Wed Mar 30 15:43:08 2022 +0100

    fix: missing init on importexport pkg (#19435)
    
    (cherry picked from commit 2451937097c0adfde34d735d9d85997d9340a20d)
---
 superset/importexport/__init__.py | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/superset/importexport/__init__.py b/superset/importexport/__init__.py
new file mode 100644
index 0000000000..13a83393a9
--- /dev/null
+++ b/superset/importexport/__init__.py
@@ -0,0 +1,16 @@
+# 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.


[superset] 01/23: perf: improve perf in SIP-68 migration (#19416)

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

villebro pushed a commit to tag 1.5.0rc1
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 038d114b07df3a6eb0669c28790a55006f8342f7
Author: Beto Dealmeida <ro...@dealmeida.net>
AuthorDate: Tue Mar 29 22:33:15 2022 -0700

    perf: improve perf in SIP-68 migration (#19416)
    
    * chore: improve perf in SIP-68 migration
    
    * Small fixes
    
    * Create tables referenced in SQL
    
    * Update logic in SqlaTable as well
    
    * Fix unit tests
    
    (cherry picked from commit 63b5e2e4fa0a1f12450a108793896db4fbd43c6d)
---
 scripts/benchmark_migration.py                     |   5 +-
 setup.py                                           |   1 +
 superset/connectors/sqla/models.py                 |  36 +++---
 superset/connectors/sqla/utils.py                  |  87 ++++++++++++++-
 superset/migrations/shared/utils.py                |  66 +++++++++++
 .../versions/b8d3a24d9131_new_dataset_models.py    | 122 +++++++++++++++++----
 .../unit_tests/migrations/__init__.py              |  24 ----
 .../unit_tests/migrations/shared/__init__.py       |  24 ----
 tests/unit_tests/migrations/shared/utils_test.py   |  56 ++++++++++
 9 files changed, 332 insertions(+), 89 deletions(-)

diff --git a/scripts/benchmark_migration.py b/scripts/benchmark_migration.py
index 27670b5d4d..baae8befec 100644
--- a/scripts/benchmark_migration.py
+++ b/scripts/benchmark_migration.py
@@ -102,7 +102,10 @@ def find_models(module: ModuleType) -> List[Type[Model]]:
     while tables:
         table = tables.pop()
         seen.add(table)
-        model = getattr(Base.classes, table)
+        try:
+            model = getattr(Base.classes, table)
+        except AttributeError:
+            continue
         model.__tablename__ = table
         models.append(model)
 
diff --git a/setup.py b/setup.py
index eabe50d6bd..6943479801 100644
--- a/setup.py
+++ b/setup.py
@@ -112,6 +112,7 @@ setup(
         "slackclient==2.5.0",  # PINNED! slack changes file upload api in the future versions
         "sqlalchemy>=1.3.16, <1.4, !=1.3.21",
         "sqlalchemy-utils>=0.37.8, <0.38",
+        "sqloxide==0.1.15",
         "sqlparse==0.3.0",  # PINNED! see https://github.com/andialbrecht/sqlparse/issues/562
         "tabulate==0.8.9",
         # needed to support Literal (3.8) and TypeGuard (3.10)
diff --git a/superset/connectors/sqla/models.py b/superset/connectors/sqla/models.py
index 70cef8ec80..535eb81a57 100644
--- a/superset/connectors/sqla/models.py
+++ b/superset/connectors/sqla/models.py
@@ -78,6 +78,7 @@ from superset.connectors.base.models import BaseColumn, BaseDatasource, BaseMetr
 from superset.connectors.sqla.utils import (
     get_physical_table_metadata,
     get_virtual_table_metadata,
+    load_or_create_tables,
     validate_adhoc_subquery,
 )
 from superset.datasets.models import Dataset as NewDataset
@@ -2272,7 +2273,10 @@ class SqlaTable(Model, BaseDatasource):  # pylint: disable=too-many-public-metho
             if column.is_active is False:
                 continue
 
-            extra_json = json.loads(column.extra or "{}")
+            try:
+                extra_json = json.loads(column.extra or "{}")
+            except json.decoder.JSONDecodeError:
+                extra_json = {}
             for attr in {"groupby", "filterable", "verbose_name", "python_date_format"}:
                 value = getattr(column, attr)
                 if value:
@@ -2299,7 +2303,10 @@ class SqlaTable(Model, BaseDatasource):  # pylint: disable=too-many-public-metho
 
         # create metrics
         for metric in dataset.metrics:
-            extra_json = json.loads(metric.extra or "{}")
+            try:
+                extra_json = json.loads(metric.extra or "{}")
+            except json.decoder.JSONDecodeError:
+                extra_json = {}
             for attr in {"verbose_name", "metric_type", "d3format"}:
                 value = getattr(metric, attr)
                 if value:
@@ -2330,8 +2337,7 @@ class SqlaTable(Model, BaseDatasource):  # pylint: disable=too-many-public-metho
             )
 
         # physical dataset
-        tables = []
-        if dataset.sql is None:
+        if not dataset.sql:
             physical_columns = [column for column in columns if column.is_physical]
 
             # create table
@@ -2344,7 +2350,7 @@ class SqlaTable(Model, BaseDatasource):  # pylint: disable=too-many-public-metho
                 is_managed_externally=dataset.is_managed_externally,
                 external_url=dataset.external_url,
             )
-            tables.append(table)
+            tables = [table]
 
         # virtual dataset
         else:
@@ -2355,18 +2361,14 @@ class SqlaTable(Model, BaseDatasource):  # pylint: disable=too-many-public-metho
             # find referenced tables
             parsed = ParsedQuery(dataset.sql)
             referenced_tables = parsed.tables
-
-            # predicate for finding the referenced tables
-            predicate = or_(
-                *[
-                    and_(
-                        NewTable.schema == (table.schema or dataset.schema),
-                        NewTable.name == table.table,
-                    )
-                    for table in referenced_tables
-                ]
+            tables = load_or_create_tables(
+                session,
+                dataset.database_id,
+                dataset.schema,
+                referenced_tables,
+                conditional_quote,
+                engine,
             )
-            tables = session.query(NewTable).filter(predicate).all()
 
         # create the new dataset
         new_dataset = NewDataset(
@@ -2375,7 +2377,7 @@ class SqlaTable(Model, BaseDatasource):  # pylint: disable=too-many-public-metho
             expression=dataset.sql or conditional_quote(dataset.table_name),
             tables=tables,
             columns=columns,
-            is_physical=dataset.sql is None,
+            is_physical=not dataset.sql,
             is_managed_externally=dataset.is_managed_externally,
             external_url=dataset.external_url,
         )
diff --git a/superset/connectors/sqla/utils.py b/superset/connectors/sqla/utils.py
index 389c5b9012..4fc11a4d1d 100644
--- a/superset/connectors/sqla/utils.py
+++ b/superset/connectors/sqla/utils.py
@@ -15,13 +15,17 @@
 # specific language governing permissions and limitations
 # under the License.
 from contextlib import closing
-from typing import Dict, List, Optional, TYPE_CHECKING
+from typing import Callable, Dict, List, Optional, Set, TYPE_CHECKING
 
 import sqlparse
 from flask_babel import lazy_gettext as _
+from sqlalchemy import and_, inspect, or_
+from sqlalchemy.engine import Engine
 from sqlalchemy.exc import NoSuchTableError
+from sqlalchemy.orm import Session
 from sqlalchemy.sql.type_api import TypeEngine
 
+from superset.columns.models import Column as NewColumn
 from superset.errors import ErrorLevel, SupersetError, SupersetErrorType
 from superset.exceptions import (
     SupersetGenericDBErrorException,
@@ -29,12 +33,16 @@ from superset.exceptions import (
 )
 from superset.models.core import Database
 from superset.result_set import SupersetResultSet
-from superset.sql_parse import has_table_query, ParsedQuery
+from superset.sql_parse import has_table_query, ParsedQuery, Table
+from superset.tables.models import Table as NewTable
 
 if TYPE_CHECKING:
     from superset.connectors.sqla.models import SqlaTable
 
 
+TEMPORAL_TYPES = {"DATETIME", "DATE", "TIME", "TIMEDELTA"}
+
+
 def get_physical_table_metadata(
     database: Database,
     table_name: str,
@@ -151,3 +159,78 @@ def validate_adhoc_subquery(raw_sql: str) -> None:
                 )
             )
     return
+
+
+def load_or_create_tables(  # pylint: disable=too-many-arguments
+    session: Session,
+    database_id: int,
+    default_schema: Optional[str],
+    tables: Set[Table],
+    conditional_quote: Callable[[str], str],
+    engine: Engine,
+) -> List[NewTable]:
+    """
+    Load or create new table model instances.
+    """
+    if not tables:
+        return []
+
+    # set the default schema in tables that don't have it
+    if default_schema:
+        fixed_tables = list(tables)
+        for i, table in enumerate(fixed_tables):
+            if table.schema is None:
+                fixed_tables[i] = Table(table.table, default_schema, table.catalog)
+        tables = set(fixed_tables)
+
+    # load existing tables
+    predicate = or_(
+        *[
+            and_(
+                NewTable.database_id == database_id,
+                NewTable.schema == table.schema,
+                NewTable.name == table.table,
+            )
+            for table in tables
+        ]
+    )
+    new_tables = session.query(NewTable).filter(predicate).all()
+
+    # add missing tables
+    existing = {(table.schema, table.name) for table in new_tables}
+    for table in tables:
+        if (table.schema, table.table) not in existing:
+            try:
+                inspector = inspect(engine)
+                column_metadata = inspector.get_columns(
+                    table.table, schema=table.schema
+                )
+            except Exception:  # pylint: disable=broad-except
+                continue
+            columns = [
+                NewColumn(
+                    name=column["name"],
+                    type=str(column["type"]),
+                    expression=conditional_quote(column["name"]),
+                    is_temporal=column["type"].python_type.__name__.upper()
+                    in TEMPORAL_TYPES,
+                    is_aggregation=False,
+                    is_physical=True,
+                    is_spatial=False,
+                    is_partition=False,
+                    is_increase_desired=True,
+                )
+                for column in column_metadata
+            ]
+            new_tables.append(
+                NewTable(
+                    name=table.table,
+                    schema=table.schema,
+                    catalog=None,
+                    database_id=database_id,
+                    columns=columns,
+                )
+            )
+            existing.add((table.schema, table.table))
+
+    return new_tables
diff --git a/superset/migrations/shared/utils.py b/superset/migrations/shared/utils.py
index 0331718117..bff25e05d1 100644
--- a/superset/migrations/shared/utils.py
+++ b/superset/migrations/shared/utils.py
@@ -14,10 +14,39 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
+import logging
+from typing import Any, Iterator, Optional, Set
+
 from alembic import op
 from sqlalchemy import engine_from_config
 from sqlalchemy.engine import reflection
 from sqlalchemy.exc import NoSuchTableError
+from sqloxide import parse_sql
+
+from superset.sql_parse import ParsedQuery, Table
+
+logger = logging.getLogger("alembic")
+
+
+# mapping between sqloxide and SQLAlchemy dialects
+sqloxide_dialects = {
+    "ansi": {"trino", "trinonative", "presto"},
+    "hive": {"hive", "databricks"},
+    "ms": {"mssql"},
+    "mysql": {"mysql"},
+    "postgres": {
+        "cockroachdb",
+        "hana",
+        "netezza",
+        "postgres",
+        "postgresql",
+        "redshift",
+        "vertica",
+    },
+    "snowflake": {"snowflake"},
+    "sqlite": {"sqlite", "gsheets", "shillelagh"},
+    "clickhouse": {"clickhouse"},
+}
 
 
 def table_has_column(table: str, column: str) -> bool:
@@ -38,3 +67,40 @@ def table_has_column(table: str, column: str) -> bool:
         return any(col["name"] == column for col in insp.get_columns(table))
     except NoSuchTableError:
         return False
+
+
+def find_nodes_by_key(element: Any, target: str) -> Iterator[Any]:
+    """
+    Find all nodes in a SQL tree matching a given key.
+    """
+    if isinstance(element, list):
+        for child in element:
+            yield from find_nodes_by_key(child, target)
+    elif isinstance(element, dict):
+        for key, value in element.items():
+            if key == target:
+                yield value
+            else:
+                yield from find_nodes_by_key(value, target)
+
+
+def extract_table_references(sql_text: str, sqla_dialect: str) -> Set[Table]:
+    """
+    Return all the dependencies from a SQL sql_text.
+    """
+    dialect = "generic"
+    for dialect, sqla_dialects in sqloxide_dialects.items():
+        if sqla_dialect in sqla_dialects:
+            break
+    try:
+        tree = parse_sql(sql_text, dialect=dialect)
+    except Exception:  # pylint: disable=broad-except
+        logger.warning("Unable to parse query with sqloxide: %s", sql_text)
+        # fallback to sqlparse
+        parsed = ParsedQuery(sql_text)
+        return parsed.tables
+
+    return {
+        Table(*[part["value"] for part in table["name"][::-1]])
+        for table in find_nodes_by_key(tree, "Table")
+    }
diff --git a/superset/migrations/versions/b8d3a24d9131_new_dataset_models.py b/superset/migrations/versions/b8d3a24d9131_new_dataset_models.py
index 35419e0066..75f5293034 100644
--- a/superset/migrations/versions/b8d3a24d9131_new_dataset_models.py
+++ b/superset/migrations/versions/b8d3a24d9131_new_dataset_models.py
@@ -25,7 +25,7 @@ Create Date: 2021-11-11 16:41:53.266965
 """
 
 import json
-from typing import List
+from typing import Callable, List, Optional, Set
 from uuid import uuid4
 
 import sqlalchemy as sa
@@ -40,7 +40,9 @@ from sqlalchemy_utils import UUIDType
 from superset import app, db
 from superset.connectors.sqla.models import ADDITIVE_METRIC_TYPES
 from superset.extensions import encrypted_field_factory
-from superset.sql_parse import ParsedQuery
+from superset.migrations.shared.utils import extract_table_references
+from superset.models.core import Database as OriginalDatabase
+from superset.sql_parse import Table
 
 # revision identifiers, used by Alembic.
 revision = "b8d3a24d9131"
@@ -228,6 +230,85 @@ class NewDataset(Base):
     external_url = sa.Column(sa.Text, nullable=True)
 
 
+TEMPORAL_TYPES = {"DATETIME", "DATE", "TIME", "TIMEDELTA"}
+
+
+def load_or_create_tables(
+    session: Session,
+    database_id: int,
+    default_schema: Optional[str],
+    tables: Set[Table],
+    conditional_quote: Callable[[str], str],
+) -> List[NewTable]:
+    """
+    Load or create new table model instances.
+    """
+    if not tables:
+        return []
+
+    # set the default schema in tables that don't have it
+    if default_schema:
+        tables = list(tables)
+        for i, table in enumerate(tables):
+            if table.schema is None:
+                tables[i] = Table(table.table, default_schema, table.catalog)
+
+    # load existing tables
+    predicate = or_(
+        *[
+            and_(
+                NewTable.database_id == database_id,
+                NewTable.schema == table.schema,
+                NewTable.name == table.table,
+            )
+            for table in tables
+        ]
+    )
+    new_tables = session.query(NewTable).filter(predicate).all()
+
+    # use original database model to get the engine
+    engine = (
+        session.query(OriginalDatabase)
+        .filter_by(id=database_id)
+        .one()
+        .get_sqla_engine(default_schema)
+    )
+    inspector = inspect(engine)
+
+    # add missing tables
+    existing = {(table.schema, table.name) for table in new_tables}
+    for table in tables:
+        if (table.schema, table.table) not in existing:
+            column_metadata = inspector.get_columns(table.table, schema=table.schema)
+            columns = [
+                NewColumn(
+                    name=column["name"],
+                    type=str(column["type"]),
+                    expression=conditional_quote(column["name"]),
+                    is_temporal=column["type"].python_type.__name__.upper()
+                    in TEMPORAL_TYPES,
+                    is_aggregation=False,
+                    is_physical=True,
+                    is_spatial=False,
+                    is_partition=False,
+                    is_increase_desired=True,
+                )
+                for column in column_metadata
+            ]
+            new_tables.append(
+                NewTable(
+                    name=table.table,
+                    schema=table.schema,
+                    catalog=None,
+                    database_id=database_id,
+                    columns=columns,
+                )
+            )
+            existing.add((table.schema, table.table))
+
+    return new_tables
+
+
 def after_insert(target: SqlaTable) -> None:  # pylint: disable=too-many-locals
     """
     Copy old datasets to the new models.
@@ -253,7 +334,10 @@ def after_insert(target: SqlaTable) -> None:  # pylint: disable=too-many-locals
         if column.is_active is False:
             continue
 
-        extra_json = json.loads(column.extra or "{}")
+        try:
+            extra_json = json.loads(column.extra or "{}")
+        except json.decoder.JSONDecodeError:
+            extra_json = {}
         for attr in {"groupby", "filterable", "verbose_name", "python_date_format"}:
             value = getattr(column, attr)
             if value:
@@ -279,7 +363,10 @@ def after_insert(target: SqlaTable) -> None:  # pylint: disable=too-many-locals
 
     # create metrics
     for metric in target.metrics:
-        extra_json = json.loads(metric.extra or "{}")
+        try:
+            extra_json = json.loads(metric.extra or "{}")
+        except json.decoder.JSONDecodeError:
+            extra_json = {}
         for attr in {"verbose_name", "metric_type", "d3format"}:
             value = getattr(metric, attr)
             if value:
@@ -309,8 +396,7 @@ def after_insert(target: SqlaTable) -> None:  # pylint: disable=too-many-locals
         )
 
     # physical dataset
-    tables = []
-    if target.sql is None:
+    if not target.sql:
         physical_columns = [column for column in columns if column.is_physical]
 
         # create table
@@ -323,7 +409,7 @@ def after_insert(target: SqlaTable) -> None:  # pylint: disable=too-many-locals
             is_managed_externally=target.is_managed_externally,
             external_url=target.external_url,
         )
-        tables.append(table)
+        tables = [table]
 
     # virtual dataset
     else:
@@ -332,20 +418,14 @@ def after_insert(target: SqlaTable) -> None:  # pylint: disable=too-many-locals
             column.is_physical = False
 
         # find referenced tables
-        parsed = ParsedQuery(target.sql)
-        referenced_tables = parsed.tables
-
-        # predicate for finding the referenced tables
-        predicate = or_(
-            *[
-                and_(
-                    NewTable.schema == (table.schema or target.schema),
-                    NewTable.name == table.table,
-                )
-                for table in referenced_tables
-            ]
+        referenced_tables = extract_table_references(target.sql, dialect_class.name)
+        tables = load_or_create_tables(
+            session,
+            target.database_id,
+            target.schema,
+            referenced_tables,
+            conditional_quote,
         )
-        tables = session.query(NewTable).filter(predicate).all()
 
     # create the new dataset
     dataset = NewDataset(
@@ -354,7 +434,7 @@ def after_insert(target: SqlaTable) -> None:  # pylint: disable=too-many-locals
         expression=target.sql or conditional_quote(target.table_name),
         tables=tables,
         columns=columns,
-        is_physical=target.sql is None,
+        is_physical=not target.sql,
         is_managed_externally=target.is_managed_externally,
         external_url=target.external_url,
     )
diff --git a/superset/migrations/shared/utils.py b/tests/unit_tests/migrations/__init__.py
similarity index 51%
copy from superset/migrations/shared/utils.py
copy to tests/unit_tests/migrations/__init__.py
index 0331718117..13a83393a9 100644
--- a/superset/migrations/shared/utils.py
+++ b/tests/unit_tests/migrations/__init__.py
@@ -14,27 +14,3 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-from alembic import op
-from sqlalchemy import engine_from_config
-from sqlalchemy.engine import reflection
-from sqlalchemy.exc import NoSuchTableError
-
-
-def table_has_column(table: str, column: str) -> bool:
-    """
-    Checks if a column exists in a given table.
-
-    :param table: A table name
-    :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)
-    try:
-        return any(col["name"] == column for col in insp.get_columns(table))
-    except NoSuchTableError:
-        return False
diff --git a/superset/migrations/shared/utils.py b/tests/unit_tests/migrations/shared/__init__.py
similarity index 51%
copy from superset/migrations/shared/utils.py
copy to tests/unit_tests/migrations/shared/__init__.py
index 0331718117..13a83393a9 100644
--- a/superset/migrations/shared/utils.py
+++ b/tests/unit_tests/migrations/shared/__init__.py
@@ -14,27 +14,3 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-from alembic import op
-from sqlalchemy import engine_from_config
-from sqlalchemy.engine import reflection
-from sqlalchemy.exc import NoSuchTableError
-
-
-def table_has_column(table: str, column: str) -> bool:
-    """
-    Checks if a column exists in a given table.
-
-    :param table: A table name
-    :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)
-    try:
-        return any(col["name"] == column for col in insp.get_columns(table))
-    except NoSuchTableError:
-        return False
diff --git a/tests/unit_tests/migrations/shared/utils_test.py b/tests/unit_tests/migrations/shared/utils_test.py
new file mode 100644
index 0000000000..cb5b2cbd0e
--- /dev/null
+++ b/tests/unit_tests/migrations/shared/utils_test.py
@@ -0,0 +1,56 @@
+# 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.
+# pylint: disable=import-outside-toplevel, unused-argument
+
+"""
+Test the SIP-68 migration.
+"""
+
+from pytest_mock import MockerFixture
+
+from superset.sql_parse import Table
+
+
+def test_extract_table_references(mocker: MockerFixture, app_context: None) -> None:
+    """
+    Test the ``extract_table_references`` helper function.
+    """
+    from superset.migrations.shared.utils import extract_table_references
+
+    assert extract_table_references("SELECT 1", "trino") == set()
+    assert extract_table_references("SELECT 1 FROM some_table", "trino") == {
+        Table(table="some_table", schema=None, catalog=None)
+    }
+    assert extract_table_references(
+        "SELECT 1 FROM some_catalog.some_schema.some_table", "trino"
+    ) == {Table(table="some_table", schema="some_schema", catalog="some_catalog")}
+    assert extract_table_references(
+        "SELECT * FROM some_table JOIN other_table ON some_table.id = other_table.id",
+        "trino",
+    ) == {
+        Table(table="some_table", schema=None, catalog=None),
+        Table(table="other_table", schema=None, catalog=None),
+    }
+
+    # test falling back to sqlparse
+    logger = mocker.patch("superset.migrations.shared.utils.logger")
+    sql = "SELECT * FROM table UNION ALL SELECT * FROM other_table"
+    assert extract_table_references(
+        sql,
+        "trino",
+    ) == {Table(table="other_table", schema=None, catalog=None)}
+    logger.warning.assert_called_with("Unable to parse query with sqloxide: %s", sql)


[superset] 13/23: refactor: Removes the CSS files from the Heatmap plugin (#19417)

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

villebro pushed a commit to tag 1.5.0rc1
in repository https://gitbox.apache.org/repos/asf/superset.git

commit a96ff005fda9a711ea754109408469f60d848765
Author: Michael S. Molina <70...@users.noreply.github.com>
AuthorDate: Thu Mar 31 13:32:20 2022 -0300

    refactor: Removes the CSS files from the Heatmap plugin (#19417)
    
    * refactor: Removes the CSS files from the Heatmap plugin
    
    * Adds peer dependencies
    
    (cherry picked from commit ac6b2f2d93a684c0d482ce3638d936568974041c)
---
 .../legacy-plugin-chart-heatmap/package.json       |  37 +++---
 .../legacy-plugin-chart-heatmap/src/Heatmap.css    |  72 ----------
 .../legacy-plugin-chart-heatmap/src/Heatmap.js     |   3 -
 .../src/ReactHeatmap.js                            |  22 ---
 .../src/ReactHeatmap.jsx                           | 148 +++++++++++++++++++++
 .../src/vendor/d3tip.css                           |  74 -----------
 6 files changed, 166 insertions(+), 190 deletions(-)

diff --git a/superset-frontend/plugins/legacy-plugin-chart-heatmap/package.json b/superset-frontend/plugins/legacy-plugin-chart-heatmap/package.json
index bb34fb817b..692a9dc107 100644
--- a/superset-frontend/plugins/legacy-plugin-chart-heatmap/package.json
+++ b/superset-frontend/plugins/legacy-plugin-chart-heatmap/package.json
@@ -2,31 +2,25 @@
   "name": "@superset-ui/legacy-plugin-chart-heatmap",
   "version": "0.18.25",
   "description": "Superset Legacy Chart - Heatmap",
-  "sideEffects": [
-    "*.css"
-  ],
-  "main": "lib/index.js",
-  "module": "esm/index.js",
-  "files": [
-    "esm",
-    "lib"
-  ],
-  "repository": {
-    "type": "git",
-    "url": "git+https://github.com/apache-superset/superset-ui.git"
-  },
   "keywords": [
     "superset"
   ],
-  "author": "Superset",
-  "license": "Apache-2.0",
+  "homepage": "https://github.com/apache-superset/superset-ui#readme",
   "bugs": {
     "url": "https://github.com/apache-superset/superset-ui/issues"
   },
-  "homepage": "https://github.com/apache-superset/superset-ui#readme",
-  "publishConfig": {
-    "access": "public"
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/apache-superset/superset-ui.git"
   },
+  "license": "Apache-2.0",
+  "author": "Superset",
+  "main": "lib/index.js",
+  "module": "esm/index.js",
+  "files": [
+    "esm",
+    "lib"
+  ],
   "dependencies": {
     "d3": "^3.5.17",
     "d3-svg-legend": "^1.x",
@@ -34,7 +28,12 @@
     "prop-types": "^15.6.2"
   },
   "peerDependencies": {
+    "@emotion/react": "^11.4.1",
     "@superset-ui/chart-controls": "*",
-    "@superset-ui/core": "*"
+    "@superset-ui/core": "*",
+    "react": "^16.13.1"
+  },
+  "publishConfig": {
+    "access": "public"
   }
 }
diff --git a/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/Heatmap.css b/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/Heatmap.css
deleted file mode 100644
index cf26d7b43e..0000000000
--- a/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/Heatmap.css
+++ /dev/null
@@ -1,72 +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.
- */
-
-.superset-legacy-chart-heatmap {
-  position: relative;
-  top: 0;
-  left: 0;
-  height: 100%;
-}
-
-.superset-legacy-chart-heatmap .axis text {
-  font: 10px sans-serif;
-  text-rendering: optimizeLegibility;
-  fill: #555;
-}
-
-.superset-legacy-chart-heatmap .background-rect {
-  stroke: #ddd;
-  fill-opacity: 0;
-  pointer-events: all;
-}
-
-.superset-legacy-chart-heatmap .axis path,
-.superset-legacy-chart-heatmap .axis line {
-  fill: none;
-  stroke: #ddd;
-  shape-rendering: crispEdges;
-}
-
-.superset-legacy-chart-heatmap canvas,
-.superset-legacy-chart-heatmap img {
-  image-rendering: optimizeSpeed; /* Older versions of FF          */
-  image-rendering: -moz-crisp-edges; /* FF 6.0+                       */
-  image-rendering: -webkit-optimize-contrast; /* Safari                        */
-  image-rendering: -o-crisp-edges; /* OS X & Windows Opera (12.02+) */
-  image-rendering: pixelated; /* Awesome future-browsers       */
-  -ms-interpolation-mode: nearest-neighbor; /* IE                            */
-}
-
-.superset-legacy-chart-heatmap .legendCells text {
-  font-size: 10px;
-  font-weight: normal;
-  opacity: 0;
-}
-
-.superset-legacy-chart-heatmap .legendCells .cell:first-child text {
-  opacity: 1;
-}
-.superset-legacy-chart-heatmap .legendCells .cell:last-child text {
-  opacity: 1;
-}
-
-.dashboard .superset-legacy-chart-heatmap .axis text {
-  font-size: 10px;
-  opacity: 0.75;
-}
diff --git a/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/Heatmap.js b/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/Heatmap.js
index 4626116aa8..dc44203911 100644
--- a/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/Heatmap.js
+++ b/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/Heatmap.js
@@ -27,9 +27,6 @@ import {
   getSequentialSchemeRegistry,
 } from '@superset-ui/core';
 
-import './vendor/d3tip.css';
-import './Heatmap.css';
-
 const propTypes = {
   data: PropTypes.shape({
     records: PropTypes.arrayOf(
diff --git a/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/ReactHeatmap.js b/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/ReactHeatmap.js
deleted file mode 100644
index 6b98c23dca..0000000000
--- a/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/ReactHeatmap.js
+++ /dev/null
@@ -1,22 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-import { reactify } from '@superset-ui/core';
-import Component from './Heatmap';
-
-export default reactify(Component);
diff --git a/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/ReactHeatmap.jsx b/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/ReactHeatmap.jsx
new file mode 100644
index 0000000000..22dde813b6
--- /dev/null
+++ b/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/ReactHeatmap.jsx
@@ -0,0 +1,148 @@
+/**
+ * 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 { reactify, css, styled } from '@superset-ui/core';
+import { Global } from '@emotion/react';
+import Component from './Heatmap';
+
+const ReactComponent = reactify(Component);
+
+const Heatmap = ({ className, ...otherProps }) => (
+  <div className={className}>
+    <Global
+      styles={theme => css`
+        .d3-tip {
+          line-height: 1;
+          padding: ${theme.gridUnit * 3}px;
+          background: ${theme.colors.grayscale.dark2};
+          color: ${theme.colors.grayscale.light5};
+          border-radius: 4px;
+          pointer-events: none;
+          z-index: 1000;
+          font-size: ${theme.typography.sizes.s}px;
+        }
+
+        /* Creates a small triangle extender for the tooltip */
+        .d3-tip:after {
+          box-sizing: border-box;
+          display: inline;
+          font-size: ${theme.typography.sizes.xs};
+          width: 100%;
+          line-height: 1;
+          color: ${theme.colors.grayscale.dark2};
+          position: absolute;
+          pointer-events: none;
+        }
+
+        /* Northward tooltips */
+        .d3-tip.n:after {
+          content: '\\25BC';
+          margin: -${theme.gridUnit}px 0 0 0;
+          top: 100%;
+          left: 0;
+          text-align: center;
+        }
+
+        /* Eastward tooltips */
+        .d3-tip.e:after {
+          content: '\\25C0';
+          margin: -${theme.gridUnit}px 0 0 0;
+          top: 50%;
+          left: -${theme.gridUnit * 2}px;
+        }
+
+        /* Southward tooltips */
+        .d3-tip.s:after {
+          content: '\\25B2';
+          margin: 0;
+          top: -${theme.gridUnit * 2}px;
+          left: 0;
+          text-align: center;
+        }
+
+        /* Westward tooltips */
+        .d3-tip.w:after {
+          content: '\\25B6';
+          margin: -${theme.gridUnit}px 0 0 0px;
+          top: 50%;
+          left: 100%;
+        }
+      `}
+    />
+    <ReactComponent {...otherProps} />
+  </div>
+);
+
+export default styled(Heatmap)`
+  ${({ theme }) => `
+    .superset-legacy-chart-heatmap {
+      position: relative;
+      top: 0;
+      left: 0;
+      height: 100%;
+    }
+
+    .superset-legacy-chart-heatmap .axis text {
+      font-size: ${theme.typography.sizes.xs}px;
+      text-rendering: optimizeLegibility;
+    }
+
+    .superset-legacy-chart-heatmap .background-rect {
+      stroke: ${theme.colors.grayscale.light2};
+      fill-opacity: 0;
+      pointer-events: all;
+    }
+
+    .superset-legacy-chart-heatmap .axis path,
+    .superset-legacy-chart-heatmap .axis line {
+      fill: none;
+      stroke: ${theme.colors.grayscale.light2};
+      shape-rendering: crispEdges;
+    }
+
+    .superset-legacy-chart-heatmap canvas,
+    .superset-legacy-chart-heatmap img {
+      image-rendering: optimizeSpeed; /* Older versions of FF */
+      image-rendering: -moz-crisp-edges; /* FF 6.0+ */
+      image-rendering: -webkit-optimize-contrast; /* Safari */
+      image-rendering: -o-crisp-edges; /* OS X & Windows Opera (12.02+) */
+      image-rendering: pixelated; /* Awesome future-browsers */
+      -ms-interpolation-mode: nearest-neighbor; /* IE */
+    }
+
+    .superset-legacy-chart-heatmap .legendCells text {
+      font-size: ${theme.typography.sizes.xs}px;
+      font-weight: ${theme.typography.weights.normal};
+      opacity: 0;
+    }
+
+    .superset-legacy-chart-heatmap .legendCells .cell:first-child text {
+      opacity: 1;
+    }
+
+    .superset-legacy-chart-heatmap .legendCells .cell:last-child text {
+      opacity: 1;
+    }
+
+    .dashboard .superset-legacy-chart-heatmap .axis text {
+      font-size: ${theme.typography.sizes.xs}px;
+      opacity: ${theme.opacity.heavy};
+    }
+  `}
+`;
diff --git a/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/vendor/d3tip.css b/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/vendor/d3tip.css
deleted file mode 100644
index 980a5be439..0000000000
--- a/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/vendor/d3tip.css
+++ /dev/null
@@ -1,74 +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.
- */
-/* from d3-tip */
-.d3-tip {
-  line-height: 1;
-  padding: 12px;
-  background: rgba(0, 0, 0, 0.8);
-  color: #fff;
-  border-radius: 2px;
-  pointer-events: none;
-  z-index: 1000;
-}
-
-/* Creates a small triangle extender for the tooltip */
-.d3-tip:after {
-  box-sizing: border-box;
-  display: inline;
-  font-size: 10px;
-  width: 100%;
-  line-height: 1;
-  color: rgba(0, 0, 0, 0.8);
-  position: absolute;
-  pointer-events: none;
-}
-
-/* Northward tooltips */
-.d3-tip.n:after {
-  content: '\25BC';
-  margin: -1px 0 0 0;
-  top: 100%;
-  left: 0;
-  text-align: center;
-}
-
-/* Eastward tooltips */
-.d3-tip.e:after {
-  content: '\25C0';
-  margin: -4px 0 0 0;
-  top: 50%;
-  left: -8px;
-}
-
-/* Southward tooltips */
-.d3-tip.s:after {
-  content: '\25B2';
-  margin: 0 0 1px 0;
-  top: -8px;
-  left: 0;
-  text-align: center;
-}
-
-/* Westward tooltips */
-.d3-tip.w:after {
-  content: '\25B6';
-  margin: -4px 0 0 -1px;
-  top: 50%;
-  left: 100%;
-}


[superset] 22/23: fix(dashboard list): do not show favorite star for anonymous users #18210 (#19409)

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

villebro pushed a commit to tag 1.5.0rc1
in repository https://gitbox.apache.org/repos/asf/superset.git

commit dba4610f9bd51120c70dc4a789343c3ee3dfde79
Author: dudasaron <68...@users.noreply.github.com>
AuthorDate: Mon Apr 4 09:38:44 2022 +0200

    fix(dashboard list): do not show favorite star for anonymous users  #18210 (#19409)
    
    * fix: Only show favorite star on dashboard list if user is logged in #18210
    
    * Fix linter errors
    
    (cherry picked from commit b8891acf4a478da8883bd168272715023d6a3351)
---
 .../src/components/ListViewCard/index.tsx          |  2 +-
 .../src/views/CRUD/dashboard/DashboardCard.tsx     | 14 ++--
 .../views/CRUD/dashboard/DashboardList.test.jsx    | 68 +++++++++++++++++--
 .../src/views/CRUD/dashboard/DashboardList.tsx     | 79 ++++++++++++++--------
 superset-frontend/src/views/CRUD/utils.tsx         |  2 +-
 5 files changed, 120 insertions(+), 45 deletions(-)

diff --git a/superset-frontend/src/components/ListViewCard/index.tsx b/superset-frontend/src/components/ListViewCard/index.tsx
index 79616c86ac..153b06489b 100644
--- a/superset-frontend/src/components/ListViewCard/index.tsx
+++ b/superset-frontend/src/components/ListViewCard/index.tsx
@@ -26,7 +26,7 @@ import CertifiedBadge from '../CertifiedBadge';
 const ActionsWrapper = styled.div`
   width: 64px;
   display: flex;
-  justify-content: space-between;
+  justify-content: flex-end;
 `;
 
 const StyledCard = styled(AntdCard)`
diff --git a/superset-frontend/src/views/CRUD/dashboard/DashboardCard.tsx b/superset-frontend/src/views/CRUD/dashboard/DashboardCard.tsx
index 25a012f86b..2da9f45515 100644
--- a/superset-frontend/src/views/CRUD/dashboard/DashboardCard.tsx
+++ b/superset-frontend/src/views/CRUD/dashboard/DashboardCard.tsx
@@ -44,7 +44,7 @@ interface DashboardCardProps {
   saveFavoriteStatus: (id: number, isStarred: boolean) => void;
   favoriteStatus: boolean;
   dashboardFilter?: string;
-  userId?: number;
+  userId?: string | number;
   showThumbnails?: boolean;
   handleBulkDashboardExport: (dashboardsToExport: Dashboard[]) => void;
 }
@@ -171,11 +171,13 @@ function DashboardCard({
               e.preventDefault();
             }}
           >
-            <FaveStar
-              itemId={dashboard.id}
-              saveFaveStar={saveFavoriteStatus}
-              isStarred={favoriteStatus}
-            />
+            {userId && (
+              <FaveStar
+                itemId={dashboard.id}
+                saveFaveStar={saveFavoriteStatus}
+                isStarred={favoriteStatus}
+              />
+            )}
             <AntdDropdown overlay={menu}>
               <Icons.MoreVert iconColor={theme.colors.grayscale.base} />
             </AntdDropdown>
diff --git a/superset-frontend/src/views/CRUD/dashboard/DashboardList.test.jsx b/superset-frontend/src/views/CRUD/dashboard/DashboardList.test.jsx
index 3561cafdbc..e42ba92ff2 100644
--- a/superset-frontend/src/views/CRUD/dashboard/DashboardList.test.jsx
+++ b/superset-frontend/src/views/CRUD/dashboard/DashboardList.test.jsx
@@ -36,6 +36,9 @@ import DashboardList from 'src/views/CRUD/dashboard/DashboardList';
 import ListView from 'src/components/ListView';
 import ListViewCard from 'src/components/ListViewCard';
 import PropertiesModal from 'src/dashboard/components/PropertiesModal';
+import FaveStar from 'src/components/FaveStar';
+import TableCollection from 'src/components/TableCollection';
+import CardCollection from 'src/components/ListView/CardCollection';
 
 // store needed for withToasts(DashboardTable)
 const mockStore = configureStore([thunk]);
@@ -104,15 +107,18 @@ describe('DashboardList', () => {
   });
 
   const mockedProps = {};
-  const wrapper = mount(
-    <MemoryRouter>
-      <Provider store={store}>
-        <DashboardList {...mockedProps} user={mockUser} />
-      </Provider>
-    </MemoryRouter>,
-  );
+  let wrapper;
 
   beforeAll(async () => {
+    fetchMock.resetHistory();
+    wrapper = mount(
+      <MemoryRouter>
+        <Provider store={store}>
+          <DashboardList {...mockedProps} user={mockUser} />
+        </Provider>
+      </MemoryRouter>,
+    );
+
     await waitForComponentToPaint(wrapper);
   });
 
@@ -178,6 +184,18 @@ describe('DashboardList', () => {
     await waitForComponentToPaint(wrapper);
     expect(wrapper.find(ConfirmStatusChange)).toExist();
   });
+
+  it('renders the Favorite Star column in list view for logged in user', async () => {
+    wrapper.find('[aria-label="list-view"]').first().simulate('click');
+    await waitForComponentToPaint(wrapper);
+    expect(wrapper.find(TableCollection).find(FaveStar)).toExist();
+  });
+
+  it('renders the Favorite Star in card view for logged in user', async () => {
+    wrapper.find('[aria-label="card-view"]').first().simulate('click');
+    await waitForComponentToPaint(wrapper);
+    expect(wrapper.find(CardCollection).find(FaveStar)).toExist();
+  });
 });
 
 describe('RTL', () => {
@@ -222,3 +240,39 @@ describe('RTL', () => {
     expect(importTooltip).toBeInTheDocument();
   });
 });
+
+describe('DashboardList - anonymous view', () => {
+  const mockedProps = {};
+  const mockUserLoggedOut = {};
+  let wrapper;
+
+  beforeAll(async () => {
+    fetchMock.resetHistory();
+    wrapper = mount(
+      <MemoryRouter>
+        <Provider store={store}>
+          <DashboardList {...mockedProps} user={mockUserLoggedOut} />
+        </Provider>
+      </MemoryRouter>,
+    );
+
+    await waitForComponentToPaint(wrapper);
+  });
+
+  afterAll(() => {
+    cleanup();
+    fetch.resetMocks();
+  });
+
+  it('does not render the Favorite Star column in list view for anonymous user', async () => {
+    wrapper.find('[aria-label="list-view"]').first().simulate('click');
+    await waitForComponentToPaint(wrapper);
+    expect(wrapper.find(TableCollection).find(FaveStar)).not.toExist();
+  });
+
+  it('does not render the Favorite Star in card view for anonymous user', async () => {
+    wrapper.find('[aria-label="card-view"]').first().simulate('click');
+    await waitForComponentToPaint(wrapper);
+    expect(wrapper.find(CardCollection).find(FaveStar)).not.toExist();
+  });
+});
diff --git a/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx b/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx
index aa1b9fc7b7..6b4bb04b1c 100644
--- a/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx
+++ b/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx
@@ -17,7 +17,7 @@
  * under the License.
  */
 import { styled, SupersetClient, t } from '@superset-ui/core';
-import React, { useState, useMemo } from 'react';
+import React, { useState, useMemo, useCallback } from 'react';
 import { Link } from 'react-router-dom';
 import rison from 'rison';
 import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags';
@@ -96,7 +96,11 @@ const Actions = styled.div`
 `;
 
 function DashboardList(props: DashboardListProps) {
-  const { addDangerToast, addSuccessToast } = props;
+  const {
+    addDangerToast,
+    addSuccessToast,
+    user: { userId },
+  } = props;
 
   const {
     state: {
@@ -144,7 +148,6 @@ function DashboardList(props: DashboardListProps) {
     addSuccessToast(t('Dashboard imported'));
   };
 
-  const { userId } = props.user;
   // TODO: Fix usage of localStorage keying on the user id
   const userKey = dangerouslyGetItemDoNotUse(userId?.toString(), null);
 
@@ -233,27 +236,25 @@ function DashboardList(props: DashboardListProps) {
 
   const columns = useMemo(
     () => [
-      ...(props.user.userId
-        ? [
-            {
-              Cell: ({
-                row: {
-                  original: { id },
-                },
-              }: any) => (
-                <FaveStar
-                  itemId={id}
-                  saveFaveStar={saveFavoriteStatus}
-                  isStarred={favoriteStatus[id]}
-                />
-              ),
-              Header: '',
-              id: 'id',
-              disableSortBy: true,
-              size: 'xs',
-            },
-          ]
-        : []),
+      {
+        Cell: ({
+          row: {
+            original: { id },
+          },
+        }: any) =>
+          userId && (
+            <FaveStar
+              itemId={id}
+              saveFaveStar={saveFavoriteStatus}
+              isStarred={favoriteStatus[id]}
+            />
+          ),
+        Header: '',
+        id: 'id',
+        disableSortBy: true,
+        size: 'xs',
+        hidden: !userId,
+      },
       {
         Cell: ({
           row: {
@@ -423,10 +424,15 @@ function DashboardList(props: DashboardListProps) {
       },
     ],
     [
+      userId,
       canEdit,
       canDelete,
       canExport,
-      ...(props.user.userId ? [favoriteStatus] : []),
+      saveFavoriteStatus,
+      favoriteStatus,
+      refreshData,
+      addSuccessToast,
+      addDangerToast,
     ],
   );
 
@@ -501,7 +507,7 @@ function DashboardList(props: DashboardListProps) {
           { label: t('Draft'), value: false },
         ],
       },
-      ...(props.user.userId ? [favoritesFilter] : []),
+      ...(userId ? [favoritesFilter] : []),
       {
         Header: t('Certified'),
         id: 'id',
@@ -545,8 +551,8 @@ function DashboardList(props: DashboardListProps) {
     },
   ];
 
-  function renderCard(dashboard: Dashboard) {
-    return (
+  const renderCard = useCallback(
+    (dashboard: Dashboard) => (
       <DashboardCard
         dashboard={dashboard}
         hasPerm={hasPerm}
@@ -557,6 +563,7 @@ function DashboardList(props: DashboardListProps) {
             ? userKey.thumbnails
             : isFeatureEnabled(FeatureFlag.THUMBNAILS)
         }
+        userId={userId}
         loading={loading}
         addDangerToast={addDangerToast}
         addSuccessToast={addSuccessToast}
@@ -565,8 +572,20 @@ function DashboardList(props: DashboardListProps) {
         favoriteStatus={favoriteStatus[dashboard.id]}
         handleBulkDashboardExport={handleBulkDashboardExport}
       />
-    );
-  }
+    ),
+    [
+      addDangerToast,
+      addSuccessToast,
+      bulkSelectEnabled,
+      favoriteStatus,
+      hasPerm,
+      loading,
+      userId,
+      refreshData,
+      saveFavoriteStatus,
+      userKey,
+    ],
+  );
 
   const subMenuButtons: SubMenuProps['buttons'] = [];
   if (canDelete || canExport) {
diff --git a/superset-frontend/src/views/CRUD/utils.tsx b/superset-frontend/src/views/CRUD/utils.tsx
index 3449d764ab..7f069a3e7c 100644
--- a/superset-frontend/src/views/CRUD/utils.tsx
+++ b/superset-frontend/src/views/CRUD/utils.tsx
@@ -285,7 +285,7 @@ export function handleDashboardDelete(
   addSuccessToast: (arg0: string) => void,
   addDangerToast: (arg0: string) => void,
   dashboardFilter?: string,
-  userId?: number,
+  userId?: string | number,
 ) {
   return SupersetClient.delete({
     endpoint: `/api/v1/dashboard/${id}`,


[superset] 23/23: update changelog, updating, version

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

villebro pushed a commit to tag 1.5.0rc1
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 96901d6c462247d65467105f9916f177ff619bb0
Author: Ville Brofeldt <vi...@gmail.com>
AuthorDate: Mon Apr 4 11:11:19 2022 +0300

    update changelog, updating, version
---
 CHANGELOG.md                   | 806 +++++++++++++++++++++++++++++++++++++++++
 UPDATING.md                    |   2 +-
 superset-frontend/package.json |   2 +-
 3 files changed, 808 insertions(+), 2 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9aa311f32a..dc75e5fe47 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,812 @@ specific language governing permissions and limitations
 under the License.
 -->
 ## Change Log
+### 1.5 (Fri Apr 1 13:05:03 2022 +0300)
+**Database Migrations**
+- [#19416](https://github.com/apache/superset/pull/19416) perf: improve perf in SIP-68 migration (@betodealmeida)
+- [#19225](https://github.com/apache/superset/pull/19225) fix(sql lab): deleting the last saved query or the last executed from history (@diegomedina248)
+- [#19078](https://github.com/apache/superset/pull/19078) feat: add permalink to dashboard and explore (@villebro)
+- [#18998](https://github.com/apache/superset/pull/18998) fix(plugin-chart-echarts): Apply temporary filters to Query B in explore (@kgabryje)
+- [#19022](https://github.com/apache/superset/pull/19022) fix: remove unneeded complexity in migration (@betodealmeida)
+- [#18992](https://github.com/apache/superset/pull/18992) fix(sqllab): Replace stringified 'null' schema column values with NULL (@john-bodley)
+- [#18948](https://github.com/apache/superset/pull/18948) fix(dataset): handle missing database in migration (@villebro)
+- [#17543](https://github.com/apache/superset/pull/17543) feat: new dataset/table/column models (@betodealmeida)
+- [#18561](https://github.com/apache/superset/pull/18561) feat: embedded dashboard (@lilykuang)
+- [#18093](https://github.com/apache/superset/pull/18093) feat: allow assets to be managed externally (@betodealmeida)
+- [#17925](https://github.com/apache/superset/pull/17925) refactor: Migration for Tabbed Sql Editor (@AAfghahi)
+- [#17928](https://github.com/apache/superset/pull/17928) fix: force_screenshot migration (@betodealmeida)
+- [#17853](https://github.com/apache/superset/pull/17853) feat: add force option to report screenshots (@betodealmeida)
+- [#17803](https://github.com/apache/superset/pull/17803) feat(plugin-chart-pivot-table): support series limit (@kgabryje)
+- [#17587](https://github.com/apache/superset/pull/17587) chore(explore): Migrate BigNumber to v1 api [ID-28][ID-55] (@kgabryje)
+- [#17360](https://github.com/apache/superset/pull/17360) fix: Change datatype of column type in BaseColumn to allow larger datatype names for complexed columns (@cccs-joel)
+- [#17728](https://github.com/apache/superset/pull/17728) fix: migration out-of-scope bind (@betodealmeida)
+- [#17604](https://github.com/apache/superset/pull/17604) feat(alerts): Column for select tabs to send (@m-ajay)
+- [#17323](https://github.com/apache/superset/pull/17323) fix: rename to schemas_allowed_for_file_upload in dbs.extra (@exemplary-citizen)
+- [#17275](https://github.com/apache/superset/pull/17275) fix: drop check constraint on dbs to rename column (@betodealmeida)
+- [#16756](https://github.com/apache/superset/pull/16756) refactor: Repeated boilerplate code between upload to database forms (@exemplary-citizen)
+
+**Features**
+- [#19328](https://github.com/apache/superset/pull/19328) feat(sql lab): enable ACE editor search in SQL editors (@diegomedina248)
+- [#19454](https://github.com/apache/superset/pull/19454) feat: improve adhoc SQL validation (@betodealmeida)
+- [#19364](https://github.com/apache/superset/pull/19364) feat: Embedded dashboard configuration (@suddjian)
+- [#19344](https://github.com/apache/superset/pull/19344) feat: disable edits on external assets (@betodealmeida)
+- [#19393](https://github.com/apache/superset/pull/19393) feat: linear imputation in Resample (@zhaoyongjie)
+- [#19259](https://github.com/apache/superset/pull/19259) feat(explore): Dataset panel option tooltips (@kgabryje)
+- [#19324](https://github.com/apache/superset/pull/19324) feat: introduce hashids permalink keys (@villebro)
+- [#19315](https://github.com/apache/superset/pull/19315) feat: import external management columns (@betodealmeida)
+- [#19317](https://github.com/apache/superset/pull/19317) feat: add duckdb as DataSource - Fixes #14563 (@rwhaling)
+- [#19318](https://github.com/apache/superset/pull/19318) feat: external management flags in CRUD (@betodealmeida)
+- [#19116](https://github.com/apache/superset/pull/19116) feat(advanced analysis): support MultiIndex column in post processing stage (@zhaoyongjie)
+- [#19289](https://github.com/apache/superset/pull/19289) feat: Implements Docusearch into docusaurus docs (@AAfghahi)
+- [#19220](https://github.com/apache/superset/pull/19220) feat: API for asset sync (@betodealmeida)
+- [#18953](https://github.com/apache/superset/pull/18953) feat: allow uploads in crud view (@pkdotson)
+- [#19232](https://github.com/apache/superset/pull/19232) feat(key-value): add superset metastore cache (@villebro)
+- [#19144](https://github.com/apache/superset/pull/19144) feat: SupersetClient config to override 401 behavior (@suddjian)
+- [#19038](https://github.com/apache/superset/pull/19038) feat: improve color consistency (save all labels) (@stephenLYZ)
+- [#19248](https://github.com/apache/superset/pull/19248) feat: add support for comments in adhoc clauses (@villebro)
+- [#19217](https://github.com/apache/superset/pull/19217) feat: import/export assets commands (@betodealmeida)
+- [#19141](https://github.com/apache/superset/pull/19141) feat(ui): Adding manifest prefix config (@craig-rueda)
+- [#19104](https://github.com/apache/superset/pull/19104) feat(sqllab): Add a configuration option to disable data preview (@cccs-Dustin)
+- [#19053](https://github.com/apache/superset/pull/19053) feat: scroll to bottom when adding a new native filter and the page is filled (@diegomedina248)
+- [#19215](https://github.com/apache/superset/pull/19215) feat: add export_related flag (@betodealmeida)
+- [#19055](https://github.com/apache/superset/pull/19055) feat: helper functions for RLS (@betodealmeida)
+- [#19085](https://github.com/apache/superset/pull/19085) feat(select): keep options order when not in async mode (@ktmud)
+- [#19031](https://github.com/apache/superset/pull/19031) feat(embedded): add optional dashboard ui configuration (@lilykuang)
+- [#18662](https://github.com/apache/superset/pull/18662) feat(viz-gallery): add 'feature' tag and fuzzy search weighting (@stephenLYZ)
+- [#18856](https://github.com/apache/superset/pull/18856) feat(select): sort exact and startsWith match to first (@ktmud)
+- [#18795](https://github.com/apache/superset/pull/18795) feat: Allow users to bust cache in report dashboard + alerts charts + alert dashboards (@hughhhh)
+- [#18793](https://github.com/apache/superset/pull/18793) feat: Adds support to multiple dependencies to the native filters (@michael-s-molina)
+- [#18799](https://github.com/apache/superset/pull/18799) feat: remove loading indicator when typing in select (@ktmud)
+- [#19004](https://github.com/apache/superset/pull/19004) feat: show user email in dashboard API (@betodealmeida)
+- [#18896](https://github.com/apache/superset/pull/18896) feat(TimeTableViz): sort by first metric (@serenajiang)
+- [#18945](https://github.com/apache/superset/pull/18945) feat(legacy-preset-chart-deckgl): Add ,.1f and ,.2f value formats to deckgl charts (@kgabryje)
+- [#18722](https://github.com/apache/superset/pull/18722) feat: DBC-UI Globally available across the app 🌎 (@hughhhh)
+- [#18773](https://github.com/apache/superset/pull/18773) feat: On window focus, redirect to login if the user has been logged out (@suddjian)
+- [#18848](https://github.com/apache/superset/pull/18848) feat(helm): allow adding pod labels (@vuongxuongminh)
+- [#18874](https://github.com/apache/superset/pull/18874) feat(native-filters): Implement filter cards (@kgabryje)
+- [#18876](https://github.com/apache/superset/pull/18876) feat(native-filters): Don't scroll main window when scrolling filter bar (@kgabryje)
+- [#18784](https://github.com/apache/superset/pull/18784) feat(native-filters): Re-arrange controls in FilterBar (@kgabryje)
+- [#18745](https://github.com/apache/superset/pull/18745) feat(maps): Adding country maps: BI/ET/RW/TZ/UG (+updated NG) (@Yann-J)
+- [#18748](https://github.com/apache/superset/pull/18748) feat(embedded): make guest token JWT audience callable or str (@lilykuang)
+- [#18576](https://github.com/apache/superset/pull/18576) feat: Improve state key generation for dashboards and charts (@michael-s-molina)
+- [#18651](https://github.com/apache/superset/pull/18651) feat(embedded): aud claim and type for guest token (@lilykuang)
+- [#18678](https://github.com/apache/superset/pull/18678) feat(explore): Implement chart empty states (@kgabryje)
+- [#18681](https://github.com/apache/superset/pull/18681) feat(explore): Implement metrics and columns popovers empty states (@kgabryje)
+- [#18712](https://github.com/apache/superset/pull/18712) feat(dashboard): Implement empty states for dashboard (@kgabryje)
+- [#18679](https://github.com/apache/superset/pull/18679) feat(explore): Implement data table empty states (@kgabryje)
+- [#18652](https://github.com/apache/superset/pull/18652) feat: Introduce a library for embedded iframe <-> host communication (@suddjian)
+- [#18676](https://github.com/apache/superset/pull/18676) feat: Implement EmptyState components (@kgabryje)
+- [#18653](https://github.com/apache/superset/pull/18653) feat: add prop to `setDBEngine` in DatabaseModal  (@hughhhh)
+- [#18642](https://github.com/apache/superset/pull/18642) feat(Helm): Redis with password supported in helm charts and redis chart version updated (@wiktor2200)
+- [#18649](https://github.com/apache/superset/pull/18649) feat(helm): allow to customize init image (@avakarev)
+- [#18626](https://github.com/apache/superset/pull/18626) feat: editable title xl certified badge (@opus-42)
+- [#18624](https://github.com/apache/superset/pull/18624) feat: Improves key expiration handling in Explore (@michael-s-molina)
+- [#17208](https://github.com/apache/superset/pull/17208) feat: Dynamic dashboard component (@simcha90)
+- [#18064](https://github.com/apache/superset/pull/18064) feat: datasource access to allow more granular access to tables on SQL Lab (@Painyjames)
+- [#18569](https://github.com/apache/superset/pull/18569) feat(explore): Allow using time formatter on temporal columns in data table (@kgabryje)
+- [#18618](https://github.com/apache/superset/pull/18618) feat(chart-data-api): download multiple csvs as zip (@villebro)
+- [#18620](https://github.com/apache/superset/pull/18620) feat: log decorator util - adding automatic logs out of the box (@ofekisr)
+- [#18080](https://github.com/apache/superset/pull/18080) feat: smart tooltip in datasourcepanel (@zhaoyongjie)
+- [#18250](https://github.com/apache/superset/pull/18250) feat: Embedded SDK (@suddjian)
+- [#18560](https://github.com/apache/superset/pull/18560) feat: 🚀 Docs V2 CI Script (@hughhhh)
+- [#18215](https://github.com/apache/superset/pull/18215) feat(explore): Keep or reset chart config after datasource change (@kgabryje)
+- [#18190](https://github.com/apache/superset/pull/18190) feat: Added success toasts for database, dataset, chart, dashboard, and query imports (@lyndsiWilliams)
+- [#18214](https://github.com/apache/superset/pull/18214) feat(docker-compose): add TAG option (@villebro)
+- [#17411](https://github.com/apache/superset/pull/17411) feat: Upgrade documentation V2 (@hughhhh)
+- [#18169](https://github.com/apache/superset/pull/18169) feat: Add toast for successful table or schema refreshes in Sqllab (@hughhhh)
+- [#18183](https://github.com/apache/superset/pull/18183) feat: add success message for database modal (@pkdotson)
+- [#17530](https://github.com/apache/superset/pull/17530) feat: embedded dashboard core (@suddjian)
+- [#18161](https://github.com/apache/superset/pull/18161) feat(helm): Add schema of values in Helm Chart (@ad-m)
+- [#18108](https://github.com/apache/superset/pull/18108) feat(explore): more toast feedback on user actions in Explore (@kgabryje)
+- [#18114](https://github.com/apache/superset/pull/18114) feat(dashboard): add toast feedback to dashboard actions (@kgabryje)
+- [#18116](https://github.com/apache/superset/pull/18116) feat: add toast feedback to annotation templates (@kgabryje)
+- [#18081](https://github.com/apache/superset/pull/18081) feat(country-map): added new countries in country-chart-map (@Hammad-Raza)
+- [#17917](https://github.com/apache/superset/pull/17917) feat(plugin-chart-echarts): support non-timeseries x-axis (@villebro)
+- [#17882](https://github.com/apache/superset/pull/17882) feat: Adds a key-value endpoint to store charts form data (@michael-s-molina)
+- [#17207](https://github.com/apache/superset/pull/17207) feat: add chart description in info tooltip (@stephenLYZ)
+- [#18045](https://github.com/apache/superset/pull/18045) feat(advanced analytics): support groupby in resample (@zhaoyongjie)
+- [#17593](https://github.com/apache/superset/pull/17593) feat: Trino Authentications (@dungdm93)
+- [#18041](https://github.com/apache/superset/pull/18041) feat: stop running CI on doc updates (@hughhhh)
+- [#18001](https://github.com/apache/superset/pull/18001) feat: shorter timeout on test_connection (@betodealmeida)
+- [#17758](https://github.com/apache/superset/pull/17758) feat(timeseries-chart): add percentage threshold input control (@corbinrobb)
+- [#18007](https://github.com/apache/superset/pull/18007) feat(chart): add Mexico to country map viz (@ktmud)
+- [#17749](https://github.com/apache/superset/pull/17749) feat(alerts): Select tabs to send backend (@m-ajay)
+- [#17968](https://github.com/apache/superset/pull/17968) feat: update time comparison choices (again) (@serenajiang)
+- [#17898](https://github.com/apache/superset/pull/17898) feat: Add support for Azure Data Explorer (Kusto) db engine spec (@Ceridan)
+- [#17804](https://github.com/apache/superset/pull/17804) feat: Update query states to use spinner vs. progress bar (@hughhhh)
+- [#17880](https://github.com/apache/superset/pull/17880) feat: Helm - custom service account creation and management (@wiktor2200)
+- [#17855](https://github.com/apache/superset/pull/17855) feat: configure force_screenshot (@betodealmeida)
+- [#17695](https://github.com/apache/superset/pull/17695) feat: bypass cache on screenshots for alerts (@betodealmeida)
+- [#17708](https://github.com/apache/superset/pull/17708) feat: Persist SQL Lab autocomplete setting across tabs and visits (@etr2460)
+- [#17776](https://github.com/apache/superset/pull/17776) feat: don't ask for password when DB exists (@betodealmeida)
+- [#17739](https://github.com/apache/superset/pull/17739) feat: add main datetime column to dataset editor (@betodealmeida)
+- [#17734](https://github.com/apache/superset/pull/17734) feat: Update makefile with frontend build (@hughhhh)
+- [#17655](https://github.com/apache/superset/pull/17655) feat: convert dataframe tests (@betodealmeida)
+- [#17589](https://github.com/apache/superset/pull/17589) feat: customize recent activity access (@villebro)
+- [#17658](https://github.com/apache/superset/pull/17658) feat(prophet): enable confidence intervals and y_hat without forecast (@exemplary-citizen)
+- [#17657](https://github.com/apache/superset/pull/17657) feat(viz): export csv with verbose_name (@chuancyzhang)
+- [#17512](https://github.com/apache/superset/pull/17512) feat(explore): export csv data pivoted for Pivot Table [ID-9] (@kgabryje)
+- [#17307](https://github.com/apache/superset/pull/17307) feat(explore): support show annotation label [ID-8] (@stephenLYZ)
+- [#17615](https://github.com/apache/superset/pull/17615) feat(legacy-preset-chart-nvd3): add richtooltip in nvd3 bar chart (@stephenLYZ)
+- [#17372](https://github.com/apache/superset/pull/17372) feat: Add single select and inverse selection to numeric range (#16722) (@mhoustonataegis)
+- [#17536](https://github.com/apache/superset/pull/17536) feat: Adds a key-value endpoint to store the state of dashboard filters (@michael-s-molina)
+- [#17581](https://github.com/apache/superset/pull/17581) feat: added option to set pod annotations on init-db pod to Helm chart. (@caviliar)
+- [#17554](https://github.com/apache/superset/pull/17554) feat: add classNames for apply and clearAll buttons for superset filters (@Nithin-George-Philips)
+- [#16346](https://github.com/apache/superset/pull/16346) feat: trino support server-cert (@dungdm93)
+- [#17410](https://github.com/apache/superset/pull/17410) feat(dashboard): Add divider component in native filters (@m-ajay)
+- [#17409](https://github.com/apache/superset/pull/17409) feat: add a config to enable retina quality images in screenshots (@eschutho)
+- [#17379](https://github.com/apache/superset/pull/17379) feat(explore): adhoc column expressions [ID-3] (@villebro)
+- [#17416](https://github.com/apache/superset/pull/17416) feat: Add quarter unit to datetrunc (@john-bodley)
+- [#16992](https://github.com/apache/superset/pull/16992) feat(native_filter_migration): add transition mode (@graceguo-supercat)
+- [#17202](https://github.com/apache/superset/pull/17202) feat(metrics): Provide override for disabling ad-hoc metrics (@john-bodley)
+- [#17336](https://github.com/apache/superset/pull/17336) feat: apply d3NumberFormat to table reports (@betodealmeida)
+- [#17329](https://github.com/apache/superset/pull/17329) feat: improve logic in is_select (@betodealmeida)
+- [#17252](https://github.com/apache/superset/pull/17252) feat: add possibility to set ingress class name in Helm chart (@Jolcik)
+- [#17237](https://github.com/apache/superset/pull/17237) feat: Make Jinja template applied in timestamp columns (@xingyc15)
+- [#17025](https://github.com/apache/superset/pull/17025) feat(dashboard): Add description to the native filter (@m-ajay)
+- [#17157](https://github.com/apache/superset/pull/17157) feat: Hide nav create with RBAC (@hughhhh)
+- [#17177](https://github.com/apache/superset/pull/17177) feat: support disabling domain sharding with a query param (@etr2460)
+- [#17134](https://github.com/apache/superset/pull/17134) feat: Add prettier to pre-commit workflow (@hughhhh)
+- [#17055](https://github.com/apache/superset/pull/17055) feat(Filter-sets): connect to api (@simcha90)
+- [#16856](https://github.com/apache/superset/pull/16856) feat: DBC UI Snowflake Form (@hughhhh)
+- [#16154](https://github.com/apache/superset/pull/16154) feat(dashboard): Let users re-arrange native filters (@m-ajay)
+- [#17039](https://github.com/apache/superset/pull/17039) feat(config): Add none force-refresh option for auto refresh (@yongchand)
+- [#17001](https://github.com/apache/superset/pull/17001) feat(linting): restrict direct use of supersetTheme in favor of ThemeProvider (@rusackas)
+
+**Fixes**
+- [#19409](https://github.com/apache/superset/pull/19409) fix(dashboard list): do not show favorite star for anonymous users #18210 (@dudasaron)
+- [#19483](https://github.com/apache/superset/pull/19483) fix: flaky test for charts changed on (@zhaoyongjie)
+- [#19366](https://github.com/apache/superset/pull/19366) fix: can't sync temporal flag on virtual table (@zhaoyongjie)
+- [#19456](https://github.com/apache/superset/pull/19456) fix: request samples with default row limit (@villebro)
+- [#19252](https://github.com/apache/superset/pull/19252) fix(drill): specify an SA URL parm of `impersonation_target` for drill+sadrill (@jnturton)
+- [#19372](https://github.com/apache/superset/pull/19372) fix(dashboard): make to show the correct owned objects (@prosdev0107)
+- [#19452](https://github.com/apache/superset/pull/19452) fix: can't save dataset modal (@zhaoyongjie)
+- [#19435](https://github.com/apache/superset/pull/19435) fix: missing init on importexport pkg (@dpgaspar)
+- [#19428](https://github.com/apache/superset/pull/19428) fix: add missing init on python pkg key_value (@dpgaspar)
+- [#19337](https://github.com/apache/superset/pull/19337) fix(database): make to allow the expose db as default in sqlalchemy form db (@prosdev0107)
+- [#19321](https://github.com/apache/superset/pull/19321) fix(readme): fix add-driver-docker url in Readme (@jqueguiner)
+- [#19369](https://github.com/apache/superset/pull/19369) fix(caching): support metastore cache for non-required caches (@villebro)
+- [#19010](https://github.com/apache/superset/pull/19010) fix(explore-sqllab): make that Timestamp column keep the Is temporal … (@prosdev0107)
+- [#19351](https://github.com/apache/superset/pull/19351) fix: number format should editable in time comparison (@zhaoyongjie)
+- [#19122](https://github.com/apache/superset/pull/19122) fix(report-add): make to add error toast message when already attached report is added again into dashboard or chart (@prosdev0107)
+- [#19138](https://github.com/apache/superset/pull/19138) fix: Remove gap from SQLLab results bottom (@codemaster08240328)
+- [#19355](https://github.com/apache/superset/pull/19355) fix: Fix NoAuthorizationError in /me endpoint (@geido)
+- [#19359](https://github.com/apache/superset/pull/19359) fix: [Explore] Adaptive formatting spelling (@kasiazjc)
+- [#19346](https://github.com/apache/superset/pull/19346) fix: typo (@etr2460)
+- [#19341](https://github.com/apache/superset/pull/19341) fix: Null values on Explore filter (@michael-s-molina)
+- [#19338](https://github.com/apache/superset/pull/19338) fix(docs): spelling (@ndbu)
+- [#19339](https://github.com/apache/superset/pull/19339) fix(embedded): download chart as image (@lilykuang)
+- [#19327](https://github.com/apache/superset/pull/19327) fix(dashboard): Chart stuck in loading state when when datasets request and chart request fail (@kgabryje)
+- [#19316](https://github.com/apache/superset/pull/19316) fix(explore): make to convert null to N/A in view results (@prosdev0107)
+- [#19326](https://github.com/apache/superset/pull/19326) fix: regression on Select component when handling null values (@diegomedina248)
+- [#18741](https://github.com/apache/superset/pull/18741) fix(dashboard): fix default filter bar visibility + add docs (@villebro)
+- [#19305](https://github.com/apache/superset/pull/19305) fix(cache): only warn about fallback cache for non-debug mode (@villebro)
+- [#19283](https://github.com/apache/superset/pull/19283) fix: Don't allow duplicated tag values in the Select (@michael-s-molina)
+- [#19296](https://github.com/apache/superset/pull/19296) fix: default toast position (@suddjian)
+- [#19258](https://github.com/apache/superset/pull/19258) fix(sql lab): most recently selected table should appear at the top of the list on the left panel (@diegomedina248)
+- [#19223](https://github.com/apache/superset/pull/19223) fix(dashboard): make to show the correct owned objects (@prosdev0107)
+- [#19263](https://github.com/apache/superset/pull/19263) fix(presto/trino): Add TIME/TIMESTAMP WITH TIME ZONE (@john-bodley)
+- [#19269](https://github.com/apache/superset/pull/19269) fix: write new dataset on update table if it doesn't exist (@eschutho)
+- [#19069](https://github.com/apache/superset/pull/19069) fix: custom SQL in Sort By Breaks Bar Chart (@diegomedina248)
+- [#19140](https://github.com/apache/superset/pull/19140) fix(chart-crud): make to update Viz type Filter label to Chart type (@prosdev0107)
+- [#19242](https://github.com/apache/superset/pull/19242) fix: allow subquery in ad-hoc SQL (@lilykuang)
+- [#18926](https://github.com/apache/superset/pull/18926) fix(explore comma): make that the comma can be added by removing it from token separators… (@prosdev0107)
+- [#19234](https://github.com/apache/superset/pull/19234) fix: adhoc column in legacy chart (@zhaoyongjie)
+- [#19076](https://github.com/apache/superset/pull/19076) fix: allow to select <NULL> in a native filter single mode (@diegomedina248)
+- [#19159](https://github.com/apache/superset/pull/19159) fix(select): make to consider the case sensitive in case of d3 format selector (@prosdev0107)
+- [#19139](https://github.com/apache/superset/pull/19139) fix: Popovers in Explore not attached to the fields they are triggered by (@diegomedina248)
+- [#19152](https://github.com/apache/superset/pull/19152) fix: auto-complete of tables and names are not working in SQL lab (@diegomedina248)
+- [#19226](https://github.com/apache/superset/pull/19226) fix: Revert "refactor: converted QueryAutoRefresh to functional component … (@hughhhh)
+- [#19158](https://github.com/apache/superset/pull/19158) fix: Logic for showing extension in Global Nav (@hughhhh)
+- [#19111](https://github.com/apache/superset/pull/19111) fix(sqllab): Updated blank states for query results and query history (@lyndsiWilliams)
+- [#19143](https://github.com/apache/superset/pull/19143) fix: clean up chart metadata config (@suddjian)
+- [#19084](https://github.com/apache/superset/pull/19084) fix(dashboard-css): make to stay custom css when reload (@prosdev0107)
+- [#19125](https://github.com/apache/superset/pull/19125) fix(dbs-dropdown): make to allow the search in supported db dropdown (@prosdev0107)
+- [#19121](https://github.com/apache/superset/pull/19121) fix(dashboard): scrolling table viz overlaps next chart (@diegomedina248)
+- [#19023](https://github.com/apache/superset/pull/19023) fix: Add perm for showing DBC-UI in Global Nav (@hughhhh)
+- [#19080](https://github.com/apache/superset/pull/19080) fix(dashboard-edge-cutting): make to be not cut without Filter (@prosdev0107)
+- [#19110](https://github.com/apache/superset/pull/19110) fix: cache key with guest token rls  (@lilykuang)
+- [#19095](https://github.com/apache/superset/pull/19095) fix(dashboard): Empty states overflowing small chart containers (@kgabryje)
+- [#18947](https://github.com/apache/superset/pull/18947) fix(plugin-chart-echarts): make to allow the custome of x & y axis title margin i… (@prosdev0107)
+- [#19088](https://github.com/apache/superset/pull/19088) fix(dashboard): import handle missing excluded charts (@villebro)
+- [#19072](https://github.com/apache/superset/pull/19072) fix: Show Totals error with sort and group by together (@codemaster08240328)
+- [#19037](https://github.com/apache/superset/pull/19037) fix: Database import error with a new field (@codemaster08240328)
+- [#19047](https://github.com/apache/superset/pull/19047) fix: SQL Lab autocomplete for selected tables (@hughhhh)
+- [#19071](https://github.com/apache/superset/pull/19071) fix: Pivot Table Conditional Formatting Doesn't Show All Options (@diegomedina248)
+- [#19030](https://github.com/apache/superset/pull/19030) fix(allow-db-explore): make that the set the allow-db-explore option (@prosdev0107)
+- [#18822](https://github.com/apache/superset/pull/18822) fix(altered-modal): make specified text fields wrap in table (@prosdev0107)
+- [#19061](https://github.com/apache/superset/pull/19061) fix: enable find text browser functionality inside SQL Lab editor (@diegomedina248)
+- [#19054](https://github.com/apache/superset/pull/19054) fix: show the total row count in the SQL Lab Query History tab when limited by DISPLAY_MAX_ROW (@diegomedina248)
+- [#19070](https://github.com/apache/superset/pull/19070) fix: Select clear and loading icons overlap (@michael-s-molina)
+- [#19065](https://github.com/apache/superset/pull/19065) fix: Dataset search when creating a chart (@michael-s-molina)
+- [#19024](https://github.com/apache/superset/pull/19024) fix(time-series table): display null values in time-series table and sortable (@graceguo-supercat)
+- [#19018](https://github.com/apache/superset/pull/19018) fix: ensure validation for db modal for googlesheets (@pkdotson)
+- [#19052](https://github.com/apache/superset/pull/19052) fix: add previous line hotkey to SQL Lab for macOS (@diegomedina248)
+- [#18690](https://github.com/apache/superset/pull/18690) fix: Update time grain expressions for Spark >= 3.x (@thomasdesr)
+- [#19048](https://github.com/apache/superset/pull/19048) fix: remove unnecessary divider (@pkdotson)
+- [#19059](https://github.com/apache/superset/pull/19059) fix(deck.gl): make Deck.gl Javascript tooltip generator works correctly (@chanyou0311)
+- [#19044](https://github.com/apache/superset/pull/19044) fix: improve cypress flakiness (@etr2460)
+- [#19043](https://github.com/apache/superset/pull/19043) fix(big-number): Big Number with Trendline Chart is not working if Time Grain is set to Month (@stephenLYZ)
+- [#19040](https://github.com/apache/superset/pull/19040) fix(docs): update impala.mdx with correct product name (@DLT1412)
+- [#18951](https://github.com/apache/superset/pull/18951) fix(SQL Editor): names new query tabs correctly (@cccs-Dustin)
+- [#18877](https://github.com/apache/superset/pull/18877) fix(nested-tab-available): make the another tabs into one tabs available (@prosdev0107)
+- [#18960](https://github.com/apache/superset/pull/18960) fix(chart): deprecate persisting url_params (@villebro)
+- [#18973](https://github.com/apache/superset/pull/18973) fix(packages): Fixing users packages (@craig-rueda)
+- [#18892](https://github.com/apache/superset/pull/18892) fix(tooltip-not-dissapeared): make the tooltip disappear when mouse away (@prosdev0107)
+- [#18982](https://github.com/apache/superset/pull/18982) fix(external-link): change the external link in creating chart dashboard (@prosdev0107)
+- [#18963](https://github.com/apache/superset/pull/18963) fix(native-filters): Default value not shown in native filters (@kgabryje)
+- [#18958](https://github.com/apache/superset/pull/18958) fix(plugin-chart-echarts): fix customize margin (@stephenLYZ)
+- [#18971](https://github.com/apache/superset/pull/18971) fix: add/find owners from report/alert modal (@graceguo-supercat)
+- [#18835](https://github.com/apache/superset/pull/18835) fix(Explore): Pivot table V2 sort by failure with D&D enabled (@geido)
+- [#18817](https://github.com/apache/superset/pull/18817) fix(sql Lab tabs): Empty SQL Lab tabs (@AAfghahi)
+- [#18758](https://github.com/apache/superset/pull/18758) fix: CSV upload with overprescribed table/schema (@john-bodley)
+- [#18849](https://github.com/apache/superset/pull/18849) fix: Null schema issue in the list of saved queries. (@codemaster08240328)
+- [#18939](https://github.com/apache/superset/pull/18939) fix(deck.gl): multiple layers map size is shrunk (@stephenLYZ)
+- [#18695](https://github.com/apache/superset/pull/18695) fix(native-filter): fix required filters of a dashboard won't load properly (@stephenLYZ)
+- [#18796](https://github.com/apache/superset/pull/18796) fix: Monospacing errors in dashboards & charts (@codemaster08240328)
+- [#18855](https://github.com/apache/superset/pull/18855) fix: Metadata browser in SQL not render nicely in Safari (@codemaster08240328)
+- [#18934](https://github.com/apache/superset/pull/18934) fix(sqllab): Un-render deselected tabs (@etr2460)
+- [#18853](https://github.com/apache/superset/pull/18853) fix: filter indicator pointing issue on popover (@codemaster08240328)
+- [#18911](https://github.com/apache/superset/pull/18911) fix: Set force=true for exporting CSV on dashboard charts (@hughhhh)
+- [#18920](https://github.com/apache/superset/pull/18920) fix(docs): add missing static files to build (@villebro)
+- [#18894](https://github.com/apache/superset/pull/18894) fix: Source from Apache Foundation's website link (@paulocauca)
+- [#18852](https://github.com/apache/superset/pull/18852) fix(dashboard): incorrect chart error with slow dataset api request (@ktmud)
+- [#18886](https://github.com/apache/superset/pull/18886) fix: Database list in Docusaurus (@geido)
+- [#18857](https://github.com/apache/superset/pull/18857) fix: FilterBox date endpoints to use [inclusive, exclusive) (@etr2460)
+- [#18846](https://github.com/apache/superset/pull/18846) fix(key-value): lost url_params after long-url feature (@zhaoyongjie)
+- [#18739](https://github.com/apache/superset/pull/18739) fix: Position of arrows in Table chart (@geido)
+- [#18789](https://github.com/apache/superset/pull/18789) fix(hive): stop query on the processing engine when user clicks STOP on SQL Lab (@saurabh3091)
+- [#18788](https://github.com/apache/superset/pull/18788) fix(Explore): Annotation cache should be ignore when adding new / updated annotation (@geido)
+- [#18746](https://github.com/apache/superset/pull/18746) fix(mssql): support top syntax for limiting queries (@sujiplr)
+- [#18813](https://github.com/apache/superset/pull/18813) fix(altered-modal): displayed the metric value in altered modal correctly (@prosdev0107)
+- [#18749](https://github.com/apache/superset/pull/18749) fix(sqllab): Removed the tooltip from CopyToClipboard button in sqllab (@prosdev0107)
+- [#18782](https://github.com/apache/superset/pull/18782) fix: contribution operator meets nan value (@zhaoyongjie)
+- [#18756](https://github.com/apache/superset/pull/18756) fix: update standalone url for slack reports (@hughhhh)
+- [#18786](https://github.com/apache/superset/pull/18786) fix(delete-modal): Changed the color of the help text with grayScale.dark1 in DeleteModal (@prosdev0107)
+- [#18717](https://github.com/apache/superset/pull/18717) fix(Dashboard): Fix blinking native filters on zoom (@geido)
+- [#18751](https://github.com/apache/superset/pull/18751) fix(helm): bump Helm chart release version (@wiktor2200)
+- [#18738](https://github.com/apache/superset/pull/18738) fix(Explore): Fix cache timeout field not being saved and unit tests (@geido)
+- [#18725](https://github.com/apache/superset/pull/18725) fix: report list search by created_by (@graceguo-supercat)
+- [#18056](https://github.com/apache/superset/pull/18056) fix(CRUD/listviews): Errors with rison and search strings using special characters (@corbinrobb)
+- [#18714](https://github.com/apache/superset/pull/18714) fix: Only redirect to relative paths when authentication expires (@geido)
+- [#15246](https://github.com/apache/superset/pull/15246) fix: passing url params in sqllab (@maltoze)
+- [#18666](https://github.com/apache/superset/pull/18666) fix(sqllab): fix query results sorting (@corbinrobb)
+- [#18686](https://github.com/apache/superset/pull/18686) fix: Fixed dancing tooltip in database list action buttons (@lyndsiWilliams)
+- [#18683](https://github.com/apache/superset/pull/18683) fix: unambiguous trino driver (@betodealmeida)
+- [#18685](https://github.com/apache/superset/pull/18685) fix(build): update webpack jsx paths (@anoojkinaginlsn)
+- [#18684](https://github.com/apache/superset/pull/18684) fix: revert handle null values in time-series table (@graceguo-supercat)
+- [#17826](https://github.com/apache/superset/pull/17826) fix(deck.gl): update view state on property changes (#17720) (@hbruch)
+- [#18603](https://github.com/apache/superset/pull/18603) fix(Explore): Force different color for same metrics in Mixed Time-Series (@geido)
+- [#18658](https://github.com/apache/superset/pull/18658) fix: dataset modal doesn't work in dev mode (@stephenLYZ)
+- [#18567](https://github.com/apache/superset/pull/18567) fix(mssql): support cte in virtual tables (@sujiplr)
+- [#18633](https://github.com/apache/superset/pull/18633) fix(csv-export): pivot v2 with verbose names (@villebro)
+- [#18084](https://github.com/apache/superset/pull/18084) fix(histogram): display correct percentile value instead of formula (@hoangminhquan-lhsdt)
+- [#18628](https://github.com/apache/superset/pull/18628) fix: check embedded feature flag in request loader (@lilykuang)
+- [#18562](https://github.com/apache/superset/pull/18562) fix: user UUIDs on export for Native Filter Configuration (@hughhhh)
+- [#18619](https://github.com/apache/superset/pull/18619) fix(helm): Fix support for ingress.ingressClassName (@ad-m)
+- [#18609](https://github.com/apache/superset/pull/18609) fix: change database database selector (@pkdotson)
+- [#18601](https://github.com/apache/superset/pull/18601) fix(jest): add superset-frontend to testRegex (@villebro)
+- [#18564](https://github.com/apache/superset/pull/18564) fix: sqllab schema select error msg (@pkdotson)
+- [#18579](https://github.com/apache/superset/pull/18579) fix(sqla): avoid unnecessary groupby in samples request (@villebro)
+- [#18246](https://github.com/apache/superset/pull/18246) fix(listview): add nowrap to view mode container (@villebro)
+- [#18577](https://github.com/apache/superset/pull/18577) fix: Update docusaurus.config.js (@hughhhh)
+- [#18575](https://github.com/apache/superset/pull/18575) fix: superset-doc.yaml workflow to not be immutable on deploy (@hughhhh)
+- [#18182](https://github.com/apache/superset/pull/18182) fix: new alert should have force_screenshot be true (@graceguo-supercat)
+- [#18252](https://github.com/apache/superset/pull/18252) fix(plugin-chart-echarts): fix forecasts on verbose metrics (@villebro)
+- [#18240](https://github.com/apache/superset/pull/18240) fix(teradata): LIMIT syntax  (@dmcnulla)
+- [#18224](https://github.com/apache/superset/pull/18224) fix(alert): remove extra < character in email report (@graceguo-supercat)
+- [#18201](https://github.com/apache/superset/pull/18201) fix: Build scripts (@geido)
+- [#18219](https://github.com/apache/superset/pull/18219) fix: Add mexico back to country map (@etr2460)
+- [#18181](https://github.com/apache/superset/pull/18181) fix: Explore long URL problem (@michael-s-molina)
+- [#18217](https://github.com/apache/superset/pull/18217) fix(plugin-chart-echarts): use verbose x-axis name when defined (@villebro)
+- [#18216](https://github.com/apache/superset/pull/18216) fix(explore): Pie chart label formatting when series is temporal (@kgabryje)
+- [#17881](https://github.com/apache/superset/pull/17881) fix(sql): unable to filter text with quotes (@stephenLYZ)
+- [#18205](https://github.com/apache/superset/pull/18205) fix(tests): Remove walrus operator for Python 3.7 compatiblity (@ad-m)
+- [#18145](https://github.com/apache/superset/pull/18145) fix(native-filters): values is not sorted when setting sort option (@stephenLYZ)
+- [#18174](https://github.com/apache/superset/pull/18174) fix(helm): allows extra top-level properties (@ad-m)
+- [#18180](https://github.com/apache/superset/pull/18180) fix: Default values getting cleared in native filters form (@geido)
+- [#17986](https://github.com/apache/superset/pull/17986) fix: Assign an owner when creating a dataset from a csv, excel or tabular (@cccs-joel)
+- [#18171](https://github.com/apache/superset/pull/18171) fix: null value and empty string in filter (@zhaoyongjie)
+- [#18137](https://github.com/apache/superset/pull/18137) fix(dashboard): support input undo when editing chart title (@stephenLYZ)
+- [#18157](https://github.com/apache/superset/pull/18157) fix: remove standalone (@AAfghahi)
+- [#18164](https://github.com/apache/superset/pull/18164) fix: Dataset column names are no longer editable (@lyndsiWilliams)
+- [#18117](https://github.com/apache/superset/pull/18117) fix: exclude node_modules from ts tracking (@etr2460)
+- [#18146](https://github.com/apache/superset/pull/18146) fix(textarea-control): ace editor input exception (@stephenLYZ)
+- [#18046](https://github.com/apache/superset/pull/18046) fix(import_datasources): --sync flag works correctly (@cccs-Dustin)
+- [#18152](https://github.com/apache/superset/pull/18152) fix: undefined error when adding extra sequential color scheme (@kgabryje)
+- [#17843](https://github.com/apache/superset/pull/17843) fix(explore): fix chart embed code modal glitch (@stephenLYZ)
+- [#18111](https://github.com/apache/superset/pull/18111) fix: logging warning on dataframe (don't use python's warnings) (@dpgaspar)
+- [#18089](https://github.com/apache/superset/pull/18089) fix(docs): load correctly image for "Create New Chart" (@ad-m)
+- [#18097](https://github.com/apache/superset/pull/18097) fix: contributing misspelling (@em0227)
+- [#18086](https://github.com/apache/superset/pull/18086) fix: can't use examples helpers on non app context based environment (@ofekisr)
+- [#18087](https://github.com/apache/superset/pull/18087) fix(docs): Correction of proper names format in README (@ad-m)
+- [#18073](https://github.com/apache/superset/pull/18073) fix(generator): add lockfile and fix styling issues (@villebro)
+- [#18027](https://github.com/apache/superset/pull/18027) fix(generator): more cleanup to plugin framework (@villebro)
+- [#18051](https://github.com/apache/superset/pull/18051) fix: revert "feat: stop running CI on doc updates" (@hughhhh)
+- [#18039](https://github.com/apache/superset/pull/18039) fix: handle null values in time-series table (@graceguo-supercat)
+- [#18021](https://github.com/apache/superset/pull/18021) fix: Disable filtering on wide result sets (@etr2460)
+- [#18017](https://github.com/apache/superset/pull/18017) fix: revert #17654 to fix subselect table name parsing (@etr2460)
+- [#17995](https://github.com/apache/superset/pull/17995) fix: Workaround for sqlparse issue #652 (@john-bodley)
+- [#17388](https://github.com/apache/superset/pull/17388) fix(sqla): Adhere to series limit ordering for pre-query (@john-bodley)
+- [#17999](https://github.com/apache/superset/pull/17999) fix(helm): pin correct psycopg2 version (@villebro)
+- [#17988](https://github.com/apache/superset/pull/17988) fix: Keep Report modal open when there's an error (@lyndsiWilliams)
+- [#17985](https://github.com/apache/superset/pull/17985) fix:  dashboard full screen layout (@pkdotson)
+- [#17931](https://github.com/apache/superset/pull/17931) fix(sqllab): Dancing Tooltip in SQL editor dropdown (@lyndsiWilliams)
+- [#17974](https://github.com/apache/superset/pull/17974) fix: null dates in table chart (@etr2460)
+- [#17878](https://github.com/apache/superset/pull/17878) fix: Returns 404 instead of 500 for unknown dashboard filter state keys (@michael-s-molina)
+- [#17982](https://github.com/apache/superset/pull/17982) fix(native-filters): apply default value correctly when it's set (@kgabryje)
+- [#17937](https://github.com/apache/superset/pull/17937) fix(docs): update clickhouse.mdx (@rfraposa)
+- [#17951](https://github.com/apache/superset/pull/17951) fix(Select): options in the select component will be overwritten due to timing of React.setState (@stephenLYZ)
+- [#17957](https://github.com/apache/superset/pull/17957) fix: package.json local file dependencies (@etr2460)
+- [#17786](https://github.com/apache/superset/pull/17786) fix: process color scheme configs correctly (@suddjian)
+- [#17918](https://github.com/apache/superset/pull/17918) fix(cypress): flake cypress test case (@zhaoyongjie)
+- [#17920](https://github.com/apache/superset/pull/17920) fix(helm): service account apiVersion indentation (@wiktor2200)
+- [#17877](https://github.com/apache/superset/pull/17877) fix(translation): include babel-compile in Dockerfile (#17876) (@hbruch)
+- [#17872](https://github.com/apache/superset/pull/17872) fix(explore): simple tab content input problem in the filter control  (@stephenLYZ)
+- [#17887](https://github.com/apache/superset/pull/17887) fix: Removes duplicated import in dashboard filter state tests (@michael-s-molina)
+- [#17885](https://github.com/apache/superset/pull/17885) fix: tests can failed on different order executions (@ofekisr)
+- [#17886](https://github.com/apache/superset/pull/17886) fix: failed mypy in master branch (@ofekisr)
+- [#17854](https://github.com/apache/superset/pull/17854) fix(ui): show SCHEDULED_QUERIES template in SqlLab (@cemremengu)
+- [#17851](https://github.com/apache/superset/pull/17851) fix(deckgl): deckgl unable to load map (@zhaoyongjie)
+- [#17856](https://github.com/apache/superset/pull/17856) fix: corrected misspelling in percent_metrics (@briannguyen4)
+- [#17784](https://github.com/apache/superset/pull/17784) fix: extra column in metrics (@betodealmeida)
+- [#17740](https://github.com/apache/superset/pull/17740) fix: dataset extra import/export (@betodealmeida)
+- [#17680](https://github.com/apache/superset/pull/17680) fix: replace datamask with key from new key value api (@pkdotson)
+- [#17726](https://github.com/apache/superset/pull/17726) fix(explore): make SORT DESCENDING visible if SORT BY has value (@stephenLYZ)
+- [#17823](https://github.com/apache/superset/pull/17823) fix: import typeddict from extensions (@villebro)
+- [#17813](https://github.com/apache/superset/pull/17813) fix: map schemes json to SequentialScheme (#17812) (@hbruch)
+- [#17788](https://github.com/apache/superset/pull/17788) fix: accept null params for validation (@eschutho)
+- [#17800](https://github.com/apache/superset/pull/17800) fix(docs): bump broken packages (@villebro)
+- [#17751](https://github.com/apache/superset/pull/17751) fix: [alert] allow decimal for alert threshold value (@graceguo-supercat)
+- [#17783](https://github.com/apache/superset/pull/17783) fix: New Members per Month time range (@betodealmeida)
+- [#17775](https://github.com/apache/superset/pull/17775) fix: running dev-server on a different port (@betodealmeida)
+- [#17499](https://github.com/apache/superset/pull/17499) fix: [alert] should run alert query from report account (@graceguo-supercat)
+- [#17777](https://github.com/apache/superset/pull/17777) fix(filter-sets): 404 on dashboard load (@villebro)
+- [#17774](https://github.com/apache/superset/pull/17774) fix(alembic): Making Alembic logger config optional (@craig-rueda)
+- [#17768](https://github.com/apache/superset/pull/17768) fix: change 401 response to a 403 for Security Exceptions (@rusackas)
+- [#17760](https://github.com/apache/superset/pull/17760) fix: miss-spelling on CONTRIBUTING.md line 1351 (@MayUWish)
+- [#17765](https://github.com/apache/superset/pull/17765) fix(plugin-chart-table): sort alphanumeric columns case insensitive (@kgabryje)
+- [#17730](https://github.com/apache/superset/pull/17730) fix: add __init__.py to key_value (@bkyryliuk)
+- [#17727](https://github.com/apache/superset/pull/17727) fix: local warning in the frontend development (@stephenLYZ)
+- [#17738](https://github.com/apache/superset/pull/17738) fix: column extra in import/export (@betodealmeida)
+- [#17748](https://github.com/apache/superset/pull/17748) fix: import DB errors (@betodealmeida)
+- [#17741](https://github.com/apache/superset/pull/17741) fix: import dashboard stale filter_scopes (@betodealmeida)
+- [#17649](https://github.com/apache/superset/pull/17649) fix(Mixed Timeseries Chart): Custom Metric Label  (@Yahyakiani)
+- [#17732](https://github.com/apache/superset/pull/17732) fix: import dash with missing immune ID (@betodealmeida)
+- [#17713](https://github.com/apache/superset/pull/17713) fix(postgres): remove redundant tz factory (@villebro)
+- [#17711](https://github.com/apache/superset/pull/17711) fix(explore): don't apply time range filter to Samples table (@kgabryje)
+- [#17690](https://github.com/apache/superset/pull/17690) fix: Dashboard header negative margin on save (@geido)
+- [#17703](https://github.com/apache/superset/pull/17703) fix(explore): deck.gl Multiple chart broken (@stephenLYZ)
+- [#17638](https://github.com/apache/superset/pull/17638) fix(select): select component sort functionality on certain options (@corbinrobb)
+- [#17692](https://github.com/apache/superset/pull/17692) fix(Dashboard): Undefined error in default value in Native Filters (@geido)
+- [#17597](https://github.com/apache/superset/pull/17597) fix: Redirect on 401 (@geido)
+- [#17654](https://github.com/apache/superset/pull/17654) fix: extract tables doesn't work with reserved keywords (@betodealmeida)
+- [#17673](https://github.com/apache/superset/pull/17673) fix(translations): extract messages from js and ts files (@hbruch)
+- [#17664](https://github.com/apache/superset/pull/17664) fix(legacy-plugin-chart-calendar): fix timestamp timezone in Calendar (@kgabryje)
+- [#17644](https://github.com/apache/superset/pull/17644) fix: Allows PUT and DELETE only for owners of dashboard filter state (@michael-s-molina)
+- [#17641](https://github.com/apache/superset/pull/17641) fix(sqla): make text clause escaping optional (@villebro)
+- [#17636](https://github.com/apache/superset/pull/17636) fix: allow for multiple columns in pivot table report (@eschutho)
+- [#17612](https://github.com/apache/superset/pull/17612) fix(dataset): modal sql editor error (@stephenLYZ)
+- [#17620](https://github.com/apache/superset/pull/17620) fix(api): add missing fields to dataset schemas (@villebro)
+- [#17618](https://github.com/apache/superset/pull/17618) fix: Revert "fix(Dashboard): Only apply changes when editing properties" (@geido)
+- [#17613](https://github.com/apache/superset/pull/17613) fix: Dashboard export loading indicator (@michael-s-molina)
+- [#17611](https://github.com/apache/superset/pull/17611) fix: Dashboard API flaky test (@michael-s-molina)
+- [#17600](https://github.com/apache/superset/pull/17600) fix: Ch31968query context (@AAfghahi)
+- [#17547](https://github.com/apache/superset/pull/17547) fix: fix text overflow in toast (@pkdotson)
+- [#17542](https://github.com/apache/superset/pull/17542) fix: Visualizations don't load when using keyboard shortcuts (@michael-s-molina)
+- [#17539](https://github.com/apache/superset/pull/17539) fix(superset.cli): superset cli group doesn't support superset extension app  (@ofekisr)
+- [#14512](https://github.com/apache/superset/pull/14512) fix: update kubernetes.mdx (@shicholas)
+- [#17527](https://github.com/apache/superset/pull/17527) fix: RBAC for `can_export` for any resource (@hughhhh)
+- [#17555](https://github.com/apache/superset/pull/17555) fix(lint): remove redis xadd type ignore (@villebro)
+- [#17048](https://github.com/apache/superset/pull/17048) fix(elasticsearch): time_zone setting does not work for cast datetime expressions (@aniaan)
+- [#17503](https://github.com/apache/superset/pull/17503) fix(explore): Metric control breaks when saved metric deleted from dataset (@kgabryje)
+- [#17515](https://github.com/apache/superset/pull/17515) fix(CRUD views): change empty rows layout in TableCollection to be consistent with its headers (@corbinrobb)
+- [#15182](https://github.com/apache/superset/pull/15182) fix: hiding HiddenControl inputs for real, so they don't add height (@rusackas)
+- [#17511](https://github.com/apache/superset/pull/17511) fix: Dashboard access when DASHBOARD_RBAC is disabled (@michael-s-molina)
+- [#16799](https://github.com/apache/superset/pull/16799) fix: Bulk update Spanish translations (@dreglad)
+- [#14302](https://github.com/apache/superset/pull/14302) fix(hive): Update _latest_partition_from_df in HiveEngineSpec to work on tables with multiple indexes (@codenamelxl)
+- [#17458](https://github.com/apache/superset/pull/17458) fix: Always use temporal type for dttm columns [ID-2] (@kgabryje)
+- [#17470](https://github.com/apache/superset/pull/17470) fix(presto): expand data with null item (@ganczarek)
+- [#15254](https://github.com/apache/superset/pull/15254) fix: feature flags typing (@dpgaspar)
+- [#17480](https://github.com/apache/superset/pull/17480) fix: Update Makefile (@hughhhh)
+- [#17471](https://github.com/apache/superset/pull/17471) fix: create-admin command in Makefile (@hughhhh)
+- [#17462](https://github.com/apache/superset/pull/17462) fix(Dashboard): Save empty label_colors in json_metadata (@geido)
+- [#14305](https://github.com/apache/superset/pull/14305) fix(views/core): iteration over None type (@rasta-rocket)
+- [#16566](https://github.com/apache/superset/pull/16566) fix(docker): add ecpg to docker image (@villebro)
+- [#10811](https://github.com/apache/superset/pull/10811) fix(mssql): week time grain should respect datefirst setting (@binome74)
+- [#17440](https://github.com/apache/superset/pull/17440) fix: should not show migration toast if user not enable dashboard filter components (@graceguo-supercat)
+- [#17385](https://github.com/apache/superset/pull/17385) fix(sql lab): NULL styling in grid cell (@lyndsiWilliams)
+- [#12092](https://github.com/apache/superset/pull/12092) fix: 'DataFrame' object has no attribute 'ix' (@minzhang2110)
+- [#17429](https://github.com/apache/superset/pull/17429) fix: allow POST chart/data request without CSRF token (@etr2460)
+- [#16900](https://github.com/apache/superset/pull/16900) fix(chart): ensure samples data is displayed (@pkdotson)
+- [#17426](https://github.com/apache/superset/pull/17426) fix: Workaround for Pandas Timestamp.isoformat issue (@john-bodley)
+- [#17392](https://github.com/apache/superset/pull/17392) fix(Dashboard): Only apply changes when editing properties (@geido)
+- [#17380](https://github.com/apache/superset/pull/17380) fix(Dashboard): Missing dashboardId when refreshing Explore (@geido)
+- [#17106](https://github.com/apache/superset/pull/17106) fix(dashboard): don't show report modal for anonymous user (@Mayur)
+- [#17135](https://github.com/apache/superset/pull/17135) fix: title case in menus (@Maxime Beauchemin)
+- [#17383](https://github.com/apache/superset/pull/17383) fix: Partial revert of 17236 (@john-bodley)
+- [#17348](https://github.com/apache/superset/pull/17348) fix: Users being able to update datasets across DBs (@hughhhh)
+- [#17382](https://github.com/apache/superset/pull/17382) fix(sqllab): Query limit dropdown number breaks to separate lines (@lyndsiWilliams)
+- [#17309](https://github.com/apache/superset/pull/17309) fix: various typo fixes in MD files (@PApostol)
+- [#17370](https://github.com/apache/superset/pull/17370) fix: failed timezone selector UT (@zhaoyongjie)
+- [#17369](https://github.com/apache/superset/pull/17369) fix: Database selector overflow (@michael-s-molina)
+- [#17356](https://github.com/apache/superset/pull/17356) fix: Remove horizontal scroll navigation from SQL Lab (@etr2460)
+- [#17357](https://github.com/apache/superset/pull/17357) fix(Dashboard): Make color scheme optional (@geido)
+- [#17287](https://github.com/apache/superset/pull/17287) fix(dashboard): Return columns and verbose_map for groupby values of Pivot Table v2 [ID-7] (@kgabryje)
+- [#17331](https://github.com/apache/superset/pull/17331) fix: Show sqllab state when deleting databases (@hughhhh)
+- [#17289](https://github.com/apache/superset/pull/17289) fix: removed redundant logger definition (@gnought)
+- [#17236](https://github.com/apache/superset/pull/17236) fix: revert default series sort-by metric (@john-bodley)
+- [#17312](https://github.com/apache/superset/pull/17312) fix(sqla): Normalize prequery result type (@john-bodley)
+- [#17268](https://github.com/apache/superset/pull/17268) fix: Unable to select None for Alert's log retention (@michael-s-molina)
+- [#17228](https://github.com/apache/superset/pull/17228) fix: remove variables from styled component (@pkdotson)
+- [#17200](https://github.com/apache/superset/pull/17200) fix(Dashboard): Sidepanel positioning (@geido)
+- [#17184](https://github.com/apache/superset/pull/17184) fix(Explore): Remove changes to the properties on cancel (@geido)
+- [#17314](https://github.com/apache/superset/pull/17314) fix: run npm install to fix a bad package version (@eschutho)
+- [#17303](https://github.com/apache/superset/pull/17303) fix: datatable crash when column is empty string [ID-59] (@zhaoyongjie)
+- [#17292](https://github.com/apache/superset/pull/17292) fix: Change 500 error to 422 in Sql Lab (@AAfghahi)
+- [#17239](https://github.com/apache/superset/pull/17239) fix: revert "feat(sqla): apply time grain to all temporal groupbys" (@john-bodley)
+- [#17223](https://github.com/apache/superset/pull/17223) fix(reports): Owners no longer showing undefined in reports (@lyndsiWilliams)
+- [#17242](https://github.com/apache/superset/pull/17242) fix: Updated connection string for Firebolt (@raghavSharmaCode)
+- [#17234](https://github.com/apache/superset/pull/17234) fix(annotations): handle required fields properly (@villebro)
+- [#17217](https://github.com/apache/superset/pull/17217) fix(dashboard): Fix issue #17144 - Resetting native filter form does not clear the values. (@m-ajay)
+- [#17221](https://github.com/apache/superset/pull/17221) fix(Dashboard): Position of the tools of the Tabs component in Edit Dashboard (@geido)
+- [#17047](https://github.com/apache/superset/pull/17047) fix: Allow chart owners to edit title (@belgren)
+- [#17205](https://github.com/apache/superset/pull/17205) fix: reenabling terser plugin for webpack (@rusackas)
+- [#17194](https://github.com/apache/superset/pull/17194) fix: use cache for csv download (@eschutho)
+- [#17169](https://github.com/apache/superset/pull/17169) fix: Order of Select items when unselecting (@michael-s-molina)
+- [#17213](https://github.com/apache/superset/pull/17213) fix: CI is blocked by mypy checking (@zhaoyongjie)
+- [#17196](https://github.com/apache/superset/pull/17196) fix(bigquery): calculated column cannot orderby in BigQuery (@zhaoyongjie)
+- [#17195](https://github.com/apache/superset/pull/17195) fix(sqla): convert prequery results to native python types (@villebro)
+- [#17112](https://github.com/apache/superset/pull/17112) fix: auto refresh interval won't be updated (@yougyoung94)
+- [#17046](https://github.com/apache/superset/pull/17046) fix: dropdown placement for cascading filters popover (@pkdotson)
+- [#17166](https://github.com/apache/superset/pull/17166) fix(explore): timestamp format when copy datatable to clipboard (@kgabryje)
+- [#17168](https://github.com/apache/superset/pull/17168) fix: avoid filters containing null value (@zhaoyongjie)
+- [#17155](https://github.com/apache/superset/pull/17155) fix: preventing sql lab None limit value (@amitmiran137)
+- [#17147](https://github.com/apache/superset/pull/17147) fix: Remove title from the Cancel SVG (@AAfghahi)
+- [#17125](https://github.com/apache/superset/pull/17125) fix: Update package-lock.json of superset-frontend and cypress-base (@lyndsiWilliams)
+- [#17079](https://github.com/apache/superset/pull/17079) fix(druid): Fix regression with ISO 8601 format (@john-bodley)
+- [#17024](https://github.com/apache/superset/pull/17024) fix(DB Connections): removing paramProperties (@AAfghahi)
+- [#17008](https://github.com/apache/superset/pull/17008) fix: Strict check to allow null values in the SelectControl component (@geido)
+- [#16838](https://github.com/apache/superset/pull/16838) fix: remove duplicate line in docstring (@exemplary-citizen)
+
+**Others**
+- [#19481](https://github.com/apache/superset/pull/19481) chore(plugin-chart-echarts): upgrade echarts 5.3.2 (@villebro)
+- [#19392](https://github.com/apache/superset/pull/19392) test(native filter): add new test for dependent filter (@jinghua-qa)
+- [#19417](https://github.com/apache/superset/pull/19417) refactor: Removes the CSS files from the Heatmap plugin (@michael-s-molina)
+- [#19436](https://github.com/apache/superset/pull/19436) refactor: Removes the CSS files from the Calendar Heatmap plugin (@michael-s-molina)
+- [#19439](https://github.com/apache/superset/pull/19439) chore: Removes hard-coded colors from plugin-chart-pivot-table (@michael-s-molina)
+- [#19443](https://github.com/apache/superset/pull/19443) chore: Removes hard-coded colors from legacy-preset-chart-nvd3 (@michael-s-molina)
+- [#19411](https://github.com/apache/superset/pull/19411) docs: add 1.4.2 changelog entries (@villebro)
+- [#19437](https://github.com/apache/superset/pull/19437) chore: Disables no-literal-colors for the theme configuration (@michael-s-molina)
+- [#19424](https://github.com/apache/superset/pull/19424) chore: attempt to force docs deploy (@srinify)
+- [#19412](https://github.com/apache/superset/pull/19412) chore: update slack invite url (@srinify)
+- [#19368](https://github.com/apache/superset/pull/19368) chore: Removes direct theme imports (@michael-s-molina)
+- [#19245](https://github.com/apache/superset/pull/19245) chore: bump storybook to 6.4.19, migrating config, and linting storybook config (@rusackas)
+- [#19239](https://github.com/apache/superset/pull/19239) chore: Eslint custom plugin to warn about hex and literal colors (@geido)
+- [#19390](https://github.com/apache/superset/pull/19390) docs: Correcting the document font size (@sujiplr)
+- [#19387](https://github.com/apache/superset/pull/19387) chore: add type checking in plugin's test directory (@stephenLYZ)
+- [#19365](https://github.com/apache/superset/pull/19365) chore: add support for npm8 (@villebro)
+- [#19350](https://github.com/apache/superset/pull/19350) docs: update lock + general cleanup (@villebro)
+- [#19331](https://github.com/apache/superset/pull/19331) docs: fix: Use three backticks for code blocks (@kaapstorm)
+- [#19320](https://github.com/apache/superset/pull/19320) chore: add type checking in package tests directory (@stephenLYZ)
+- [#19233](https://github.com/apache/superset/pull/19233) docs: SECRET_KEY Rotation Documentation (@sujiplr)
+- [#19323](https://github.com/apache/superset/pull/19323) chore: bump FAB to 3.4.5 (@dpgaspar)
+- [#19322](https://github.com/apache/superset/pull/19322) ci: add more code owners to helm chart source (@dpgaspar)
+- [#19295](https://github.com/apache/superset/pull/19295) chore: fixing a pluralization typo (@rusackas)
+- [#19236](https://github.com/apache/superset/pull/19236) chore: Make font-weights themable, fix font faces (@kgabryje)
+- [#19293](https://github.com/apache/superset/pull/19293) chore: allow overriding the guest token pyjwt instance (@suddjian)
+- [#19161](https://github.com/apache/superset/pull/19161) chore: add missing keys in components inside lists (@diegomedina248)
+- [#18221](https://github.com/apache/superset/pull/18221) chore: Various docstring fixes (@PApostol)
+- [#19270](https://github.com/apache/superset/pull/19270) docs: update readme.md (@prassanna-helixsense-com)
+- [#18648](https://github.com/apache/superset/pull/18648) chore: update changelog and updating files from 1.4.1 (@eschutho)
+- [#19235](https://github.com/apache/superset/pull/19235) docs: fix broken links in the documentation (@vchrombie)
+- [#19172](https://github.com/apache/superset/pull/19172) chore: use order_desc shared control consistently (@stephenLYZ)
+- [#19227](https://github.com/apache/superset/pull/19227) chore: upgrade mypy check in pre-commit (@ktmud)
+- [#19216](https://github.com/apache/superset/pull/19216) chore: refactor import command (@betodealmeida)
+- [#19156](https://github.com/apache/superset/pull/19156) test: fix TimezoneSelector tests on daylight saving time (@ktmud)
+- [#19115](https://github.com/apache/superset/pull/19115) test(native filter): add new native filter test (@jinghua-qa)
+- [#19132](https://github.com/apache/superset/pull/19132) chore(embedded): refresh the guest token (@suddjian)
+- [#19090](https://github.com/apache/superset/pull/19090) refactor(TimezoneSelector): simplify override logics and tests (@ktmud)
+- [#19091](https://github.com/apache/superset/pull/19091) docs: Modified alerts and reports docs for clarity (@7vikpeculiar)
+- [#19077](https://github.com/apache/superset/pull/19077) perf(dashboard): Send chart requests before native filter requests (@kgabryje)
+- [#19064](https://github.com/apache/superset/pull/19064) perf(dashboard): Improve performance of complex dashboards (@kgabryje)
+- [#18950](https://github.com/apache/superset/pull/18950) chore: Change Sort by and Sort descending controls for Time-series Bar Chart (@geido)
+- [#14064](https://github.com/apache/superset/pull/14064) chore: log multiple errors (@eschutho)
+- [#19068](https://github.com/apache/superset/pull/19068) docs: renamed yugabyte to yugabytedb (@srinify)
+- [#19029](https://github.com/apache/superset/pull/19029) chore: Moves Chart components to src/components folder (@michael-s-molina)
+- [#19050](https://github.com/apache/superset/pull/19050) docs(building): link frontend asset instructions correctly (@cancan101)
+- [#19041](https://github.com/apache/superset/pull/19041) chore(plugin-chart-echarts): bump echarts 5.3.1 (@villebro)
+- [#18965](https://github.com/apache/superset/pull/18965) chore: adding Dutch language translations (NL) (@pablo-guerra)
+- [#19032](https://github.com/apache/superset/pull/19032) chore: Moves CRUD components to the Datasource component (@michael-s-molina)
+- [#19042](https://github.com/apache/superset/pull/19042) chore: Fix deprecated unittest aliases. (@tirkarthi)
+- [#18999](https://github.com/apache/superset/pull/18999) refactor: Deletes the common/components folder (@michael-s-molina)
+- [#19021](https://github.com/apache/superset/pull/19021) chore: change case for upload selection (@pkdotson)
+- [#19019](https://github.com/apache/superset/pull/19019) chore(deps): bump url-parse from 1.5.7 to 1.5.10 in /docs (@dependabot[bot])
+- [#18976](https://github.com/apache/superset/pull/18976) chore(cache): default to SimpleCache in debug mode (@villebro)
+- [#18983](https://github.com/apache/superset/pull/18983) docs: renamed yugabytedb to yugabyte as per request by Yugabyte team (@srinify)
+- [#18179](https://github.com/apache/superset/pull/18179) refactor: converted QueryAutoRefresh to functional component (@briannguyen4)
+- [#18922](https://github.com/apache/superset/pull/18922) chore(deps): bump urijs and xss (@villebro)
+- [#18967](https://github.com/apache/superset/pull/18967) chore(native-filters): Add unit tests for filter cards (@kgabryje)
+- [#16860](https://github.com/apache/superset/pull/16860) chore(security): Remove obsolete FAB metric_access permission (@john-bodley)
+- [#18955](https://github.com/apache/superset/pull/18955) docs: fix sql templating typos (@garyng)
+- [#18931](https://github.com/apache/superset/pull/18931) chore: Moves components out of the commons folder (@michael-s-molina)
+- [#18915](https://github.com/apache/superset/pull/18915) test(Native Filter): User can create parent filters using "Filter is hierarchical (@jinghua-qa)
+- [#18811](https://github.com/apache/superset/pull/18811) chore: add intersect method from backend to fontend for upload extensions (@pkdotson)
+- [#18935](https://github.com/apache/superset/pull/18935) chore: update `UPDATING.md` re: SIP-68 (@betodealmeida)
+- [#18866](https://github.com/apache/superset/pull/18866) refactor(ImportModelsCommand): clean code, extract logic into methods (@michael-hoffman-26)
+- [#18850](https://github.com/apache/superset/pull/18850) test(native filter): add new native filter test for time column filter (@jinghua-qa)
+- [#18923](https://github.com/apache/superset/pull/18923) refactor(docs): clean up deploy of static files (@villebro)
+- [#18914](https://github.com/apache/superset/pull/18914) docs: remove old viz plugin docs (@villebro)
+- [#17977](https://github.com/apache/superset/pull/17977) chore(deps-dev): bump css-loader from 6.2.0 to 6.5.1 in /superset-frontend (@dependabot[bot])
+- [#18882](https://github.com/apache/superset/pull/18882) docs(Import API): update function description, overwrite real meaning (@michael-hoffman-26)
+- [#18854](https://github.com/apache/superset/pull/18854) chore(tests-backend): migrate snowflake and sqlite to unit tests (@corbinrobb)
+- [#18878](https://github.com/apache/superset/pull/18878) docs: fix toc position scroll bug (#18868) (@michaelzusev)
+- [#18861](https://github.com/apache/superset/pull/18861) docs: add GA conf to config (@villebro)
+- [#18820](https://github.com/apache/superset/pull/18820) chore: npm audit fix in cypress-base (@rusackas)
+- [#18828](https://github.com/apache/superset/pull/18828) chore: unify usages of nvd3 library (@stephenLYZ)
+- [#18819](https://github.com/apache/superset/pull/18819) docs: fix typo in configuring-superset.mdx (@ardeego)
+- [#18824](https://github.com/apache/superset/pull/18824) docs: add Unvired to INTHEWILD.md (@srinisubramanian)
+- [#18818](https://github.com/apache/superset/pull/18818) docs: add_db_logos (@srinify)
+- [#18815](https://github.com/apache/superset/pull/18815) docs: Added reference and link to configure instructions for docker-compose… (@jacobcroope)
+- [#18810](https://github.com/apache/superset/pull/18810) refactor: move date_parser to unittest (@zhaoyongjie)
+- [#18798](https://github.com/apache/superset/pull/18798) chore(readme): Adding pinot logo into Readme Supported Databases Section (@xiangfu0)
+- [#18800](https://github.com/apache/superset/pull/18800) docs(pinot): Update pinot docs with fixed pinot connection (@xiangfu0)
+- [#18805](https://github.com/apache/superset/pull/18805) chore(deps): bump hosted-git-info from 2.7.1 to 2.8.9 in /superset-frontend (@dependabot[bot])
+- [#18804](https://github.com/apache/superset/pull/18804) chore(deps): bump url-parse from 1.5.3 to 1.5.7 in /docs (@dependabot[bot])
+- [#18801](https://github.com/apache/superset/pull/18801) chore(FF): adding generic X axis Feature Flag (default: False) (@rusackas)
+- [#18142](https://github.com/apache/superset/pull/18142) refactor: migrate ExploreCtasResultsButton component to typescript (@EugeneTorap)
+- [#18761](https://github.com/apache/superset/pull/18761) chore(deps): bump cached-path-relative from 1.0.2 to 1.1.0 in /superset-frontend/cypress-base (@dependabot[bot])
+- [#18765](https://github.com/apache/superset/pull/18765) chore(deps): bump y18n from 4.0.0 to 4.0.3 in /superset-frontend (@dependabot[bot])
+- [#18769](https://github.com/apache/superset/pull/18769) chore(deps): bump ws from 5.2.2 to 5.2.3 in /superset-frontend (@dependabot[bot])
+- [#18779](https://github.com/apache/superset/pull/18779) refactor: move post_processing to unittest (@zhaoyongjie)
+- [#18767](https://github.com/apache/superset/pull/18767) chore(deps): bump shelljs from 0.8.4 to 0.8.5 in /superset-frontend (@dependabot[bot])
+- [#18764](https://github.com/apache/superset/pull/18764) chore(deps): bump follow-redirects from 1.14.3 to 1.14.8 in /superset-frontend (@dependabot[bot])
+- [#18762](https://github.com/apache/superset/pull/18762) chore(deps): bump underscore from 1.12.0 to 1.12.1 in /superset-frontend (@dependabot[bot])
+- [#18772](https://github.com/apache/superset/pull/18772) chore(deps): bump path-parse from 1.0.6 to 1.0.7 in /superset-frontend (@dependabot[bot])
+- [#18759](https://github.com/apache/superset/pull/18759) chore(deps): bump shelljs from 0.8.4 to 0.8.5 in /docs (@dependabot[bot])
+- [#18760](https://github.com/apache/superset/pull/18760) chore(deps): bump follow-redirects from 1.14.5 to 1.14.8 in /docs (@dependabot[bot])
+- [#18763](https://github.com/apache/superset/pull/18763) chore(deps): bump nanoid from 3.1.30 to 3.3.1 in /docs (@dependabot[bot])
+- [#18766](https://github.com/apache/superset/pull/18766) chore(deps-dev): bump node-fetch from 2.6.1 to 2.6.7 in /superset-frontend (@dependabot[bot])
+- [#18768](https://github.com/apache/superset/pull/18768) chore(deps): bump tmpl from 1.0.4 to 1.0.5 in /superset-frontend (@dependabot[bot])
+- [#18770](https://github.com/apache/superset/pull/18770) chore(deps): bump dns-packet from 1.3.1 to 1.3.4 in /superset-frontend (@dependabot[bot])
+- [#18771](https://github.com/apache/superset/pull/18771) chore(deps): bump url-parse from 1.4.7 to 1.5.7 in /superset-frontend (@dependabot[bot])
+- [#18629](https://github.com/apache/superset/pull/18629) chore: move data nav menu to plus menu (@pkdotson)
+- [#18705](https://github.com/apache/superset/pull/18705) chore: bumping caniuse-lite from 1.0.30001257 to 1.0.30001312 (@rusackas)
+- [#18728](https://github.com/apache/superset/pull/18728) chore: use switchboard as dependency for embedded sdk (@suddjian)
+- [#18121](https://github.com/apache/superset/pull/18121) refactor: migrate QueryAndSaveBtns to TypeScript and add stories (@ad-m)
+- [#18664](https://github.com/apache/superset/pull/18664) chore: Remove non-breaking changes from UPDATING.md (@john-bodley)
+- [#18707](https://github.com/apache/superset/pull/18707) chore: updating link to newer viz plugin blog post (@rusackas)
+- [#18710](https://github.com/apache/superset/pull/18710) refactor: decouple pandas postprocessing operator (@zhaoyongjie)
+- [#18708](https://github.com/apache/superset/pull/18708) chore: remove config of temporary_superset_ui (@zhaoyongjie)
+- [#18709](https://github.com/apache/superset/pull/18709) docs: add docs for viz plugin development (@villebro)
+- [#18706](https://github.com/apache/superset/pull/18706) chore: removing temporary_superset_ui folder (no longer needed) (@rusackas)
+- [#18669](https://github.com/apache/superset/pull/18669) test(Native filter): add cypress test user can add time grain filter (@jinghua-qa)
+- [#18621](https://github.com/apache/superset/pull/18621) chore: Add craig-rueda as codeowner of Helm Chart (@ad-m)
+- [#18136](https://github.com/apache/superset/pull/18136) refactor: migrate DeleteComponentButton to TypeScript (@Scrip7)
+- [#17944](https://github.com/apache/superset/pull/17944) chore: migrate QueryTable component from jsx to tsx (@MayUWish)
+- [#18608](https://github.com/apache/superset/pull/18608) chore: improve React.useRef etc. import consistency (@ad-m)
+- [#18647](https://github.com/apache/superset/pull/18647) test(native filter): add test for time range filter (@jinghua-qa)
+- [#18581](https://github.com/apache/superset/pull/18581) chore: Update Docusaurus links (@geido)
+- [#18120](https://github.com/apache/superset/pull/18120) refactor: migrate SliderControl to TypeScript & added story (@ad-m)
+- [#18657](https://github.com/apache/superset/pull/18657) chore(docs): add note about quote handling in filters (@villebro)
+- [#17939](https://github.com/apache/superset/pull/17939) refactor(ExploreCtasResultsButton): convert to functional component (@ChristopherCFleming)
+- [#18558](https://github.com/apache/superset/pull/18558) chore: Rename and reposition native filter modal fields (@michael-s-molina)
+- [#18143](https://github.com/apache/superset/pull/18143) refactor: migrate ExploreResultsButton component to FC & tsx (@EugeneTorap)
+- [#18200](https://github.com/apache/superset/pull/18200) chore: upgrade BoundsControl to TS (@ad-m)
+- [#18585](https://github.com/apache/superset/pull/18585) chore: allow sqla docs to use supersettext (@pkdotson)
+- [#18144](https://github.com/apache/superset/pull/18144) refactor: remove redundant propTypes in ColumnElement (@EugeneTorap)
+- [#18600](https://github.com/apache/superset/pull/18600) docs: fixed URL for contributing landing page (@srinify)
+- [#18580](https://github.com/apache/superset/pull/18580) docs: add password example to dataset import API (@villebro)
+- [#17921](https://github.com/apache/superset/pull/17921) chore(explore): make adhoc columns available without UX BETA ff (@kgabryje)
+- [#18574](https://github.com/apache/superset/pull/18574) chore: update gitignore in the main folder, to hide PyCharm configuration (@michael-hoffman-26)
+- [#18568](https://github.com/apache/superset/pull/18568) chore: Remove outdated roadmap page from Docusaurus (@geido)
+- [#18254](https://github.com/apache/superset/pull/18254) docs(i18n): make translation guide more explicit (@villebro)
+- [#18231](https://github.com/apache/superset/pull/18231) chore: add ci for docs-v2 (@ad-m)
+- [#18559](https://github.com/apache/superset/pull/18559) chore: Update Docusaurus dependencies (@geido)
+- [#18229](https://github.com/apache/superset/pull/18229) chore: Reference Github code in Docusaurus (@geido)
+- [#18251](https://github.com/apache/superset/pull/18251) chore(tests): migrate mssql tests to pytest (@villebro)
+- [#18188](https://github.com/apache/superset/pull/18188) refactor: upgrade ControlHeader to TSX & FC and add storybook  (@ad-m)
+- [#18230](https://github.com/apache/superset/pull/18230) chore: Docusaurus throw on broken links (@geido)
+- [#18170](https://github.com/apache/superset/pull/18170) refactor: extract json_required view decorator (@ad-m)
+- [#17926](https://github.com/apache/superset/pull/17926) refactor: sqleditorleftbar to typescript (@JosueLugaro)
+- [#18199](https://github.com/apache/superset/pull/18199) chore: Update links and navigation in Docusaurus (@geido)
+- [#18206](https://github.com/apache/superset/pull/18206) chore: added release notes for 1.4 (@srinify)
+- [#18196](https://github.com/apache/superset/pull/18196) docs(docker compose installation): add pull command to avoid errors when installing superset using docker compose (@ancasarb)
+- [#18160](https://github.com/apache/superset/pull/18160) chore: move controlPanel into time_table (@zhaoyongjie)
+- [#18162](https://github.com/apache/superset/pull/18162) chore: bump echarts to 5.3.0 (@villebro)
+- [#18134](https://github.com/apache/superset/pull/18134) chore: bump pillow to major version (@eschutho)
+- [#18151](https://github.com/apache/superset/pull/18151) refactor: Moves the Explore form_data endpoint (@michael-s-molina)
+- [#18130](https://github.com/apache/superset/pull/18130) chore: use pkg_resources for cleaner config (@betodealmeida)
+- [#18125](https://github.com/apache/superset/pull/18125) docs: fix typo on security page (@proteusvacuum)
+- [#18133](https://github.com/apache/superset/pull/18133) chore: migrating storybook jsx to typescript #18100 (@jayakrishnankk)
+- [#18083](https://github.com/apache/superset/pull/18083) chore: update changelog and updating for 1.4.0 (@eschutho)
+- [#18105](https://github.com/apache/superset/pull/18105) refactor: migrate RowCountLabel to TypeScript & added story (@ad-m)
+- [#18101](https://github.com/apache/superset/pull/18101) refactor: migrate Checkbox story to TypeScript - see #18100 (@ad-m)
+- [#17807](https://github.com/apache/superset/pull/17807) refactor: sqleditorleftbar to functional (@JosueLugaro)
+- [#18082](https://github.com/apache/superset/pull/18082) chore: split CLI into multiple files (@betodealmeida)
+- [#18060](https://github.com/apache/superset/pull/18060) refactor(example_data): replace the way the birth_names data is loaded to DB (@ofekisr)
+- [#17771](https://github.com/apache/superset/pull/17771) refactor(sql_lab): SQL Lab Persistent Saved State (@AAfghahi)
+- [#18059](https://github.com/apache/superset/pull/18059) chore(plugin-chart-echarts): add types to controls (@villebro)
+- [#18058](https://github.com/apache/superset/pull/18058) chore(supeset.utils.core): move all database utils to database utils module (@ofekisr)
+- [#18037](https://github.com/apache/superset/pull/18037) chore: important change to MakeFile (@AAfghahi)
+- [#17569](https://github.com/apache/superset/pull/17569) chore: column_type_mappings (@dungdm93)
+- [#18003](https://github.com/apache/superset/pull/18003) chore(DatasourceEditor): Create Datasource Legacy Editor Feature Flag (@AAfghahi)
+- [#17996](https://github.com/apache/superset/pull/17996) refactor: remove unused ts-jest (@zhaoyongjie)
+- [#17893](https://github.com/apache/superset/pull/17893) refactor: examples data loading for tests  (@ofekisr)
+- [#17967](https://github.com/apache/superset/pull/17967) chore: use new FAB functionality for component schemas update OAS (@dpgaspar)
+- [#17979](https://github.com/apache/superset/pull/17979) chore: reenable lint for test files (@zhaoyongjie)
+- [#17965](https://github.com/apache/superset/pull/17965) refactor: move superset-ui dependencies to peerDependencies (@zhaoyongjie)
+- [#17930](https://github.com/apache/superset/pull/17930) chore: Move edit dataset lock (@lyndsiWilliams)
+- [#17718](https://github.com/apache/superset/pull/17718) docs(contributing): update contributing section on docs page to match CONTRIBUTING.md (@riahk)
+- [#17975](https://github.com/apache/superset/pull/17975) docs: close bracket on helm chart reference (@brnnnfx)
+- [#17971](https://github.com/apache/superset/pull/17971) chore: bump typescript to 4.5.4, and adjust storybook story. (@rusackas)
+- [#17972](https://github.com/apache/superset/pull/17972) chore: fixing annoying warning about escaping in Emotion styling (@rusackas)
+- [#17947](https://github.com/apache/superset/pull/17947) refactor: import value name from root of superset-ui/core (@zhaoyongjie)
+- [#17959](https://github.com/apache/superset/pull/17959) chore(deps): bump underscore from 1.12.0 to 1.12.1 in /superset-frontend (@dependabot[bot])
+- [#17958](https://github.com/apache/superset/pull/17958) chore(deps): bump babel from 2.5.3 to 2.9.1 in /superset/translations (@dependabot[bot])
+- [#17904](https://github.com/apache/superset/pull/17904) chore(deps): bump @storybook/addon-actions from 6.3.12 to 6.4.9 in /superset-frontend (@dependabot[bot])
+- [#17902](https://github.com/apache/superset/pull/17902) chore: Moves spec files to the src folder - iteration 10 (@michael-s-molina)
+- [#17901](https://github.com/apache/superset/pull/17901) chore: Moves spec files to the src folder - iteration 9 (@michael-s-molina)
+- [#17956](https://github.com/apache/superset/pull/17956) chore: Adding more viz category mappings for viz picker (@rusackas)
+- [#17963](https://github.com/apache/superset/pull/17963) chore: unbreak pip-compile-multi (@villebro)
+- [#17899](https://github.com/apache/superset/pull/17899) chore: Moves spec files to the src folder - iteration 8 (@michael-s-molina)
+- [#17897](https://github.com/apache/superset/pull/17897) chore: Moves the Menu to views/components folder (@michael-s-molina)
+- [#17896](https://github.com/apache/superset/pull/17896) chore: Moves the hooks folder to the src folder (@michael-s-molina)
+- [#17962](https://github.com/apache/superset/pull/17962) chore(deps): bump urijs from 1.19.6 to 1.19.7 in /superset-frontend (@dependabot[bot])
+- [#17942](https://github.com/apache/superset/pull/17942) chore: Fix 'button' misspelling in list_with_checkboxes (@acrks)
+- [#17860](https://github.com/apache/superset/pull/17860) chore: remove unused eslintrc (@zhaoyongjie)
+- [#17909](https://github.com/apache/superset/pull/17909) refactor(storybok): upgrade to webpack5 in storybook of plugins (@zhaoyongjie)
+- [#17924](https://github.com/apache/superset/pull/17924) chore: fix misspelling of button (@DavidWoolner)
+- [#17782](https://github.com/apache/superset/pull/17782) ci: make presto hive tests to cover only chartData and sqljson (@amitmiran137)
+- [#17717](https://github.com/apache/superset/pull/17717) chore: Fix UPDATING.md (@john-bodley)
+- [#17824](https://github.com/apache/superset/pull/17824) chore: Help user to find the input fields in the dataset editor (@lyndsiWilliams)
+- [#17837](https://github.com/apache/superset/pull/17837) refactor(monorepo): update plugin dependencies (@zhaoyongjie)
+- [#17829](https://github.com/apache/superset/pull/17829) chore(generator-superset): migrate to monorepo (@villebro)
+- [#17832](https://github.com/apache/superset/pull/17832) chore: Refactor localstorage into typesafe version (@etr2460)
+- [#17811](https://github.com/apache/superset/pull/17811) other: Add Helm chart option for configOverrides through file (@dd-willgan)
+- [#17828](https://github.com/apache/superset/pull/17828) chore: add lerna version (@zhaoyongjie)
+- [#17827](https://github.com/apache/superset/pull/17827) refactor(monorepo): migrate plugins release workflow (@zhaoyongjie)
+- [#17754](https://github.com/apache/superset/pull/17754) docs: update contribuing docs (@zhaoyongjie)
+- [#17650](https://github.com/apache/superset/pull/17650) chore: updated screenshots in README.md and added recent events (@srinify)
+- [#17797](https://github.com/apache/superset/pull/17797) chore(plugin-chart-table): move adhoc filters control closer to the metrics (@kgabryje)
+- [#17798](https://github.com/apache/superset/pull/17798) chore(plugin-chart-pivot-table): change order of Columns and Rows controls (@kgabryje)
+- [#17769](https://github.com/apache/superset/pull/17769) chore(plugin-chart-pivot-table): migrate react-pivottable into superset codebase (@kgabryje)
+- [#17785](https://github.com/apache/superset/pull/17785) docs: Fix typo on line 967 (@ChristopherCFleming)
+- [#17780](https://github.com/apache/superset/pull/17780) chore: update fixtures scope that are loading data into the analytical db (@bkyryliuk)
+- [#17770](https://github.com/apache/superset/pull/17770) chore: print test runtime (@bkyryliuk)
+- [#17772](https://github.com/apache/superset/pull/17772) chore: skip flaky tests (@betodealmeida)
+- [#17764](https://github.com/apache/superset/pull/17764) chore: bump react-resize-detector (@villebro)
+- [#17723](https://github.com/apache/superset/pull/17723) chore: bump FAB to 3.4.1 (@dpgaspar)
+- [#17698](https://github.com/apache/superset/pull/17698) refactor(monorepo): change coverage of core to 100% (@zhaoyongjie)
+- [#17737](https://github.com/apache/superset/pull/17737) chore: fixed spelling error on line 1342 of CONTRIBUTING.md (@JosueLugaro)
+- [#17735](https://github.com/apache/superset/pull/17735) chore: fix misspelling of apprear to appear (@jcahela)
+- [#17693](https://github.com/apache/superset/pull/17693) chore(de-translations): Add missing German translations (@hbruch)
+- [#17715](https://github.com/apache/superset/pull/17715) chore: Rename CertifiedIcon to CertifiedBadge (@geido)
+- [#17691](https://github.com/apache/superset/pull/17691) chore: Update TS tasklist script (@etr2460)
+- [#17705](https://github.com/apache/superset/pull/17705) chore: unblock ci on postgres lint (@villebro)
+- [#17596](https://github.com/apache/superset/pull/17596) refactor(monorepo): relocate deckgl to Superset (@zhaoyongjie)
+- [#17689](https://github.com/apache/superset/pull/17689) chore: convert feature flag tests to unit tests (@betodealmeida)
+- [#17687](https://github.com/apache/superset/pull/17687) chore: removing redundant docker-entrypoint (@ofekisr)
+- [#17679](https://github.com/apache/superset/pull/17679) chore: update babel doc (@zhaoyongjie)
+- [#17622](https://github.com/apache/superset/pull/17622) chore: Remove unreachable return statements (@PApostol)
+- [#17605](https://github.com/apache/superset/pull/17605) docs(exploring-data): add a native filters section to the exploring data docs (@riahk)
+- [#17639](https://github.com/apache/superset/pull/17639) refactor(monorepo): frontend code cleanup after monorepo (@zhaoyongjie)
+- [#17631](https://github.com/apache/superset/pull/17631) chore(de-translations): Add and fix German translations (@hbruch)
+- [#17626](https://github.com/apache/superset/pull/17626) chore(superset-ui): clean up legacy-plugin-chart-time-table (@ktmud)
+- [#17634](https://github.com/apache/superset/pull/17634) chore(plugin-chart-pivot-table): Add more descriptive tooltips for sorting [ID-29] (@kgabryje)
+- [#17606](https://github.com/apache/superset/pull/17606) refactor: Add AutoSizer to react-virtualized `Grid` (@hughhhh)
+- [#17624](https://github.com/apache/superset/pull/17624) chore: Added unit test to query context change (@AAfghahi)
+- [#17623](https://github.com/apache/superset/pull/17623) chore: Update docstrings in presto.py (@PApostol)
+- [#17575](https://github.com/apache/superset/pull/17575) chore: bumping `polished` package (@rusackas)
+- [#17603](https://github.com/apache/superset/pull/17603) chore: migrate EstimateQueryCostButton component from jsx to tsx (@Damans227)
+- [#17516](https://github.com/apache/superset/pull/17516) docs(exploring-data): various updates to match latest superset version (@riahk)
+- [#17577](https://github.com/apache/superset/pull/17577) chore: auditing cypress-base dependencies (@rusackas)
+- [#17533](https://github.com/apache/superset/pull/17533) chore(deps-dev): bump @types/jquery from 3.5.8 to 3.5.9 in /superset-frontend (@dependabot[bot])
+- [#17534](https://github.com/apache/superset/pull/17534) chore(deps-dev): bump @types/enzyme from 3.10.5 to 3.10.10 in /superset-frontend (@dependabot[bot])
+- [#17535](https://github.com/apache/superset/pull/17535) chore(deps-dev): bump mini-css-extract-plugin from 2.3.0 to 2.4.5 in /superset-frontend (@dependabot[bot])
+- [#17585](https://github.com/apache/superset/pull/17585) chore: disable merge button (@zhaoyongjie)
+- [#17584](https://github.com/apache/superset/pull/17584) chore: temporarily update .asf.yml (@zhaoyongjie)
+- [#17552](https://github.com/apache/superset/pull/17552) refactor(monorepo): move superset-ui to superset(stage 2) (@zhaoyongjie)
+- [#17474](https://github.com/apache/superset/pull/17474) chore: migrate Fieldset component from jsx to tsx (@Damans227)
+- [#17556](https://github.com/apache/superset/pull/17556) chore: bump mysqlclient version (@gnought)
+- [#17525](https://github.com/apache/superset/pull/17525) chore(translations): Add missing i18n (@hbruch)
+- [#17562](https://github.com/apache/superset/pull/17562) chore: Fix deprecated unittest aliases for Python 3.11 compatibility. (@tirkarthi)
+- [#17561](https://github.com/apache/superset/pull/17561) chore: disable merge commit option (@villebro)
+- [#17560](https://github.com/apache/superset/pull/17560) chore: temporarily enable merge in .asf.yml (@zhaoyongjie)
+- [#17326](https://github.com/apache/superset/pull/17326) chore: migrate DragHandle component from jsx to tsx (@Damans227)
+- [#17521](https://github.com/apache/superset/pull/17521) chore: bump major on Pillow, optional dependency (@dpgaspar)
+- [#17452](https://github.com/apache/superset/pull/17452) chore(deps-dev): bump @types/jquery from 3.3.32 to 3.5.8 in /superset-frontend (@dependabot[bot])
+- [#14783](https://github.com/apache/superset/pull/14783) chore: Slovak translation, enabling language and adding first translations  (@minho95)
+- [#17453](https://github.com/apache/superset/pull/17453) chore(deps-dev): bump eslint-import-resolver-webpack from 0.13.1 to 0.13.2 in /superset-frontend (@dependabot[bot])
+- [#17502](https://github.com/apache/superset/pull/17502) docs(exploring-data): Add upload excel notes (@aniaan)
+- [#17522](https://github.com/apache/superset/pull/17522) chore: remove deprecated selects and deps (@villebro)
+- [#17492](https://github.com/apache/superset/pull/17492) chore: update node version in nvmrc to v16 (@ktmud)
+- [#17500](https://github.com/apache/superset/pull/17500) chore(deps-dev): bump @babel/cli from 7.15.7 to 7.16.0 in /superset-frontend (@dependabot[bot])
+- [#17478](https://github.com/apache/superset/pull/17478) chore(de-translation): update German translations (based on master) (#17441) (@hbruch)
+- [#17501](https://github.com/apache/superset/pull/17501) chore(translations): fix translation literals (@villebro)
+- [#17504](https://github.com/apache/superset/pull/17504) docs: add dropbox to users list (@exemplary-citizen)
+- [#17361](https://github.com/apache/superset/pull/17361) chore: migrate FormattedNumber component from jsx to tsx (@Damans227)
+- [#17509](https://github.com/apache/superset/pull/17509) refactor(chart.commands): separate commands into two different modules (@ofekisr)
+- [#17434](https://github.com/apache/superset/pull/17434) chore: Slovenian translation update (@dkrat7)
+- [#17497](https://github.com/apache/superset/pull/17497) refactor(QueryContext): separate all processing concerns into QueryContextProcessor (@ofekisr)
+- [#17484](https://github.com/apache/superset/pull/17484) docs: mention that username should be admin to load examples in superset docs (@jayakrishnankk)
+- [#14837](https://github.com/apache/superset/pull/14837) docs: Update frequently-asked-questions-page.mdx (@kurtiswen)
+- [#17496](https://github.com/apache/superset/pull/17496) refactor(QueryContext): move df methods utils to utils package (@ofekisr)
+- [#17495](https://github.com/apache/superset/pull/17495) refactor(QueryContext): add QueryContextFactory to meet SRP (@ofekisr)
+- [#17482](https://github.com/apache/superset/pull/17482) chore(deps): bump cachelib from 0.1.1 to 0.4.1 (@villebro)
+- [#17464](https://github.com/apache/superset/pull/17464) chore(sql_lab): Added Unit Test for stop query exception (@AAfghahi)
+- [#17454](https://github.com/apache/superset/pull/17454) chore(deps-dev): bump @types/react-loadable from 5.5.4 to 5.5.6 in /superset-frontend (@dependabot[bot])
+- [#17479](https://github.com/apache/superset/pull/17479) refactor(QueryObject):  decouple from superset (@ofekisr)
+- [#17466](https://github.com/apache/superset/pull/17466) refactor(QueryObject): add QueryObjectFactory to meet SRP (@ofekisr)
+- [#17465](https://github.com/apache/superset/pull/17465) refactor(QueryObject): decouple from queryContext and clean code (@ofekisr)
+- [#16868](https://github.com/apache/superset/pull/16868) chore(fr-translation): update whole French translation (based on 1.3.0 release) (@audour)
+- [#17461](https://github.com/apache/superset/pull/17461) refactor(ChartDataCommand): remove create queryContext command's responsibly  (@ofekisr)
+- [#17427](https://github.com/apache/superset/pull/17427) refactor(monorepo): stage 1 (@zhaoyongjie)
+- [#17451](https://github.com/apache/superset/pull/17451) chore: Ignore docs directory for dependabot (@hughhhh)
+- [#17398](https://github.com/apache/superset/pull/17398) test: add native filter default value e2e (@jinghua-qa)
+- [#17439](https://github.com/apache/superset/pull/17439) chore: bumping react syntax highlighter and thusly prismjs (@rusackas)
+- [#17422](https://github.com/apache/superset/pull/17422) chore: Notify user of custom label colors and related Dashboard color scheme (@geido)
+- [#14576](https://github.com/apache/superset/pull/14576) chore(deps): bump hosted-git-info from 2.8.8 to 2.8.9 in /docs (@dependabot[bot])
+- [#17438](https://github.com/apache/superset/pull/17438) chore: bump superset-ui 0.18.25 (@zhaoyongjie)
+- [#17425](https://github.com/apache/superset/pull/17425) refactor(ChartDataCommand): into two separate commands (@ofekisr)
+- [#17407](https://github.com/apache/superset/pull/17407) refactor(TestChartApi):  move chart data api tests into TestChartDataApi (@ofekisr)
+- [#17405](https://github.com/apache/superset/pull/17405) refactor(ChartDataCommand): separate loading query_context form cache into different module (@ofekisr)
+- [#17403](https://github.com/apache/superset/pull/17403) chore: add dependencies for monorepo (@zhaoyongjie)
+- [#17400](https://github.com/apache/superset/pull/17400) refactor(ChartData): move chart_data_apis from ChartRestApi ChartDataRestApi (@ofekisr)
+- [#17399](https://github.com/apache/superset/pull/17399) refactor(ChartData): move ChartDataResult enums to common (@ofekisr)
+- [#17350](https://github.com/apache/superset/pull/17350) docs: add mishmash io to users list (@ntsvetanov)
+- [#17387](https://github.com/apache/superset/pull/17387) docs: Reorder install instructions in CONTRIBUTING.md (@willbarrett)
+- [#17367](https://github.com/apache/superset/pull/17367) chore: bump @typescript-eslint/eslint-plugin to 5.3.0 (@zhaoyongjie)
+- [#17362](https://github.com/apache/superset/pull/17362) chore: bump superset-ui 0.18.20 (@zhaoyongjie)
+- [#17343](https://github.com/apache/superset/pull/17343) test: add native filter e2e test part3 (@jinghua-qa)
+- [#17325](https://github.com/apache/superset/pull/17325) chore(websocket): bump dependencies (@villebro)
+- [#17246](https://github.com/apache/superset/pull/17246) chore: Test cases for annotations and annotation layers incorrect creation through API (@geido)
+- [#17306](https://github.com/apache/superset/pull/17306) docs: fix numbering on getting started with kubernetes section (@jameskim0987)
+- [#17315](https://github.com/apache/superset/pull/17315) chore: migrate HiddenControl component from jsx to tsx (@Damans227)
+- [#17310](https://github.com/apache/superset/pull/17310) chore: bump superset-ui to 0.18.18 (@villebro)
+- [#17284](https://github.com/apache/superset/pull/17284) style: remove redundant parentheses from setup.py (@PApostol)
+- [#17283](https://github.com/apache/superset/pull/17283) test: add native filter part2 (@jinghua-qa)
+- [#17290](https://github.com/apache/superset/pull/17290) chore: Bumping pyarrow / pandas (@craig-rueda)
+- [#17293](https://github.com/apache/superset/pull/17293) docs(UPDATING): Adding downtime for #16756 (@exemplary-citizen)
+- [#17191](https://github.com/apache/superset/pull/17191) chore(doc): Update BigQuery Connection database connection UI into doc (@rosemarie-chiu)
+- [#17231](https://github.com/apache/superset/pull/17231) test: added some sanity test for native filter (@jinghua-qa)
+- [#17270](https://github.com/apache/superset/pull/17270) chore: bump superset-ui to 0.18.17 (@john-bodley)
+- [#17266](https://github.com/apache/superset/pull/17266) chore: bump deckgl 0.4.13 (@zhaoyongjie)
+- [#17069](https://github.com/apache/superset/pull/17069) chore: remove supersetTheme with withTheme (@pkdotson)
+- [#17251](https://github.com/apache/superset/pull/17251) chore: bump superset-ui v0.18.16 (@zhaoyongjie)
+- [#16950](https://github.com/apache/superset/pull/16950) other(revert): "fix: the calculated columns explicit type convert into date" (@john-bodley)
+- [#17220](https://github.com/apache/superset/pull/17220) chore: bump Dremio to 1.2.1 (@hughhhh)
+- [#17216](https://github.com/apache/superset/pull/17216) chore: Removes the ChartIcon component (@michael-s-molina)
+- [#17232](https://github.com/apache/superset/pull/17232) chore: bump to superset-ui 0.18.15 (@zhaoyongjie)
+- [#17171](https://github.com/apache/superset/pull/17171) chore: Add docs to the Select component props (@geido)
+- [#17132](https://github.com/apache/superset/pull/17132) chore: Breaks the dataViewCommon folder into TableCollection and Pagination folders (@michael-s-molina)
+- [#17215](https://github.com/apache/superset/pull/17215) chore: use official isort in pre-commmit (@zhaoyongjie)
+- [#17145](https://github.com/apache/superset/pull/17145) refactor: dashboard->explore url generation (@suddjian)
+- [#17130](https://github.com/apache/superset/pull/17130) chore: Moves the datasource components to the components folder (@michael-s-molina)
+- [#17028](https://github.com/apache/superset/pull/17028) docs: add Symmetrics to users list (@symmetricsfyi)
+- [#17151](https://github.com/apache/superset/pull/17151) chore: update ui packages to 18.14 (@pkdotson)
+- [#17146](https://github.com/apache/superset/pull/17146) chore(druid): No transform without time granularity for consistency (@john-bodley)
+- [#17101](https://github.com/apache/superset/pull/17101) chore(druid): Explicitly cast col to TIMESTAMP (@john-bodley)
+- [#17115](https://github.com/apache/superset/pull/17115) perf(native-filters): Decrease number of unnecessary rerenders in native filters (@kgabryje)
+- [#17067](https://github.com/apache/superset/pull/17067) chore: upgrade highlight.js (@suddjian)
+- [#17121](https://github.com/apache/superset/pull/17121) chore: bump superset-ui packages to 18.3 (@pkdotson)
+- [#17129](https://github.com/apache/superset/pull/17129) chore: remove redundant po2json dev dependency (@villebro)
+- [#17102](https://github.com/apache/superset/pull/17102) chore: Include codeowners for the Select component (@michael-s-molina)
+- [#16911](https://github.com/apache/superset/pull/16911) test: Cypress 7.0.0 migration (@lyndsiWilliams)
+- [#17017](https://github.com/apache/superset/pull/17017) chore: enable BQ tests (@betodealmeida)
+- [#14577](https://github.com/apache/superset/pull/14577) build(deps): bump lodash from 4.17.19 to 4.17.21 in /docs (@dependabot[bot])
+- [#17050](https://github.com/apache/superset/pull/17050) chore(druid): Standardizing time grain transformations (@john-bodley)
+- [#17045](https://github.com/apache/superset/pull/17045) docs: add vscode linter instructions to CONTRIBUTING.md (@suddjian)
+- [#16993](https://github.com/apache/superset/pull/16993) style: Update text for SLL Tooltip (@gabester78)
+- [#16959](https://github.com/apache/superset/pull/16959) chore(Dashboard): Implement dedicated message for missing dataset (@geido)
+- [#17016](https://github.com/apache/superset/pull/17016) docs(pylint): Add context about disablement of Pylint messages (@john-bodley)
+- [#17009](https://github.com/apache/superset/pull/17009) chore: bump superset-ui 0.18.10 (@zhaoyongjie)
+- [#16974](https://github.com/apache/superset/pull/16974) refactor(filterset): improve response body (@ofekisr)
+- [#16999](https://github.com/apache/superset/pull/16999) chore: no direct use of supersetTheme (or bad LESS vars) in SqlEditor (@rusackas)
+- [#17000](https://github.com/apache/superset/pull/17000) chore: removing use of supersetTheme in favor of ThemeProvider (@rusackas)
+- [#16885](https://github.com/apache/superset/pull/16885) chore: upgrade babel (@suddjian)
+- [#16973](https://github.com/apache/superset/pull/16973) test: update required tests to python 3.8 (@villebro)
+- [#16689](https://github.com/apache/superset/pull/16689) chore: refactor header menu to show in header grid component (@pkdotson)
 
 ### 1.4.2 (Sat Mar 19 00:08:06 2022 +0200)
 **Features**
diff --git a/UPDATING.md b/UPDATING.md
index a7c2116d02..c02b327385 100644
--- a/UPDATING.md
+++ b/UPDATING.md
@@ -22,7 +22,7 @@ under the License.
 This file documents any backwards-incompatible changes in Superset and
 assists people when migrating to a new version.
 
-## Next
+## 1.5.0
 
 ### Breaking Changes
 
diff --git a/superset-frontend/package.json b/superset-frontend/package.json
index 0eb2ccef08..c157d71960 100644
--- a/superset-frontend/package.json
+++ b/superset-frontend/package.json
@@ -1,6 +1,6 @@
 {
   "name": "superset",
-  "version": "0.0.0dev",
+  "version": "1.5.0",
   "description": "Superset is a data exploration platform designed to be visual, intuitive, and interactive.",
   "keywords": [
     "big",


[superset] 16/23: feat(sql lab): enable ACE editor search in SQL editors (#19328)

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

villebro pushed a commit to tag 1.5.0rc1
in repository https://gitbox.apache.org/repos/asf/superset.git

commit af0dc7520773d57c735be4517b7cfdc402562202
Author: Diego Medina <di...@gmail.com>
AuthorDate: Thu Mar 31 19:05:00 2022 -0400

    feat(sql lab): enable ACE editor search in SQL editors (#19328)
    
    (cherry picked from commit eab9388f7cdaca20588d4c94c929225fd9d59870)
---
 superset-frontend/src/SqlLab/components/AceEditorWrapper/index.tsx | 2 --
 superset-frontend/src/components/AsyncAceEditor/index.tsx          | 4 +++-
 superset-frontend/src/types/brace.d.ts                             | 1 +
 3 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/superset-frontend/src/SqlLab/components/AceEditorWrapper/index.tsx b/superset-frontend/src/SqlLab/components/AceEditorWrapper/index.tsx
index 25da49137a..35722ba866 100644
--- a/superset-frontend/src/SqlLab/components/AceEditorWrapper/index.tsx
+++ b/superset-frontend/src/SqlLab/components/AceEditorWrapper/index.tsx
@@ -127,8 +127,6 @@ class AceEditorWrapper extends React.PureComponent<Props, State> {
   }
 
   onEditorLoad(editor: any) {
-    editor.commands.removeCommand('find');
-
     editor.commands.addCommand({
       name: 'runQuery',
       bindKey: { win: 'Alt-enter', mac: 'Alt-enter' },
diff --git a/superset-frontend/src/components/AsyncAceEditor/index.tsx b/superset-frontend/src/components/AsyncAceEditor/index.tsx
index 6d4619b326..521ae357be 100644
--- a/superset-frontend/src/components/AsyncAceEditor/index.tsx
+++ b/superset-frontend/src/components/AsyncAceEditor/index.tsx
@@ -68,6 +68,7 @@ const aceModuleLoaders = {
   'theme/textmate': () => import('brace/theme/textmate'),
   'theme/github': () => import('brace/theme/github'),
   'ext/language_tools': () => import('brace/ext/language_tools'),
+  'ext/searchbox': () => import('brace/ext/searchbox'),
 };
 
 export type AceModule = keyof typeof aceModuleLoaders;
@@ -164,10 +165,11 @@ export const SQLEditor = AsyncAceEditor([
   'mode/sql',
   'theme/github',
   'ext/language_tools',
+  'ext/searchbox',
 ]);
 
 export const FullSQLEditor = AsyncAceEditor(
-  ['mode/sql', 'theme/github', 'ext/language_tools'],
+  ['mode/sql', 'theme/github', 'ext/language_tools', 'ext/searchbox'],
   {
     // a custom placeholder in SQL lab for less jumpy re-renders
     placeholder: () => {
diff --git a/superset-frontend/src/types/brace.d.ts b/superset-frontend/src/types/brace.d.ts
index 70d9275e41..0b3770eebe 100644
--- a/superset-frontend/src/types/brace.d.ts
+++ b/superset-frontend/src/types/brace.d.ts
@@ -26,3 +26,4 @@ declare module 'brace/mode/javascript';
 declare module 'brace/theme/textmate';
 declare module 'brace/theme/github';
 declare module 'brace/ext/language_tools';
+declare module 'brace/ext/searchbox';


[superset] 20/23: fix: flaky test for dashboard changed on (#19483)

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

villebro pushed a commit to tag 1.5.0rc1
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 90f49e261e557cbc195f95e7b33a912053a56aa1
Author: Yongjie Zhao <yo...@gmail.com>
AuthorDate: Fri Apr 1 16:47:56 2022 +0800

    fix: flaky test for dashboard changed on (#19483)
    
    (cherry picked from commit a2e921bd035ff1af54c22ecc8e4203af68c6baa8)
---
 tests/integration_tests/charts/api_tests.py | 9 +++------
 1 file changed, 3 insertions(+), 6 deletions(-)

diff --git a/tests/integration_tests/charts/api_tests.py b/tests/integration_tests/charts/api_tests.py
index 3c92caceea..6b8d625d56 100644
--- a/tests/integration_tests/charts/api_tests.py
+++ b/tests/integration_tests/charts/api_tests.py
@@ -17,11 +17,9 @@
 # isort:skip_file
 """Unit tests for Superset"""
 import json
-from datetime import datetime
 from io import BytesIO
 from zipfile import is_zipfile, ZipFile
 
-import humanize
 import prison
 import pytest
 import yaml
@@ -803,7 +801,6 @@ class TestChartApi(SupersetTestCase, ApiOwnersTestCaseMixin, InsertChartMixin):
         Dashboard API: Test get charts changed on
         """
         admin = self.get_user("admin")
-        start_changed_on = datetime.now()
         chart = self.insert_chart("foo_a", [admin.id], 1, description="ZY_bar")
 
         self.login(username="admin")
@@ -817,9 +814,9 @@ 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["result"][0]["changed_on_delta_humanized"],
-            humanize.naturaltime(datetime.now() - start_changed_on),
+        assert data["result"][0]["changed_on_delta_humanized"] in (
+            "now",
+            "a second ago",
         )
 
         # rollback changes


[superset] 03/23: fix: add missing init on python pkg key_value (#19428)

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

villebro pushed a commit to tag 1.5.0rc1
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 7aba89c486e58a94df99ca3a12da946d4a0eb5b9
Author: Daniel Vaz Gaspar <da...@gmail.com>
AuthorDate: Wed Mar 30 12:46:42 2022 +0100

    fix: add missing init on python pkg key_value (#19428)
    
    * fix: add missing init on python pkg key_value
    
    * fix lint issues
    
    * fix lint issues
    
    (cherry picked from commit fa35109bf2a416462e3c83f527aa82c20b67818d)
---
 superset/dashboards/permalink/commands/create.py         |  2 ++
 superset/explore/permalink/commands/create.py            |  2 ++
 superset/key_value/commands/__init__.py                  | 16 ++++++++++++++++
 superset/key_value/commands/create.py                    |  1 +
 superset/key_value/commands/update.py                    |  1 +
 superset/key_value/commands/upsert.py                    | 16 ++++++++--------
 .../integration_tests/key_value/commands/update_test.py  |  2 ++
 .../integration_tests/key_value/commands/upsert_test.py  |  3 +++
 8 files changed, 35 insertions(+), 8 deletions(-)

diff --git a/superset/dashboards/permalink/commands/create.py b/superset/dashboards/permalink/commands/create.py
index 27ddf0534d..4ffd41104e 100644
--- a/superset/dashboards/permalink/commands/create.py
+++ b/superset/dashboards/permalink/commands/create.py
@@ -53,6 +53,8 @@ class CreateDashboardPermalinkCommand(BaseDashboardPermalinkCommand):
                 resource=self.resource,
                 value=value,
             ).run()
+            if key.id is None:
+                raise DashboardPermalinkCreateFailedError("Unexpected missing key id")
             return encode_permalink_key(key=key.id, salt=self.salt)
         except SQLAlchemyError as ex:
             logger.exception("Error running create command")
diff --git a/superset/explore/permalink/commands/create.py b/superset/explore/permalink/commands/create.py
index 55fb0820cd..c09ca3b372 100644
--- a/superset/explore/permalink/commands/create.py
+++ b/superset/explore/permalink/commands/create.py
@@ -53,6 +53,8 @@ class CreateExplorePermalinkCommand(BaseExplorePermalinkCommand):
                 value=value,
             )
             key = command.run()
+            if key.id is None:
+                raise ExplorePermalinkCreateFailedError("Unexpected missing key id")
             return encode_permalink_key(key=key.id, salt=self.salt)
         except SQLAlchemyError as ex:
             logger.exception("Error running create command")
diff --git a/superset/key_value/commands/__init__.py b/superset/key_value/commands/__init__.py
new file mode 100644
index 0000000000..13a83393a9
--- /dev/null
+++ b/superset/key_value/commands/__init__.py
@@ -0,0 +1,16 @@
+# 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.
diff --git a/superset/key_value/commands/create.py b/superset/key_value/commands/create.py
index 613fabcdeb..5125ce7b01 100644
--- a/superset/key_value/commands/create.py
+++ b/superset/key_value/commands/create.py
@@ -39,6 +39,7 @@ class CreateKeyValueCommand(BaseCommand):
     key: Optional[Union[int, UUID]]
     expires_on: Optional[datetime]
 
+    # pylint: disable=too-many-arguments
     def __init__(
         self,
         resource: KeyValueResource,
diff --git a/superset/key_value/commands/update.py b/superset/key_value/commands/update.py
index 7333b48c5c..48fd8daa8a 100644
--- a/superset/key_value/commands/update.py
+++ b/superset/key_value/commands/update.py
@@ -41,6 +41,7 @@ class UpdateKeyValueCommand(BaseCommand):
     key: Union[int, UUID]
     expires_on: Optional[datetime]
 
+    # pylint: disable=too-many-argumentsåå
     def __init__(
         self,
         resource: KeyValueResource,
diff --git a/superset/key_value/commands/upsert.py b/superset/key_value/commands/upsert.py
index aa495f7cc7..8fd0bd240f 100644
--- a/superset/key_value/commands/upsert.py
+++ b/superset/key_value/commands/upsert.py
@@ -42,6 +42,7 @@ class UpsertKeyValueCommand(BaseCommand):
     key: Union[int, UUID]
     expires_on: Optional[datetime]
 
+    # pylint: disable=too-many-arguments
     def __init__(
         self,
         resource: KeyValueResource,
@@ -96,11 +97,10 @@ class UpsertKeyValueCommand(BaseCommand):
             db.session.merge(entry)
             db.session.commit()
             return Key(entry.id, entry.uuid)
-        else:
-            return CreateKeyValueCommand(
-                resource=self.resource,
-                value=self.value,
-                actor=self.actor,
-                key=self.key,
-                expires_on=self.expires_on,
-            ).run()
+        return CreateKeyValueCommand(
+            resource=self.resource,
+            value=self.value,
+            actor=self.actor,
+            key=self.key,
+            expires_on=self.expires_on,
+        ).run()
diff --git a/tests/integration_tests/key_value/commands/update_test.py b/tests/integration_tests/key_value/commands/update_test.py
index 62a8126ba2..3b24ecdf0a 100644
--- a/tests/integration_tests/key_value/commands/update_test.py
+++ b/tests/integration_tests/key_value/commands/update_test.py
@@ -53,6 +53,7 @@ def test_update_id_entry(
         key=ID_KEY,
         value=NEW_VALUE,
     ).run()
+    assert key is not None
     assert key.id == ID_KEY
     entry = db.session.query(KeyValueEntry).filter_by(id=ID_KEY).autoflush(False).one()
     assert pickle.loads(entry.value) == NEW_VALUE
@@ -73,6 +74,7 @@ def test_update_uuid_entry(
         key=UUID_KEY,
         value=NEW_VALUE,
     ).run()
+    assert key is not None
     assert key.uuid == UUID_KEY
     entry = (
         db.session.query(KeyValueEntry).filter_by(uuid=UUID_KEY).autoflush(False).one()
diff --git a/tests/integration_tests/key_value/commands/upsert_test.py b/tests/integration_tests/key_value/commands/upsert_test.py
index adb652e66a..1970a1fc2c 100644
--- a/tests/integration_tests/key_value/commands/upsert_test.py
+++ b/tests/integration_tests/key_value/commands/upsert_test.py
@@ -53,6 +53,7 @@ def test_upsert_id_entry(
         key=ID_KEY,
         value=NEW_VALUE,
     ).run()
+    assert key is not None
     assert key.id == ID_KEY
     entry = (
         db.session.query(KeyValueEntry).filter_by(id=int(ID_KEY)).autoflush(False).one()
@@ -75,6 +76,7 @@ def test_upsert_uuid_entry(
         key=UUID_KEY,
         value=NEW_VALUE,
     ).run()
+    assert key is not None
     assert key.uuid == UUID_KEY
     entry = (
         db.session.query(KeyValueEntry).filter_by(uuid=UUID_KEY).autoflush(False).one()
@@ -93,6 +95,7 @@ def test_upsert_missing_entry(app_context: AppContext, admin: User) -> None:
         key=456,
         value=NEW_VALUE,
     ).run()
+    assert key is not None
     assert key.id == 456
     db.session.query(KeyValueEntry).filter_by(id=456).delete()
     db.session.commit()


[superset] 19/23: fix: can't sync temporal flag on virtual table (#19366)

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

villebro pushed a commit to tag 1.5.0rc1
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 3ff9cdeb654959e864524f49d4ed0dd95b3780bb
Author: Yongjie Zhao <yo...@gmail.com>
AuthorDate: Fri Apr 1 16:40:50 2022 +0800

    fix: can't sync temporal flag on virtual table (#19366)
    
    (cherry picked from commit d954c3df8604dc4b6a2459a17dd39450a1d57638)
---
 .../cypress/integration/sqllab/query.test.ts       |   4 +-
 .../src/SqlLab/components/ResultSet/index.tsx      |   3 +-
 superset-frontend/src/SqlLab/fixtures.ts           |  38 +++----
 superset-frontend/src/SqlLab/types.ts              |   4 +-
 superset/connectors/sqla/models.py                 |   3 +-
 superset/connectors/sqla/utils.py                  |   3 +-
 superset/db_engine_specs/base.py                   |   7 +-
 superset/db_engine_specs/presto.py                 |  34 +++---
 superset/result_set.py                             |   8 +-
 superset/superset_typing.py                        |  10 ++
 superset/views/core.py                             |   2 +-
 tests/integration_tests/celery_tests.py            |   4 +-
 .../db_engine_specs/presto_tests.py                | 114 +++++++++++++++------
 tests/integration_tests/result_set_tests.py        |  20 ++--
 tests/integration_tests/sqllab_tests.py            |   8 +-
 15 files changed, 170 insertions(+), 92 deletions(-)

diff --git a/superset-frontend/cypress-base/cypress/integration/sqllab/query.test.ts b/superset-frontend/cypress-base/cypress/integration/sqllab/query.test.ts
index ea43c66f97..f5033313fc 100644
--- a/superset-frontend/cypress-base/cypress/integration/sqllab/query.test.ts
+++ b/superset-frontend/cypress-base/cypress/integration/sqllab/query.test.ts
@@ -37,8 +37,8 @@ describe('SqlLab query panel', () => {
     const sampleResponse = {
       status: 'success',
       data: [{ '?column?': 1 }],
-      columns: [{ name: '?column?', type: 'INT', is_date: false }],
-      selected_columns: [{ name: '?column?', type: 'INT', is_date: false }],
+      columns: [{ name: '?column?', type: 'INT', is_dttm: false }],
+      selected_columns: [{ name: '?column?', type: 'INT', is_dttm: false }],
       expanded_columns: [],
     };
 
diff --git a/superset-frontend/src/SqlLab/components/ResultSet/index.tsx b/superset-frontend/src/SqlLab/components/ResultSet/index.tsx
index dbde25ef2b..b81b471462 100644
--- a/superset-frontend/src/SqlLab/components/ResultSet/index.tsx
+++ b/superset-frontend/src/SqlLab/components/ResultSet/index.tsx
@@ -280,7 +280,8 @@ export default class ResultSet extends React.PureComponent<
       sql,
       results.selected_columns.map(d => ({
         column_name: d.name,
-        is_dttm: d.is_date,
+        type: d.type,
+        is_dttm: d.is_dttm,
       })),
       datasetToOverwrite.owners.map((o: DatasetOwner) => o.id),
       true,
diff --git a/superset-frontend/src/SqlLab/fixtures.ts b/superset-frontend/src/SqlLab/fixtures.ts
index 5725bcf75e..5b12ee2921 100644
--- a/superset-frontend/src/SqlLab/fixtures.ts
+++ b/superset-frontend/src/SqlLab/fixtures.ts
@@ -221,24 +221,24 @@ export const queries = [
     results: {
       columns: [
         {
-          is_date: true,
+          is_dttm: true,
           name: 'ds',
           type: 'STRING',
         },
         {
-          is_date: false,
+          is_dttm: false,
           name: 'gender',
           type: 'STRING',
         },
       ],
       selected_columns: [
         {
-          is_date: true,
+          is_dttm: true,
           name: 'ds',
           type: 'STRING',
         },
         {
-          is_date: false,
+          is_dttm: false,
           name: 'gender',
           type: 'STRING',
         },
@@ -313,24 +313,24 @@ export const queryWithNoQueryLimit = {
   results: {
     columns: [
       {
-        is_date: true,
+        is_dttm: true,
         name: 'ds',
         type: 'STRING',
       },
       {
-        is_date: false,
+        is_dttm: false,
         name: 'gender',
         type: 'STRING',
       },
     ],
     selected_columns: [
       {
-        is_date: true,
+        is_dttm: true,
         name: 'ds',
         type: 'STRING',
       },
       {
-        is_date: false,
+        is_dttm: false,
         name: 'gender',
         type: 'STRING',
       },
@@ -350,57 +350,57 @@ export const queryWithBadColumns = {
     data: queries[0].results?.data,
     selected_columns: [
       {
-        is_date: true,
+        is_dttm: true,
         name: 'COUNT(*)',
         type: 'STRING',
       },
       {
-        is_date: false,
+        is_dttm: false,
         name: 'this_col_is_ok',
         type: 'STRING',
       },
       {
-        is_date: false,
+        is_dttm: false,
         name: 'a',
         type: 'STRING',
       },
       {
-        is_date: false,
+        is_dttm: false,
         name: '1',
         type: 'STRING',
       },
       {
-        is_date: false,
+        is_dttm: false,
         name: '123',
         type: 'STRING',
       },
       {
-        is_date: false,
+        is_dttm: false,
         name: 'CASE WHEN 1=1 THEN 1 ELSE 0 END',
         type: 'STRING',
       },
       {
-        is_date: true,
+        is_dttm: true,
         name: '_TIMESTAMP',
         type: 'TIMESTAMP',
       },
       {
-        is_date: true,
+        is_dttm: true,
         name: '__TIME',
         type: 'TIMESTAMP',
       },
       {
-        is_date: false,
+        is_dttm: false,
         name: 'my_dupe_col__2',
         type: 'STRING',
       },
       {
-        is_date: true,
+        is_dttm: true,
         name: '__timestamp',
         type: 'TIMESTAMP',
       },
       {
-        is_date: true,
+        is_dttm: true,
         name: '__TIMESTAMP',
         type: 'TIMESTAMP',
       },
diff --git a/superset-frontend/src/SqlLab/types.ts b/superset-frontend/src/SqlLab/types.ts
index f1e088f003..d5dfddbe2b 100644
--- a/superset-frontend/src/SqlLab/types.ts
+++ b/superset-frontend/src/SqlLab/types.ts
@@ -21,9 +21,11 @@ import { CtasEnum } from 'src/SqlLab/actions/sqlLab';
 import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes';
 import { ToastType } from 'src/components/MessageToasts/types';
 
+// same as superset.result_set.ResultSetColumnType
 export type Column = {
   name: string;
-  is_date?: boolean;
+  type: string | null;
+  is_dttm: boolean;
 };
 
 export type QueryState =
diff --git a/superset/connectors/sqla/models.py b/superset/connectors/sqla/models.py
index 31f3339541..8721f6ea81 100644
--- a/superset/connectors/sqla/models.py
+++ b/superset/connectors/sqla/models.py
@@ -716,8 +716,9 @@ class SqlaTable(Model, BaseDatasource):  # pylint: disable=too-many-public-metho
         return self.database.sql_url + "?table_name=" + str(self.table_name)
 
     def external_metadata(self) -> List[Dict[str, str]]:
+        # todo(yongjie): create a pysical table column type in seprated PR
         if self.sql:
-            return get_virtual_table_metadata(dataset=self)
+            return get_virtual_table_metadata(dataset=self)  # type: ignore
         return get_physical_table_metadata(
             database=self.database,
             table_name=self.table_name,
diff --git a/superset/connectors/sqla/utils.py b/superset/connectors/sqla/utils.py
index 766b74e57c..14b9071d1d 100644
--- a/superset/connectors/sqla/utils.py
+++ b/superset/connectors/sqla/utils.py
@@ -34,6 +34,7 @@ from superset.exceptions import (
 from superset.models.core import Database
 from superset.result_set import SupersetResultSet
 from superset.sql_parse import has_table_query, insert_rls, ParsedQuery, Table
+from superset.superset_typing import ResultSetColumnType
 from superset.tables.models import Table as NewTable
 
 if TYPE_CHECKING:
@@ -91,7 +92,7 @@ def get_physical_table_metadata(
     return cols
 
 
-def get_virtual_table_metadata(dataset: "SqlaTable") -> List[Dict[str, str]]:
+def get_virtual_table_metadata(dataset: "SqlaTable") -> List[ResultSetColumnType]:
     """Use SQLparser to get virtual dataset metadata"""
     if not dataset.sql:
         raise SupersetGenericDBErrorException(
diff --git a/superset/db_engine_specs/base.py b/superset/db_engine_specs/base.py
index 1f4cb51cb8..123376bbb8 100644
--- a/superset/db_engine_specs/base.py
+++ b/superset/db_engine_specs/base.py
@@ -62,6 +62,7 @@ from superset.errors import ErrorLevel, SupersetError, SupersetErrorType
 from superset.models.sql_lab import Query
 from superset.models.sql_types.base import literal_dttm_type_factory
 from superset.sql_parse import ParsedQuery, Table
+from superset.superset_typing import ResultSetColumnType
 from superset.utils import core as utils
 from superset.utils.core import ColumnSpec, GenericDataType
 from superset.utils.hashing import md5_sha_from_str
@@ -566,8 +567,10 @@ class BaseEngineSpec:  # pylint: disable=too-many-public-methods
 
     @classmethod
     def expand_data(
-        cls, columns: List[Dict[Any, Any]], data: List[Dict[Any, Any]]
-    ) -> Tuple[List[Dict[Any, Any]], List[Dict[Any, Any]], List[Dict[Any, Any]]]:
+        cls, columns: List[ResultSetColumnType], data: List[Dict[Any, Any]]
+    ) -> Tuple[
+        List[ResultSetColumnType], List[Dict[Any, Any]], List[ResultSetColumnType]
+    ]:
         """
         Some engines support expanding nested fields. See implementation in Presto
         spec for details.
diff --git a/superset/db_engine_specs/presto.py b/superset/db_engine_specs/presto.py
index 60cb9c7aca..77c3d4c2e3 100644
--- a/superset/db_engine_specs/presto.py
+++ b/superset/db_engine_specs/presto.py
@@ -53,6 +53,7 @@ from superset.models.sql_types.presto_sql_types import (
 )
 from superset.result_set import destringify
 from superset.sql_parse import ParsedQuery
+from superset.superset_typing import ResultSetColumnType
 from superset.utils import core as utils
 from superset.utils.core import ColumnSpec, GenericDataType
 
@@ -85,24 +86,26 @@ CONNECTION_UNKNOWN_DATABASE_ERROR = re.compile(
 logger = logging.getLogger(__name__)
 
 
-def get_children(column: Dict[str, str]) -> List[Dict[str, str]]:
+def get_children(column: ResultSetColumnType) -> List[ResultSetColumnType]:
     """
     Get the children of a complex Presto type (row or array).
 
     For arrays, we return a single list with the base type:
 
-        >>> get_children(dict(name="a", type="ARRAY(BIGINT)"))
-        [{"name": "a", "type": "BIGINT"}]
+        >>> get_children(dict(name="a", type="ARRAY(BIGINT)", is_dttm=False))
+        [{"name": "a", "type": "BIGINT", "is_dttm": False}]
 
     For rows, we return a list of the columns:
 
-        >>> get_children(dict(name="a", type="ROW(BIGINT,FOO VARCHAR)"))
-        [{'name': 'a._col0', 'type': 'BIGINT'}, {'name': 'a.foo', 'type': 'VARCHAR'}]
+        >>> get_children(dict(name="a", type="ROW(BIGINT,FOO VARCHAR)",  is_dttm=False))
+        [{'name': 'a._col0', 'type': 'BIGINT', 'is_dttm': False}, {'name': 'a.foo', 'type': 'VARCHAR', 'is_dttm': False}]  # pylint: disable=line-too-long
 
     :param column: dictionary representing a Presto column
     :return: list of dictionaries representing children columns
     """
     pattern = re.compile(r"(?P<type>\w+)\((?P<children>.*)\)")
+    if not column["type"]:
+        raise ValueError
     match = pattern.match(column["type"])
     if not match:
         raise Exception(f"Unable to parse column type {column['type']}")
@@ -111,7 +114,7 @@ def get_children(column: Dict[str, str]) -> List[Dict[str, str]]:
     type_ = group["type"].upper()
     children_type = group["children"]
     if type_ == "ARRAY":
-        return [{"name": column["name"], "type": children_type}]
+        return [{"name": column["name"], "type": children_type, "is_dttm": False}]
 
     if type_ == "ROW":
         nameless_columns = 0
@@ -125,7 +128,12 @@ def get_children(column: Dict[str, str]) -> List[Dict[str, str]]:
                 name = f"_col{nameless_columns}"
                 type_ = parts[0]
                 nameless_columns += 1
-            columns.append({"name": f"{column['name']}.{name.lower()}", "type": type_})
+            _column: ResultSetColumnType = {
+                "name": f"{column['name']}.{name.lower()}",
+                "type": type_,
+                "is_dttm": False,
+            }
+            columns.append(_column)
         return columns
 
     raise Exception(f"Unknown type {type_}!")
@@ -779,8 +787,10 @@ class PrestoEngineSpec(BaseEngineSpec):  # pylint: disable=too-many-public-metho
 
     @classmethod
     def expand_data(  # pylint: disable=too-many-locals
-        cls, columns: List[Dict[Any, Any]], data: List[Dict[Any, Any]]
-    ) -> Tuple[List[Dict[Any, Any]], List[Dict[Any, Any]], List[Dict[Any, Any]]]:
+        cls, columns: List[ResultSetColumnType], data: List[Dict[Any, Any]]
+    ) -> Tuple[
+        List[ResultSetColumnType], List[Dict[Any, Any]], List[ResultSetColumnType]
+    ]:
         """
         We do not immediately display rows and arrays clearly in the data grid. This
         method separates out nested fields and data values to help clearly display
@@ -808,7 +818,7 @@ class PrestoEngineSpec(BaseEngineSpec):  # pylint: disable=too-many-public-metho
         # process each column, unnesting ARRAY types and
         # expanding ROW types into new columns
         to_process = deque((column, 0) for column in columns)
-        all_columns: List[Dict[str, Any]] = []
+        all_columns: List[ResultSetColumnType] = []
         expanded_columns = []
         current_array_level = None
         while to_process:
@@ -828,7 +838,7 @@ class PrestoEngineSpec(BaseEngineSpec):  # pylint: disable=too-many-public-metho
             name = column["name"]
             values: Optional[Union[str, List[Any]]]
 
-            if column["type"].startswith("ARRAY("):
+            if column["type"] and column["type"].startswith("ARRAY("):
                 # keep processing array children; we append to the right so that
                 # multiple nested arrays are processed breadth-first
                 to_process.append((get_children(column)[0], level + 1))
@@ -862,7 +872,7 @@ class PrestoEngineSpec(BaseEngineSpec):  # pylint: disable=too-many-public-metho
 
                     i += 1
 
-            if column["type"].startswith("ROW("):
+            if column["type"] and column["type"].startswith("ROW("):
                 # expand columns; we append them to the left so they are added
                 # immediately after the parent
                 expanded = get_children(column)
diff --git a/superset/result_set.py b/superset/result_set.py
index 19035b6d23..82b0a31393 100644
--- a/superset/result_set.py
+++ b/superset/result_set.py
@@ -26,7 +26,7 @@ import pandas as pd
 import pyarrow as pa
 
 from superset.db_engine_specs import BaseEngineSpec
-from superset.superset_typing import DbapiDescription, DbapiResult
+from superset.superset_typing import DbapiDescription, DbapiResult, ResultSetColumnType
 from superset.utils import core as utils
 
 logger = logging.getLogger(__name__)
@@ -210,17 +210,17 @@ class SupersetResultSet:
         return self.table.num_rows
 
     @property
-    def columns(self) -> List[Dict[str, Any]]:
+    def columns(self) -> List[ResultSetColumnType]:
         if not self.table.column_names:
             return []
 
         columns = []
         for col in self.table.schema:
             db_type_str = self.data_type(col.name, col.type)
-            column = {
+            column: ResultSetColumnType = {
                 "name": col.name,
                 "type": db_type_str,
-                "is_date": self.is_temporal(db_type_str),
+                "is_dttm": self.is_temporal(db_type_str),
             }
             columns.append(column)
 
diff --git a/superset/superset_typing.py b/superset/superset_typing.py
index 253d2b6355..1af04494d0 100644
--- a/superset/superset_typing.py
+++ b/superset/superset_typing.py
@@ -57,6 +57,16 @@ class AdhocColumn(TypedDict, total=False):
     sqlExpression: Optional[str]
 
 
+class ResultSetColumnType(TypedDict):
+    """
+    Superset virtual dataset column interface
+    """
+
+    name: str
+    type: Optional[str]
+    is_dttm: bool
+
+
 CacheConfig = Dict[str, Any]
 DbapiDescriptionRow = Tuple[
     str, str, Optional[str], Optional[str], Optional[int], Optional[int], bool
diff --git a/superset/views/core.py b/superset/views/core.py
index 4dd7d4729f..6e329d1f48 100755
--- a/superset/views/core.py
+++ b/superset/views/core.py
@@ -2217,7 +2217,7 @@ class Superset(BaseSupersetView):  # pylint: disable=too-many-public-methods
                 column_name=column_name,
                 filterable=True,
                 groupby=True,
-                is_dttm=config_.get("is_date", False),
+                is_dttm=config_.get("is_dttm", False),
                 type=config_.get("type", False),
             )
             cols.append(col)
diff --git a/tests/integration_tests/celery_tests.py b/tests/integration_tests/celery_tests.py
index 802684ba3b..3d4ba5e901 100644
--- a/tests/integration_tests/celery_tests.py
+++ b/tests/integration_tests/celery_tests.py
@@ -130,8 +130,8 @@ def cta_result(ctas_method: CtasMethod):
     if backend() != "presto":
         return [], []
     if ctas_method == CtasMethod.TABLE:
-        return [{"rows": 1}], [{"name": "rows", "type": "BIGINT", "is_date": False}]
-    return [{"result": True}], [{"name": "result", "type": "BOOLEAN", "is_date": False}]
+        return [{"rows": 1}], [{"name": "rows", "type": "BIGINT", "is_dttm": False}]
+    return [{"result": True}], [{"name": "result", "type": "BOOLEAN", "is_dttm": False}]
 
 
 # TODO(bkyryliuk): quote table and schema names for all databases
diff --git a/tests/integration_tests/db_engine_specs/presto_tests.py b/tests/integration_tests/db_engine_specs/presto_tests.py
index 558f4322a0..17c7c2d900 100644
--- a/tests/integration_tests/db_engine_specs/presto_tests.py
+++ b/tests/integration_tests/db_engine_specs/presto_tests.py
@@ -207,8 +207,8 @@ class TestPrestoDbEngineSpec(TestDbEngineSpec):
     )
     def test_presto_expand_data_with_simple_structural_columns(self):
         cols = [
-            {"name": "row_column", "type": "ROW(NESTED_OBJ VARCHAR)"},
-            {"name": "array_column", "type": "ARRAY(BIGINT)"},
+            {"name": "row_column", "type": "ROW(NESTED_OBJ VARCHAR)", "is_dttm": False},
+            {"name": "array_column", "type": "ARRAY(BIGINT)", "is_dttm": False},
         ]
         data = [
             {"row_column": ["a"], "array_column": [1, 2, 3]},
@@ -218,9 +218,9 @@ class TestPrestoDbEngineSpec(TestDbEngineSpec):
             cols, data
         )
         expected_cols = [
-            {"name": "row_column", "type": "ROW(NESTED_OBJ VARCHAR)"},
-            {"name": "row_column.nested_obj", "type": "VARCHAR"},
-            {"name": "array_column", "type": "ARRAY(BIGINT)"},
+            {"name": "row_column", "type": "ROW(NESTED_OBJ VARCHAR)", "is_dttm": False},
+            {"name": "row_column.nested_obj", "type": "VARCHAR", "is_dttm": False},
+            {"name": "array_column", "type": "ARRAY(BIGINT)", "is_dttm": False},
         ]
 
         expected_data = [
@@ -232,7 +232,9 @@ class TestPrestoDbEngineSpec(TestDbEngineSpec):
             {"array_column": 6, "row_column": "", "row_column.nested_obj": ""},
         ]
 
-        expected_expanded_cols = [{"name": "row_column.nested_obj", "type": "VARCHAR"}]
+        expected_expanded_cols = [
+            {"name": "row_column.nested_obj", "type": "VARCHAR", "is_dttm": False}
+        ]
         self.assertEqual(actual_cols, expected_cols)
         self.assertEqual(actual_data, expected_data)
         self.assertEqual(actual_expanded_cols, expected_expanded_cols)
@@ -247,6 +249,7 @@ class TestPrestoDbEngineSpec(TestDbEngineSpec):
             {
                 "name": "row_column",
                 "type": "ROW(NESTED_OBJ1 VARCHAR, NESTED_ROW ROW(NESTED_OBJ2 VARCHAR))",
+                "is_dttm": False,
             }
         ]
         data = [{"row_column": ["a1", ["a2"]]}, {"row_column": ["b1", ["b2"]]}]
@@ -257,10 +260,19 @@ class TestPrestoDbEngineSpec(TestDbEngineSpec):
             {
                 "name": "row_column",
                 "type": "ROW(NESTED_OBJ1 VARCHAR, NESTED_ROW ROW(NESTED_OBJ2 VARCHAR))",
+                "is_dttm": False,
+            },
+            {"name": "row_column.nested_obj1", "type": "VARCHAR", "is_dttm": False},
+            {
+                "name": "row_column.nested_row",
+                "type": "ROW(NESTED_OBJ2 VARCHAR)",
+                "is_dttm": False,
+            },
+            {
+                "name": "row_column.nested_row.nested_obj2",
+                "type": "VARCHAR",
+                "is_dttm": False,
             },
-            {"name": "row_column.nested_obj1", "type": "VARCHAR"},
-            {"name": "row_column.nested_row", "type": "ROW(NESTED_OBJ2 VARCHAR)"},
-            {"name": "row_column.nested_row.nested_obj2", "type": "VARCHAR"},
         ]
         expected_data = [
             {
@@ -278,9 +290,17 @@ class TestPrestoDbEngineSpec(TestDbEngineSpec):
         ]
 
         expected_expanded_cols = [
-            {"name": "row_column.nested_obj1", "type": "VARCHAR"},
-            {"name": "row_column.nested_row", "type": "ROW(NESTED_OBJ2 VARCHAR)"},
-            {"name": "row_column.nested_row.nested_obj2", "type": "VARCHAR"},
+            {"name": "row_column.nested_obj1", "type": "VARCHAR", "is_dttm": False},
+            {
+                "name": "row_column.nested_row",
+                "type": "ROW(NESTED_OBJ2 VARCHAR)",
+                "is_dttm": False,
+            },
+            {
+                "name": "row_column.nested_row.nested_obj2",
+                "type": "VARCHAR",
+                "is_dttm": False,
+            },
         ]
         self.assertEqual(actual_cols, expected_cols)
         self.assertEqual(actual_data, expected_data)
@@ -296,6 +316,7 @@ class TestPrestoDbEngineSpec(TestDbEngineSpec):
             {
                 "name": "row_column",
                 "type": "ROW(NESTED_ROW ROW(NESTED_OBJ VARCHAR))",
+                "is_dttm": False,
             }
         ]
         data = [
@@ -311,9 +332,18 @@ class TestPrestoDbEngineSpec(TestDbEngineSpec):
             {
                 "name": "row_column",
                 "type": "ROW(NESTED_ROW ROW(NESTED_OBJ VARCHAR))",
+                "is_dttm": False,
+            },
+            {
+                "name": "row_column.nested_row",
+                "type": "ROW(NESTED_OBJ VARCHAR)",
+                "is_dttm": False,
+            },
+            {
+                "name": "row_column.nested_row.nested_obj",
+                "type": "VARCHAR",
+                "is_dttm": False,
             },
-            {"name": "row_column.nested_row", "type": "ROW(NESTED_OBJ VARCHAR)"},
-            {"name": "row_column.nested_row.nested_obj", "type": "VARCHAR"},
         ]
         expected_data = [
             {
@@ -339,8 +369,16 @@ class TestPrestoDbEngineSpec(TestDbEngineSpec):
         ]
 
         expected_expanded_cols = [
-            {"name": "row_column.nested_row", "type": "ROW(NESTED_OBJ VARCHAR)"},
-            {"name": "row_column.nested_row.nested_obj", "type": "VARCHAR"},
+            {
+                "name": "row_column.nested_row",
+                "type": "ROW(NESTED_OBJ VARCHAR)",
+                "is_dttm": False,
+            },
+            {
+                "name": "row_column.nested_row.nested_obj",
+                "type": "VARCHAR",
+                "is_dttm": False,
+            },
         ]
         self.assertEqual(actual_cols, expected_cols)
         self.assertEqual(actual_data, expected_data)
@@ -353,10 +391,11 @@ class TestPrestoDbEngineSpec(TestDbEngineSpec):
     )
     def test_presto_expand_data_with_complex_array_columns(self):
         cols = [
-            {"name": "int_column", "type": "BIGINT"},
+            {"name": "int_column", "type": "BIGINT", "is_dttm": False},
             {
                 "name": "array_column",
                 "type": "ARRAY(ROW(NESTED_ARRAY ARRAY(ROW(NESTED_OBJ VARCHAR))))",
+                "is_dttm": False,
             },
         ]
         data = [
@@ -367,16 +406,22 @@ class TestPrestoDbEngineSpec(TestDbEngineSpec):
             cols, data
         )
         expected_cols = [
-            {"name": "int_column", "type": "BIGINT"},
+            {"name": "int_column", "type": "BIGINT", "is_dttm": False},
             {
                 "name": "array_column",
                 "type": "ARRAY(ROW(NESTED_ARRAY ARRAY(ROW(NESTED_OBJ VARCHAR))))",
+                "is_dttm": False,
             },
             {
                 "name": "array_column.nested_array",
                 "type": "ARRAY(ROW(NESTED_OBJ VARCHAR))",
+                "is_dttm": False,
+            },
+            {
+                "name": "array_column.nested_array.nested_obj",
+                "type": "VARCHAR",
+                "is_dttm": False,
             },
-            {"name": "array_column.nested_array.nested_obj", "type": "VARCHAR"},
         ]
         expected_data = [
             {
@@ -432,8 +477,13 @@ class TestPrestoDbEngineSpec(TestDbEngineSpec):
             {
                 "name": "array_column.nested_array",
                 "type": "ARRAY(ROW(NESTED_OBJ VARCHAR))",
+                "is_dttm": False,
+            },
+            {
+                "name": "array_column.nested_array.nested_obj",
+                "type": "VARCHAR",
+                "is_dttm": False,
             },
-            {"name": "array_column.nested_array.nested_obj", "type": "VARCHAR"},
         ]
         self.assertEqual(actual_cols, expected_cols)
         self.assertEqual(actual_data, expected_data)
@@ -545,12 +595,12 @@ class TestPrestoDbEngineSpec(TestDbEngineSpec):
     )
     def test_presto_expand_data_array(self):
         cols = [
-            {"name": "event_id", "type": "VARCHAR", "is_date": False},
-            {"name": "timestamp", "type": "BIGINT", "is_date": False},
+            {"name": "event_id", "type": "VARCHAR", "is_dttm": False},
+            {"name": "timestamp", "type": "BIGINT", "is_dttm": False},
             {
                 "name": "user",
                 "type": "ROW(ID BIGINT, FIRST_NAME VARCHAR, LAST_NAME VARCHAR)",
-                "is_date": False,
+                "is_dttm": False,
             },
         ]
         data = [
@@ -564,16 +614,16 @@ class TestPrestoDbEngineSpec(TestDbEngineSpec):
             cols, data
         )
         expected_cols = [
-            {"name": "event_id", "type": "VARCHAR", "is_date": False},
-            {"name": "timestamp", "type": "BIGINT", "is_date": False},
+            {"name": "event_id", "type": "VARCHAR", "is_dttm": False},
+            {"name": "timestamp", "type": "BIGINT", "is_dttm": False},
             {
                 "name": "user",
                 "type": "ROW(ID BIGINT, FIRST_NAME VARCHAR, LAST_NAME VARCHAR)",
-                "is_date": False,
+                "is_dttm": False,
             },
-            {"name": "user.id", "type": "BIGINT"},
-            {"name": "user.first_name", "type": "VARCHAR"},
-            {"name": "user.last_name", "type": "VARCHAR"},
+            {"name": "user.id", "type": "BIGINT", "is_dttm": False},
+            {"name": "user.first_name", "type": "VARCHAR", "is_dttm": False},
+            {"name": "user.last_name", "type": "VARCHAR", "is_dttm": False},
         ]
         expected_data = [
             {
@@ -586,9 +636,9 @@ class TestPrestoDbEngineSpec(TestDbEngineSpec):
             }
         ]
         expected_expanded_cols = [
-            {"name": "user.id", "type": "BIGINT"},
-            {"name": "user.first_name", "type": "VARCHAR"},
-            {"name": "user.last_name", "type": "VARCHAR"},
+            {"name": "user.id", "type": "BIGINT", "is_dttm": False},
+            {"name": "user.first_name", "type": "VARCHAR", "is_dttm": False},
+            {"name": "user.last_name", "type": "VARCHAR", "is_dttm": False},
         ]
 
         self.assertEqual(actual_cols, expected_cols)
diff --git a/tests/integration_tests/result_set_tests.py b/tests/integration_tests/result_set_tests.py
index bd44661d8d..626468fc5a 100644
--- a/tests/integration_tests/result_set_tests.py
+++ b/tests/integration_tests/result_set_tests.py
@@ -48,9 +48,9 @@ class TestSupersetResultSet(SupersetTestCase):
         self.assertEqual(
             results.columns,
             [
-                {"is_date": False, "type": "STRING", "name": "a"},
-                {"is_date": False, "type": "STRING", "name": "b"},
-                {"is_date": False, "type": "STRING", "name": "c"},
+                {"is_dttm": False, "type": "STRING", "name": "a"},
+                {"is_dttm": False, "type": "STRING", "name": "b"},
+                {"is_dttm": False, "type": "STRING", "name": "c"},
             ],
         )
 
@@ -61,8 +61,8 @@ class TestSupersetResultSet(SupersetTestCase):
         self.assertEqual(
             results.columns,
             [
-                {"is_date": False, "type": "STRING", "name": "a"},
-                {"is_date": False, "type": "INT", "name": "b"},
+                {"is_dttm": False, "type": "STRING", "name": "a"},
+                {"is_dttm": False, "type": "INT", "name": "b"},
             ],
         )
 
@@ -76,11 +76,11 @@ class TestSupersetResultSet(SupersetTestCase):
         self.assertEqual(
             results.columns,
             [
-                {"is_date": False, "type": "FLOAT", "name": "a"},
-                {"is_date": False, "type": "INT", "name": "b"},
-                {"is_date": False, "type": "STRING", "name": "c"},
-                {"is_date": True, "type": "DATETIME", "name": "d"},
-                {"is_date": False, "type": "BOOL", "name": "e"},
+                {"is_dttm": False, "type": "FLOAT", "name": "a"},
+                {"is_dttm": False, "type": "INT", "name": "b"},
+                {"is_dttm": False, "type": "STRING", "name": "c"},
+                {"is_dttm": True, "type": "DATETIME", "name": "d"},
+                {"is_dttm": False, "type": "BOOL", "name": "e"},
             ],
         )
 
diff --git a/tests/integration_tests/sqllab_tests.py b/tests/integration_tests/sqllab_tests.py
index 891d409a56..dbfd31e937 100644
--- a/tests/integration_tests/sqllab_tests.py
+++ b/tests/integration_tests/sqllab_tests.py
@@ -483,8 +483,8 @@ class TestSqlLab(SupersetTestCase):
             "datasourceName": f"test_viz_flow_table_{random()}",
             "schema": "superset",
             "columns": [
-                {"is_date": False, "type": "STRING", "name": f"viz_type_{random()}"},
-                {"is_date": False, "type": "OBJECT", "name": f"ccount_{random()}"},
+                {"is_dttm": False, "type": "STRING", "name": f"viz_type_{random()}"},
+                {"is_dttm": False, "type": "OBJECT", "name": f"ccount_{random()}"},
             ],
             "sql": """\
                 SELECT *
@@ -513,8 +513,8 @@ class TestSqlLab(SupersetTestCase):
             "chartType": "dist_bar",
             "schema": "superset",
             "columns": [
-                {"is_date": False, "type": "STRING", "name": f"viz_type_{random()}"},
-                {"is_date": False, "type": "OBJECT", "name": f"ccount_{random()}"},
+                {"is_dttm": False, "type": "STRING", "name": f"viz_type_{random()}"},
+                {"is_dttm": False, "type": "OBJECT", "name": f"ccount_{random()}"},
             ],
             "sql": """\
                 SELECT *


[superset] 15/23: feat: improve adhoc SQL validation (#19454)

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

villebro pushed a commit to tag 1.5.0rc1
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 840be9972fd3318d70c9a3a153c4b0db3867a6d7
Author: Beto Dealmeida <ro...@dealmeida.net>
AuthorDate: Thu Mar 31 11:55:19 2022 -0700

    feat: improve adhoc SQL validation (#19454)
    
    * feat: improve adhoc SQL validation
    
    * Small changes
    
    * Add more unit tests
    
    (cherry picked from commit 6828624f61fff21485b0b2e91ac53701d43cb0d7)
---
 superset/connectors/sqla/models.py  | 38 +++++++++++++----
 superset/connectors/sqla/utils.py   | 40 +++++++++++-------
 superset/sql_parse.py               | 84 ++++++++++++++++++++++++-------------
 tests/unit_tests/sql_parse_tests.py | 80 ++++++++++++++++++++++++++---------
 4 files changed, 170 insertions(+), 72 deletions(-)

diff --git a/superset/connectors/sqla/models.py b/superset/connectors/sqla/models.py
index 535eb81a57..31f3339541 100644
--- a/superset/connectors/sqla/models.py
+++ b/superset/connectors/sqla/models.py
@@ -923,7 +923,11 @@ class SqlaTable(Model, BaseDatasource):  # pylint: disable=too-many-public-metho
         elif expression_type == utils.AdhocMetricExpressionType.SQL:
             tp = self.get_template_processor()
             expression = tp.process_template(cast(str, metric["sqlExpression"]))
-            validate_adhoc_subquery(expression)
+            expression = validate_adhoc_subquery(
+                expression,
+                self.database_id,
+                self.schema,
+            )
             try:
                 expression = sanitize_clause(expression)
             except QueryClauseValidationException as ex:
@@ -952,7 +956,11 @@ class SqlaTable(Model, BaseDatasource):  # pylint: disable=too-many-public-metho
         if template_processor and expression:
             expression = template_processor.process_template(expression)
         if expression:
-            validate_adhoc_subquery(expression)
+            expression = validate_adhoc_subquery(
+                expression,
+                self.database_id,
+                self.schema,
+            )
             try:
                 expression = sanitize_clause(expression)
             except QueryClauseValidationException as ex:
@@ -1006,9 +1014,9 @@ class SqlaTable(Model, BaseDatasource):  # pylint: disable=too-many-public-metho
             if is_alias_used_in_orderby(col):
                 col.name = f"{col.name}__"
 
-    def _get_sqla_row_level_filters(
+    def get_sqla_row_level_filters(
         self, template_processor: BaseTemplateProcessor
-    ) -> List[str]:
+    ) -> List[TextClause]:
         """
         Return the appropriate row level security filters for
         this table and the current user.
@@ -1016,7 +1024,6 @@ class SqlaTable(Model, BaseDatasource):  # pylint: disable=too-many-public-metho
         :param BaseTemplateProcessor template_processor: The template
         processor to apply to the filters.
         :returns: A list of SQL clauses to be ANDed together.
-        :rtype: List[str]
         """
         all_filters: List[TextClause] = []
         filter_groups: Dict[Union[int, str], List[TextClause]] = defaultdict(list)
@@ -1169,6 +1176,12 @@ class SqlaTable(Model, BaseDatasource):  # pylint: disable=too-many-public-metho
             col: Union[AdhocMetric, ColumnElement] = orig_col
             if isinstance(col, dict):
                 col = cast(AdhocMetric, col)
+                if col.get("sqlExpression"):
+                    col["sqlExpression"] = validate_adhoc_subquery(
+                        cast(str, col["sqlExpression"]),
+                        self.database_id,
+                        self.schema,
+                    )
                 if utils.is_adhoc_metric(col):
                     # add adhoc sort by column to columns_by_name if not exists
                     col = self.adhoc_metric_to_sqla(col, columns_by_name)
@@ -1218,7 +1231,11 @@ class SqlaTable(Model, BaseDatasource):  # pylint: disable=too-many-public-metho
                     elif selected in columns_by_name:
                         outer = columns_by_name[selected].get_sqla_col()
                     else:
-                        validate_adhoc_subquery(selected)
+                        selected = validate_adhoc_subquery(
+                            selected,
+                            self.database_id,
+                            self.schema,
+                        )
                         outer = literal_column(f"({selected})")
                         outer = self.make_sqla_column_compatible(outer, selected)
                 else:
@@ -1231,7 +1248,11 @@ class SqlaTable(Model, BaseDatasource):  # pylint: disable=too-many-public-metho
                 select_exprs.append(outer)
         elif columns:
             for selected in columns:
-                validate_adhoc_subquery(selected)
+                selected = validate_adhoc_subquery(
+                    selected,
+                    self.database_id,
+                    self.schema,
+                )
                 select_exprs.append(
                     columns_by_name[selected].get_sqla_col()
                     if selected in columns_by_name
@@ -1402,7 +1423,7 @@ class SqlaTable(Model, BaseDatasource):  # pylint: disable=too-many-public-metho
                             _("Invalid filter operation type: %(op)s", op=op)
                         )
         if is_feature_enabled("ROW_LEVEL_SECURITY"):
-            where_clause_and += self._get_sqla_row_level_filters(template_processor)
+            where_clause_and += self.get_sqla_row_level_filters(template_processor)
         if extras:
             where = extras.get("where")
             if where:
@@ -1449,7 +1470,6 @@ class SqlaTable(Model, BaseDatasource):  # pylint: disable=too-many-public-metho
                 and db_engine_spec.allows_hidden_cc_in_orderby
                 and col.name in [select_col.name for select_col in select_exprs]
             ):
-                validate_adhoc_subquery(str(col.expression))
                 col = literal_column(col.name)
             direction = asc if ascending else desc
             qry = qry.order_by(direction(col))
diff --git a/superset/connectors/sqla/utils.py b/superset/connectors/sqla/utils.py
index 4fc11a4d1d..766b74e57c 100644
--- a/superset/connectors/sqla/utils.py
+++ b/superset/connectors/sqla/utils.py
@@ -33,7 +33,7 @@ from superset.exceptions import (
 )
 from superset.models.core import Database
 from superset.result_set import SupersetResultSet
-from superset.sql_parse import has_table_query, ParsedQuery, Table
+from superset.sql_parse import has_table_query, insert_rls, ParsedQuery, Table
 from superset.tables.models import Table as NewTable
 
 if TYPE_CHECKING:
@@ -136,29 +136,39 @@ def get_virtual_table_metadata(dataset: "SqlaTable") -> List[Dict[str, str]]:
     return cols
 
 
-def validate_adhoc_subquery(raw_sql: str) -> None:
+def validate_adhoc_subquery(
+    sql: str,
+    database_id: int,
+    default_schema: str,
+) -> str:
     """
-    Check if adhoc SQL contains sub-queries or nested sub-queries with table
-    :param raw_sql: adhoc sql expression
+    Check if adhoc SQL contains sub-queries or nested sub-queries with table.
+
+    If sub-queries are allowed, the adhoc SQL is modified to insert any applicable RLS
+    predicates to it.
+
+    :param sql: adhoc sql expression
     :raise SupersetSecurityException if sql contains sub-queries or
     nested sub-queries with table
     """
     # pylint: disable=import-outside-toplevel
     from superset import is_feature_enabled
 
-    if is_feature_enabled("ALLOW_ADHOC_SUBQUERY"):
-        return
-
-    for statement in sqlparse.parse(raw_sql):
+    statements = []
+    for statement in sqlparse.parse(sql):
         if has_table_query(statement):
-            raise SupersetSecurityException(
-                SupersetError(
-                    error_type=SupersetErrorType.ADHOC_SUBQUERY_NOT_ALLOWED_ERROR,
-                    message=_("Custom SQL fields cannot contain sub-queries."),
-                    level=ErrorLevel.ERROR,
+            if not is_feature_enabled("ALLOW_ADHOC_SUBQUERY"):
+                raise SupersetSecurityException(
+                    SupersetError(
+                        error_type=SupersetErrorType.ADHOC_SUBQUERY_NOT_ALLOWED_ERROR,
+                        message=_("Custom SQL fields cannot contain sub-queries."),
+                        level=ErrorLevel.ERROR,
+                    )
                 )
-            )
-    return
+            statement = insert_rls(statement, database_id, default_schema)
+        statements.append(statement)
+
+    return ";\n".join(str(statement) for statement in statements)
 
 
 def load_or_create_tables(  # pylint: disable=too-many-arguments
diff --git a/superset/sql_parse.py b/superset/sql_parse.py
index 95361b39a6..6bfb63c425 100644
--- a/superset/sql_parse.py
+++ b/superset/sql_parse.py
@@ -18,10 +18,11 @@ import logging
 import re
 from dataclasses import dataclass
 from enum import Enum
-from typing import List, Optional, Set, Tuple
+from typing import cast, List, Optional, Set, Tuple
 from urllib import parse
 
 import sqlparse
+from sqlalchemy import and_
 from sqlparse.sql import (
     Identifier,
     IdentifierList,
@@ -283,7 +284,7 @@ class ParsedQuery:
         return statements
 
     @staticmethod
-    def _get_table(tlist: TokenList) -> Optional[Table]:
+    def get_table(tlist: TokenList) -> Optional[Table]:
         """
         Return the table if valid, i.e., conforms to the [[catalog.]schema.]table
         construct.
@@ -324,7 +325,7 @@ class ParsedQuery:
         """
         # exclude subselects
         if "(" not in str(token_list):
-            table = self._get_table(token_list)
+            table = self.get_table(token_list)
             if table and not table.table.startswith(CTE_PREFIX):
                 self._tables.add(table)
             return
@@ -500,7 +501,7 @@ def has_table_query(token_list: TokenList) -> bool:
     state = InsertRLSState.SCANNING
     for token in token_list.tokens:
 
-        # # Recurse into child token list
+        # Recurse into child token list
         if isinstance(token, TokenList) and has_table_query(token):
             return True
 
@@ -523,7 +524,7 @@ def has_table_query(token_list: TokenList) -> bool:
 
 def add_table_name(rls: TokenList, table: str) -> None:
     """
-    Modify a RLS expression ensuring columns are fully qualified.
+    Modify a RLS expression inplace ensuring columns are fully qualified.
     """
     tokens = rls.tokens[:]
     while tokens:
@@ -539,45 +540,70 @@ def add_table_name(rls: TokenList, table: str) -> None:
             tokens.extend(token.tokens)
 
 
-def matches_table_name(candidate: Token, table: str) -> bool:
+def get_rls_for_table(
+    candidate: Token,
+    database_id: int,
+    default_schema: Optional[str],
+) -> Optional[TokenList]:
     """
-    Returns if the token represents a reference to the table.
-
-    Tables can be fully qualified with periods.
-
-    Note that in theory a table should be represented as an identifier, but due to
-    sqlparse's aggressive list of keywords (spanning multiple dialects) often it gets
-    classified as a keyword.
+    Given a table name, return any associated RLS predicates.
     """
+    # pylint: disable=import-outside-toplevel
+    from superset import db
+    from superset.connectors.sqla.models import SqlaTable
+
     if not isinstance(candidate, Identifier):
         candidate = Identifier([Token(Name, candidate.value)])
 
-    target = sqlparse.parse(table)[0].tokens[0]
-    if not isinstance(target, Identifier):
-        target = Identifier([Token(Name, target.value)])
+    table = ParsedQuery.get_table(candidate)
+    if not table:
+        return None
 
-    # match from right to left, splitting on the period, eg, schema.table == table
-    for left, right in zip(candidate.tokens[::-1], target.tokens[::-1]):
-        if left.value != right.value:
-            return False
+    dataset = (
+        db.session.query(SqlaTable)
+        .filter(
+            and_(
+                SqlaTable.database_id == database_id,
+                SqlaTable.schema == (table.schema or default_schema),
+                SqlaTable.table_name == table.table,
+            )
+        )
+        .one_or_none()
+    )
+    if not dataset:
+        return None
+
+    template_processor = dataset.get_template_processor()
+    # pylint: disable=protected-access
+    predicate = " AND ".join(
+        str(filter_)
+        for filter_ in dataset.get_sqla_row_level_filters(template_processor)
+    )
+    if not predicate:
+        return None
+
+    rls = sqlparse.parse(predicate)[0]
+    add_table_name(rls, str(dataset))
 
-    return True
+    return rls
 
 
-def insert_rls(token_list: TokenList, table: str, rls: TokenList) -> TokenList:
+def insert_rls(
+    token_list: TokenList,
+    database_id: int,
+    default_schema: Optional[str],
+) -> TokenList:
     """
-    Update a statement inplace applying an RLS associated with a given table.
+    Update a statement inplace applying any associated RLS predicates.
     """
-    # make sure the identifier has the table name
-    add_table_name(rls, table)
-
+    rls: Optional[TokenList] = None
     state = InsertRLSState.SCANNING
     for token in token_list.tokens:
 
         # Recurse into child token list
         if isinstance(token, TokenList):
             i = token_list.tokens.index(token)
-            token_list.tokens[i] = insert_rls(token, table, rls)
+            token_list.tokens[i] = insert_rls(token, database_id, default_schema)
 
         # Found a source keyword (FROM/JOIN)
         if imt(token, m=[(Keyword, "FROM"), (Keyword, "JOIN")]):
@@ -587,12 +613,14 @@ def insert_rls(token_list: TokenList, table: str, rls: TokenList) -> TokenList:
         elif state == InsertRLSState.SEEN_SOURCE and (
             isinstance(token, Identifier) or token.ttype == Keyword
         ):
-            if matches_table_name(token, table):
+            rls = get_rls_for_table(token, database_id, default_schema)
+            if rls:
                 state = InsertRLSState.FOUND_TABLE
 
         # Found WHERE clause, insert RLS. Note that we insert it even it already exists,
         # to be on the safe side: it could be present in a clause like `1=1 OR RLS`.
         elif state == InsertRLSState.FOUND_TABLE and isinstance(token, Where):
+            rls = cast(TokenList, rls)
             token.tokens[1:1] = [Token(Whitespace, " "), Token(Punctuation, "(")]
             token.tokens.extend(
                 [
diff --git a/tests/unit_tests/sql_parse_tests.py b/tests/unit_tests/sql_parse_tests.py
index 75f099e52b..4a1ff89d74 100644
--- a/tests/unit_tests/sql_parse_tests.py
+++ b/tests/unit_tests/sql_parse_tests.py
@@ -14,21 +14,24 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-
-# pylint: disable=invalid-name, too-many-lines
+# pylint: disable=invalid-name, redefined-outer-name, unused-argument, protected-access, too-many-lines
 
 import unittest
-from typing import Set
+from typing import Optional, Set
 
 import pytest
 import sqlparse
+from pytest_mock import MockerFixture
+from sqlalchemy import text
+from sqlparse.sql import Identifier, Token, TokenList
+from sqlparse.tokens import Name
 
 from superset.exceptions import QueryClauseValidationException
 from superset.sql_parse import (
     add_table_name,
+    get_rls_for_table,
     has_table_query,
     insert_rls,
-    matches_table_name,
     ParsedQuery,
     sanitize_clause,
     strip_comments_from_sql,
@@ -1391,13 +1394,37 @@ def test_has_table_query(sql: str, expected: bool) -> None:
         ),
     ],
 )
-def test_insert_rls(sql: str, table: str, rls: str, expected: str) -> None:
+def test_insert_rls(
+    mocker: MockerFixture, sql: str, table: str, rls: str, expected: str
+) -> None:
     """
     Insert into a statement a given RLS condition associated with a table.
     """
-    statement = sqlparse.parse(sql)[0]
     condition = sqlparse.parse(rls)[0]
-    assert str(insert_rls(statement, table, condition)).strip() == expected.strip()
+    add_table_name(condition, table)
+
+    # pylint: disable=unused-argument
+    def get_rls_for_table(
+        candidate: Token, database_id: int, default_schema: str
+    ) -> Optional[TokenList]:
+        """
+        Return the RLS ``condition`` if ``candidate`` matches ``table``.
+        """
+        # compare ignoring schema
+        for left, right in zip(str(candidate).split(".")[::-1], table.split(".")[::-1]):
+            if left != right:
+                return None
+        return condition
+
+    mocker.patch("superset.sql_parse.get_rls_for_table", new=get_rls_for_table)
+
+    statement = sqlparse.parse(sql)[0]
+    assert (
+        str(
+            insert_rls(token_list=statement, database_id=1, default_schema="my_schema")
+        ).strip()
+        == expected.strip()
+    )
 
 
 @pytest.mark.parametrize(
@@ -1415,16 +1442,29 @@ def test_add_table_name(rls: str, table: str, expected: str) -> None:
     assert str(condition) == expected
 
 
-@pytest.mark.parametrize(
-    "candidate,table,expected",
-    [
-        ("table", "table", True),
-        ("schema.table", "table", True),
-        ("table", "schema.table", True),
-        ('schema."my table"', '"my table"', True),
-        ('schema."my.table"', '"my.table"', True),
-    ],
-)
-def test_matches_table_name(candidate: str, table: str, expected: bool) -> None:
-    token = sqlparse.parse(candidate)[0].tokens[0]
-    assert matches_table_name(token, table) == expected
+def test_get_rls_for_table(mocker: MockerFixture, app_context: None) -> None:
+    """
+    Tests for ``get_rls_for_table``.
+    """
+    candidate = Identifier([Token(Name, "some_table")])
+    db = mocker.patch("superset.db")
+    dataset = db.session.query().filter().one_or_none()
+    dataset.__str__.return_value = "some_table"
+
+    dataset.get_sqla_row_level_filters.return_value = [text("organization_id = 1")]
+    assert (
+        str(get_rls_for_table(candidate, 1, "public"))
+        == "some_table.organization_id = 1"
+    )
+
+    dataset.get_sqla_row_level_filters.return_value = [
+        text("organization_id = 1"),
+        text("foo = 'bar'"),
+    ]
+    assert (
+        str(get_rls_for_table(candidate, 1, "public"))
+        == "some_table.organization_id = 1 AND some_table.foo = 'bar'"
+    )
+
+    dataset.get_sqla_row_level_filters.return_value = []
+    assert get_rls_for_table(candidate, 1, "public") is None


[superset] 08/23: fix: can't save dataset modal (#19452)

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

villebro pushed a commit to tag 1.5.0rc1
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 3c09690ed2fda6110d811710754c7ec23f0215a8
Author: Yongjie Zhao <yo...@gmail.com>
AuthorDate: Thu Mar 31 16:29:52 2022 +0800

    fix: can't save dataset modal (#19452)
    
    (cherry picked from commit 6b136c2bc9a6c9756e5319b045e3c42da06243cb)
---
 superset/views/datasource/views.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/superset/views/datasource/views.py b/superset/views/datasource/views.py
index 2504f458ea..791f7c500e 100644
--- a/superset/views/datasource/views.py
+++ b/superset/views/datasource/views.py
@@ -81,7 +81,7 @@ class Datasource(BaseSupersetView):
 
         if "owners" in datasource_dict and orm_datasource.owner_class is not None:
             # Check ownership
-            if app.config["OLD_API_CHECK_DATASET_OWNERSHIP"]:
+            if app.config.get("OLD_API_CHECK_DATASET_OWNERSHIP"):
                 # mimic the behavior of the new dataset command that
                 # checks ownership and ensures that non-admins aren't locked out
                 # of the object


[superset] 12/23: refactor: Removes the CSS files from the Calendar Heatmap plugin (#19436)

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

villebro pushed a commit to tag 1.5.0rc1
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 70d800dc274f75803ba719882489a09a80769e88
Author: Michael S. Molina <70...@users.noreply.github.com>
AuthorDate: Thu Mar 31 13:31:20 2022 -0300

    refactor: Removes the CSS files from the Calendar Heatmap plugin (#19436)
    
    (cherry picked from commit 13467beb537f08714c49c6336bc812bb7c1e5ba3)
---
 .../legacy-plugin-chart-calendar/package.json      |  34 ++--
 .../legacy-plugin-chart-calendar/src/Calendar.js   |   1 -
 .../src/ReactCalendar.jsx                          | 207 +++++++++++++++++++--
 .../src/vendor/cal-heatmap.css                     | 129 -------------
 .../src/vendor/cal-heatmap.js                      |   1 -
 .../src/vendor/d3tip.css                           |  74 --------
 6 files changed, 212 insertions(+), 234 deletions(-)

diff --git a/superset-frontend/plugins/legacy-plugin-chart-calendar/package.json b/superset-frontend/plugins/legacy-plugin-chart-calendar/package.json
index 4736432709..cb11164183 100644
--- a/superset-frontend/plugins/legacy-plugin-chart-calendar/package.json
+++ b/superset-frontend/plugins/legacy-plugin-chart-calendar/package.json
@@ -2,31 +2,25 @@
   "name": "@superset-ui/legacy-plugin-chart-calendar",
   "version": "0.18.25",
   "description": "Superset Legacy Chart - Calendar Heatmap",
-  "sideEffects": [
-    "*.css"
-  ],
-  "main": "lib/index.js",
-  "module": "esm/index.js",
-  "files": [
-    "esm",
-    "lib"
-  ],
-  "repository": {
-    "type": "git",
-    "url": "git+https://github.com/apache-superset/superset-ui.git"
-  },
   "keywords": [
     "superset"
   ],
-  "author": "Superset",
-  "license": "Apache-2.0",
+  "homepage": "https://github.com/apache-superset/superset-ui#readme",
   "bugs": {
     "url": "https://github.com/apache-superset/superset-ui/issues"
   },
-  "homepage": "https://github.com/apache-superset/superset-ui#readme",
-  "publishConfig": {
-    "access": "public"
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/apache-superset/superset-ui.git"
   },
+  "license": "Apache-2.0",
+  "author": "Superset",
+  "main": "lib/index.js",
+  "module": "esm/index.js",
+  "files": [
+    "esm",
+    "lib"
+  ],
   "dependencies": {
     "d3-array": "^2.0.3",
     "d3-selection": "^1.4.0",
@@ -34,8 +28,12 @@
     "prop-types": "^15.6.2"
   },
   "peerDependencies": {
+    "@emotion/react": "^11.4.1",
     "@superset-ui/chart-controls": "*",
     "@superset-ui/core": "*",
     "react": "^16.13.1"
+  },
+  "publishConfig": {
+    "access": "public"
   }
 }
diff --git a/superset-frontend/plugins/legacy-plugin-chart-calendar/src/Calendar.js b/superset-frontend/plugins/legacy-plugin-chart-calendar/src/Calendar.js
index a1adc28189..74a9aecb49 100644
--- a/superset-frontend/plugins/legacy-plugin-chart-calendar/src/Calendar.js
+++ b/superset-frontend/plugins/legacy-plugin-chart-calendar/src/Calendar.js
@@ -21,7 +21,6 @@ import { extent as d3Extent, range as d3Range } from 'd3-array';
 import { select as d3Select } from 'd3-selection';
 import { getSequentialSchemeRegistry } from '@superset-ui/core';
 import CalHeatMap from './vendor/cal-heatmap';
-import './vendor/cal-heatmap.css';
 
 const propTypes = {
   data: PropTypes.shape({
diff --git a/superset-frontend/plugins/legacy-plugin-chart-calendar/src/ReactCalendar.jsx b/superset-frontend/plugins/legacy-plugin-chart-calendar/src/ReactCalendar.jsx
index cf2097ed95..e7d39d6310 100644
--- a/superset-frontend/plugins/legacy-plugin-chart-calendar/src/ReactCalendar.jsx
+++ b/superset-frontend/plugins/legacy-plugin-chart-calendar/src/ReactCalendar.jsx
@@ -18,13 +18,69 @@
  */
 import React from 'react';
 import PropTypes from 'prop-types';
-import { reactify, styled } from '@superset-ui/core';
+import { reactify, styled, css } from '@superset-ui/core';
+import { Global } from '@emotion/react';
 import Component from './Calendar';
 
 const ReactComponent = reactify(Component);
 
 const Calender = ({ className, ...otherProps }) => (
   <div className={className}>
+    <Global
+      styles={theme => css`
+        .d3-tip {
+          line-height: 1;
+          padding: ${theme.gridUnit * 3}px;
+          background: ${theme.colors.grayscale.dark2};
+          color: ${theme.colors.grayscale.light5};
+          border-radius: 4px;
+          pointer-events: none;
+          z-index: 1000;
+          font-size: ${theme.typography.sizes.s}px;
+        }
+        /* Creates a small triangle extender for the tooltip */
+        .d3-tip:after {
+          box-sizing: border-box;
+          display: inline;
+          font-size: ${theme.typography.sizes.xs};
+          width: 100%;
+          line-height: 1;
+          color: ${theme.colors.grayscale.dark2};
+          position: absolute;
+          pointer-events: none;
+        }
+        /* Northward tooltips */
+        .d3-tip.n:after {
+          content: '\\25BC';
+          margin: -${theme.gridUnit}px 0 0 0;
+          top: 100%;
+          left: 0;
+          text-align: center;
+        }
+        /* Eastward tooltips */
+        .d3-tip.e:after {
+          content: '\\25C0';
+          margin: -${theme.gridUnit}px 0 0 0;
+          top: 50%;
+          left: -${theme.gridUnit * 2}px;
+        }
+        /* Southward tooltips */
+        .d3-tip.s:after {
+          content: '\\25B2';
+          margin: 0;
+          top: -${theme.gridUnit * 2}px;
+          left: 0;
+          text-align: center;
+        }
+        /* Westward tooltips */
+        .d3-tip.w:after {
+          content: '\\25B6';
+          margin: -${theme.gridUnit}px 0 0 0px;
+          top: 50%;
+          left: 100%;
+        }
+      `}
+    />
     <ReactComponent {...otherProps} />
   </div>
 );
@@ -39,14 +95,143 @@ Calender.propTypes = {
 };
 
 export default styled(Calender)`
-  .superset-legacy-chart-calendar {
-    padding: 10px;
-    position: static !important;
-    overflow: auto !important;
-  }
-
-  .superset-legacy-chart-calendar .ch-tooltip {
-    margin-left: 20px;
-    margin-top: 5px;
-  }
+  ${({ theme }) => `
+    .superset-legacy-chart-calendar {
+      padding: ${theme.gridUnit * 3}px;
+      position: static !important;
+      overflow: auto !important;
+    }
+
+    .superset-legacy-chart-calendar .ch-tooltip {
+      margin-left: ${theme.gridUnit * 5}px;
+      margin-top: ${theme.gridUnit}px;
+    }
+
+    .superset-legacy-chart-calendar .d3-tip {
+      line-height: 1;
+      padding: ${theme.gridUnit * 3}px;
+      background: ${theme.colors.grayscale.dark2};
+      color: ${theme.colors.grayscale.light5};
+      border-radius: ${theme.borderRadius}px;
+      pointer-events: none;
+      z-index: 1000;
+    }
+
+    .cal-heatmap-container {
+      display: block;
+    }
+
+    .cal-heatmap-container .graph-label {
+      fill: ${theme.colors.grayscale.base};
+      font-size: ${theme.typography.sizes.xs}px;
+    }
+
+    .cal-heatmap-container .graph,
+    .cal-heatmap-container .graph-legend rect {
+      shape-rendering: crispedges;
+    }
+
+    .cal-heatmap-container .graph-rect {
+      fill: ${theme.colors.grayscale.light2};
+    }
+
+    .cal-heatmap-container .graph-subdomain-group rect:hover {
+      stroke: ${theme.colors.grayscale.dark2};
+      stroke-width: 1px;
+    }
+
+    .cal-heatmap-container .subdomain-text {
+      font-size: ${theme.typography.sizes.xs}px;
+      pointer-events: none;
+    }
+
+    .cal-heatmap-container .hover_cursor:hover {
+      cursor: pointer;
+    }
+
+    .cal-heatmap-container .qi {
+      background-color: ${theme.colors.grayscale.base};
+      fill: ${theme.colors.grayscale.base};
+    }
+
+    .cal-heatmap-container .q1 {
+      background-color: ${theme.colors.alert.light2};
+      fill: ${theme.colors.alert.light2};
+    }
+
+    .cal-heatmap-container .q2 {
+      background-color: ${theme.colors.alert.light1};
+      fill: ${theme.colors.alert.light1};
+    }
+
+    .cal-heatmap-container .q3 {
+      background-color: ${theme.colors.success.light1};
+      fill: ${theme.colors.success.light1};
+    }
+
+    .cal-heatmap-container .q4 {
+      background-color: ${theme.colors.success.base};
+      fill: ${theme.colors.success.base};
+    }
+
+    .cal-heatmap-container .q5 {
+      background-color: ${theme.colors.success.dark1};
+      fill: ${theme.colors.success.dark1};
+    }
+
+    .cal-heatmap-container rect.highlight {
+      stroke: ${theme.colors.grayscale.dark1};
+      stroke-width: 1;
+    }
+
+    .cal-heatmap-container text.highlight {
+      fill: ${theme.colors.grayscale.dark1};
+    }
+
+    .cal-heatmap-container rect.highlight-now {
+      stroke: ${theme.colors.error.base};
+    }
+
+    .cal-heatmap-container text.highlight-now {
+      fill: ${theme.colors.error.base};
+      font-weight: ${theme.typography.weights.bold};
+    }
+
+    .cal-heatmap-container .domain-background {
+      fill: none;
+      shape-rendering: crispedges;
+    }
+
+    .ch-tooltip {
+      padding: ${theme.gridUnit * 2}px;
+      background: ${theme.colors.grayscale.dark1};
+      color: ${theme.colors.grayscale.light1};
+      font-size: ${theme.typography.sizes.s}px;
+      line-height: 1.4;
+      width: 140px;
+      position: absolute;
+      z-index: 99999;
+      text-align: center;
+      border-radius: ${theme.borderRadius}px;
+      box-shadow: 2px 2px 2px ${theme.colors.grayscale.dark2};
+      display: none;
+      box-sizing: border-box;
+    }
+
+    .ch-tooltip::after {
+      position: absolute;
+      width: 0;
+      height: 0;
+      border-color: transparent;
+      border-style: solid;
+      content: '';
+      padding: 0;
+      display: block;
+      bottom: -${theme.gridUnit}px;
+      left: 50%;
+      margin-left: -${theme.gridUnit}px;
+      border-width: ${theme.gridUnit}px ${theme.gridUnit}px 0;
+      border-top-color: ${theme.colors.grayscale.dark1};
+    }
+  `}
 `;
diff --git a/superset-frontend/plugins/legacy-plugin-chart-calendar/src/vendor/cal-heatmap.css b/superset-frontend/plugins/legacy-plugin-chart-calendar/src/vendor/cal-heatmap.css
deleted file mode 100644
index de282154a5..0000000000
--- a/superset-frontend/plugins/legacy-plugin-chart-calendar/src/vendor/cal-heatmap.css
+++ /dev/null
@@ -1,129 +0,0 @@
-/* [LICENSE TBD] */
-/* Cal-HeatMap CSS */
-
-.cal-heatmap-container {
-  display: block;
-}
-
-.cal-heatmap-container .graph-label {
-  fill: #999;
-  font-size: 10px;
-}
-
-.cal-heatmap-container .graph,
-.cal-heatmap-container .graph-legend rect {
-  shape-rendering: crispedges;
-}
-
-.cal-heatmap-container .graph-rect {
-  fill: #ededed;
-}
-
-.cal-heatmap-container .graph-subdomain-group rect:hover {
-  stroke: #000;
-  stroke-width: 1px;
-}
-
-.cal-heatmap-container .subdomain-text {
-  font-size: 8px;
-  pointer-events: none;
-}
-
-.cal-heatmap-container .hover_cursor:hover {
-  cursor: pointer;
-}
-
-.cal-heatmap-container .qi {
-  background-color: #999;
-  fill: #999;
-}
-
-/*
-Remove comment to apply this style to date with value equal to 0
-.q0
-{
-  background-color: #fff;
-  fill: #fff;
-  stroke: #ededed
-}
-*/
-
-.cal-heatmap-container .q1 {
-  background-color: #dae289;
-  fill: #dae289;
-}
-
-.cal-heatmap-container .q2 {
-  background-color: #cedb9c;
-  fill: #9cc069;
-}
-
-.cal-heatmap-container .q3 {
-  background-color: #b5cf6b;
-  fill: #669d45;
-}
-
-.cal-heatmap-container .q4 {
-  background-color: #637939;
-  fill: #637939;
-}
-
-.cal-heatmap-container .q5 {
-  background-color: #3b6427;
-  fill: #3b6427;
-}
-
-.cal-heatmap-container rect.highlight {
-  stroke: #444;
-  stroke-width: 1;
-}
-
-.cal-heatmap-container text.highlight {
-  fill: #444;
-}
-
-.cal-heatmap-container rect.highlight-now {
-  stroke: red;
-}
-
-.cal-heatmap-container text.highlight-now {
-  fill: red;
-  font-weight: 800;
-}
-
-.cal-heatmap-container .domain-background {
-  fill: none;
-  shape-rendering: crispedges;
-}
-
-.ch-tooltip {
-  padding: 10px;
-  background: #222;
-  color: #bbb;
-  font-size: 12px;
-  line-height: 1.4;
-  width: 140px;
-  position: absolute;
-  z-index: 99999;
-  text-align: center;
-  border-radius: 2px;
-  box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.2);
-  display: none;
-  box-sizing: border-box;
-}
-
-.ch-tooltip::after {
-  position: absolute;
-  width: 0;
-  height: 0;
-  border-color: transparent;
-  border-style: solid;
-  content: '';
-  padding: 0;
-  display: block;
-  bottom: -6px;
-  left: 50%;
-  margin-left: -6px;
-  border-width: 6px 6px 0;
-  border-top-color: #222;
-}
diff --git a/superset-frontend/plugins/legacy-plugin-chart-calendar/src/vendor/cal-heatmap.js b/superset-frontend/plugins/legacy-plugin-chart-calendar/src/vendor/cal-heatmap.js
index 572070f614..3320693f5c 100644
--- a/superset-frontend/plugins/legacy-plugin-chart-calendar/src/vendor/cal-heatmap.js
+++ b/superset-frontend/plugins/legacy-plugin-chart-calendar/src/vendor/cal-heatmap.js
@@ -10,7 +10,6 @@
 
 import d3tip from 'd3-tip';
 import { getContrastingColor } from '@superset-ui/core';
-import './d3tip.css';
 
 var d3 = typeof require === 'function' ? require('d3') : window.d3;
 
diff --git a/superset-frontend/plugins/legacy-plugin-chart-calendar/src/vendor/d3tip.css b/superset-frontend/plugins/legacy-plugin-chart-calendar/src/vendor/d3tip.css
deleted file mode 100644
index 980a5be439..0000000000
--- a/superset-frontend/plugins/legacy-plugin-chart-calendar/src/vendor/d3tip.css
+++ /dev/null
@@ -1,74 +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.
- */
-/* from d3-tip */
-.d3-tip {
-  line-height: 1;
-  padding: 12px;
-  background: rgba(0, 0, 0, 0.8);
-  color: #fff;
-  border-radius: 2px;
-  pointer-events: none;
-  z-index: 1000;
-}
-
-/* Creates a small triangle extender for the tooltip */
-.d3-tip:after {
-  box-sizing: border-box;
-  display: inline;
-  font-size: 10px;
-  width: 100%;
-  line-height: 1;
-  color: rgba(0, 0, 0, 0.8);
-  position: absolute;
-  pointer-events: none;
-}
-
-/* Northward tooltips */
-.d3-tip.n:after {
-  content: '\25BC';
-  margin: -1px 0 0 0;
-  top: 100%;
-  left: 0;
-  text-align: center;
-}
-
-/* Eastward tooltips */
-.d3-tip.e:after {
-  content: '\25C0';
-  margin: -4px 0 0 0;
-  top: 50%;
-  left: -8px;
-}
-
-/* Southward tooltips */
-.d3-tip.s:after {
-  content: '\25B2';
-  margin: 0 0 1px 0;
-  top: -8px;
-  left: 0;
-  text-align: center;
-}
-
-/* Westward tooltips */
-.d3-tip.w:after {
-  content: '\25B6';
-  margin: -4px 0 0 -1px;
-  top: 50%;
-  left: 100%;
-}


[superset] 09/23: fix(dashboard): make to show the correct owned objects (#19372)

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

villebro pushed a commit to tag 1.5.0rc1
in repository https://gitbox.apache.org/repos/asf/superset.git

commit e93d64d58e36763757974a1f7a66ba90803e9f7a
Author: smileydev <47...@users.noreply.github.com>
AuthorDate: Thu Mar 31 10:10:21 2022 -0400

    fix(dashboard): make to show the correct owned objects (#19372)
    
    * fix(dashboard): make to show the correct owned objects
    
    * fix(dashboard): make to filter is reusable
    
    * fix(homepage): make sure the type Array<Filters>
    
    * fix(homepage): make to display dashboard correctly by owners
    
    (cherry picked from commit 6d89ffbcb7533dc04093a66852aab26db0b5bc11)
---
 superset-frontend/src/views/CRUD/utils.tsx           | 4 ++--
 superset-frontend/src/views/CRUD/welcome/Welcome.tsx | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/superset-frontend/src/views/CRUD/utils.tsx b/superset-frontend/src/views/CRUD/utils.tsx
index 6606fd459f..3449d764ab 100644
--- a/superset-frontend/src/views/CRUD/utils.tsx
+++ b/superset-frontend/src/views/CRUD/utils.tsx
@@ -166,8 +166,8 @@ export const getUserOwnedObjects = (
   resource: string,
   filters: Array<Filters> = [
     {
-      col: 'created_by',
-      opr: 'rel_o_m',
+      col: 'owners',
+      opr: 'rel_m_m',
       value: `${userId}`,
     },
   ],
diff --git a/superset-frontend/src/views/CRUD/welcome/Welcome.tsx b/superset-frontend/src/views/CRUD/welcome/Welcome.tsx
index a1f7707a23..056e02e8f6 100644
--- a/superset-frontend/src/views/CRUD/welcome/Welcome.tsx
+++ b/superset-frontend/src/views/CRUD/welcome/Welcome.tsx
@@ -204,8 +204,8 @@ function Welcome({ user, addDangerToast }: WelcomeProps) {
     // Sets other activity data in parallel with recents api call
     const ownSavedQueryFilters = [
       {
-        col: 'owners',
-        opr: 'rel_m_m',
+        col: 'created_by',
+        opr: 'rel_o_m',
         value: `${id}`,
       },
     ];


[superset] 02/23: chore: attempt to force docs deploy (#19424)

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

villebro pushed a commit to tag 1.5.0rc1
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 625555ac7ee07f4f9bb50ccd8aad8d751144203c
Author: Srini Kadamati <sk...@gmail.com>
AuthorDate: Wed Mar 30 03:13:09 2022 -0400

    chore: attempt to force docs deploy (#19424)
    
    (cherry picked from commit a8e7624eb5635ed9b84f1454d35029e1c18ebdee)
---
 docs/docs/contributing/contributing-page.mdx | 4 ++--
 setup.py                                     | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/docs/docs/contributing/contributing-page.mdx b/docs/docs/contributing/contributing-page.mdx
index d6cdd2cecf..f4f3cd6400 100644
--- a/docs/docs/contributing/contributing-page.mdx
+++ b/docs/docs/contributing/contributing-page.mdx
@@ -8,8 +8,8 @@ version: 1
 ## Contributing to Superset
 
 Superset is an [Apache Software foundation](https://www.apache.org/theapacheway/index.html) project.
-The core contributors (or committers) to Superset communicate primarily in the following channels (all of
-which you can join):
+The core contributors (or committers) to Superset communicate primarily in the following channels (
+which can be joined by anyone):
 
 - [Mailing list](https://lists.apache.org/list.html?dev@superset.apache.org)
 - [Apache Superset Slack community](https://join.slack.com/t/apache-superset/shared_invite/zt-16jvzmoi8-sI7jKWp~xc2zYRe~NqiY9Q)
diff --git a/setup.py b/setup.py
index 6943479801..02b3924c72 100644
--- a/setup.py
+++ b/setup.py
@@ -23,8 +23,8 @@ import sys
 from setuptools import find_packages, setup
 
 BASE_DIR = os.path.abspath(os.path.dirname(__file__))
-
 PACKAGE_JSON = os.path.join(BASE_DIR, "superset-frontend", "package.json")
+
 with open(PACKAGE_JSON, "r") as package_file:
     version_string = json.load(package_file)["version"]
 


[superset] 14/23: fix(drill): specify an SA URL parm of `impersonation_target` for drill+sadrill (#19252)

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

villebro pushed a commit to tag 1.5.0rc1
in repository https://gitbox.apache.org/repos/asf/superset.git

commit e4cbbdc65399de33c44002fc2ae7996942123762
Author: James Turton <91...@users.noreply.github.com>
AuthorDate: Thu Mar 31 18:42:27 2022 +0200

    fix(drill): specify an SA URL parm of `impersonation_target` for drill+sadrill (#19252)
    
    * Update drill+sadrill to specify an SA URL parm of "impersonation_target".
    
    Sqlalchemy-drill is being updated to support impersonation with the
    drill+sadrill driver, where previously it did not.  The way that callers
    should specify impersonation matches that for the drill+jdbc driver in that
    a SA URL parameter of impersonation_target should be set to the username
    of the user to be impersonated, while the stadard SA username and password
    should be those of the proxy user.
    
    * Remove lint.
    
    * Address review comments.
    
    * Use idiomatic pytest to test for a raised exception.
    
    * Fix import statement order in drill.py.
    
    (cherry picked from commit 85e330e94bc490d5a35fa0536f44e7189e299d20)
---
 superset/db_engine_specs/drill.py              |  7 +++++--
 tests/unit_tests/db_engine_specs/test_drill.py | 24 ++++++++++++++++++++++--
 2 files changed, 27 insertions(+), 4 deletions(-)

diff --git a/superset/db_engine_specs/drill.py b/superset/db_engine_specs/drill.py
index 3c06017fa2..de8c8397f6 100644
--- a/superset/db_engine_specs/drill.py
+++ b/superset/db_engine_specs/drill.py
@@ -21,6 +21,7 @@ from urllib import parse
 from sqlalchemy.engine.url import URL
 
 from superset.db_engine_specs.base import BaseEngineSpec
+from superset.db_engine_specs.exceptions import SupersetDBAPIProgrammingError
 from superset.utils import core as utils
 
 
@@ -84,7 +85,9 @@ class DrillEngineSpec(BaseEngineSpec):
         if impersonate_user and username is not None:
             if url.drivername == "drill+odbc":
                 url.query["DelegationUID"] = username
-            elif url.drivername == "drill+jdbc":
+            elif url.drivername in ["drill+sadrill", "drill+jdbc"]:
                 url.query["impersonation_target"] = username
             else:
-                url.username = username
+                raise SupersetDBAPIProgrammingError(
+                    f"impersonation is not supported for {url.drivername}"
+                )
diff --git a/tests/unit_tests/db_engine_specs/test_drill.py b/tests/unit_tests/db_engine_specs/test_drill.py
index 08f7972d80..ad7254870f 100644
--- a/tests/unit_tests/db_engine_specs/test_drill.py
+++ b/tests/unit_tests/db_engine_specs/test_drill.py
@@ -17,6 +17,7 @@
 # pylint: disable=unused-argument, import-outside-toplevel, protected-access
 
 from flask.ctx import AppContext
+from pytest import raises
 
 
 def test_odbc_impersonation(app_context: AppContext) -> None:
@@ -55,7 +56,7 @@ def test_sadrill_impersonation(app_context: AppContext) -> None:
     """
     Test ``modify_url_for_impersonation`` method when driver == sadrill.
 
-    The method changes the username of URL Object.
+    The method adds the parameter ``impersonation_target`` to the query string.
     """
     from sqlalchemy.engine.url import URL
 
@@ -64,4 +65,23 @@ def test_sadrill_impersonation(app_context: AppContext) -> None:
     url = URL("drill+sadrill")
     username = "DoAsUser"
     DrillEngineSpec.modify_url_for_impersonation(url, True, username)
-    assert url.username == username
+    assert url.query["impersonation_target"] == username
+
+
+def test_invalid_impersonation(app_context: AppContext) -> None:
+    """
+    Test ``modify_url_for_impersonation`` method when driver == foobar.
+
+    The method raises an exception because impersonation is not supported
+    for drill+foobar.
+    """
+    from sqlalchemy.engine.url import URL
+
+    from superset.db_engine_specs.drill import DrillEngineSpec
+    from superset.db_engine_specs.exceptions import SupersetDBAPIProgrammingError
+
+    url = URL("drill+foobar")
+    username = "DoAsUser"
+
+    with raises(SupersetDBAPIProgrammingError):
+        DrillEngineSpec.modify_url_for_impersonation(url, True, username)