changeSchema({ value: schema }, true)}
tooltipContent={t('Force refresh table list')}
/>
);
-
return renderSelectRow(select, refresh);
}
+ function renderSeeTableLabel() {
+ return (
+
+
+ {t('See table schema')}{' '}
+ {schema && (
+
+ {tableOptions.length} in {schema}
+
+ )}
+
+
+ );
+ }
+
return (
{renderDatabaseSelector()}
- {sqlLabMode && !formMode && }
+ {!formMode && }
+ {sqlLabMode && renderSeeTableLabel()}
+ {formMode && {t('Table')}}
{renderTableSelect()}
);
diff --git a/superset-frontend/src/components/WarningIconWithTooltip/index.tsx b/superset-frontend/src/components/WarningIconWithTooltip/index.tsx
index f732554e15aaa..f160ade50ec72 100644
--- a/superset-frontend/src/components/WarningIconWithTooltip/index.tsx
+++ b/superset-frontend/src/components/WarningIconWithTooltip/index.tsx
@@ -18,17 +18,16 @@
*/
import React from 'react';
import { useTheme, SafeMarkdown } from '@superset-ui/core';
-import Icons, { IconType } from 'src/components/Icons';
+import Icons from 'src/components/Icons';
import { Tooltip } from 'src/components/Tooltip';
export interface WarningIconWithTooltipProps {
warningMarkdown: string;
- size?: IconType['iconSize'];
+ size?: number;
}
function WarningIconWithTooltip({
warningMarkdown,
- size,
}: WarningIconWithTooltipProps) {
const theme = useTheme();
return (
@@ -38,7 +37,6 @@ function WarningIconWithTooltip({
>
diff --git a/superset-frontend/src/dashboard/actions/hydrate.js b/superset-frontend/src/dashboard/actions/hydrate.js
index 9c524e2d05be4..7b0e0ae144b1e 100644
--- a/superset-frontend/src/dashboard/actions/hydrate.js
+++ b/superset-frontend/src/dashboard/actions/hydrate.js
@@ -371,8 +371,8 @@ export const hydrateDashboard = (dashboardData, chartData) => (
// only persistent refreshFrequency will be saved to backend
shouldPersistRefreshFrequency: false,
css: dashboardData.css || '',
- colorNamespace: metadata?.color_namespace || null,
- colorScheme: metadata?.color_scheme || null,
+ colorNamespace: metadata?.color_namespace,
+ colorScheme: metadata?.color_scheme,
editMode: canEdit && editMode,
isPublished: dashboardData.published,
hasUnsavedChanges: false,
diff --git a/superset-frontend/src/dashboard/components/gridComponents/Chart.jsx b/superset-frontend/src/dashboard/components/gridComponents/Chart.jsx
index ae40888fcc4dc..60cbab7295303 100644
--- a/superset-frontend/src/dashboard/components/gridComponents/Chart.jsx
+++ b/superset-frontend/src/dashboard/components/gridComponents/Chart.jsx
@@ -87,7 +87,8 @@ const defaultProps = {
// resizing across all slices on a dashboard on every update
const RESIZE_TIMEOUT = 350;
const SHOULD_UPDATE_ON_PROP_CHANGES = Object.keys(propTypes).filter(
- prop => prop !== 'width' && prop !== 'height',
+ prop =>
+ prop !== 'width' && prop !== 'height' && prop !== 'isComponentVisible',
);
const OVERFLOWABLE_VIZ_TYPES = new Set(['filter_box']);
const DEFAULT_HEADER_HEIGHT = 22;
diff --git a/superset-frontend/src/dashboard/components/gridComponents/ChartHolder.jsx b/superset-frontend/src/dashboard/components/gridComponents/ChartHolder.jsx
index 23f46f292d9dd..87f1cbc78de40 100644
--- a/superset-frontend/src/dashboard/components/gridComponents/ChartHolder.jsx
+++ b/superset-frontend/src/dashboard/components/gridComponents/ChartHolder.jsx
@@ -20,7 +20,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import { useTheme } from '@superset-ui/core';
-import { useSelector } from 'react-redux';
+import { useSelector, connect } from 'react-redux';
import { getChartIdsInFilterScope } from 'src/dashboard/util/activeDashboardFilters';
import Chart from '../../containers/Chart';
@@ -381,4 +381,10 @@ class ChartHolder extends React.Component {
ChartHolder.propTypes = propTypes;
ChartHolder.defaultProps = defaultProps;
-export default ChartHolder;
+function mapStateToProps(state) {
+ return {
+ directPathToChild: state.dashboardState.directPathToChild,
+ directPathLastUpdated: state.dashboardState.directPathLastUpdated,
+ };
+}
+export default connect(mapStateToProps)(ChartHolder);
diff --git a/superset-frontend/src/dashboard/components/gridComponents/Markdown.jsx b/superset-frontend/src/dashboard/components/gridComponents/Markdown.jsx
index 1465b92db1ee9..a21402c3ab24d 100644
--- a/superset-frontend/src/dashboard/components/gridComponents/Markdown.jsx
+++ b/superset-frontend/src/dashboard/components/gridComponents/Markdown.jsx
@@ -18,8 +18,9 @@
*/
import React from 'react';
import PropTypes from 'prop-types';
-
+import { connect } from 'react-redux';
import cx from 'classnames';
+
import { t, SafeMarkdown } from '@superset-ui/core';
import { Logger, LOG_ACTIONS_RENDER_CHART } from 'src/logger/LogUtils';
import { MarkdownEditor } from 'src/components/AsyncAceEditor';
@@ -366,4 +367,10 @@ class Markdown extends React.PureComponent {
Markdown.propTypes = propTypes;
Markdown.defaultProps = defaultProps;
-export default Markdown;
+function mapStateToProps(state) {
+ return {
+ undoLength: state.dashboardLayout.past.length,
+ redoLength: state.dashboardLayout.future.length,
+ };
+}
+export default connect(mapStateToProps)(Markdown);
diff --git a/superset-frontend/src/dashboard/components/gridComponents/Tabs.jsx b/superset-frontend/src/dashboard/components/gridComponents/Tabs.jsx
index a2a74829d0679..9916520c76116 100644
--- a/superset-frontend/src/dashboard/components/gridComponents/Tabs.jsx
+++ b/superset-frontend/src/dashboard/components/gridComponents/Tabs.jsx
@@ -405,6 +405,10 @@ Tabs.propTypes = propTypes;
Tabs.defaultProps = defaultProps;
function mapStateToProps(state) {
- return { nativeFilters: state.nativeFilters };
+ return {
+ nativeFilters: state.nativeFilters,
+ directPathToChild: state.dashboardState.directPathToChild,
+ activeTabs: state.dashboardState.activeTabs,
+ };
}
export default connect(mapStateToProps)(Tabs);
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/utils.ts b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/utils.ts
index 84bd6cbf369ee..af358a09fe214 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/utils.ts
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/utils.ts
@@ -56,7 +56,7 @@ export const checkIsMissingRequiredValue = (
const value = filterState?.value;
// TODO: this property should be unhardcoded
return (
- filter.controlValues.enableEmptyFilter &&
+ filter.controlValues?.enableEmptyFilter &&
(value === null || value === undefined)
);
};
diff --git a/superset-frontend/src/dashboard/containers/Chart.jsx b/superset-frontend/src/dashboard/containers/Chart.jsx
index 4fdfcfdb952cd..c629209d42541 100644
--- a/superset-frontend/src/dashboard/containers/Chart.jsx
+++ b/superset-frontend/src/dashboard/containers/Chart.jsx
@@ -37,7 +37,7 @@ import getFormDataWithExtraFilters from '../util/charts/getFormDataWithExtraFilt
import Chart from '../components/gridComponents/Chart';
import { PLACEHOLDER_DATASOURCE } from '../constants';
-const EMPTY_FILTERS = {};
+const EMPTY_OBJECT = {};
function mapStateToProps(
{
@@ -54,7 +54,7 @@ function mapStateToProps(
ownProps,
) {
const { id } = ownProps;
- const chart = chartQueries[id] || {};
+ const chart = chartQueries[id] || EMPTY_OBJECT;
const datasource =
(chart && chart.form_data && datasources[chart.form_data.datasource]) ||
PLACEHOLDER_DATASOURCE;
@@ -82,7 +82,7 @@ function mapStateToProps(
datasource,
slice: sliceEntities.slices[id],
timeout: dashboardInfo.common.conf.SUPERSET_WEBSERVER_TIMEOUT,
- filters: getActiveFilters() || EMPTY_FILTERS,
+ filters: getActiveFilters() || EMPTY_OBJECT,
formData,
editMode: dashboardState.editMode,
isExpanded: !!dashboardState.expandedSlices[id],
diff --git a/superset-frontend/src/dashboard/containers/DashboardComponent.jsx b/superset-frontend/src/dashboard/containers/DashboardComponent.jsx
index c7ead005701cb..50497152f8509 100644
--- a/superset-frontend/src/dashboard/containers/DashboardComponent.jsx
+++ b/superset-frontend/src/dashboard/containers/DashboardComponent.jsx
@@ -63,8 +63,6 @@ const propTypes = {
};
const defaultProps = {
- directPathToChild: [],
- directPathLastUpdated: 0,
isComponentVisible: true,
};
@@ -77,15 +75,9 @@ function mapStateToProps(
const component = dashboardLayout[id];
const props = {
component,
- dashboardLayout,
parentComponent: dashboardLayout[parentId],
editMode: dashboardState.editMode,
- undoLength: undoableLayout.past.length,
- redoLength: undoableLayout.future.length,
filters: getActiveFilters(),
- directPathToChild: dashboardState.directPathToChild,
- activeTabs: dashboardState.activeTabs,
- directPathLastUpdated: dashboardState.directPathLastUpdated,
dashboardId: dashboardInfo.id,
fullSizeChartId: dashboardState.fullSizeChartId,
};
diff --git a/superset-frontend/src/dashboard/util/activeDashboardFilters.js b/superset-frontend/src/dashboard/util/activeDashboardFilters.js
index 96db2c3129411..30bdc2540aa4b 100644
--- a/superset-frontend/src/dashboard/util/activeDashboardFilters.js
+++ b/superset-frontend/src/dashboard/util/activeDashboardFilters.js
@@ -33,9 +33,7 @@ let allComponents = {};
// output: { [id_column]: { values, scope } }
export function getActiveFilters() {
- return {
- ...activeFilters,
- };
+ return activeFilters;
}
// currently filter_box is a chart,
diff --git a/superset-frontend/src/dashboard/util/charts/getFormDataWithExtraFilters.ts b/superset-frontend/src/dashboard/util/charts/getFormDataWithExtraFilters.ts
index 48cfd8ffcddc8..2587588eac0c3 100644
--- a/superset-frontend/src/dashboard/util/charts/getFormDataWithExtraFilters.ts
+++ b/superset-frontend/src/dashboard/util/charts/getFormDataWithExtraFilters.ts
@@ -25,6 +25,7 @@ import {
import { ChartQueryPayload, Charts, LayoutItem } from 'src/dashboard/types';
import { getExtraFormData } from 'src/dashboard/components/nativeFilters/utils';
import { DataMaskStateWithId } from 'src/dataMask/types';
+import { areObjectsEqual } from 'src/reduxUtils';
import getEffectiveExtraFilters from './getEffectiveExtraFilters';
import { ChartConfiguration, NativeFiltersState } from '../../reducers/types';
import { getAllActiveFilters } from '../activeAllDashboardFilters';
@@ -67,16 +68,18 @@ export default function getFormDataWithExtraFilters({
const labelColors = scale.getColorMap();
// if dashboard metadata + filters have not changed, use cache if possible
+ const cachedFormData = cachedFormdataByChart[sliceId];
if (
- (cachedFiltersByChart[sliceId] || {}) === filters &&
- (colorScheme == null ||
- cachedFormdataByChart[sliceId].color_scheme === colorScheme) &&
- cachedFormdataByChart[sliceId].color_namespace === colorNamespace &&
- isEqual(cachedFormdataByChart[sliceId].label_colors, labelColors) &&
- !!cachedFormdataByChart[sliceId] &&
- dataMask === undefined
+ cachedFiltersByChart[sliceId] === filters &&
+ cachedFormData?.color_scheme === colorScheme &&
+ cachedFormData?.color_namespace === colorNamespace &&
+ isEqual(cachedFormData?.label_colors, labelColors) &&
+ !!cachedFormData &&
+ areObjectsEqual(cachedFormData?.dataMask, dataMask, {
+ ignoreUndefined: true,
+ })
) {
- return cachedFormdataByChart[sliceId];
+ return cachedFormData;
}
let extraData: { extra_form_data?: JsonObject } = {};
diff --git a/superset-frontend/src/datasource/DatasourceEditor.jsx b/superset-frontend/src/datasource/DatasourceEditor.jsx
index d8a0a3425f244..e11b8310bb75c 100644
--- a/superset-frontend/src/datasource/DatasourceEditor.jsx
+++ b/superset-frontend/src/datasource/DatasourceEditor.jsx
@@ -775,47 +775,41 @@ class DatasourceEditor extends React.PureComponent {
{this.state.isSqla && (
<>
-
-
-
- this.state.isEditMode &&
- this.onDatasourcePropChange('schema', schema)
- }
- onDbChange={database =>
- this.state.isEditMode &&
- this.onDatasourcePropChange('database', database)
- }
- formMode={false}
- handleError={this.props.addDangerToast}
- readOnly={!this.state.isEditMode}
- />
-
- }
- />
-
- {
- this.onDatasourcePropChange('table_name', table);
- }}
- placeholder={t('Dataset name')}
- disabled={!this.state.isEditMode}
- />
+
+ this.state.isEditMode &&
+ this.onDatasourcePropChange('schema', schema)
+ }
+ onDbChange={database =>
+ this.state.isEditMode &&
+ this.onDatasourcePropChange('database', database)
}
+ formMode={false}
+ handleError={this.props.addDangerToast}
+ readOnly={!this.state.isEditMode}
/>
-
-
+ }
+ />
+ {
+ this.onDatasourcePropChange('table_name', table);
+ }}
+ placeholder={t('Dataset name')}
+ disabled={!this.state.isEditMode}
+ />
+ }
+ />
-
- this.onDatasourcePropChange('schema', schema)
- : undefined
- }
- onDbChange={
- this.state.isEditMode
- ? database =>
- this.onDatasourcePropChange(
- 'database',
- database,
- )
- : undefined
- }
- onTableChange={
- this.state.isEditMode
- ? table =>
- this.onDatasourcePropChange('table_name', table)
- : undefined
- }
- readOnly={!this.state.isEditMode}
- />
-
+
+ this.onDatasourcePropChange('schema', schema)
+ : undefined
+ }
+ onDbChange={
+ this.state.isEditMode
+ ? database =>
+ this.onDatasourcePropChange('database', database)
+ : undefined
+ }
+ onTableChange={
+ this.state.isEditMode
+ ? table =>
+ this.onDatasourcePropChange('table_name', table)
+ : undefined
+ }
+ readOnly={!this.state.isEditMode}
+ />
}
description={t(
'The pointer to a physical table (or view). Keep in mind that the chart is ' +
diff --git a/superset-frontend/src/explore/components/controls/DatasourceControl/index.jsx b/superset-frontend/src/explore/components/controls/DatasourceControl/index.jsx
index 3df55323d11d7..9278c29e79c09 100644
--- a/superset-frontend/src/explore/components/controls/DatasourceControl/index.jsx
+++ b/superset-frontend/src/explore/components/controls/DatasourceControl/index.jsx
@@ -227,7 +227,10 @@ class DatasourceControl extends React.PureComponent {
)}
{extra?.warning_markdown && (
-
+
)}
{
const onNewMetric = useCallback(
(newMetric: Metric) => {
- const newValue = props.multi
- ? [...value, newMetric.metric_name]
- : [newMetric.metric_name];
+ const newValue = props.multi ? [...value, newMetric] : [newMetric];
setValue(newValue);
handleChange(newValue);
},
diff --git a/superset-frontend/src/views/CRUD/data/dataset/DatasetList.tsx b/superset-frontend/src/views/CRUD/data/dataset/DatasetList.tsx
index f50473cb155e1..496decf71e774 100644
--- a/superset-frontend/src/views/CRUD/data/dataset/DatasetList.tsx
+++ b/superset-frontend/src/views/CRUD/data/dataset/DatasetList.tsx
@@ -243,13 +243,11 @@ const DatasetList: FunctionComponent = ({
)}
{parsedExtra?.warning_markdown && (
)}
{titleLink}
diff --git a/superset/commands/importers/v1/examples.py b/superset/commands/importers/v1/examples.py
index 66563f856b620..b7431358dd9f3 100644
--- a/superset/commands/importers/v1/examples.py
+++ b/superset/commands/importers/v1/examples.py
@@ -43,8 +43,8 @@
from superset.datasets.commands.importers.v1 import ImportDatasetsCommand
from superset.datasets.commands.importers.v1.utils import import_dataset
from superset.datasets.schemas import ImportV1DatasetSchema
-from superset.models.core import Database
from superset.models.dashboard import dashboard_slices
+from superset.utils.core import get_example_database
class ImportExamplesCommand(ImportModelsCommand):
@@ -104,9 +104,7 @@ def _import(
# If database_uuid is not in the list of UUIDs it means that the examples
# database was created before its UUID was frozen, so it has a random UUID.
# We need to determine its ID so we can point the dataset to it.
- examples_db = (
- db.session.query(Database).filter_by(database_name="examples").first()
- )
+ examples_db = get_example_database()
dataset_info: Dict[str, Dict[str, Any]] = {}
for file_name, config in configs.items():
if file_name.startswith("datasets/"):
diff --git a/superset/datasets/api.py b/superset/datasets/api.py
index 261a7d749e40e..8d8eb6efee875 100644
--- a/superset/datasets/api.py
+++ b/superset/datasets/api.py
@@ -160,7 +160,7 @@ class DatasetRestApi(BaseSupersetModelRestApi):
"url",
"extra",
]
- show_columns = show_select_columns + ["columns.type_generic", "database.backend"]
+ show_columns = show_select_columns + ["columns.type_generic"]
add_model_schema = DatasetPostSchema()
edit_model_schema = DatasetPutSchema()
add_columns = ["database", "schema", "table_name", "owners"]
diff --git a/superset/models/slice.py b/superset/models/slice.py
index b4c7f6604f9a0..9093cfa43acc7 100644
--- a/superset/models/slice.py
+++ b/superset/models/slice.py
@@ -53,7 +53,7 @@
logger = logging.getLogger(__name__)
-class Slice( # pylint: disable=too-many-public-methods
+class Slice( # pylint: disable=too-many-public-methods, too-many-instance-attributes
Model, AuditMixinNullable, ImportExportMixin
):
"""A slice is essentially a report or a view on data"""
diff --git a/superset/views/core.py b/superset/views/core.py
index 29109f13c3b53..d7e626e261b64 100755
--- a/superset/views/core.py
+++ b/superset/views/core.py
@@ -1058,14 +1058,8 @@ def schemas( # pylint: disable=no-self-use
@event_logger.log_this
@expose("/tables////")
@expose("/tables/////")
- @expose("/tables/////")
- def tables( # pylint: disable=too-many-locals,no-self-use,too-many-arguments
- self,
- db_id: int,
- schema: str,
- substr: str,
- force_refresh: str = "false",
- exact_match: str = "false",
+ def tables( # pylint: disable=too-many-locals,no-self-use
+ self, db_id: int, schema: str, substr: str, force_refresh: str = "false"
) -> FlaskResponse:
"""Endpoint to fetch the list of tables for given database"""
# Guarantees database filtering by security access
@@ -1078,7 +1072,6 @@ def tables( # pylint: disable=too-many-locals,no-self-use,too-many-arguments
return json_error_response("Not found", 404)
force_refresh_parsed = force_refresh.lower() == "true"
- exact_match_parsed = exact_match.lower() == "true"
schema_parsed = utils.parse_js_uri_path_item(schema, eval_undefined=True)
substr_parsed = utils.parse_js_uri_path_item(substr, eval_undefined=True)
@@ -1120,15 +1113,9 @@ def get_datasource_label(ds_name: utils.DatasourceName) -> str:
ds_name.table if schema_parsed else f"{ds_name.schema}.{ds_name.table}"
)
- def is_match(src: str, target: utils.DatasourceName) -> bool:
- target_label = get_datasource_label(target)
- if exact_match_parsed:
- return src == target_label
- return src in target_label
-
if substr_parsed:
- tables = [tn for tn in tables if is_match(substr_parsed, tn)]
- views = [vn for vn in views if is_match(substr_parsed, vn)]
+ tables = [tn for tn in tables if substr_parsed in get_datasource_label(tn)]
+ views = [vn for vn in views if substr_parsed in get_datasource_label(vn)]
if not schema_parsed and database.default_schemas:
user_schemas = (
diff --git a/tests/integration_tests/datasets/api_tests.py b/tests/integration_tests/datasets/api_tests.py
index c9701534b2664..385025e3835cc 100644
--- a/tests/integration_tests/datasets/api_tests.py
+++ b/tests/integration_tests/datasets/api_tests.py
@@ -222,7 +222,6 @@ def test_get_dataset_item(self):
Dataset API: Test get dataset item
"""
table = self.get_energy_usage_dataset()
- main_db = get_main_database()
self.login(username="admin")
uri = f"api/v1/dataset/{table.id}"
rv = self.get_assert_metric(uri, "get")
@@ -230,11 +229,7 @@ def test_get_dataset_item(self):
response = json.loads(rv.data.decode("utf-8"))
expected_result = {
"cache_timeout": None,
- "database": {
- "backend": main_db.backend,
- "database_name": "examples",
- "id": 1,
- },
+ "database": {"database_name": "examples", "id": 1},
"default_endpoint": None,
"description": "Energy consumption",
"extra": None,
@@ -249,10 +244,9 @@ def test_get_dataset_item(self):
"table_name": "energy_usage",
"template_params": None,
}
- if response["result"]["database"]["backend"] not in ("presto", "hive"):
- assert {
- k: v for k, v in response["result"].items() if k in expected_result
- } == expected_result
+ assert {
+ k: v for k, v in response["result"].items() if k in expected_result
+ } == expected_result
assert len(response["result"]["columns"]) == 3
assert len(response["result"]["metrics"]) == 2