You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by mi...@apache.org on 2024/01/09 12:31:37 UTC

(superset) branch 3.1 updated (28c518e578 -> c8c8136ac3)

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

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


    from 28c518e578 fix: Invalid references in the basic template (#26302)
     new f6803f628c feat(deckgl-map): use an arbitraty Mabpox style URL (#26027) (#26031)
     new ac7d281e8a fix: Removes non-existent columns in the 2018 FCC Survey dataset (#26380)
     new d356d83bf6 fix(SelectControl): select zero value (#26353)
     new 79e7f2732d fix(embed):  an error occurred while rendering the visualization: error: Item with key ... is not registered. (#26398)
     new bd195893af fix: Reactivates native filters E2E tests (#26362)
     new 802dc24a76 fix(dashboard): Chart menu disable is fixed on chart-fullscreen in issue #25992 (#26410)
     new 0e85a43acf fix(dashboard): narrow empty drop area (#26313)
     new faad565fab fix(sqllab): Bump duckdb-engine version to 0.9.5 (#26405)
     new ed29c5b855 fix(chart): Resolve incorrect column customization when switching metrics in table chart (#26393)
     new 1daab2731d fix: In chart gallery thumbnail is rendered in case of no example in #16707 (#26415)
     new 136c0e3b4a fix(plugin-chart-echarts): support forced categorical x-axis (#26404)
     new 8db06b4cb2 fix(translations): Clear all (#26424)
     new c8c8136ac3 fix(post-processing): handle missing values in cumulative operator (#26429)

The 13 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 setup.py                                           |    2 +-
 .../cypress/e2e/dashboard/nativeFilters.test.ts    |    2 +-
 .../superset-ui-chart-controls/src/constants.ts    |   11 +-
 .../superset-ui-chart-controls/src/fixtures.ts     |   12 +-
 .../src/sections/echartsTimeSeriesQuery.tsx        |    2 +
 .../src/shared-controls/customControls.tsx         |   54 +-
 .../superset-ui-chart-controls/src/types.ts        |    6 +-
 .../src/utils/checkColumnType.ts}                  |   53 +-
 .../src/utils/columnChoices.ts                     |    8 +-
 .../superset-ui-chart-controls/src/utils/index.ts  |    1 +
 .../test/utils/checkColumnType.test.ts}            |   49 +-
 .../test/utils/columnChoices.test.tsx              |   16 +-
 .../test/utils/getTemporalColumns.test.ts          |    9 +-
 .../superset-ui-core/src/query/types/Query.ts      |   11 +
 .../superset-ui-core/src/validator/index.ts        |    1 +
 ...lidateInteger.ts => validateMapboxStylesUrl.ts} |   14 +-
 .../validateMapboxStylesUrl.test.ts}               |   43 +-
 .../src/controlPanel.ts                            |   14 +-
 .../src/Multi/controlPanel.ts                      |    3 +-
 .../src/layers/Arc/controlPanel.ts                 |    5 +-
 .../src/layers/Contour/controlPanel.ts             |    4 +-
 .../src/layers/Grid/controlPanel.ts                |    3 +-
 .../src/layers/Heatmap/controlPanel.ts             |    3 +-
 .../src/layers/Hex/controlPanel.ts                 |    4 +-
 .../src/layers/Path/controlPanel.ts                |    3 +-
 .../src/layers/Scatter/controlPanel.ts             |    5 +-
 .../src/layers/Screengrid/controlPanel.ts          |    5 +-
 .../src/utilities/Shared_DeckGL.jsx                |    8 +-
 .../src/MixedTimeseries/transformProps.ts          |    3 +-
 .../src/MixedTimeseries/types.ts                   |    1 +
 .../src/Timeseries/constants.ts                    |    1 +
 .../src/Timeseries/transformProps.ts               |    3 +-
 .../plugin-chart-echarts/src/Timeseries/types.ts   |    1 +
 .../plugins/plugin-chart-echarts/src/controls.tsx  |   11 +
 .../plugin-chart-echarts/src/utils/series.ts       |   10 +-
 .../plugin-chart-echarts/test/utils/series.test.ts |   26 +-
 superset-frontend/src/SqlLab/fixtures.ts           |    6 +
 .../src/components/Dropdown/index.tsx              |    8 +-
 .../DashboardBuilder/DashboardBuilder.tsx          |   70 +-
 .../DashboardBuilder/DashboardWrapper.test.tsx     |   75 +
 .../DashboardBuilder/DashboardWrapper.tsx          |  128 +
 .../src/dashboard/components/DashboardGrid.jsx     |   19 +-
 .../components/SliceHeaderControls/index.tsx       |    7 +
 .../src/dashboard/components/dnd/DragDroppable.jsx |   11 +-
 .../dashboard/components/gridComponents/Tab.jsx    |    8 +-
 superset-frontend/src/embedded/index.tsx           |    3 +
 .../src/explore/actions/exploreActions.test.js     |  180 ++
 .../explore/components/controls/SelectControl.jsx  |    2 +-
 .../controls/VizTypeControl/VizTypeGallery.tsx     |   12 +-
 .../src/explore/reducers/exploreReducer.js         |    5 +
 .../ReportModal/HeaderReportDropdown/index.tsx     |    7 +
 .../configs/datasets/examples/FCC_2018_Survey.yaml | 2940 ++++++++++----------
 superset/result_set.py                             |   16 +-
 superset/translations/zh/LC_MESSAGES/messages.po   |    6 +-
 superset/utils/pandas_postprocessing/cum.py        |    1 +
 tests/integration_tests/result_set_tests.py        |   92 +-
 tests/unit_tests/fixtures/dataframes.py            |    5 +
 tests/unit_tests/pandas_postprocessing/test_cum.py |   14 +
 58 files changed, 2290 insertions(+), 1732 deletions(-)
 copy superset-frontend/{plugins/legacy-preset-chart-deckgl/src/utilities/controls.ts => packages/superset-ui-chart-controls/src/utils/checkColumnType.ts} (52%)
 copy superset-frontend/{src/utils/findPermission.test.ts => packages/superset-ui-chart-controls/test/utils/checkColumnType.test.ts} (50%)
 copy superset-frontend/packages/superset-ui-core/src/validator/{legacyValidateInteger.ts => validateMapboxStylesUrl.ts} (73%)
 copy superset-frontend/packages/superset-ui-core/test/{query/getColumnLabel.test.ts => validator/validateMapboxStylesUrl.test.ts} (53%)
 create mode 100644 superset-frontend/src/dashboard/components/DashboardBuilder/DashboardWrapper.test.tsx
 create mode 100644 superset-frontend/src/dashboard/components/DashboardBuilder/DashboardWrapper.tsx


(superset) 05/13: fix: Reactivates native filters E2E tests (#26362)

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

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

commit bd195893af59df9bd97327da28d41b48f71837fe
Author: Michael S. Molina <70...@users.noreply.github.com>
AuthorDate: Wed Jan 3 19:06:55 2024 -0300

    fix: Reactivates native filters E2E tests (#26362)
    
    (cherry picked from commit 6f6c37ec264eaea5775cc4b550cc1a1e14f84204)
---
 .../cypress-base/cypress/e2e/dashboard/nativeFilters.test.ts            | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/superset-frontend/cypress-base/cypress/e2e/dashboard/nativeFilters.test.ts b/superset-frontend/cypress-base/cypress/e2e/dashboard/nativeFilters.test.ts
index 7683d7f878..4e0309a2da 100644
--- a/superset-frontend/cypress-base/cypress/e2e/dashboard/nativeFilters.test.ts
+++ b/superset-frontend/cypress-base/cypress/e2e/dashboard/nativeFilters.test.ts
@@ -414,7 +414,7 @@ describe('Native filters', () => {
       cy.createSampleDashboards([0]);
     });
 
-    it.only('Verify that default value is respected after revisit', () => {
+    it('Verify that default value is respected after revisit', () => {
       prepareDashboardFilters([
         { name: 'country_name', column: 'country_name', datasetId: 2 },
       ]);


(superset) 08/13: fix(sqllab): Bump duckdb-engine version to 0.9.5 (#26405)

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

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

commit faad565fab0e07f71d08226f8aa43b343277a8b8
Author: Guen Prawiroatmodjo <gu...@motherduck.com>
AuthorDate: Thu Jan 4 11:30:45 2024 -0800

    fix(sqllab): Bump duckdb-engine version to 0.9.5 (#26405)
    
    Co-authored-by: John Bodley <45...@users.noreply.github.com>
    (cherry picked from commit bba1b14bf00556ea224454e6201305d13d625e3a)
---
 setup.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/setup.py b/setup.py
index 18fb4dd1e6..563cd32342 100644
--- a/setup.py
+++ b/setup.py
@@ -155,7 +155,7 @@ setup(
         "dremio": ["sqlalchemy-dremio>=1.1.5, <1.3"],
         "drill": ["sqlalchemy-drill==0.1.dev"],
         "druid": ["pydruid>=0.6.5,<0.7"],
-        "duckdb": ["duckdb-engine==0.9.2"],
+        "duckdb": ["duckdb-engine>=0.9.5, <0.10"],
         "dynamodb": ["pydynamodb>=0.4.2"],
         "solr": ["sqlalchemy-solr >= 0.2.0"],
         "elasticsearch": ["elasticsearch-dbapi>=0.2.9, <0.3.0"],


(superset) 03/13: fix(SelectControl): select zero value (#26353)

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

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

commit d356d83bf6925f707cac4bb241a5d6e11c2afaa5
Author: Irina Shebarshina <76...@users.noreply.github.com>
AuthorDate: Wed Jan 3 21:11:26 2024 +0300

    fix(SelectControl): select zero value (#26353)
    
    (cherry picked from commit a0f0f698f4f7d5b71891b929f24398cce582c9f9)
---
 superset-frontend/src/explore/components/controls/SelectControl.jsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/superset-frontend/src/explore/components/controls/SelectControl.jsx b/superset-frontend/src/explore/components/controls/SelectControl.jsx
index 166382a15c..7881fc6858 100644
--- a/superset-frontend/src/explore/components/controls/SelectControl.jsx
+++ b/superset-frontend/src/explore/components/controls/SelectControl.jsx
@@ -215,7 +215,7 @@ export default class SelectControl extends React.PureComponent {
 
     const getValue = () => {
       const currentValue =
-        value ||
+        value ??
         (this.props.default !== undefined ? this.props.default : undefined);
 
       // safety check - the value is intended to be undefined but null was used


(superset) 13/13: fix(post-processing): handle missing values in cumulative operator (#26429)

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

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

commit c8c8136ac36569dd2e3368a1dd8f62af504a35cf
Author: Ville Brofeldt <33...@users.noreply.github.com>
AuthorDate: Tue Jan 9 03:56:19 2024 -0800

    fix(post-processing): handle missing values in cumulative operator (#26429)
    
    (cherry picked from commit ff025b78f35f917f9d4a959154047727d3fc46f7)
---
 superset/utils/pandas_postprocessing/cum.py        |  1 +
 tests/unit_tests/fixtures/dataframes.py            |  5 +++++
 tests/unit_tests/pandas_postprocessing/test_cum.py | 14 ++++++++++++++
 3 files changed, 20 insertions(+)

diff --git a/superset/utils/pandas_postprocessing/cum.py b/superset/utils/pandas_postprocessing/cum.py
index 128fa970f5..d3eb969f79 100644
--- a/superset/utils/pandas_postprocessing/cum.py
+++ b/superset/utils/pandas_postprocessing/cum.py
@@ -46,6 +46,7 @@ def cum(
     """
     columns = columns or {}
     df_cum = df.loc[:, columns.keys()]
+    df_cum = df_cum.fillna(0)
     operation = "cum" + operator
     if operation not in ALLOWLIST_CUMULATIVE_FUNCTIONS or not hasattr(
         df_cum, operation
diff --git a/tests/unit_tests/fixtures/dataframes.py b/tests/unit_tests/fixtures/dataframes.py
index 31a275b735..e1499792cb 100644
--- a/tests/unit_tests/fixtures/dataframes.py
+++ b/tests/unit_tests/fixtures/dataframes.py
@@ -130,6 +130,11 @@ timeseries_df = DataFrame(
     data={"label": ["x", "y", "z", "q"], "y": [1.0, 2.0, 3.0, 4.0]},
 )
 
+timeseries_with_gap_df = DataFrame(
+    index=to_datetime(["2019-01-01", "2019-01-02", "2019-01-05", "2019-01-07"]),
+    data={"label": ["x", "y", "z", "q"], "y": [1.0, 2.0, None, 4.0]},
+)
+
 timeseries_df2 = DataFrame(
     index=to_datetime(["2019-01-01", "2019-01-02", "2019-01-05", "2019-01-07"]),
     data={
diff --git a/tests/unit_tests/pandas_postprocessing/test_cum.py b/tests/unit_tests/pandas_postprocessing/test_cum.py
index 130e060252..25d7fd045f 100644
--- a/tests/unit_tests/pandas_postprocessing/test_cum.py
+++ b/tests/unit_tests/pandas_postprocessing/test_cum.py
@@ -24,6 +24,7 @@ from tests.unit_tests.fixtures.dataframes import (
     multiple_metrics_df,
     single_metric_df,
     timeseries_df,
+    timeseries_with_gap_df,
 )
 from tests.unit_tests.pandas_postprocessing.utils import series_to_list
 
@@ -77,6 +78,19 @@ def test_cum():
         )
 
 
+def test_cum_with_gap():
+    # create new column (cumsum)
+    post_df = pp.cum(
+        df=timeseries_with_gap_df,
+        columns={"y": "y2"},
+        operator="sum",
+    )
+    assert post_df.columns.tolist() == ["label", "y", "y2"]
+    assert series_to_list(post_df["label"]) == ["x", "y", "z", "q"]
+    assert series_to_list(post_df["y"]) == [1.0, 2.0, None, 4.0]
+    assert series_to_list(post_df["y2"]) == [1.0, 3.0, 3.0, 7.0]
+
+
 def test_cum_after_pivot_with_single_metric():
     pivot_df = pp.pivot(
         df=single_metric_df,


(superset) 04/13: fix(embed): an error occurred while rendering the visualization: error: Item with key ... is not registered. (#26398)

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

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

commit 79e7f2732d62bb6d563eba3aadfb97a2c54baee6
Author: Boris Strelchik <b....@gmail.com>
AuthorDate: Wed Jan 3 18:15:28 2024 +0000

    fix(embed):  an error occurred while rendering the visualization: error: Item with key ... is not registered. (#26398)
    
    (cherry picked from commit 9ffcd6410eb39c2cc911707e1b264df4a1b74a8c)
---
 superset-frontend/src/embedded/index.tsx | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/superset-frontend/src/embedded/index.tsx b/superset-frontend/src/embedded/index.tsx
index 50c026fba8..27c80e9279 100644
--- a/superset-frontend/src/embedded/index.tsx
+++ b/superset-frontend/src/embedded/index.tsx
@@ -23,6 +23,7 @@ import { makeApi, t, logging } from '@superset-ui/core';
 import Switchboard from '@superset-ui/switchboard';
 import getBootstrapData from 'src/utils/getBootstrapData';
 import setupClient from 'src/setup/setupClient';
+import setupPlugins from 'src/setup/setupPlugins';
 import { RootContextProviders } from 'src/views/RootContextProviders';
 import { store, USER_LOADED } from 'src/views/store';
 import ErrorBoundary from 'src/components/ErrorBoundary';
@@ -32,6 +33,8 @@ import ToastContainer from 'src/components/MessageToasts/ToastContainer';
 import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes';
 import { embeddedApi } from './api';
 
+setupPlugins();
+
 const debugMode = process.env.WEBPACK_MODE === 'development';
 const bootstrapData = getBootstrapData();
 


(superset) 11/13: fix(plugin-chart-echarts): support forced categorical x-axis (#26404)

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

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

commit 136c0e3b4aea4158bae4dd41953241cf82135935
Author: Ville Brofeldt <33...@users.noreply.github.com>
AuthorDate: Mon Jan 8 12:04:22 2024 -0800

    fix(plugin-chart-echarts): support forced categorical x-axis (#26404)
    
    (cherry picked from commit 219c4a14b359b77dbfcda74e66b7d06c3792b861)
---
 .../superset-ui-chart-controls/src/constants.ts    | 11 +--
 .../superset-ui-chart-controls/src/fixtures.ts     | 12 +--
 .../src/sections/echartsTimeSeriesQuery.tsx        |  2 +
 .../src/shared-controls/customControls.tsx         | 54 ++++++++++---
 .../superset-ui-chart-controls/src/types.ts        |  6 +-
 .../src/utils/checkColumnType.ts                   | 49 ++++++++++++
 .../src/utils/columnChoices.ts                     |  8 +-
 .../superset-ui-chart-controls/src/utils/index.ts  |  1 +
 .../test/utils/checkColumnType.test.ts             | 48 +++++++++++
 .../test/utils/columnChoices.test.tsx              | 16 +++-
 .../test/utils/getTemporalColumns.test.ts          |  9 ++-
 .../superset-ui-core/src/query/types/Query.ts      | 11 +++
 .../src/MixedTimeseries/transformProps.ts          |  3 +-
 .../src/MixedTimeseries/types.ts                   |  1 +
 .../src/Timeseries/constants.ts                    |  1 +
 .../src/Timeseries/transformProps.ts               |  3 +-
 .../plugin-chart-echarts/src/Timeseries/types.ts   |  1 +
 .../plugins/plugin-chart-echarts/src/controls.tsx  | 11 +++
 .../plugin-chart-echarts/src/utils/series.ts       | 10 ++-
 .../plugin-chart-echarts/test/utils/series.test.ts | 26 ++++--
 superset-frontend/src/SqlLab/fixtures.ts           |  6 ++
 superset/result_set.py                             | 16 +++-
 tests/integration_tests/result_set_tests.py        | 92 +++++++++++++++++++---
 23 files changed, 346 insertions(+), 51 deletions(-)

diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/constants.ts b/superset-frontend/packages/superset-ui-chart-controls/src/constants.ts
index ae6dd8f894..1aefc25464 100644
--- a/superset-frontend/packages/superset-ui-chart-controls/src/constants.ts
+++ b/superset-frontend/packages/superset-ui-chart-controls/src/constants.ts
@@ -17,12 +17,11 @@
  * under the License.
  */
 import {
-  t,
-  QueryMode,
   DTTM_ALIAS,
   GenericDataType,
   QueryColumn,
-  DatasourceType,
+  QueryMode,
+  t,
 } from '@superset-ui/core';
 import { ColumnMeta, SortSeriesData, SortSeriesType } from './types';
 
@@ -43,6 +42,7 @@ export const COLUMN_NAME_ALIASES: Record<string, string> = {
 export const DATASET_TIME_COLUMN_OPTION: ColumnMeta = {
   verbose_name: COLUMN_NAME_ALIASES[DTTM_ALIAS],
   column_name: DTTM_ALIAS,
+  type: 'TIMESTAMP',
   type_generic: GenericDataType.TEMPORAL,
   description: t(
     'A reference to the [Time] configuration, taking granularity into account',
@@ -51,8 +51,9 @@ export const DATASET_TIME_COLUMN_OPTION: ColumnMeta = {
 
 export const QUERY_TIME_COLUMN_OPTION: QueryColumn = {
   column_name: DTTM_ALIAS,
-  type: DatasourceType.Query,
-  is_dttm: false,
+  is_dttm: true,
+  type: 'TIMESTAMP',
+  type_generic: GenericDataType.TEMPORAL,
 };
 
 export const QueryModeLabel = {
diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/fixtures.ts b/superset-frontend/packages/superset-ui-chart-controls/src/fixtures.ts
index cc0b678f6d..b12d5be048 100644
--- a/superset-frontend/packages/superset-ui-chart-controls/src/fixtures.ts
+++ b/superset-frontend/packages/superset-ui-chart-controls/src/fixtures.ts
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { DatasourceType } from '@superset-ui/core';
+import { DatasourceType, GenericDataType } from '@superset-ui/core';
 import { Dataset } from './types';
 
 export const TestDataset: Dataset = {
@@ -37,7 +37,7 @@ export const TestDataset: Dataset = {
       is_dttm: false,
       python_date_format: null,
       type: 'BIGINT',
-      type_generic: 0,
+      type_generic: GenericDataType.NUMERIC,
       verbose_name: null,
       warning_markdown: null,
     },
@@ -55,7 +55,7 @@ export const TestDataset: Dataset = {
       is_dttm: false,
       python_date_format: null,
       type: 'VARCHAR(16)',
-      type_generic: 1,
+      type_generic: GenericDataType.STRING,
       verbose_name: '',
       warning_markdown: null,
     },
@@ -73,7 +73,7 @@ export const TestDataset: Dataset = {
       is_dttm: false,
       python_date_format: null,
       type: 'VARCHAR(10)',
-      type_generic: 1,
+      type_generic: GenericDataType.STRING,
       verbose_name: null,
       warning_markdown: null,
     },
@@ -91,7 +91,7 @@ export const TestDataset: Dataset = {
       is_dttm: true,
       python_date_format: null,
       type: 'TIMESTAMP WITHOUT TIME ZONE',
-      type_generic: 2,
+      type_generic: GenericDataType.TEMPORAL,
       verbose_name: null,
       warning_markdown: null,
     },
@@ -109,7 +109,7 @@ export const TestDataset: Dataset = {
       is_dttm: false,
       python_date_format: null,
       type: 'VARCHAR(255)',
-      type_generic: 1,
+      type_generic: GenericDataType.STRING,
       verbose_name: null,
       warning_markdown: null,
     },
diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/sections/echartsTimeSeriesQuery.tsx b/superset-frontend/packages/superset-ui-chart-controls/src/sections/echartsTimeSeriesQuery.tsx
index 53c9aa7447..596c6f0e55 100644
--- a/superset-frontend/packages/superset-ui-chart-controls/src/sections/echartsTimeSeriesQuery.tsx
+++ b/superset-frontend/packages/superset-ui-chart-controls/src/sections/echartsTimeSeriesQuery.tsx
@@ -20,6 +20,7 @@ import { hasGenericChartAxes, t } from '@superset-ui/core';
 import { ControlPanelSectionConfig, ControlSetRow } from '../types';
 import {
   contributionModeControl,
+  xAxisForceCategoricalControl,
   xAxisSortAscControl,
   xAxisSortControl,
   xAxisSortSeriesAscendingControl,
@@ -55,6 +56,7 @@ export const echartsTimeSeriesQueryWithXAxisSort: ControlPanelSectionConfig = {
   controlSetRows: [
     [hasGenericChartAxes ? 'x_axis' : null],
     [hasGenericChartAxes ? 'time_grain_sqla' : null],
+    [hasGenericChartAxes ? xAxisForceCategoricalControl : null],
     [hasGenericChartAxes ? xAxisSortControl : null],
     [hasGenericChartAxes ? xAxisSortAscControl : null],
     [hasGenericChartAxes ? xAxisSortSeriesControl : null],
diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/customControls.tsx b/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/customControls.tsx
index 82ba6dfeeb..b0f3006353 100644
--- a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/customControls.tsx
+++ b/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/customControls.tsx
@@ -20,9 +20,9 @@
 import {
   ContributionType,
   ensureIsArray,
+  GenericDataType,
   getColumnLabel,
   getMetricLabel,
-  isDefined,
   QueryFormColumn,
   QueryFormMetric,
   t,
@@ -38,6 +38,7 @@ import {
   DEFAULT_XAXIS_SORT_SERIES_DATA,
   SORT_SERIES_CHOICES,
 } from '../constants';
+import { checkColumnType } from '../utils/checkColumnType';
 
 export const contributionModeControl = {
   name: 'contributionMode',
@@ -54,18 +55,29 @@ export const contributionModeControl = {
   },
 };
 
-function isTemporal(controls: ControlStateMapping): boolean {
-  return !(
-    isDefined(controls?.x_axis?.value) &&
-    !isTemporalColumn(
+function isForcedCategorical(controls: ControlStateMapping): boolean {
+  return (
+    checkColumnType(
       getColumnLabel(controls?.x_axis?.value as QueryFormColumn),
       controls?.datasource?.datasource,
+      [GenericDataType.NUMERIC],
+    ) && !!controls?.xAxisForceCategorical?.value
+  );
+}
+
+function isSortable(controls: ControlStateMapping): boolean {
+  return (
+    isForcedCategorical(controls) ||
+    checkColumnType(
+      getColumnLabel(controls?.x_axis?.value as QueryFormColumn),
+      controls?.datasource?.datasource,
+      [GenericDataType.STRING, GenericDataType.BOOLEAN],
     )
   );
 }
 
 const xAxisSortVisibility = ({ controls }: { controls: ControlStateMapping }) =>
-  !isTemporal(controls) &&
+  isSortable(controls) &&
   ensureIsArray(controls?.groupby?.value).length === 0 &&
   ensureIsArray(controls?.metrics?.value).length === 1;
 
@@ -74,7 +86,7 @@ const xAxisMultiSortVisibility = ({
 }: {
   controls: ControlStateMapping;
 }) =>
-  !isTemporal(controls) &&
+  isSortable(controls) &&
   (!!ensureIsArray(controls?.groupby?.value).length ||
     ensureIsArray(controls?.metrics?.value).length > 1);
 
@@ -141,7 +153,29 @@ export const xAxisSortAscControl = {
         : t('X-Axis Sort Ascending'),
     default: true,
     description: t('Whether to sort ascending or descending on the base Axis.'),
-    visibility: xAxisSortVisibility,
+    visibility: ({ controls }: { controls: ControlStateMapping }) =>
+      controls?.x_axis_sort?.value !== undefined &&
+      xAxisSortVisibility({ controls }),
+  },
+};
+
+export const xAxisForceCategoricalControl = {
+  name: 'xAxisForceCategorical',
+  config: {
+    type: 'CheckboxControl',
+    label: () => t('Force categorical'),
+    default: false,
+    description: t('Treat values as categorical.'),
+    initialValue: (control: ControlState, state: ControlPanelState | null) =>
+      state?.form_data?.x_axis_sort !== undefined || control.value,
+    renderTrigger: true,
+    visibility: ({ controls }: { controls: ControlStateMapping }) =>
+      checkColumnType(
+        getColumnLabel(controls?.x_axis?.value as QueryFormColumn),
+        controls?.datasource?.datasource,
+        [GenericDataType.NUMERIC],
+      ),
+    shouldMapStateToProps: () => true,
   },
 };
 
@@ -173,6 +207,8 @@ export const xAxisSortSeriesAscendingControl = {
     default: DEFAULT_XAXIS_SORT_SERIES_DATA.sort_series_ascending,
     description: t('Whether to sort ascending or descending on the base Axis.'),
     renderTrigger: true,
-    visibility: xAxisMultiSortVisibility,
+    visibility: ({ controls }: { controls: ControlStateMapping }) =>
+      controls?.x_axis_sort_series?.value !== undefined &&
+      xAxisMultiSortVisibility({ controls }),
   },
 };
diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/types.ts b/superset-frontend/packages/superset-ui-chart-controls/src/types.ts
index 09e4f63ee3..9314f8d33f 100644
--- a/superset-frontend/packages/superset-ui-chart-controls/src/types.ts
+++ b/superset-frontend/packages/superset-ui-chart-controls/src/types.ts
@@ -479,13 +479,15 @@ export function isControlPanelSectionConfig(
 export function isDataset(
   datasource: Dataset | QueryResponse | null | undefined,
 ): datasource is Dataset {
-  return !!datasource && 'columns' in datasource;
+  return (
+    !!datasource && 'columns' in datasource && !('sqlEditorId' in datasource)
+  );
 }
 
 export function isQueryResponse(
   datasource: Dataset | QueryResponse | null | undefined,
 ): datasource is QueryResponse {
-  return !!datasource && 'results' in datasource && 'sql' in datasource;
+  return !!datasource && 'results' in datasource && 'sqlEditorId' in datasource;
 }
 
 export enum SortSeriesType {
diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/utils/checkColumnType.ts b/superset-frontend/packages/superset-ui-chart-controls/src/utils/checkColumnType.ts
new file mode 100644
index 0000000000..202b9605d5
--- /dev/null
+++ b/superset-frontend/packages/superset-ui-chart-controls/src/utils/checkColumnType.ts
@@ -0,0 +1,49 @@
+/**
+ * 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 { ensureIsArray, GenericDataType, ValueOf } from '@superset-ui/core';
+import {
+  ControlPanelState,
+  isDataset,
+  isQueryResponse,
+} from '@superset-ui/chart-controls';
+
+export function checkColumnType(
+  columnName: string,
+  datasource: ValueOf<Pick<ControlPanelState, 'datasource'>>,
+  columnTypes: GenericDataType[],
+): boolean {
+  if (isDataset(datasource)) {
+    return ensureIsArray(datasource.columns).some(
+      c =>
+        c.type_generic !== undefined &&
+        columnTypes.includes(c.type_generic) &&
+        columnName === c.column_name,
+    );
+  }
+  if (isQueryResponse(datasource)) {
+    return ensureIsArray(datasource.columns)
+      .filter(
+        c =>
+          c.type_generic !== undefined && columnTypes.includes(c.type_generic),
+      )
+      .map(c => c.column_name)
+      .some(c => columnName === c);
+  }
+  return false;
+}
diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/utils/columnChoices.ts b/superset-frontend/packages/superset-ui-chart-controls/src/utils/columnChoices.ts
index c76cd79031..f056151750 100644
--- a/superset-frontend/packages/superset-ui-chart-controls/src/utils/columnChoices.ts
+++ b/superset-frontend/packages/superset-ui-chart-controls/src/utils/columnChoices.ts
@@ -17,7 +17,7 @@
  * under the License.
  */
 import { QueryResponse } from '@superset-ui/core';
-import { Dataset, isColumnMeta, isDataset } from '../types';
+import { Dataset, isDataset, isQueryResponse } from '../types';
 
 /**
  * Convert Datasource columns to column choices
@@ -25,11 +25,13 @@ import { Dataset, isColumnMeta, isDataset } from '../types';
 export default function columnChoices(
   datasource?: Dataset | QueryResponse | null,
 ): [string, string][] {
-  if (isDataset(datasource) && isColumnMeta(datasource.columns[0])) {
+  if (isDataset(datasource) || isQueryResponse(datasource)) {
     return datasource.columns
       .map((col): [string, string] => [
         col.column_name,
-        col.verbose_name || col.column_name,
+        'verbose_name' in col
+          ? col.verbose_name || col.column_name
+          : col.column_name,
       ])
       .sort((opt1, opt2) =>
         opt1[1].toLowerCase() > opt2[1].toLowerCase() ? 1 : -1,
diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/utils/index.ts b/superset-frontend/packages/superset-ui-chart-controls/src/utils/index.ts
index 4fa4243c1e..208d708a96 100644
--- a/superset-frontend/packages/superset-ui-chart-controls/src/utils/index.ts
+++ b/superset-frontend/packages/superset-ui-chart-controls/src/utils/index.ts
@@ -16,6 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+export * from './checkColumnType';
 export * from './selectOptions';
 export * from './D3Formatting';
 export * from './expandControlConfig';
diff --git a/superset-frontend/packages/superset-ui-chart-controls/test/utils/checkColumnType.test.ts b/superset-frontend/packages/superset-ui-chart-controls/test/utils/checkColumnType.test.ts
new file mode 100644
index 0000000000..44ebbf605c
--- /dev/null
+++ b/superset-frontend/packages/superset-ui-chart-controls/test/utils/checkColumnType.test.ts
@@ -0,0 +1,48 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { GenericDataType, testQueryResponse } from '@superset-ui/core';
+import { checkColumnType, TestDataset } from '../../src';
+
+test('checkColumnType columns from a Dataset', () => {
+  expect(
+    checkColumnType('num', TestDataset, [GenericDataType.NUMERIC]),
+  ).toEqual(true);
+  expect(checkColumnType('num', TestDataset, [GenericDataType.STRING])).toEqual(
+    false,
+  );
+  expect(
+    checkColumnType('gender', TestDataset, [GenericDataType.STRING]),
+  ).toEqual(true);
+  expect(
+    checkColumnType('gender', TestDataset, [GenericDataType.NUMERIC]),
+  ).toEqual(false);
+});
+
+test('checkColumnType from a QueryResponse', () => {
+  expect(
+    checkColumnType('Column 1', testQueryResponse, [GenericDataType.STRING]),
+  ).toEqual(true);
+  expect(
+    checkColumnType('Column 1', testQueryResponse, [GenericDataType.NUMERIC]),
+  ).toEqual(false);
+});
+
+test('checkColumnType from null', () => {
+  expect(checkColumnType('col', null, [])).toEqual(false);
+});
diff --git a/superset-frontend/packages/superset-ui-chart-controls/test/utils/columnChoices.test.tsx b/superset-frontend/packages/superset-ui-chart-controls/test/utils/columnChoices.test.tsx
index 70018ddc67..de5bb1ab69 100644
--- a/superset-frontend/packages/superset-ui-chart-controls/test/utils/columnChoices.test.tsx
+++ b/superset-frontend/packages/superset-ui-chart-controls/test/utils/columnChoices.test.tsx
@@ -16,7 +16,11 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { DatasourceType, testQueryResponse } from '@superset-ui/core';
+import {
+  DatasourceType,
+  GenericDataType,
+  testQueryResponse,
+} from '@superset-ui/core';
 import { columnChoices } from '../../src';
 
 describe('columnChoices()', () => {
@@ -31,14 +35,20 @@ describe('columnChoices()', () => {
         columns: [
           {
             column_name: 'fiz',
+            type: 'INT',
+            type_generic: GenericDataType.NUMERIC,
           },
           {
             column_name: 'about',
             verbose_name: 'right',
+            type: 'VARCHAR',
+            type_generic: GenericDataType.STRING,
           },
           {
             column_name: 'foo',
-            verbose_name: 'bar',
+            verbose_name: undefined,
+            type: 'TIMESTAMP',
+            type_generic: GenericDataType.TEMPORAL,
           },
         ],
         verbose_map: {},
@@ -48,8 +58,8 @@ describe('columnChoices()', () => {
         description: 'this is my datasource',
       }),
     ).toEqual([
-      ['foo', 'bar'],
       ['fiz', 'fiz'],
+      ['foo', 'foo'],
       ['about', 'right'],
     ]);
   });
diff --git a/superset-frontend/packages/superset-ui-chart-controls/test/utils/getTemporalColumns.test.ts b/superset-frontend/packages/superset-ui-chart-controls/test/utils/getTemporalColumns.test.ts
index 7227173045..f0a6529de7 100644
--- a/superset-frontend/packages/superset-ui-chart-controls/test/utils/getTemporalColumns.test.ts
+++ b/superset-frontend/packages/superset-ui-chart-controls/test/utils/getTemporalColumns.test.ts
@@ -16,7 +16,11 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { testQueryResponse, testQueryResults } from '@superset-ui/core';
+import {
+  GenericDataType,
+  testQueryResponse,
+  testQueryResults,
+} from '@superset-ui/core';
 import {
   Dataset,
   getTemporalColumns,
@@ -55,8 +59,9 @@ test('get temporal columns from a QueryResponse', () => {
     temporalColumns: [
       {
         column_name: 'Column 2',
-        type: 'TIMESTAMP',
         is_dttm: true,
+        type: 'TIMESTAMP',
+        type_generic: GenericDataType.TEMPORAL,
       },
     ],
     defaultTemporalColumn: 'Column 2',
diff --git a/superset-frontend/packages/superset-ui-core/src/query/types/Query.ts b/superset-frontend/packages/superset-ui-core/src/query/types/Query.ts
index f42b01abdc..1271be2fff 100644
--- a/superset-frontend/packages/superset-ui-core/src/query/types/Query.ts
+++ b/superset-frontend/packages/superset-ui-core/src/query/types/Query.ts
@@ -31,6 +31,7 @@ import { Maybe } from '../../types';
 import { PostProcessingRule } from './PostProcessing';
 import { JsonObject } from '../../connection';
 import { TimeGranularity } from '../../time-format';
+import { GenericDataType } from './QueryResponse';
 
 export type BaseQueryObjectFilterClause = {
   col: QueryFormColumn;
@@ -250,6 +251,7 @@ export type QueryColumn = {
   name?: string;
   column_name: string;
   type: string | null;
+  type_generic: GenericDataType;
   is_dttm: boolean;
 };
 
@@ -383,16 +385,19 @@ export const testQuery: Query = {
       column_name: 'Column 1',
       type: 'STRING',
       is_dttm: false,
+      type_generic: GenericDataType.STRING,
     },
     {
       column_name: 'Column 3',
       type: 'STRING',
       is_dttm: false,
+      type_generic: GenericDataType.STRING,
     },
     {
       column_name: 'Column 2',
       type: 'TIMESTAMP',
       is_dttm: true,
+      type_generic: GenericDataType.TEMPORAL,
     },
   ],
 };
@@ -404,16 +409,19 @@ export const testQueryResults = {
       {
         column_name: 'Column 1',
         type: 'STRING',
+        type_generic: GenericDataType.STRING,
         is_dttm: false,
       },
       {
         column_name: 'Column 3',
         type: 'STRING',
+        type_generic: GenericDataType.STRING,
         is_dttm: false,
       },
       {
         column_name: 'Column 2',
         type: 'TIMESTAMP',
+        type_generic: GenericDataType.TEMPORAL,
         is_dttm: true,
       },
     ],
@@ -425,16 +433,19 @@ export const testQueryResults = {
       {
         column_name: 'Column 1',
         type: 'STRING',
+        type_generic: GenericDataType.STRING,
         is_dttm: false,
       },
       {
         column_name: 'Column 3',
         type: 'STRING',
+        type_generic: GenericDataType.STRING,
         is_dttm: false,
       },
       {
         column_name: 'Column 2',
         type: 'TIMESTAMP',
+        type_generic: GenericDataType.TEMPORAL,
         is_dttm: true,
       },
     ],
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/transformProps.ts b/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/transformProps.ts
index 6c96ab7104..1d4eceb33f 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/transformProps.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/transformProps.ts
@@ -189,6 +189,7 @@ export default function transformProps(
     groupby,
     groupbyB,
     xAxis: xAxisOrig,
+    xAxisForceCategorical,
     xAxisTitle,
     yAxisTitle,
     xAxisTitleMargin,
@@ -227,7 +228,7 @@ export default function transformProps(
 
   const dataTypes = getColtypesMapping(queriesData[0]);
   const xAxisDataType = dataTypes?.[xAxisLabel] ?? dataTypes?.[xAxisOrig];
-  const xAxisType = getAxisType(stack, xAxisDataType);
+  const xAxisType = getAxisType(stack, xAxisForceCategorical, xAxisDataType);
   const series: SeriesOption[] = [];
   const formatter = contributionMode
     ? getNumberFormatter(',.0%')
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/types.ts b/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/types.ts
index c027ed7ac4..e79523d176 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/types.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/types.ts
@@ -106,6 +106,7 @@ export const DEFAULT_FORM_DATA: EchartsMixedTimeseriesFormData = {
   yAxisTitleSecondary: DEFAULT_TITLE_FORM_DATA.yAxisTitle,
   tooltipTimeFormat: TIMESERIES_DEFAULTS.tooltipTimeFormat,
   xAxisBounds: TIMESERIES_DEFAULTS.xAxisBounds,
+  xAxisForceCategorical: TIMESERIES_DEFAULTS.xAxisForceCategorical,
   xAxisTimeFormat: TIMESERIES_DEFAULTS.xAxisTimeFormat,
   area: TIMESERIES_DEFAULTS.area,
   areaB: TIMESERIES_DEFAULTS.area,
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/constants.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/constants.ts
index 215996ab12..839bc607f7 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/constants.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/constants.ts
@@ -63,6 +63,7 @@ export const DEFAULT_FORM_DATA: EchartsTimeseriesFormData = {
   yAxisBounds: [null, null],
   zoomable: false,
   richTooltip: true,
+  xAxisForceCategorical: false,
   xAxisLabelRotation: defaultXAxis.xAxisLabelRotation,
   groupby: [],
   showValue: false,
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts
index bbb624c0e0..3bbe285aec 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts
@@ -167,6 +167,7 @@ export default function transformProps(
     truncateYAxis,
     xAxis: xAxisOrig,
     xAxisBounds,
+    xAxisForceCategorical,
     xAxisLabelRotation,
     xAxisSortSeries,
     xAxisSortSeriesAscending,
@@ -248,7 +249,7 @@ export default function transformProps(
   const isAreaExpand = stack === StackControlsValue.Expand;
   const xAxisDataType = dataTypes?.[xAxisLabel] ?? dataTypes?.[xAxisOrig];
 
-  const xAxisType = getAxisType(stack, xAxisDataType);
+  const xAxisType = getAxisType(stack, xAxisForceCategorical, xAxisDataType);
   const series: SeriesOption[] = [];
 
   const forcePercentFormatter = Boolean(contributionMode || isAreaExpand);
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/types.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/types.ts
index 751f262ec1..692abb2c79 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/types.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/types.ts
@@ -79,6 +79,7 @@ export type EchartsTimeseriesFormData = QueryFormData & {
   truncateXAxis: boolean;
   truncateYAxis: boolean;
   yAxisFormat?: string;
+  xAxisForceCategorical?: boolean;
   xAxisTimeFormat?: string;
   timeGrainSqla?: TimeGranularity;
   xAxisBounds: [number | undefined | null, number | undefined | null];
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/controls.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/controls.tsx
index 21f8c7d97d..c91d27acc6 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/controls.tsx
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/controls.tsx
@@ -309,3 +309,14 @@ export const minorTicks: ControlSetItem = {
     description: t('Show minor ticks on axes.'),
   },
 };
+
+export const forceCategorical: ControlSetItem = {
+  name: 'forceCategorical',
+  config: {
+    type: 'CheckboxControl',
+    label: t('Force categorical'),
+    default: false,
+    renderTrigger: true,
+    description: t('Make the x-axis categorical'),
+  },
+};
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/utils/series.ts b/superset-frontend/plugins/plugin-chart-echarts/src/utils/series.ts
index a294fa44c3..6a51e7cbf7 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/utils/series.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/utils/series.ts
@@ -229,8 +229,10 @@ export function sortRows(
     }
 
     const value =
-      xAxisSortSeries === SortSeriesType.Name && typeof sortKey === 'string'
-        ? sortKey.toLowerCase()
+      xAxisSortSeries === SortSeriesType.Name
+        ? typeof sortKey === 'string'
+          ? sortKey.toLowerCase()
+          : sortKey
         : aggregate;
 
     return {
@@ -515,8 +517,12 @@ export function sanitizeHtml(text: string): string {
 
 export function getAxisType(
   stack: StackType,
+  forceCategorical?: boolean,
   dataType?: GenericDataType,
 ): AxisType {
+  if (forceCategorical) {
+    return AxisType.category;
+  }
   if (dataType === GenericDataType.TEMPORAL) {
     return AxisType.time;
   }
diff --git a/superset-frontend/plugins/plugin-chart-echarts/test/utils/series.test.ts b/superset-frontend/plugins/plugin-chart-echarts/test/utils/series.test.ts
index 8e55b125b8..3d7d21c8d0 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/test/utils/series.test.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/test/utils/series.test.ts
@@ -878,14 +878,28 @@ test('calculateLowerLogTick', () => {
   expect(calculateLowerLogTick(0.005)).toEqual(0.001);
 });
 
-test('getAxisType', () => {
-  expect(getAxisType(false, GenericDataType.TEMPORAL)).toEqual(AxisType.time);
-  expect(getAxisType(false, GenericDataType.NUMERIC)).toEqual(AxisType.value);
-  expect(getAxisType(true, GenericDataType.NUMERIC)).toEqual(AxisType.category);
-  expect(getAxisType(false, GenericDataType.BOOLEAN)).toEqual(
+test('getAxisType without forced categorical', () => {
+  expect(getAxisType(false, false, GenericDataType.TEMPORAL)).toEqual(
+    AxisType.time,
+  );
+  expect(getAxisType(false, false, GenericDataType.NUMERIC)).toEqual(
+    AxisType.value,
+  );
+  expect(getAxisType(true, false, GenericDataType.NUMERIC)).toEqual(
+    AxisType.category,
+  );
+  expect(getAxisType(false, false, GenericDataType.BOOLEAN)).toEqual(
+    AxisType.category,
+  );
+  expect(getAxisType(false, false, GenericDataType.STRING)).toEqual(
+    AxisType.category,
+  );
+});
+
+test('getAxisType with forced categorical', () => {
+  expect(getAxisType(false, true, GenericDataType.NUMERIC)).toEqual(
     AxisType.category,
   );
-  expect(getAxisType(false, GenericDataType.STRING)).toEqual(AxisType.category);
 });
 
 test('getMinAndMaxFromBounds returns empty object when not truncating', () => {
diff --git a/superset-frontend/src/SqlLab/fixtures.ts b/superset-frontend/src/SqlLab/fixtures.ts
index c578aac3fc..7a08c51876 100644
--- a/superset-frontend/src/SqlLab/fixtures.ts
+++ b/superset-frontend/src/SqlLab/fixtures.ts
@@ -22,6 +22,7 @@ import { ColumnKeyTypeType } from 'src/SqlLab/components/ColumnElement';
 import {
   DatasourceType,
   denormalizeTimestamp,
+  GenericDataType,
   QueryResponse,
   QueryState,
 } from '@superset-ui/core';
@@ -581,11 +582,13 @@ const baseQuery: QueryResponse = {
         is_dttm: true,
         column_name: 'ds',
         type: 'STRING',
+        type_generic: GenericDataType.STRING,
       },
       {
         is_dttm: false,
         column_name: 'gender',
         type: 'STRING',
+        type_generic: GenericDataType.STRING,
       },
     ],
     selected_columns: [
@@ -593,11 +596,13 @@ const baseQuery: QueryResponse = {
         is_dttm: true,
         column_name: 'ds',
         type: 'STRING',
+        type_generic: GenericDataType.TEMPORAL,
       },
       {
         is_dttm: false,
         column_name: 'gender',
         type: 'STRING',
+        type_generic: GenericDataType.STRING,
       },
     ],
     expanded_columns: [
@@ -605,6 +610,7 @@ const baseQuery: QueryResponse = {
         is_dttm: true,
         column_name: 'ds',
         type: 'STRING',
+        type_generic: GenericDataType.STRING,
       },
     ],
     data: [
diff --git a/superset/result_set.py b/superset/result_set.py
index 82832eb8ea..5483271035 100644
--- a/superset/result_set.py
+++ b/superset/result_set.py
@@ -29,6 +29,7 @@ from numpy.typing import NDArray
 from superset.db_engine_specs import BaseEngineSpec
 from superset.superset_typing import DbapiDescription, DbapiResult, ResultSetColumnType
 from superset.utils import core as utils
+from superset.utils.core import GenericDataType
 
 logger = logging.getLogger(__name__)
 
@@ -222,6 +223,18 @@ class SupersetResultSet:
             return False
         return column_spec.is_dttm
 
+    def type_generic(
+        self, db_type_str: Optional[str]
+    ) -> Optional[utils.GenericDataType]:
+        column_spec = self.db_engine_spec.get_column_spec(db_type_str)
+        if column_spec is None:
+            return None
+
+        if column_spec.is_dttm:
+            return GenericDataType.TEMPORAL
+
+        return column_spec.generic_type
+
     def data_type(self, col_name: str, pa_dtype: pa.DataType) -> Optional[str]:
         """Given a pyarrow data type, Returns a generic database type"""
         if set_type := self._type_dict.get(col_name):
@@ -255,7 +268,8 @@ class SupersetResultSet:
                 "column_name": col.name,
                 "name": col.name,
                 "type": db_type_str,
-                "is_dttm": self.is_temporal(db_type_str),
+                "type_generic": self.type_generic(db_type_str),
+                "is_dttm": self.is_temporal(db_type_str) or False,
             }
             columns.append(column)
         return columns
diff --git a/tests/integration_tests/result_set_tests.py b/tests/integration_tests/result_set_tests.py
index a39e0ac0d4..3e2b3656c2 100644
--- a/tests/integration_tests/result_set_tests.py
+++ b/tests/integration_tests/result_set_tests.py
@@ -21,6 +21,7 @@ import tests.integration_tests.test_app
 from superset.dataframe import df_to_records
 from superset.db_engine_specs import BaseEngineSpec
 from superset.result_set import dedup, SupersetResultSet
+from superset.utils.core import GenericDataType
 
 from .base_tests import SupersetTestCase
 
@@ -48,9 +49,27 @@ class TestSupersetResultSet(SupersetTestCase):
         self.assertEqual(
             results.columns,
             [
-                {"is_dttm": False, "type": "STRING", "column_name": "a", "name": "a"},
-                {"is_dttm": False, "type": "STRING", "column_name": "b", "name": "b"},
-                {"is_dttm": False, "type": "STRING", "column_name": "c", "name": "c"},
+                {
+                    "is_dttm": False,
+                    "type": "STRING",
+                    "type_generic": GenericDataType.STRING,
+                    "column_name": "a",
+                    "name": "a",
+                },
+                {
+                    "is_dttm": False,
+                    "type": "STRING",
+                    "type_generic": GenericDataType.STRING,
+                    "column_name": "b",
+                    "name": "b",
+                },
+                {
+                    "is_dttm": False,
+                    "type": "STRING",
+                    "type_generic": GenericDataType.STRING,
+                    "column_name": "c",
+                    "name": "c",
+                },
             ],
         )
 
@@ -61,8 +80,20 @@ class TestSupersetResultSet(SupersetTestCase):
         self.assertEqual(
             results.columns,
             [
-                {"is_dttm": False, "type": "STRING", "column_name": "a", "name": "a"},
-                {"is_dttm": False, "type": "INT", "column_name": "b", "name": "b"},
+                {
+                    "is_dttm": False,
+                    "type": "STRING",
+                    "type_generic": GenericDataType.STRING,
+                    "column_name": "a",
+                    "name": "a",
+                },
+                {
+                    "is_dttm": False,
+                    "type": "INT",
+                    "type_generic": GenericDataType.NUMERIC,
+                    "column_name": "b",
+                    "name": "b",
+                },
             ],
         )
 
@@ -76,11 +107,41 @@ class TestSupersetResultSet(SupersetTestCase):
         self.assertEqual(
             results.columns,
             [
-                {"is_dttm": False, "type": "FLOAT", "column_name": "a", "name": "a"},
-                {"is_dttm": False, "type": "INT", "column_name": "b", "name": "b"},
-                {"is_dttm": False, "type": "STRING", "column_name": "c", "name": "c"},
-                {"is_dttm": True, "type": "DATETIME", "column_name": "d", "name": "d"},
-                {"is_dttm": False, "type": "BOOL", "column_name": "e", "name": "e"},
+                {
+                    "is_dttm": False,
+                    "type": "FLOAT",
+                    "type_generic": GenericDataType.NUMERIC,
+                    "column_name": "a",
+                    "name": "a",
+                },
+                {
+                    "is_dttm": False,
+                    "type": "INT",
+                    "type_generic": GenericDataType.NUMERIC,
+                    "column_name": "b",
+                    "name": "b",
+                },
+                {
+                    "is_dttm": False,
+                    "type": "STRING",
+                    "type_generic": GenericDataType.STRING,
+                    "column_name": "c",
+                    "name": "c",
+                },
+                {
+                    "is_dttm": True,
+                    "type": "DATETIME",
+                    "type_generic": GenericDataType.TEMPORAL,
+                    "column_name": "d",
+                    "name": "d",
+                },
+                {
+                    "is_dttm": False,
+                    "type": "BOOL",
+                    "type_generic": GenericDataType.BOOLEAN,
+                    "column_name": "e",
+                    "name": "e",
+                },
             ],
         )
 
@@ -108,6 +169,7 @@ class TestSupersetResultSet(SupersetTestCase):
         cursor_descr = [("user_id", "bigint", None, None, None, None, True)]
         results = SupersetResultSet(data, cursor_descr, BaseEngineSpec)
         self.assertEqual(results.columns[0]["type"], "BIGINT")
+        self.assertEqual(results.columns[0]["type_generic"], GenericDataType.NUMERIC)
 
     def test_data_as_list_of_lists(self):
         data = [[1, "a"], [2, "b"]]
@@ -127,6 +189,7 @@ class TestSupersetResultSet(SupersetTestCase):
         cursor_descr = [("is_test", "bool", None, None, None, None, True)]
         results = SupersetResultSet(data, cursor_descr, BaseEngineSpec)
         self.assertEqual(results.columns[0]["type"], "BOOL")
+        self.assertEqual(results.columns[0]["type_generic"], GenericDataType.BOOLEAN)
         df = results.to_pandas_df()
         self.assertEqual(
             df_to_records(df),
@@ -158,9 +221,13 @@ class TestSupersetResultSet(SupersetTestCase):
         cursor_descr = [("id",), ("dict_arr",), ("num_arr",), ("map_col",)]
         results = SupersetResultSet(data, cursor_descr, BaseEngineSpec)
         self.assertEqual(results.columns[0]["type"], "INT")
+        self.assertEqual(results.columns[0]["type_generic"], GenericDataType.NUMERIC)
         self.assertEqual(results.columns[1]["type"], "STRING")
+        self.assertEqual(results.columns[1]["type_generic"], GenericDataType.STRING)
         self.assertEqual(results.columns[2]["type"], "STRING")
+        self.assertEqual(results.columns[2]["type_generic"], GenericDataType.STRING)
         self.assertEqual(results.columns[3]["type"], "STRING")
+        self.assertEqual(results.columns[3]["type_generic"], GenericDataType.STRING)
         df = results.to_pandas_df()
         self.assertEqual(
             df_to_records(df),
@@ -204,6 +271,7 @@ class TestSupersetResultSet(SupersetTestCase):
         cursor_descr = [("metadata",)]
         results = SupersetResultSet(data, cursor_descr, BaseEngineSpec)
         self.assertEqual(results.columns[0]["type"], "STRING")
+        self.assertEqual(results.columns[0]["type_generic"], GenericDataType.STRING)
         df = results.to_pandas_df()
         self.assertEqual(
             df_to_records(df),
@@ -219,6 +287,7 @@ class TestSupersetResultSet(SupersetTestCase):
         cursor_descr = [("metadata",)]
         results = SupersetResultSet(data, cursor_descr, BaseEngineSpec)
         self.assertEqual(results.columns[0]["type"], "STRING")
+        self.assertEqual(results.columns[0]["type_generic"], GenericDataType.STRING)
         df = results.to_pandas_df()
         self.assertEqual(
             df_to_records(df), [{"metadata": '[{"TestKey": [123456, "foo"]}]'}]
@@ -229,6 +298,7 @@ class TestSupersetResultSet(SupersetTestCase):
         cursor_descr = [("ds", "timestamp", None, None, None, None, True)]
         results = SupersetResultSet(data, cursor_descr, BaseEngineSpec)
         self.assertEqual(results.columns[0]["type"], "TIMESTAMP")
+        self.assertEqual(results.columns[0]["type_generic"], GenericDataType.TEMPORAL)
 
     def test_no_type_coercion(self):
         data = [("a", 1), ("b", 2)]
@@ -238,7 +308,9 @@ class TestSupersetResultSet(SupersetTestCase):
         ]
         results = SupersetResultSet(data, cursor_descr, BaseEngineSpec)
         self.assertEqual(results.columns[0]["type"], "VARCHAR")
+        self.assertEqual(results.columns[0]["type_generic"], GenericDataType.STRING)
         self.assertEqual(results.columns[1]["type"], "INT")
+        self.assertEqual(results.columns[1]["type_generic"], GenericDataType.NUMERIC)
 
     def test_empty_data(self):
         data = []


(superset) 09/13: fix(chart): Resolve incorrect column customization when switching metrics in table chart (#26393)

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

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

commit ed29c5b855dfb26b5c8d2d0012c4a15e799d6c49
Author: Sonia Gautam <96...@users.noreply.github.com>
AuthorDate: Fri Jan 5 02:31:24 2024 +0700

    fix(chart): Resolve incorrect column customization when switching metrics in table chart (#26393)
    
    Co-authored-by: Sonia <so...@agoda.com>
    (cherry picked from commit dfde2adf27faef3e73c44e7280e77d9d3928dc97)
---
 .../src/explore/actions/exploreActions.test.js     | 180 +++++++++++++++++++++
 .../src/explore/reducers/exploreReducer.js         |   5 +
 2 files changed, 185 insertions(+)

diff --git a/superset-frontend/src/explore/actions/exploreActions.test.js b/superset-frontend/src/explore/actions/exploreActions.test.js
index 9dd5375680..54cf8f16c5 100644
--- a/superset-frontend/src/explore/actions/exploreActions.test.js
+++ b/superset-frontend/src/explore/actions/exploreActions.test.js
@@ -21,6 +21,63 @@ import { defaultState } from 'src/explore/store';
 import exploreReducer from 'src/explore/reducers/exploreReducer';
 import * as actions from 'src/explore/actions/exploreActions';
 
+const METRICS = [
+  {
+    expressionType: 'SIMPLE',
+    column: {
+      advanced_data_type: null,
+      certification_details: null,
+      certified_by: null,
+      column_name: 'a',
+      description: null,
+      expression: null,
+      filterable: true,
+      groupby: true,
+      id: 1,
+      is_certified: false,
+      is_dttm: false,
+      python_date_format: null,
+      type: 'DOUBLE PRECISION',
+      type_generic: 0,
+      verbose_name: null,
+      warning_markdown: null,
+    },
+    aggregate: 'SUM',
+    sqlExpression: null,
+    datasourceWarning: false,
+    hasCustomLabel: false,
+    label: 'SUM(a)',
+    optionName: 'metric_1a2b3c4d5f_1a2b3c4d5f',
+  },
+  {
+    expressionType: 'SIMPLE',
+    column: {
+      advanced_data_type: null,
+      certification_details: null,
+      certified_by: null,
+      column_name: 'b',
+      description: null,
+      expression: null,
+      filterable: true,
+      groupby: true,
+      id: 2,
+      is_certified: false,
+      is_dttm: false,
+      python_date_format: null,
+      type: 'BIGINT',
+      type_generic: 0,
+      verbose_name: null,
+      warning_markdown: null,
+    },
+    aggregate: 'AVG',
+    sqlExpression: null,
+    datasourceWarning: false,
+    hasCustomLabel: false,
+    label: 'AVG(b)',
+    optionName: 'metric_6g7h8i9j0k_6g7h8i9j0k',
+  },
+];
+
 describe('reducers', () => {
   it('Does not set a control value if control does not exist', () => {
     const newState = exploreReducer(
@@ -37,4 +94,127 @@ describe('reducers', () => {
     expect(newState.controls.y_axis_format.value).toBe('$,.2f');
     expect(newState.form_data.y_axis_format).toBe('$,.2f');
   });
+  it('Keeps the column config when metric column positions are swapped', () => {
+    const mockedState = {
+      ...defaultState,
+      controls: {
+        ...defaultState.controls,
+        metrics: {
+          ...defaultState.controls.metrics,
+          value: METRICS,
+        },
+        column_config: {
+          ...defaultState.controls.column_config,
+          value: {
+            'AVG(b)': {
+              currencyFormat: {
+                symbolPosition: 'prefix',
+                symbol: 'USD',
+              },
+            },
+          },
+        },
+      },
+      form_data: {
+        ...defaultState.form_data,
+        metrics: METRICS,
+        column_config: {
+          'AVG(b)': {
+            currencyFormat: {
+              symbolPosition: 'prefix',
+              symbol: 'USD',
+            },
+          },
+        },
+      },
+    };
+
+    const swappedMetrics = [METRICS[1], METRICS[0]];
+    const newState = exploreReducer(
+      mockedState,
+      actions.setControlValue('metrics', swappedMetrics, []),
+    );
+
+    const expectedColumnConfig = {
+      'AVG(b)': {
+        currencyFormat: {
+          symbolPosition: 'prefix',
+          symbol: 'USD',
+        },
+      },
+    };
+
+    expect(newState.controls.metrics.value).toStrictEqual(swappedMetrics);
+    expect(newState.form_data.metrics).toStrictEqual(swappedMetrics);
+    expect(newState.controls.column_config.value).toStrictEqual(
+      expectedColumnConfig,
+    );
+    expect(newState.form_data.column_config).toStrictEqual(
+      expectedColumnConfig,
+    );
+  });
+
+  it('Keeps the column config when metric column name is updated', () => {
+    const mockedState = {
+      ...defaultState,
+      controls: {
+        ...defaultState.controls,
+        metrics: {
+          ...defaultState.controls.metrics,
+          value: METRICS,
+        },
+        column_config: {
+          ...defaultState.controls.column_config,
+          value: {
+            'AVG(b)': {
+              currencyFormat: {
+                symbolPosition: 'prefix',
+                symbol: 'USD',
+              },
+            },
+          },
+        },
+      },
+      form_data: {
+        ...defaultState.form_data,
+        metrics: METRICS,
+        column_config: {
+          'AVG(b)': {
+            currencyFormat: {
+              symbolPosition: 'prefix',
+              symbol: 'USD',
+            },
+          },
+        },
+      },
+    };
+
+    const updatedMetrics = [
+      METRICS[0],
+      {
+        ...METRICS[1],
+        hasCustomLabel: true,
+        label: 'AVG of b',
+      },
+    ];
+
+    const newState = exploreReducer(
+      mockedState,
+      actions.setControlValue('metrics', updatedMetrics, []),
+    );
+
+    const expectedColumnConfig = {
+      'AVG of b': {
+        currencyFormat: {
+          symbolPosition: 'prefix',
+          symbol: 'USD',
+        },
+      },
+    };
+    expect(newState.controls.metrics.value).toStrictEqual(updatedMetrics);
+    expect(newState.form_data.metrics).toStrictEqual(updatedMetrics);
+    expect(newState.form_data.column_config).toStrictEqual(
+      expectedColumnConfig,
+    );
+  });
 });
diff --git a/superset-frontend/src/explore/reducers/exploreReducer.js b/superset-frontend/src/explore/reducers/exploreReducer.js
index d5565a0dad..1797c57637 100644
--- a/superset-frontend/src/explore/reducers/exploreReducer.js
+++ b/superset-frontend/src/explore/reducers/exploreReducer.js
@@ -115,7 +115,12 @@ export default function exploreReducer(state = {}, action) {
       // need to update column config as well to keep the previous config.
       if (controlName === 'metrics' && old_metrics_data && new_column_config) {
         value.forEach((item, index) => {
+          const itemExist = old_metrics_data.some(
+            oldItem => oldItem?.label === item?.label,
+          );
+
           if (
+            !itemExist &&
             item?.label !== old_metrics_data[index]?.label &&
             !!new_column_config[old_metrics_data[index]?.label]
           ) {


(superset) 10/13: fix: In chart gallery thumbnail is rendered in case of no example in #16707 (#26415)

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

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

commit 1daab2731ddcb546aeeb14c97dc429941a27836a
Author: Siva Sathyaseelan <95...@users.noreply.github.com>
AuthorDate: Tue Jan 9 00:14:58 2024 +0530

    fix: In chart gallery thumbnail is rendered in case of no example in #16707 (#26415)
    
    (cherry picked from commit 6d585667799d9fdc511c45fe062f54bf0ce9022f)
---
 .../components/controls/VizTypeControl/VizTypeGallery.tsx    | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/superset-frontend/src/explore/components/controls/VizTypeControl/VizTypeGallery.tsx b/superset-frontend/src/explore/components/controls/VizTypeControl/VizTypeGallery.tsx
index 2563dba01c..2d14376516 100644
--- a/superset-frontend/src/explore/components/controls/VizTypeControl/VizTypeGallery.tsx
+++ b/superset-frontend/src/explore/components/controls/VizTypeControl/VizTypeGallery.tsx
@@ -849,10 +849,18 @@ export default function VizTypeGallery(props: VizTypeGalleryProps) {
                 grid-area: examples-header;
               `}
             >
-              {!!selectedVizMetadata?.exampleGallery?.length && t('Examples')}
+              {t('Examples')}
             </SectionTitle>
             <Examples>
-              {(selectedVizMetadata?.exampleGallery || []).map(example => (
+              {(selectedVizMetadata?.exampleGallery?.length
+                ? selectedVizMetadata.exampleGallery
+                : [
+                    {
+                      url: selectedVizMetadata?.thumbnail,
+                      caption: selectedVizMetadata?.name,
+                    },
+                  ]
+              ).map(example => (
                 <img
                   key={example.url}
                   src={example.url}


(superset) 12/13: fix(translations): Clear all (#26424)

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

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

commit 8db06b4cb2dda434ea125c79d131063aeeb28d58
Author: Xuebin Zhang <z....@gmail.com>
AuthorDate: Tue Jan 9 05:18:45 2024 +0800

    fix(translations): Clear all (#26424)
    
    (cherry picked from commit 4c2e818cd37ea0fde313ddacb71bf49990bc9ac5)
---
 superset/translations/zh/LC_MESSAGES/messages.po | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/superset/translations/zh/LC_MESSAGES/messages.po b/superset/translations/zh/LC_MESSAGES/messages.po
index e21a83f32b..791ea2c4db 100644
--- a/superset/translations/zh/LC_MESSAGES/messages.po
+++ b/superset/translations/zh/LC_MESSAGES/messages.po
@@ -3723,12 +3723,12 @@ msgstr "清除"
 
 #: superset-frontend/src/dashboard/components/nativeFilters/FilterBar/ActionButtons/index.tsx:152
 msgid "Clear all"
-msgstr "清楚所有"
+msgstr "清除所有"
 
 #: superset-frontend/src/components/Table/index.tsx:210
 #, fuzzy
 msgid "Clear all data"
-msgstr "清楚所有"
+msgstr "清除所有"
 
 #: superset-frontend/src/explore/components/ControlPanelsContainer.tsx:675
 #, fuzzy
@@ -19600,7 +19600,7 @@ msgstr "无法更新您的查询"
 #: superset-frontend/plugins/legacy-plugin-chart-horizon/src/controlPanel.ts:86
 #, fuzzy
 msgid "overall"
-msgstr "清楚所有"
+msgstr "清除所有"
 
 #: superset-frontend/plugins/legacy-plugin-chart-paired-t-test/src/controlPanel.ts:77
 msgid "p-value precision"


(superset) 02/13: fix: Removes non-existent columns in the 2018 FCC Survey dataset (#26380)

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

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

commit ac7d281e8ae33d5afcb1f9b084c93a66c9289ab4
Author: Michael S. Molina <70...@users.noreply.github.com>
AuthorDate: Wed Jan 3 11:13:07 2024 -0300

    fix: Removes non-existent columns in the 2018 FCC Survey dataset (#26380)
    
    (cherry picked from commit 77f58fc8fa26803c56f9e6cd6b4bfd8a0a8c9d30)
---
 .../configs/datasets/examples/FCC_2018_Survey.yaml | 2940 ++++++++++----------
 1 file changed, 1459 insertions(+), 1481 deletions(-)

diff --git a/superset/examples/configs/datasets/examples/FCC_2018_Survey.yaml b/superset/examples/configs/datasets/examples/FCC_2018_Survey.yaml
index 5bbbe2f74b..85aeb51eb9 100644
--- a/superset/examples/configs/datasets/examples/FCC_2018_Survey.yaml
+++ b/superset/examples/configs/datasets/examples/FCC_2018_Survey.yaml
@@ -21,7 +21,7 @@ default_endpoint: null
 offset: 0
 cache_timeout: null
 schema: null
-sql: ''
+sql: ""
 params: null
 template_params: null
 filter_select_enabled: true
@@ -29,1487 +29,1465 @@ fetch_values_predicate: null
 extra: null
 uuid: d95a2865-53ce-1f82-a53d-8e3c89331469
 metrics:
-- metric_name: count
-  verbose_name: COUNT(*)
-  metric_type: null
-  expression: COUNT(*)
-  description: null
-  d3format: null
-  extra: null
-  warning_text: null
+  - metric_name: count
+    verbose_name: COUNT(*)
+    metric_type: null
+    expression: COUNT(*)
+    description: null
+    d3format: null
+    extra: null
+    warning_text: null
 columns:
-- column_name: highest_degree_earned
-  verbose_name: Highest Degree Earned
-  is_dttm: false
-  is_active: null
-  type: STRING
-  groupby: true
-  filterable: true
-  expression: "CASE \n  WHEN school_degree = 'no high school (secondary school)'\
-    \ THEN 'A. No high school (secondary school)'\n  WHEN school_degree =  'some\
-    \ high school' THEN 'B. Some high school'\n  WHEN school_degree = 'high school\
-    \ diploma or equivalent (GED)' THEN 'C. High school diploma or equivalent (GED)'\
-    \n  WHEN school_degree = 'associate''s degree' THEN 'D. Associate''s degree'\
-    \n  WHEN school_degree = 'some college credit, no degree' THEN 'E. Some college\
-    \ credit, no degree'\n  WHEN school_degree = 'bachelor''s degree' THEN 'F.\
-    \ Bachelor''s degree'\n  WHEN school_degree = 'trade, technical, or vocational\
-    \ training' THEN 'G. Trade, technical, or vocational training'\n  WHEN school_degree\
-    \ = 'master''s degree (non-professional)' THEN 'H. Master''s degree (non-professional)'\
-    \n  WHEN school_degree = 'Ph.D.' THEN 'I. Ph.D.'\n  WHEN school_degree = '\
-    professional degree (MBA, MD, JD, etc.)' THEN 'J. Professional degree (MBA,\
-    \ MD, JD, etc.)'\nEND"
-  description: Highest Degree Earned
-  python_date_format: null
-- column_name: job_location_preference
-  verbose_name: Job Location Preference
-  is_dttm: false
-  is_active: null
-  type: null
-  groupby: true
-  filterable: true
-  expression: "case \nwhen job_lctn_pref is Null then 'No Answer' \nwhen job_lctn_pref\
-    \ = 'from home' then 'From Home'\nwhen job_lctn_pref = 'no preference' then 'No\
-    \ Preference'\nwhen job_lctn_pref = 'in an office with other developers' then\
-    \ 'In an Office (with Other Developers)'\nelse job_lctn_pref\nend "
-  description: null
-  python_date_format: null
-- column_name: ethnic_minority
-  verbose_name: Ethnic Minority
-  is_dttm: null
-  is_active: null
-  type: STRING
-  groupby: true
-  filterable: true
-  expression: "CASE \nWHEN is_ethnic_minority = 0 THEN 'No, not an ethnic minority'\
-    \ \nWHEN is_ethnic_minority = 1 THEN 'Yes, an ethnic minority' \nELSE 'No Answer'\n\
-    END"
-  description: null
-  python_date_format: null
-- column_name: willing_to_relocate
-  verbose_name: Willing To Relocate
-  is_dttm: false
-  is_active: null
-  type: STRING
-  groupby: true
-  filterable: true
-  expression: "CASE \nWHEN job_relocate = 0 THEN 'No: Not Willing to' \nWHEN job_relocate\
-    \ = 1 THEN 'Yes: Willing To'\nELSE 'No Answer'\nEND"
-  description: null
-  python_date_format: null
-- column_name: developer_type
-  verbose_name: Developer Type
-  is_dttm: false
-  is_active: null
-  type: STRING
-  groupby: true
-  filterable: true
-  expression: CASE WHEN is_software_dev = 0 THEN 'Aspiring Developer' WHEN is_software_dev
-    = 1 THEN 'Currently A Developer' END
-  description: null
-  python_date_format: null
-- column_name: first_time_developer
-  verbose_name: First Time Developer
-  is_dttm: false
-  is_active: null
-  type: null
-  groupby: true
-  filterable: true
-  expression: "CASE \nWHEN is_first_dev_job = 0 THEN 'No' \nWHEN is_first_dev_job\
-    \ = 1 THEN 'Yes' \nELSE 'No Answer'\nEND"
-  description: null
-  python_date_format: null
-- column_name: gender
-  verbose_name: null
-  is_dttm: null
-  is_active: null
-  type: STRING
-  groupby: true
-  filterable: true
-  expression: "CASE \nWHEN gender = 'Male' THEN 'Male'\nWHEN gender = 'Female' THEN\
-    \ 'Female'\nELSE 'Prefer Not to Say'\nEND"
-  description: null
-  python_date_format: null
-- column_name: calc_first_time_dev
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: STRING
-  groupby: true
-  filterable: true
-  expression: CASE WHEN is_first_dev_job = 0 THEN 'No' WHEN is_first_dev_job = 1 THEN
-    'Yes' END
-  description: null
-  python_date_format: null
-- column_name: yt_codingtuts360
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: is_recv_disab_bnft
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: job_intr_qa_engn
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: has_high_spd_ntnet
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: is_first_dev_job
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: job_intr_ux_engn
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: bootcamp_have_loan
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: podcast_js_jabber
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: job_intr_datasci
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: job_intr_dataengn
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: rsrc_khan_acdm
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: has_finance_depends
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: has_served_military
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: job_intr_backend
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: job_intr_teacher
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: months_job_search
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: student_debt_has
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: student_debt_amt
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: job_intr_gamedev
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: rsrc_code_wars
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: do_finance_support
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: last_yr_income
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: is_software_dev
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: money_for_learning
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: home_mrtg_has
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: job_intr_mobile
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: job_intr_infosec
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: job_intr_fllstck
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: job_intr_frntend
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: job_intr_devops
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: job_intr_projm
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: rsrc_css_tricks
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: yt_cs_dojo
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: is_ethnic_minority
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: yt_mit_ocw
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: is_self_employed
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: home_mrtg_owe
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: yt_engn_truth
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: bootcamp_attend
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: yt_derekbanas
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: yt_learncodeacdm
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: podcast_changelog
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: rsrc_hackerrank
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: podcast_devtea
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: podcast_sedaily
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: podcast_seradio
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: codeevnt_gamejam
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: podcast_geekspeak
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: podcast_talkpythonme
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: podcast_hanselmnts
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: podcast_syntaxfm
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: podcast_shoptalk
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: yt_mozillahacks
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: podcast_codingblcks
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: podcast_codenewbie
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: bootcamp_recommend
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: codeevnt_railsbrdg
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: bootcamp_finished
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: podcast_rubyrogues
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: job_relocate
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: debt_amt
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: rsrc_codeacdm
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: podcast_fcc
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: podcast_codepenrd
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: podcast_fullstckrd
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: codeevnt_hackthn
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: rsrc_udacity
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: podcast_ltcwm
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: rsrc_coursera
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: codeevnt_djangogrls
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: codeevnt_startupwknd
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: podcast_progthrwdwn
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: expected_earn
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: rsrc_egghead
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: codeevnt_railsgrls
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: has_children
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: podcast_frnthppyhr
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: yt_codingtrain
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: rsrc_lynda
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: rsrc_pluralsight
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: hours_learning
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: yt_simplilearn
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: codeevnt_wkndbtcmp
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: codeevnt_fcc
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: rsrc_fcc
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: codeevnt_coderdojo
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: codeevnt_nodeschl
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: codeevnt_womenwc
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: codeevnt_confs
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: yt_fcc
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: codeevnt_girldevit
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: codeevnt_meetup
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: codeevnt_workshps
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: rsrc_frntendmstr
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: num_children
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: rsrc_udemy
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: rsrc_edx
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: rsrc_mdn
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: rsrc_treehouse
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: yt_computerphile
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: yt_funfunfunct
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: rsrc_so
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: yt_googledevs
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: yt_devtips
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: yt_simpleprog
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: yt_lvluptuts
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: DOUBLE PRECISION
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: time_start
-  verbose_name: null
-  is_dttm: true
-  is_active: null
-  type: DATETIME
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: time_total_sec
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: BIGINT
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: months_programming
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: BIGINT
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: age
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: BIGINT
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: ID
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: TEXT
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: reasons_to_code_other
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: TEXT
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: lang_at_home
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: TEXT
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: when_appl_job
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: TEXT
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: reasons_to_code
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: TEXT
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: live_city_population
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: TEXT
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: job_lctn_pref
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: TEXT
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: job_intr_other
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: TEXT
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: curr_employment_other
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: TEXT
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: marital_status
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: TEXT
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: bootcamp_name
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: TEXT
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: podcast_other
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: TEXT
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: school_major
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: TEXT
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: job_pref
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: TEXT
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: country_citizen
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: TEXT
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: school_degree
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: TEXT
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: codeevnt_other
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: TEXT
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: curr_field
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: TEXT
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: communite_time
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: TEXT
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: rsrc_other
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: TEXT
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: country_live
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: TEXT
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: curr_employment
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: TEXT
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: gender_other
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: TEXT
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: time_end
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: TEXT
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: network_id
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: TEXT
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: yt_other
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: TEXT
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
-- column_name: gender
-  verbose_name: null
-  is_dttm: false
-  is_active: null
-  type: TEXT
-  groupby: true
-  filterable: true
-  expression: null
-  description: null
-  python_date_format: null
+  - column_name: highest_degree_earned
+    verbose_name: Highest Degree Earned
+    is_dttm: false
+    is_active: null
+    type: STRING
+    groupby: true
+    filterable: true
+    expression:
+      "CASE \n  WHEN school_degree = 'no high school (secondary school)'\
+      \ THEN 'A. No high school (secondary school)'\n  WHEN school_degree =  'some\
+      \ high school' THEN 'B. Some high school'\n  WHEN school_degree = 'high school\
+      \ diploma or equivalent (GED)' THEN 'C. High school diploma or equivalent (GED)'\
+      \n  WHEN school_degree = 'associate''s degree' THEN 'D. Associate''s degree'\
+      \n  WHEN school_degree = 'some college credit, no degree' THEN 'E. Some college\
+      \ credit, no degree'\n  WHEN school_degree = 'bachelor''s degree' THEN 'F.\
+      \ Bachelor''s degree'\n  WHEN school_degree = 'trade, technical, or vocational\
+      \ training' THEN 'G. Trade, technical, or vocational training'\n  WHEN school_degree\
+      \ = 'master''s degree (non-professional)' THEN 'H. Master''s degree (non-professional)'\
+      \n  WHEN school_degree = 'Ph.D.' THEN 'I. Ph.D.'\n  WHEN school_degree = '\
+      professional degree (MBA, MD, JD, etc.)' THEN 'J. Professional degree (MBA,\
+      \ MD, JD, etc.)'\nEND"
+    description: Highest Degree Earned
+    python_date_format: null
+  - column_name: job_location_preference
+    verbose_name: Job Location Preference
+    is_dttm: false
+    is_active: null
+    type: null
+    groupby: true
+    filterable: true
+    expression:
+      "case \nwhen job_lctn_pref is Null then 'No Answer' \nwhen job_lctn_pref\
+      \ = 'from home' then 'From Home'\nwhen job_lctn_pref = 'no preference' then 'No\
+      \ Preference'\nwhen job_lctn_pref = 'in an office with other developers' then\
+      \ 'In an Office (with Other Developers)'\nelse job_lctn_pref\nend "
+    description: null
+    python_date_format: null
+  - column_name: ethnic_minority
+    verbose_name: Ethnic Minority
+    is_dttm: null
+    is_active: null
+    type: STRING
+    groupby: true
+    filterable: true
+    expression:
+      "CASE \nWHEN is_ethnic_minority = 0 THEN 'No, not an ethnic minority'\
+      \ \nWHEN is_ethnic_minority = 1 THEN 'Yes, an ethnic minority' \nELSE 'No Answer'\n\
+      END"
+    description: null
+    python_date_format: null
+  - column_name: willing_to_relocate
+    verbose_name: Willing To Relocate
+    is_dttm: false
+    is_active: null
+    type: STRING
+    groupby: true
+    filterable: true
+    expression:
+      "CASE \nWHEN job_relocate = 0 THEN 'No: Not Willing to' \nWHEN job_relocate\
+      \ = 1 THEN 'Yes: Willing To'\nELSE 'No Answer'\nEND"
+    description: null
+    python_date_format: null
+  - column_name: developer_type
+    verbose_name: Developer Type
+    is_dttm: false
+    is_active: null
+    type: STRING
+    groupby: true
+    filterable: true
+    expression:
+      CASE WHEN is_software_dev = 0 THEN 'Aspiring Developer' WHEN is_software_dev
+      = 1 THEN 'Currently A Developer' END
+    description: null
+    python_date_format: null
+  - column_name: first_time_developer
+    verbose_name: First Time Developer
+    is_dttm: false
+    is_active: null
+    type: null
+    groupby: true
+    filterable: true
+    expression:
+      "CASE \nWHEN is_first_dev_job = 0 THEN 'No' \nWHEN is_first_dev_job\
+      \ = 1 THEN 'Yes' \nELSE 'No Answer'\nEND"
+    description: null
+    python_date_format: null
+  - column_name: gender
+    verbose_name: null
+    is_dttm: null
+    is_active: null
+    type: STRING
+    groupby: true
+    filterable: true
+    expression:
+      "CASE \nWHEN gender = 'Male' THEN 'Male'\nWHEN gender = 'Female' THEN\
+      \ 'Female'\nELSE 'Prefer Not to Say'\nEND"
+    description: null
+    python_date_format: null
+  - column_name: calc_first_time_dev
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: STRING
+    groupby: true
+    filterable: true
+    expression:
+      CASE WHEN is_first_dev_job = 0 THEN 'No' WHEN is_first_dev_job = 1 THEN
+      'Yes' END
+    description: null
+    python_date_format: null
+  - column_name: yt_codingtuts360
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: is_recv_disab_bnft
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: job_intr_qa_engn
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: has_high_spd_ntnet
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: is_first_dev_job
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: job_intr_ux_engn
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: bootcamp_have_loan
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: podcast_js_jabber
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: job_intr_datasci
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: job_intr_dataengn
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: rsrc_khan_acdm
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: has_finance_depends
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: has_served_military
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: job_intr_backend
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: job_intr_teacher
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: months_job_search
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: student_debt_has
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: student_debt_amt
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: job_intr_gamedev
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: rsrc_code_wars
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: do_finance_support
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: last_yr_income
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: is_software_dev
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: money_for_learning
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: home_mrtg_has
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: job_intr_mobile
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: job_intr_infosec
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: job_intr_fllstck
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: job_intr_frntend
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: job_intr_devops
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: job_intr_projm
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: rsrc_css_tricks
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: yt_cs_dojo
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: is_ethnic_minority
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: yt_mit_ocw
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: is_self_employed
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: home_mrtg_owe
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: yt_engn_truth
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: bootcamp_attend
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: yt_derekbanas
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: yt_learncodeacdm
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: podcast_changelog
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: rsrc_hackerrank
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: podcast_devtea
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: podcast_sedaily
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: podcast_seradio
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: codeevnt_gamejam
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: podcast_geekspeak
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: podcast_talkpythonme
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: podcast_hanselmnts
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: podcast_syntaxfm
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: podcast_shoptalk
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: yt_mozillahacks
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: podcast_codingblcks
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: podcast_codenewbie
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: bootcamp_recommend
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: codeevnt_railsbrdg
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: bootcamp_finished
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: podcast_rubyrogues
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: job_relocate
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: debt_amt
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: rsrc_codeacdm
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: podcast_fcc
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: podcast_codepenrd
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: podcast_fullstckrd
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: codeevnt_hackthn
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: rsrc_udacity
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: podcast_ltcwm
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: rsrc_coursera
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: codeevnt_djangogrls
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: codeevnt_startupwknd
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: podcast_progthrwdwn
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: expected_earn
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: rsrc_egghead
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: codeevnt_railsgrls
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: has_children
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: podcast_frnthppyhr
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: yt_codingtrain
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: rsrc_lynda
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: hours_learning
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: yt_simplilearn
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: codeevnt_wkndbtcmp
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: codeevnt_fcc
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: rsrc_fcc
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: codeevnt_coderdojo
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: codeevnt_nodeschl
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: codeevnt_womenwc
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: codeevnt_confs
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: yt_fcc
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: codeevnt_girldevit
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: codeevnt_meetup
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: codeevnt_workshps
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: rsrc_frntendmstr
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: num_children
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: rsrc_udemy
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: rsrc_edx
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: rsrc_mdn
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: rsrc_treehouse
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: yt_computerphile
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: yt_funfunfunct
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: rsrc_so
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: yt_googledevs
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: yt_devtips
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: yt_simpleprog
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: yt_lvluptuts
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: DOUBLE PRECISION
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: time_start
+    verbose_name: null
+    is_dttm: true
+    is_active: null
+    type: DATETIME
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: time_total_sec
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: BIGINT
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: months_programming
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: BIGINT
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: age
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: BIGINT
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: ID
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: TEXT
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: reasons_to_code_other
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: TEXT
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: lang_at_home
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: TEXT
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: when_appl_job
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: TEXT
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: reasons_to_code
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: TEXT
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: live_city_population
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: TEXT
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: job_lctn_pref
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: TEXT
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: job_intr_other
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: TEXT
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: marital_status
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: TEXT
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: bootcamp_name
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: TEXT
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: podcast_other
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: TEXT
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: school_major
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: TEXT
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: job_pref
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: TEXT
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: country_citizen
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: TEXT
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: school_degree
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: TEXT
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: codeevnt_other
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: TEXT
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: curr_field
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: TEXT
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: communite_time
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: TEXT
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: rsrc_other
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: TEXT
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: country_live
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: TEXT
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: gender_other
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: TEXT
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: time_end
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: TEXT
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: network_id
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: TEXT
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: yt_other
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: TEXT
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
+  - column_name: gender
+    verbose_name: null
+    is_dttm: false
+    is_active: null
+    type: TEXT
+    groupby: true
+    filterable: true
+    expression: null
+    description: null
+    python_date_format: null
 version: 1.0.0
 database_uuid: a2dc77af-e654-49bb-b321-40f6b559a1ee
 data: https://github.com/apache-superset/examples-data/raw/master/datasets/examples/fcc_survey_2018.csv.gz


(superset) 07/13: fix(dashboard): narrow empty drop area (#26313)

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

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

commit 0e85a43acfc31b3c6cd2733d20b06471aff71a7f
Author: JUST.in DO IT <ju...@airbnb.com>
AuthorDate: Thu Jan 4 09:44:23 2024 -0800

    fix(dashboard): narrow empty drop area (#26313)
    
    (cherry picked from commit 300ddaedf97f582a8d73bc78e02ff3c434a4934f)
---
 .../DashboardBuilder/DashboardBuilder.tsx          |  70 +----------
 .../DashboardBuilder/DashboardWrapper.test.tsx     |  75 ++++++++++++
 .../DashboardBuilder/DashboardWrapper.tsx          | 128 +++++++++++++++++++++
 .../src/dashboard/components/DashboardGrid.jsx     |  19 ++-
 .../src/dashboard/components/dnd/DragDroppable.jsx |  11 +-
 .../dashboard/components/gridComponents/Tab.jsx    |   8 +-
 6 files changed, 235 insertions(+), 76 deletions(-)

diff --git a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx
index a17b168374..51de15c7a1 100644
--- a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx
+++ b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx
@@ -86,72 +86,10 @@ import {
 import { getRootLevelTabsComponent, shouldFocusTabs } from './utils';
 import DashboardContainer from './DashboardContainer';
 import { useNativeFilters } from './state';
+import DashboardWrapper from './DashboardWrapper';
 
 type DashboardBuilderProps = {};
 
-const StyledDiv = styled.div`
-  ${({ theme }) => css`
-    display: grid;
-    grid-template-columns: auto 1fr;
-    grid-template-rows: auto 1fr;
-    flex: 1;
-    /* Special cases */
-
-    /* A row within a column has inset hover menu */
-    .dragdroppable-column .dragdroppable-row .hover-menu--left {
-      left: ${theme.gridUnit * -3}px;
-      background: ${theme.colors.grayscale.light5};
-      border: 1px solid ${theme.colors.grayscale.light2};
-    }
-
-    .dashboard-component-tabs {
-      position: relative;
-    }
-
-    /* A column within a column or tabs has inset hover menu */
-    .dragdroppable-column .dragdroppable-column .hover-menu--top,
-    .dashboard-component-tabs .dragdroppable-column .hover-menu--top {
-      top: ${theme.gridUnit * -3}px;
-      background: ${theme.colors.grayscale.light5};
-      border: 1px solid ${theme.colors.grayscale.light2};
-    }
-
-    /* move Tabs hover menu to top near actual Tabs */
-    .dashboard-component-tabs > .hover-menu-container > .hover-menu--left {
-      top: 0;
-      transform: unset;
-      background: transparent;
-    }
-
-    /* push Chart actions to upper right */
-    .dragdroppable-column .dashboard-component-chart-holder .hover-menu--top,
-    .dragdroppable .dashboard-component-header .hover-menu--top {
-      right: ${theme.gridUnit * 2}px;
-      top: ${theme.gridUnit * 2}px;
-      background: transparent;
-      border: none;
-      transform: unset;
-      left: unset;
-    }
-    div:hover > .hover-menu-container .hover-menu,
-    .hover-menu-container .hover-menu:hover {
-      opacity: 1;
-    }
-
-    p {
-      margin: 0 0 ${theme.gridUnit * 2}px 0;
-    }
-
-    i.danger {
-      color: ${theme.colors.error.base};
-    }
-
-    i.warning {
-      color: ${theme.colors.alert.base};
-    }
-  `}
-`;
-
 // @z-index-above-dashboard-charts + 1 = 11
 const FiltersPanel = styled.div<{ width: number; hidden: boolean }>`
   grid-column: 1;
@@ -317,7 +255,7 @@ const DashboardContentWrapper = styled.div`
         width: 100%;
       }
 
-      & > .empty-droptarget:first-child {
+      & > .empty-droptarget:first-child:not(.empty-droptarget--full) {
         height: ${theme.gridUnit * 4}px;
         top: -2px;
         z-index: 10;
@@ -640,7 +578,7 @@ const DashboardBuilder: FC<DashboardBuilderProps> = () => {
       : theme.gridUnit * 8;
 
   return (
-    <StyledDiv>
+    <DashboardWrapper>
       {showFilterBar && filterBarOrientation === FilterBarOrientation.VERTICAL && (
         <>
           <ResizableSidebar
@@ -749,7 +687,7 @@ const DashboardBuilder: FC<DashboardBuilderProps> = () => {
           `}
         />
       )}
-    </StyledDiv>
+    </DashboardWrapper>
   );
 };
 
diff --git a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardWrapper.test.tsx b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardWrapper.test.tsx
new file mode 100644
index 0000000000..fb913b4627
--- /dev/null
+++ b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardWrapper.test.tsx
@@ -0,0 +1,75 @@
+/**
+ * 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 { fireEvent, render } from 'spec/helpers/testing-library';
+import { OptionControlLabel } from 'src/explore/components/controls/OptionControls';
+
+import DashboardWrapper from './DashboardWrapper';
+
+test('should render children', () => {
+  const { getByTestId } = render(
+    <DashboardWrapper>
+      <div data-test="mock-children" />
+    </DashboardWrapper>,
+    { useRedux: true, useDnd: true },
+  );
+  expect(getByTestId('mock-children')).toBeInTheDocument();
+});
+
+test('should update the style on dragging state', () => {
+  const defaultProps = {
+    label: <span>Test label</span>,
+    tooltipTitle: 'This is a tooltip title',
+    onRemove: jest.fn(),
+    onMoveLabel: jest.fn(),
+    onDropLabel: jest.fn(),
+    type: 'test',
+    index: 0,
+  };
+  const { container, getByText } = render(
+    <DashboardWrapper>
+      <OptionControlLabel
+        {...defaultProps}
+        index={1}
+        label={<span>Label 1</span>}
+      />
+      <OptionControlLabel
+        {...defaultProps}
+        index={2}
+        label={<span>Label 2</span>}
+      />
+    </DashboardWrapper>,
+    {
+      useRedux: true,
+      useDnd: true,
+      initialState: {
+        dashboardState: {
+          editMode: true,
+        },
+      },
+    },
+  );
+  expect(
+    container.getElementsByClassName('dragdroppable--dragging'),
+  ).toHaveLength(0);
+  fireEvent.dragStart(getByText('Label 1'));
+  expect(
+    container.getElementsByClassName('dragdroppable--dragging'),
+  ).toHaveLength(1);
+});
diff --git a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardWrapper.tsx b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardWrapper.tsx
new file mode 100644
index 0000000000..f39c7ed630
--- /dev/null
+++ b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardWrapper.tsx
@@ -0,0 +1,128 @@
+/**
+ * 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, { useEffect } from 'react';
+import { css, styled } from '@superset-ui/core';
+import { RootState } from 'src/dashboard/types';
+import { useSelector } from 'react-redux';
+import { useDragDropManager } from 'react-dnd';
+import classNames from 'classnames';
+
+const StyledDiv = styled.div`
+  ${({ theme }) => css`
+    display: grid;
+    grid-template-columns: auto 1fr;
+    grid-template-rows: auto 1fr;
+    flex: 1;
+    /* Special cases */
+
+    &.dragdroppable--dragging
+      .dashboard-component-tabs-content
+      > .empty-droptarget.empty-droptarget--full {
+      height: 100%;
+    }
+
+    /* A row within a column has inset hover menu */
+    .dragdroppable-column .dragdroppable-row .hover-menu--left {
+      left: ${theme.gridUnit * -3}px;
+      background: ${theme.colors.grayscale.light5};
+      border: 1px solid ${theme.colors.grayscale.light2};
+    }
+
+    .dashboard-component-tabs {
+      position: relative;
+    }
+
+    /* A column within a column or tabs has inset hover menu */
+    .dragdroppable-column .dragdroppable-column .hover-menu--top,
+    .dashboard-component-tabs .dragdroppable-column .hover-menu--top {
+      top: ${theme.gridUnit * -3}px;
+      background: ${theme.colors.grayscale.light5};
+      border: 1px solid ${theme.colors.grayscale.light2};
+    }
+
+    /* move Tabs hover menu to top near actual Tabs */
+    .dashboard-component-tabs > .hover-menu-container > .hover-menu--left {
+      top: 0;
+      transform: unset;
+      background: transparent;
+    }
+
+    /* push Chart actions to upper right */
+    .dragdroppable-column .dashboard-component-chart-holder .hover-menu--top,
+    .dragdroppable .dashboard-component-header .hover-menu--top {
+      right: ${theme.gridUnit * 2}px;
+      top: ${theme.gridUnit * 2}px;
+      background: transparent;
+      border: none;
+      transform: unset;
+      left: unset;
+    }
+    div:hover > .hover-menu-container .hover-menu,
+    .hover-menu-container .hover-menu:hover {
+      opacity: 1;
+    }
+
+    p {
+      margin: 0 0 ${theme.gridUnit * 2}px 0;
+    }
+
+    i.danger {
+      color: ${theme.colors.error.base};
+    }
+
+    i.warning {
+      color: ${theme.colors.alert.base};
+    }
+  `}
+`;
+
+type Props = {};
+
+const DashboardWrapper: React.FC<Props> = ({ children }) => {
+  const editMode = useSelector<RootState, boolean>(
+    state => state.dashboardState.editMode,
+  );
+  const dragDropManager = useDragDropManager();
+  const [isDragged, setIsDragged] = React.useState(
+    dragDropManager.getMonitor().isDragging(),
+  );
+
+  useEffect(() => {
+    const monitor = dragDropManager.getMonitor();
+    const unsub = monitor.subscribeToStateChange(() => {
+      setIsDragged(monitor.isDragging());
+    });
+
+    return () => {
+      unsub();
+    };
+  }, [dragDropManager]);
+
+  return (
+    <StyledDiv
+      className={classNames({
+        'dragdroppable--dragging': editMode && isDragged,
+      })}
+    >
+      {children}
+    </StyledDiv>
+  );
+};
+
+export default DashboardWrapper;
diff --git a/superset-frontend/src/dashboard/components/DashboardGrid.jsx b/superset-frontend/src/dashboard/components/DashboardGrid.jsx
index 601dbac4a4..70cf65218f 100644
--- a/superset-frontend/src/dashboard/components/DashboardGrid.jsx
+++ b/superset-frontend/src/dashboard/components/DashboardGrid.jsx
@@ -18,6 +18,7 @@
  */
 import React from 'react';
 import PropTypes from 'prop-types';
+import classNames from 'classnames';
 import { addAlpha, css, styled, t } from '@superset-ui/core';
 import { EmptyStateBig } from 'src/components/EmptyState';
 import { componentShape } from '../util/propShapes';
@@ -76,10 +77,14 @@ const GridContent = styled.div`
     & > .empty-droptarget:first-child {
       height: ${theme.gridUnit * 12}px;
       margin-top: ${theme.gridUnit * -6}px;
-      margin-bottom: ${theme.gridUnit * -6}px;
     }
 
-    & > .empty-droptarget:only-child {
+    & > .empty-droptarget:last-child {
+      height: ${theme.gridUnit * 12}px;
+      margin-top: ${theme.gridUnit * -6}px;
+    }
+
+    & > .empty-droptarget.empty-droptarget--full:only-child {
       height: 80vh;
     }
   `}
@@ -270,10 +275,14 @@ class DashboardGrid extends React.PureComponent {
                 index={0}
                 orientation="column"
                 onDrop={this.handleTopDropTargetDrop}
-                className="empty-droptarget"
+                className={classNames({
+                  'empty-droptarget': true,
+                  'empty-droptarget--full':
+                    gridComponent?.children?.length === 0,
+                })}
                 editMode
               >
-                {renderDraggableContentBottom}
+                {renderDraggableContentTop}
               </DragDroppable>
             )}
             {gridComponent?.children?.map((id, index) => (
@@ -304,7 +313,7 @@ class DashboardGrid extends React.PureComponent {
                 className="empty-droptarget"
                 editMode
               >
-                {renderDraggableContentTop}
+                {renderDraggableContentBottom}
               </DragDroppable>
             )}
             {isResizing &&
diff --git a/superset-frontend/src/dashboard/components/dnd/DragDroppable.jsx b/superset-frontend/src/dashboard/components/dnd/DragDroppable.jsx
index 3bc9f4d299..6a49f98875 100644
--- a/superset-frontend/src/dashboard/components/dnd/DragDroppable.jsx
+++ b/superset-frontend/src/dashboard/components/dnd/DragDroppable.jsx
@@ -90,6 +90,11 @@ const DragDroppableStyles = styled.div`
       z-index: 10;
     }
 
+    &.empty-droptarget--full > .drop-indicator--top {
+      height: 100%;
+      opacity: 0.3;
+    }
+
     & {
       .drop-indicator {
         display: block;
@@ -99,7 +104,7 @@ const DragDroppableStyles = styled.div`
       }
 
       .drop-indicator--top {
-        top: 0;
+        top: ${-theme.gridUnit - 2}px;
         left: 0;
         height: ${theme.gridUnit}px;
         width: 100%;
@@ -107,7 +112,7 @@ const DragDroppableStyles = styled.div`
       }
 
       .drop-indicator--bottom {
-        top: 100%;
+        bottom: ${-theme.gridUnit - 2}px;
         left: 0;
         height: ${theme.gridUnit}px;
         width: 100%;
@@ -116,7 +121,7 @@ const DragDroppableStyles = styled.div`
 
       .drop-indicator--right {
         top: 0;
-        left: 100%;
+        left: calc(100% - ${theme.gridUnit}px);
         height: 100%;
         width: ${theme.gridUnit}px;
         min-height: ${theme.gridUnit * 4}px;
diff --git a/superset-frontend/src/dashboard/components/gridComponents/Tab.jsx b/superset-frontend/src/dashboard/components/gridComponents/Tab.jsx
index 32ac77936c..d1d08176ba 100644
--- a/superset-frontend/src/dashboard/components/gridComponents/Tab.jsx
+++ b/superset-frontend/src/dashboard/components/gridComponents/Tab.jsx
@@ -18,6 +18,7 @@
  */
 import React from 'react';
 import PropTypes from 'prop-types';
+import classNames from 'classnames';
 import { bindActionCreators } from 'redux';
 import { connect } from 'react-redux';
 import { styled, t } from '@superset-ui/core';
@@ -173,7 +174,10 @@ class Tab extends React.PureComponent {
             depth={depth}
             onDrop={this.handleTopDropTargetDrop}
             editMode
-            className="empty-droptarget"
+            className={classNames({
+              'empty-droptarget': true,
+              'empty-droptarget--full': tabComponent.children.length === 0,
+            })}
           >
             {renderDraggableContentTop}
           </DragDroppable>
@@ -234,7 +238,7 @@ class Tab extends React.PureComponent {
           />
         ))}
         {/* Make bottom of tab droppable */}
-        {editMode && (
+        {editMode && tabComponent.children.length > 0 && (
           <DragDroppable
             component={tabComponent}
             parentComponent={tabParentComponent}


(superset) 01/13: feat(deckgl-map): use an arbitraty Mabpox style URL (#26027) (#26031)

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

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

commit f6803f628cf40f5f2eae36ae349f1d645fa0df63
Author: François Travais <fr...@travais.fr>
AuthorDate: Tue Nov 28 23:32:19 2023 +0100

    feat(deckgl-map): use an arbitraty Mabpox style URL (#26027) (#26031)
    
    Signed-off-by: François Travais <fr...@solinum.org>
---
 .../superset-ui-core/src/validator/index.ts        |  1 +
 .../{index.ts => validateMapboxStylesUrl.ts}       | 23 ++++++++++----
 .../validator/validateMapboxStylesUrl.test.ts}     | 36 +++++++++++++++++-----
 .../src/controlPanel.ts                            | 14 +++++++--
 .../src/Multi/controlPanel.ts                      |  3 +-
 .../src/layers/Arc/controlPanel.ts                 |  5 +--
 .../src/layers/Contour/controlPanel.ts             |  4 +--
 .../src/layers/Grid/controlPanel.ts                |  3 +-
 .../src/layers/Heatmap/controlPanel.ts             |  3 +-
 .../src/layers/Hex/controlPanel.ts                 |  4 +--
 .../src/layers/Path/controlPanel.ts                |  3 +-
 .../src/layers/Scatter/controlPanel.ts             |  5 +--
 .../src/layers/Screengrid/controlPanel.ts          |  5 +--
 .../src/utilities/Shared_DeckGL.jsx                |  8 ++++-
 14 files changed, 81 insertions(+), 36 deletions(-)

diff --git a/superset-frontend/packages/superset-ui-core/src/validator/index.ts b/superset-frontend/packages/superset-ui-core/src/validator/index.ts
index fb37328c02..6294bddec7 100644
--- a/superset-frontend/packages/superset-ui-core/src/validator/index.ts
+++ b/superset-frontend/packages/superset-ui-core/src/validator/index.ts
@@ -23,3 +23,4 @@ export { default as validateInteger } from './validateInteger';
 export { default as validateNumber } from './validateNumber';
 export { default as validateNonEmpty } from './validateNonEmpty';
 export { default as validateMaxValue } from './validateMaxValue';
+export { default as validateMapboxStylesUrl } from './validateMapboxStylesUrl';
diff --git a/superset-frontend/packages/superset-ui-core/src/validator/index.ts b/superset-frontend/packages/superset-ui-core/src/validator/validateMapboxStylesUrl.ts
similarity index 66%
copy from superset-frontend/packages/superset-ui-core/src/validator/index.ts
copy to superset-frontend/packages/superset-ui-core/src/validator/validateMapboxStylesUrl.ts
index fb37328c02..bfbbaa7168 100644
--- a/superset-frontend/packages/superset-ui-core/src/validator/index.ts
+++ b/superset-frontend/packages/superset-ui-core/src/validator/validateMapboxStylesUrl.ts
@@ -17,9 +17,20 @@
  * under the License.
  */
 
-export { default as legacyValidateInteger } from './legacyValidateInteger';
-export { default as legacyValidateNumber } from './legacyValidateNumber';
-export { default as validateInteger } from './validateInteger';
-export { default as validateNumber } from './validateNumber';
-export { default as validateNonEmpty } from './validateNonEmpty';
-export { default as validateMaxValue } from './validateMaxValue';
+import { t } from '../translation';
+
+/**
+ * Validate a [Mapbox styles URL](https://docs.mapbox.com/help/glossary/style-url/)
+ * @param v
+ */
+export default function validateMapboxStylesUrl(v: unknown) {
+  if (
+    typeof v === 'string' &&
+    v.trim().length > 0 &&
+    v.trim().startsWith('mapbox://styles/')
+  ) {
+    return false;
+  }
+
+  return t('is expected to be a Mapbox URL');
+}
diff --git a/superset-frontend/packages/superset-ui-core/src/validator/index.ts b/superset-frontend/packages/superset-ui-core/test/validator/validateMapboxStylesUrl.test.ts
similarity index 50%
copy from superset-frontend/packages/superset-ui-core/src/validator/index.ts
copy to superset-frontend/packages/superset-ui-core/test/validator/validateMapboxStylesUrl.test.ts
index fb37328c02..dbd5822666 100644
--- a/superset-frontend/packages/superset-ui-core/src/validator/index.ts
+++ b/superset-frontend/packages/superset-ui-core/test/validator/validateMapboxStylesUrl.test.ts
@@ -1,4 +1,4 @@
-/*
+/**
  * 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
@@ -16,10 +16,32 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+import { validateMapboxStylesUrl } from '@superset-ui/core';
+import './setup';
 
-export { default as legacyValidateInteger } from './legacyValidateInteger';
-export { default as legacyValidateNumber } from './legacyValidateNumber';
-export { default as validateInteger } from './validateInteger';
-export { default as validateNumber } from './validateNumber';
-export { default as validateNonEmpty } from './validateNonEmpty';
-export { default as validateMaxValue } from './validateMaxValue';
+describe('validateMapboxStylesUrl', () => {
+  it('should validate mapbox style URLs', () => {
+    expect(
+      validateMapboxStylesUrl('mapbox://styles/mapbox/streets-v9'),
+    ).toEqual(false);
+    expect(
+      validateMapboxStylesUrl(
+        'mapbox://styles/foobar/clp2dr5r4008a01pcg4ad45m8',
+      ),
+    ).toEqual(false);
+  });
+
+  [
+    123,
+    ['mapbox://styles/mapbox/streets-v9'],
+    { url: 'mapbox://styles/mapbox/streets-v9' },
+    'https://superset.apache.org/',
+    'mapbox://tileset/mapbox/streets-v9',
+  ].forEach(value => {
+    it(`should not validate ${value}`, () => {
+      expect(validateMapboxStylesUrl(value)).toEqual(
+        'is expected to be a Mapbox URL',
+      );
+    });
+  });
+});
diff --git a/superset-frontend/plugins/legacy-plugin-chart-map-box/src/controlPanel.ts b/superset-frontend/plugins/legacy-plugin-chart-map-box/src/controlPanel.ts
index 1dc75d96ef..e0b6524609 100644
--- a/superset-frontend/plugins/legacy-plugin-chart-map-box/src/controlPanel.ts
+++ b/superset-frontend/plugins/legacy-plugin-chart-map-box/src/controlPanel.ts
@@ -16,7 +16,12 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { FeatureFlag, isFeatureEnabled, t } from '@superset-ui/core';
+import {
+  FeatureFlag,
+  isFeatureEnabled,
+  t,
+  validateMapboxStylesUrl,
+} from '@superset-ui/core';
 import {
   columnChoices,
   ControlPanelConfig,
@@ -224,6 +229,8 @@ const config: ControlPanelConfig = {
               label: t('Map Style'),
               clearable: false,
               renderTrigger: true,
+              freeForm: true,
+              validators: [validateMapboxStylesUrl],
               choices: [
                 ['mapbox://styles/mapbox/streets-v9', t('Streets')],
                 ['mapbox://styles/mapbox/dark-v9', t('Dark')],
@@ -236,7 +243,10 @@ const config: ControlPanelConfig = {
                 ['mapbox://styles/mapbox/outdoors-v9', t('Outdoors')],
               ],
               default: 'mapbox://styles/mapbox/light-v9',
-              description: t('Base layer map style'),
+              description: t(
+                'Base layer map style. See Mapbox documentation: %s',
+                'https://docs.mapbox.com/help/glossary/style-url/',
+              ),
             },
           },
         ],
diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/Multi/controlPanel.ts b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/Multi/controlPanel.ts
index 8571fe23d0..8f4df671c3 100644
--- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/Multi/controlPanel.ts
+++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/Multi/controlPanel.ts
@@ -27,7 +27,8 @@ export default {
       label: t('Map'),
       expanded: true,
       controlSetRows: [
-        [mapboxStyle, viewport],
+        [mapboxStyle],
+        [viewport],
         [
           {
             name: 'deck_slices',
diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Arc/controlPanel.ts b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Arc/controlPanel.ts
index 3794ef38da..664f389a0b 100644
--- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Arc/controlPanel.ts
+++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Arc/controlPanel.ts
@@ -76,10 +76,7 @@ const config: ControlPanelConfig = {
     },
     {
       label: t('Map'),
-      controlSetRows: [
-        [mapboxStyle, viewport],
-        [autozoom, null],
-      ],
+      controlSetRows: [[mapboxStyle], [autozoom, viewport]],
     },
     {
       label: t('Arc'),
diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Contour/controlPanel.ts b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Contour/controlPanel.ts
index 238029aada..407cab3162 100644
--- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Contour/controlPanel.ts
+++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Contour/controlPanel.ts
@@ -52,8 +52,8 @@ const config: ControlPanelConfig = {
       label: t('Map'),
       expanded: true,
       controlSetRows: [
-        [mapboxStyle, viewport],
-        [autozoom],
+        [mapboxStyle],
+        [autozoom, viewport],
         [
           {
             name: 'cellSize',
diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Grid/controlPanel.ts b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Grid/controlPanel.ts
index 9b8e33d739..fa9a03a8f3 100644
--- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Grid/controlPanel.ts
+++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Grid/controlPanel.ts
@@ -53,7 +53,8 @@ const config: ControlPanelConfig = {
     {
       label: t('Map'),
       controlSetRows: [
-        [mapboxStyle, viewport],
+        [mapboxStyle],
+        [viewport],
         ['color_scheme'],
         [autozoom],
         [gridSize],
diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Heatmap/controlPanel.ts b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Heatmap/controlPanel.ts
index 6fa41c2e21..fd343eed16 100644
--- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Heatmap/controlPanel.ts
+++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Heatmap/controlPanel.ts
@@ -99,7 +99,8 @@ const config: ControlPanelConfig = {
     {
       label: t('Map'),
       controlSetRows: [
-        [mapboxStyle, viewport],
+        [mapboxStyle],
+        [viewport],
         ['linear_color_scheme'],
         [autozoom],
         [
diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Hex/controlPanel.ts b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Hex/controlPanel.ts
index 2f9293c521..8865ed0052 100644
--- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Hex/controlPanel.ts
+++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Hex/controlPanel.ts
@@ -53,8 +53,8 @@ const config: ControlPanelConfig = {
     {
       label: t('Map'),
       controlSetRows: [
-        [mapboxStyle, viewport],
-        ['color_scheme'],
+        [mapboxStyle],
+        ['color_scheme', viewport],
         [autozoom],
         [gridSize],
         [extruded],
diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Path/controlPanel.ts b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Path/controlPanel.ts
index 80691efa6b..b0403b3596 100644
--- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Path/controlPanel.ts
+++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Path/controlPanel.ts
@@ -67,7 +67,8 @@ const config: ControlPanelConfig = {
       label: t('Map'),
       expanded: true,
       controlSetRows: [
-        [mapboxStyle, viewport],
+        [mapboxStyle],
+        [viewport],
         ['color_picker'],
         [lineWidth],
         [
diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Scatter/controlPanel.ts b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Scatter/controlPanel.ts
index ef3d45a956..9afeb1b415 100644
--- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Scatter/controlPanel.ts
+++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Scatter/controlPanel.ts
@@ -62,10 +62,7 @@ const config: ControlPanelConfig = {
     {
       label: t('Map'),
       expanded: true,
-      controlSetRows: [
-        [mapboxStyle, viewport],
-        [autozoom, null],
-      ],
+      controlSetRows: [[mapboxStyle], [autozoom, viewport]],
     },
     {
       label: t('Point Size'),
diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Screengrid/controlPanel.ts b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Screengrid/controlPanel.ts
index caf052581c..82aeda1745 100644
--- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Screengrid/controlPanel.ts
+++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Screengrid/controlPanel.ts
@@ -52,10 +52,7 @@ const config: ControlPanelConfig = {
     },
     {
       label: t('Map'),
-      controlSetRows: [
-        [mapboxStyle, viewport],
-        [autozoom, null],
-      ],
+      controlSetRows: [[mapboxStyle], [autozoom, viewport]],
     },
     {
       label: t('Grid'),
diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utilities/Shared_DeckGL.jsx b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utilities/Shared_DeckGL.jsx
index 9a123e91c3..5b307efd90 100644
--- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utilities/Shared_DeckGL.jsx
+++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utilities/Shared_DeckGL.jsx
@@ -25,6 +25,7 @@ import {
   isFeatureEnabled,
   t,
   validateNonEmpty,
+  validateMapboxStylesUrl,
 } from '@superset-ui/core';
 import { D3_FORMAT_OPTIONS, sharedControls } from '@superset-ui/chart-controls';
 import { columnChoices, PRIMARY_COLOR } from './controls';
@@ -370,6 +371,8 @@ export const mapboxStyle = {
     label: t('Map Style'),
     clearable: false,
     renderTrigger: true,
+    freeForm: true,
+    validators: [validateMapboxStylesUrl],
     choices: [
       ['mapbox://styles/mapbox/streets-v9', t('Streets')],
       ['mapbox://styles/mapbox/dark-v9', t('Dark')],
@@ -379,7 +382,10 @@ export const mapboxStyle = {
       ['mapbox://styles/mapbox/outdoors-v9', t('Outdoors')],
     ],
     default: 'mapbox://styles/mapbox/light-v9',
-    description: t('Base layer map style'),
+    description: t(
+      'Base layer map style. See Mapbox documentation: %s',
+      'https://docs.mapbox.com/help/glossary/style-url/',
+    ),
   },
 };
 


(superset) 06/13: fix(dashboard): Chart menu disable is fixed on chart-fullscreen in issue #25992 (#26410)

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

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

commit 802dc24a76ee55e09b23048c79bd4b15c8a0a712
Author: Siva Sathyaseelan <95...@users.noreply.github.com>
AuthorDate: Thu Jan 4 20:03:07 2024 +0530

    fix(dashboard): Chart menu disable is fixed on chart-fullscreen in issue #25992 (#26410)
    
    (cherry picked from commit d0ffe9af7c802b2a996e3b2029838eedb93aa03b)
---
 superset-frontend/src/components/Dropdown/index.tsx               | 8 +-------
 .../src/dashboard/components/SliceHeaderControls/index.tsx        | 7 +++++++
 .../features/reports/ReportModal/HeaderReportDropdown/index.tsx   | 7 +++++++
 3 files changed, 15 insertions(+), 7 deletions(-)

diff --git a/superset-frontend/src/components/Dropdown/index.tsx b/superset-frontend/src/components/Dropdown/index.tsx
index c40f479579..1e2e03ceb2 100644
--- a/superset-frontend/src/components/Dropdown/index.tsx
+++ b/superset-frontend/src/components/Dropdown/index.tsx
@@ -104,12 +104,6 @@ interface ExtendedDropDownProps extends DropDownProps {
   ref?: RefObject<HTMLDivElement>;
 }
 
-// @z-index-below-dashboard-header (100) - 1 = 99
 export const NoAnimationDropdown = (
   props: ExtendedDropDownProps & { children?: React.ReactNode },
-) => (
-  <AntdDropdown
-    overlayStyle={{ zIndex: 99, animationDuration: '0s' }}
-    {...props}
-  />
-);
+) => <AntdDropdown overlayStyle={props.overlayStyle} {...props} />;
diff --git a/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx b/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx
index 287d83692f..17d5bdc83e 100644
--- a/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx
+++ b/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx
@@ -373,6 +373,12 @@ const SliceHeaderControls = (props: SliceHeaderControlsPropsWithRouter) => {
     ? t('Exit fullscreen')
     : t('Enter fullscreen');
 
+  // @z-index-below-dashboard-header (100) - 1 = 99 for !isFullSize and 101 for isFullSize
+  const dropdownOverlayStyle = {
+    zIndex: isFullSize ? 101 : 99,
+    animationDuration: '0s',
+  };
+
   const menu = (
     <Menu
       onClick={handleMenuClick}
@@ -541,6 +547,7 @@ const SliceHeaderControls = (props: SliceHeaderControlsPropsWithRouter) => {
       )}
       <NoAnimationDropdown
         overlay={menu}
+        overlayStyle={dropdownOverlayStyle}
         trigger={['click']}
         placement="bottomRight"
       >
diff --git a/superset-frontend/src/features/reports/ReportModal/HeaderReportDropdown/index.tsx b/superset-frontend/src/features/reports/ReportModal/HeaderReportDropdown/index.tsx
index b38d44a710..84d19bae69 100644
--- a/superset-frontend/src/features/reports/ReportModal/HeaderReportDropdown/index.tsx
+++ b/superset-frontend/src/features/reports/ReportModal/HeaderReportDropdown/index.tsx
@@ -191,6 +191,12 @@ export default function HeaderReportDropDown({
 
   const showReportSubMenu = report && setShowReportSubMenu && canAddReports();
 
+  // @z-index-below-dashboard-header (100) - 1 = 99
+  const dropdownOverlayStyle = {
+    zIndex: 99,
+    animationDuration: '0s',
+  };
+
   useEffect(() => {
     if (showReportSubMenu) {
       setShowReportSubMenu(true);
@@ -288,6 +294,7 @@ export default function HeaderReportDropDown({
       <>
         <NoAnimationDropdown
           overlay={menu()}
+          overlayStyle={dropdownOverlayStyle}
           trigger={['click']}
           getPopupContainer={(triggerNode: any) =>
             triggerNode.closest('.action-button')