diff --git a/docs/static/resources/openapi.json b/docs/static/resources/openapi.json index 13d5c8c21c..7c941662ad 100644 --- a/docs/static/resources/openapi.json +++ b/docs/static/resources/openapi.json @@ -3613,6 +3613,9 @@ "disable_data_preview": { "type": "boolean" }, + "disable_drill_to_detail": { + "type": "boolean" + }, "explore_database_id": { "type": "integer" }, diff --git a/superset-frontend/src/components/Chart/DrillDetail/DrillDetailMenuItems.test.tsx b/superset-frontend/src/components/Chart/DrillDetail/DrillDetailMenuItems.test.tsx index 580bf4c522..73cc4a2868 100644 --- a/superset-frontend/src/components/Chart/DrillDetail/DrillDetailMenuItems.test.tsx +++ b/superset-frontend/src/components/Chart/DrillDetail/DrillDetailMenuItems.test.tsx @@ -285,11 +285,11 @@ test('context menu for supported chart, no dimensions, no filters', async () => isContextMenu: true, }); - await expectDrillToDetailDisabled( - 'Drill to detail is disabled because this chart does not group data by dimension value.', - ); + const message = + 'Drill to detail is disabled because this chart does not group data by dimension value.'; - await expectDrillToDetailByDisabled(); + await expectDrillToDetailDisabled(message); + await expectDrillToDetailByDisabled(message); }); test('context menu for supported chart, no dimensions, 1 filter', async () => { @@ -299,11 +299,11 @@ test('context menu for supported chart, no dimensions, 1 filter', async () => { filters: [filterA], }); - await expectDrillToDetailDisabled( - 'Drill to detail is disabled because this chart does not group data by dimension value.', - ); + const message = + 'Drill to detail is disabled because this chart does not group data by dimension value.'; - await expectDrillToDetailByDisabled(); + await expectDrillToDetailDisabled(message); + await expectDrillToDetailByDisabled(message); }); test('dropdown menu for supported chart, dimensions', async () => { diff --git a/superset-frontend/src/components/Chart/DrillDetail/DrillDetailMenuItems.tsx b/superset-frontend/src/components/Chart/DrillDetail/DrillDetailMenuItems.tsx index 6ef9b331e6..6c1669933b 100644 --- a/superset-frontend/src/components/Chart/DrillDetail/DrillDetailMenuItems.tsx +++ b/superset-frontend/src/components/Chart/DrillDetail/DrillDetailMenuItems.tsx @@ -30,13 +30,30 @@ import { styled, t, } from '@superset-ui/core'; +import { useSelector } from 'react-redux'; import { Menu } from 'src/components/Menu'; +import { RootState } from 'src/dashboard/types'; import DrillDetailModal from './DrillDetailModal'; import { getSubmenuYOffset } from '../utils'; import { MenuItemTooltip } from '../DisabledMenuItemTooltip'; import { MenuItemWithTruncation } from '../MenuItemWithTruncation'; -const DRILL_TO_DETAIL_TEXT = t('Drill to detail by'); +const DRILL_TO_DETAIL = t('Drill to detail'); +const DRILL_TO_DETAIL_BY = t('Drill to detail by'); +const DISABLED_REASONS = { + DATABASE: t( + 'Drill to detail is disabled for this database. Change the database settings to enable it.', + ), + NO_AGGREGATIONS: t( + 'Drill to detail is disabled because this chart does not group data by dimension value.', + ), + NO_FILTERS: t( + 'Right-click on a dimension value to drill to detail by that value.', + ), + NOT_SUPPORTED: t( + 'Drill to detail by value is not yet supported for this chart type.', + ), +}; const DisabledMenuItem = ({ children, ...props }: { children: ReactNode }) => ( @@ -94,6 +111,11 @@ const DrillDetailMenuItems = ({ submenuIndex = 0, ...props }: DrillDetailMenuItemsProps) => { + const drillToDetailDisabled = useSelector( + ({ datasources }) => + datasources[formData.datasource]?.database?.disable_drill_to_detail, + ); + const [modalFilters, setFilters] = useState( [], ); @@ -132,52 +154,6 @@ const DrillDetailMenuItems = ({ return isEmpty(metrics); }, [formData]); - let drillToDetailMenuItem; - if (handlesDimensionContextMenu && noAggregations) { - drillToDetailMenuItem = ( - - {t('Drill to detail')} - - - ); - } else { - drillToDetailMenuItem = ( - - {t('Drill to detail')} - - ); - } - - let drillToDetailByMenuItem; - if (!handlesDimensionContextMenu) { - drillToDetailByMenuItem = ( - - {DRILL_TO_DETAIL_TEXT} - - - ); - } - - if (handlesDimensionContextMenu && noAggregations) { - drillToDetailByMenuItem = ( - - {DRILL_TO_DETAIL_TEXT} - - ); - } - // Ensure submenu doesn't appear offscreen const submenuYOffset = useMemo( () => @@ -189,55 +165,76 @@ const DrillDetailMenuItems = ({ [contextMenuY, filters.length, submenuIndex], ); - if (handlesDimensionContextMenu && !noAggregations && filters?.length) { - drillToDetailByMenuItem = ( - -
- {filters.map((filter, i) => ( - - {`${DRILL_TO_DETAIL_TEXT} `} - {filter.formattedVal} - - ))} - {filters.length > 1 && ( - -
- {`${DRILL_TO_DETAIL_TEXT} `} - {t('all')} -
-
- )} -
-
- ); + let drillDisabled; + let drillByDisabled; + if (drillToDetailDisabled) { + drillDisabled = DISABLED_REASONS.DATABASE; + drillByDisabled = DISABLED_REASONS.DATABASE; + } else if (handlesDimensionContextMenu) { + if (noAggregations) { + drillDisabled = DISABLED_REASONS.NO_AGGREGATIONS; + drillByDisabled = DISABLED_REASONS.NO_AGGREGATIONS; + } else if (!filters?.length) { + drillByDisabled = DISABLED_REASONS.NO_FILTERS; + } + } else { + drillByDisabled = DISABLED_REASONS.NOT_SUPPORTED; } - if (handlesDimensionContextMenu && !noAggregations && !filters?.length) { - drillToDetailByMenuItem = ( - - {DRILL_TO_DETAIL_TEXT} - - - ); - } + const drillToDetailMenuItem = drillDisabled ? ( + + {DRILL_TO_DETAIL} + + + ) : ( + + {DRILL_TO_DETAIL} + + ); + + const drillToDetailByMenuItem = drillByDisabled ? ( + + {DRILL_TO_DETAIL_BY} + + + ) : ( + +
+ {filters.map((filter, i) => ( + + {`${DRILL_TO_DETAIL_BY} `} + {filter.formattedVal} + + ))} + {filters.length > 1 && ( + +
+ {`${DRILL_TO_DETAIL_BY} `} + {t('all')} +
+
+ )} +
+
+ ); return ( <> diff --git a/superset-frontend/src/components/DatabaseSelector/DatabaseSelector.test.tsx b/superset-frontend/src/components/DatabaseSelector/DatabaseSelector.test.tsx index 18e6769912..c3ad51cf60 100644 --- a/superset-frontend/src/components/DatabaseSelector/DatabaseSelector.test.tsx +++ b/superset-frontend/src/components/DatabaseSelector/DatabaseSelector.test.tsx @@ -62,6 +62,7 @@ const fakeDatabaseApiResult = { allows_subquery: 'Allows Subquery', allows_virtual_table_explore: 'Allows Virtual Table Explore', disable_data_preview: 'Disables SQL Lab Data Preview', + disable_drill_to_detail: 'Disable Drill To Detail', backend: 'Backend', changed_on: 'Changed On', changed_on_delta_humanized: 'Changed On Delta Humanized', @@ -83,6 +84,7 @@ const fakeDatabaseApiResult = { 'allows_subquery', 'allows_virtual_table_explore', 'disable_data_preview', + 'disable_drill_to_detail', 'backend', 'changed_on', 'changed_on_delta_humanized', @@ -116,6 +118,7 @@ const fakeDatabaseApiResult = { allows_subquery: true, allows_virtual_table_explore: true, disable_data_preview: false, + disable_drill_to_detail: false, backend: 'postgresql', changed_on: '2021-03-09T19:02:07.141095', changed_on_delta_humanized: 'a day ago', @@ -136,6 +139,7 @@ const fakeDatabaseApiResult = { allows_subquery: true, allows_virtual_table_explore: true, disable_data_preview: false, + disable_drill_to_detail: false, backend: 'mysql', changed_on: '2021-03-09T19:02:07.141095', changed_on_delta_humanized: 'a day ago', diff --git a/superset-frontend/src/dashboard/types.ts b/superset-frontend/src/dashboard/types.ts index bd70398eca..5aa8020b4d 100644 --- a/superset-frontend/src/dashboard/types.ts +++ b/superset-frontend/src/dashboard/types.ts @@ -29,6 +29,7 @@ import { import { Dataset } from '@superset-ui/chart-controls'; import { chart } from 'src/components/Chart/chartReducer'; import componentTypes from 'src/dashboard/util/componentTypes'; +import Database from 'src/types/Database'; import { UrlParamEntries } from 'src/utils/urlUtils'; import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes'; @@ -143,6 +144,7 @@ export type Datasource = Dataset & { uid: string; column_types: GenericDataType[]; table_name: string; + database?: Database; }; export type DatasourcesState = { [key: string]: Datasource; diff --git a/superset-frontend/src/features/databases/DatabaseModal/ExtraOptions.tsx b/superset-frontend/src/features/databases/DatabaseModal/ExtraOptions.tsx index 45706da586..28e02a02f0 100644 --- a/superset-frontend/src/features/databases/DatabaseModal/ExtraOptions.tsx +++ b/superset-frontend/src/features/databases/DatabaseModal/ExtraOptions.tsx @@ -571,6 +571,22 @@ const ExtraOptions = ({ )} + +
+ + +
+
); diff --git a/superset-frontend/src/features/databases/types.ts b/superset-frontend/src/features/databases/types.ts index 58d533c7be..2dff61d5e8 100644 --- a/superset-frontend/src/features/databases/types.ts +++ b/superset-frontend/src/features/databases/types.ts @@ -222,6 +222,7 @@ export interface ExtraJson { cancel_query_on_windows_unload?: boolean; // in Performance cost_estimate_enabled?: boolean; // in SQL Lab disable_data_preview?: boolean; // in SQL Lab + disable_drill_to_detail?: boolean; engine_params?: { catalog?: Record; connect_args?: { diff --git a/superset-frontend/src/features/datasets/AddDataset/LeftPanel/LeftPanel.test.tsx b/superset-frontend/src/features/datasets/AddDataset/LeftPanel/LeftPanel.test.tsx index 5156073281..36eee842a9 100644 --- a/superset-frontend/src/features/datasets/AddDataset/LeftPanel/LeftPanel.test.tsx +++ b/superset-frontend/src/features/datasets/AddDataset/LeftPanel/LeftPanel.test.tsx @@ -43,6 +43,7 @@ beforeEach(() => { allows_subquery: 'Allows Subquery', allows_virtual_table_explore: 'Allows Virtual Table Explore', disable_data_preview: 'Disables SQL Lab Data Preview', + disable_drill_to_detail: 'Disable Drill To Detail', backend: 'Backend', changed_on: 'Changed On', changed_on_delta_humanized: 'Changed On Delta Humanized', @@ -65,6 +66,7 @@ beforeEach(() => { 'allows_subquery', 'allows_virtual_table_explore', 'disable_data_preview', + 'disable_drill_to_detail', 'backend', 'changed_on', 'changed_on_delta_humanized', @@ -99,6 +101,7 @@ beforeEach(() => { allows_subquery: true, allows_virtual_table_explore: true, disable_data_preview: false, + disable_drill_to_detail: false, backend: 'postgresql', changed_on: '2021-03-09T19:02:07.141095', changed_on_delta_humanized: 'a day ago', @@ -120,6 +123,7 @@ beforeEach(() => { allows_subquery: true, allows_virtual_table_explore: true, disable_data_preview: false, + disable_drill_to_detail: false, backend: 'mysql', changed_on: '2021-03-09T19:02:07.141095', changed_on_delta_humanized: 'a day ago', diff --git a/superset-frontend/src/types/Database.ts b/superset-frontend/src/types/Database.ts index 575d69e2f2..69e86e3a7b 100644 --- a/superset-frontend/src/types/Database.ts +++ b/superset-frontend/src/types/Database.ts @@ -28,4 +28,5 @@ export default interface Database { sqlalchemy_uri: string; catalog: object; parameters: any; + disable_drill_to_detail?: boolean; } diff --git a/superset/dashboards/schemas.py b/superset/dashboards/schemas.py index a208bae0f9..83c533b86e 100644 --- a/superset/dashboards/schemas.py +++ b/superset/dashboards/schemas.py @@ -215,6 +215,7 @@ class DatabaseSchema(Schema): allows_cost_estimate = fields.Bool() allows_virtual_table_explore = fields.Bool() disable_data_preview = fields.Bool() + disable_drill_to_detail = fields.Bool() explore_database_id = fields.Int() diff --git a/superset/databases/api.py b/superset/databases/api.py index 1e44a52106..6f9cb2a921 100644 --- a/superset/databases/api.py +++ b/superset/databases/api.py @@ -176,6 +176,7 @@ class DatabaseRestApi(BaseSupersetModelRestApi): "id", "uuid", "disable_data_preview", + "disable_drill_to_detail", "engine_information", ] add_columns = [ diff --git a/superset/databases/schemas.py b/superset/databases/schemas.py index 65005824d5..d4a2fc021f 100644 --- a/superset/databases/schemas.py +++ b/superset/databases/schemas.py @@ -132,7 +132,9 @@ extra_description = markdown( "5. The ``allows_virtual_table_explore`` field is a boolean specifying " "whether or not the Explore button in SQL Lab results is shown.
" "6. The ``disable_data_preview`` field is a boolean specifying whether or not data " - "preview queries will be run when fetching table metadata in SQL Lab.", + "preview queries will be run when fetching table metadata in SQL Lab." + "7. The ``disable_drill_to_detail`` field is a boolean specifying whether or not" + "drill to detail is disabled for the database.", True, ) get_export_ids_schema = {"type": "array", "items": {"type": "integer"}} @@ -750,6 +752,7 @@ class ImportV1DatabaseExtraSchema(Schema): allows_virtual_table_explore = fields.Boolean(required=False) cancel_query_on_windows_unload = fields.Boolean(required=False) disable_data_preview = fields.Boolean(required=False) + disable_drill_to_detail = fields.Boolean(required=False) version = fields.String(required=False, allow_none=True) diff --git a/superset/models/core.py b/superset/models/core.py index 71a6e9d042..ee8297eb11 100755 --- a/superset/models/core.py +++ b/superset/models/core.py @@ -91,7 +91,6 @@ DB_CONNECTION_MUTATOR = config["DB_CONNECTION_MUTATOR"] class KeyValue(Model): # pylint: disable=too-few-public-methods - """Used for any type of key-value store""" __tablename__ = "keyvalue" @@ -116,7 +115,6 @@ class ConfigurationMethod(StrEnum): class Database( Model, AuditMixinNullable, ImportExportMixin ): # pylint: disable=too-many-public-methods - """An ORM object that stores Database related information""" __tablename__ = "dbs" @@ -229,6 +227,11 @@ class Database( # this will prevent any 'trash value' strings from going through return self.get_extra().get("disable_data_preview", False) is True + @property + def disable_drill_to_detail(self) -> bool: + # this will prevent any 'trash value' strings from going through + return self.get_extra().get("disable_drill_to_detail", False) is True + @property def schema_options(self) -> dict[str, Any]: """Additional schema display config for engines with complex schemas""" @@ -248,6 +251,7 @@ class Database( "schema_options": self.schema_options, "parameters": self.parameters, "disable_data_preview": self.disable_data_preview, + "disable_drill_to_detail": self.disable_drill_to_detail, "parameters_schema": self.parameters_schema, "engine_information": self.engine_information, } diff --git a/superset/sqllab/utils.py b/superset/sqllab/utils.py index ca331d1e34..bbf3919640 100644 --- a/superset/sqllab/utils.py +++ b/superset/sqllab/utils.py @@ -38,6 +38,7 @@ DATABASE_KEYS = [ "force_ctas_schema", "id", "disable_data_preview", + "disable_drill_to_detail", ] diff --git a/superset/views/database/mixins.py b/superset/views/database/mixins.py index dc48950353..c8fdaae500 100644 --- a/superset/views/database/mixins.py +++ b/superset/views/database/mixins.py @@ -147,7 +147,9 @@ class DatabaseMixin: "whether or not the Explore button in SQL Lab results is shown
" "6. The ``disable_data_preview`` field is a boolean specifying whether or" "not data preview queries will be run when fetching table metadata in" - "SQL Lab.", + "SQL Lab." + "7. The ``disable_drill_to_detail`` field is a boolean specifying whether or" + "not drill to detail is disabled for the database.", True, ), "encrypted_extra": utils.markdown( diff --git a/tests/integration_tests/core_tests.py b/tests/integration_tests/core_tests.py index ceb2bf7782..0437e3a92a 100644 --- a/tests/integration_tests/core_tests.py +++ b/tests/integration_tests/core_tests.py @@ -1073,6 +1073,29 @@ class TestCore(SupersetTestCase): database.extra = json.dumps(extra) self.assertEqual(database.disable_data_preview, False) + def test_disable_drill_to_detail(self): + # test that disable_drill_to_detail is False by default + database = utils.get_example_database() + self.assertEqual(database.disable_drill_to_detail, False) + + # test that disable_drill_to_detail can be set to True + extra = database.get_extra() + extra["disable_drill_to_detail"] = True + database.extra = json.dumps(extra) + self.assertEqual(database.disable_drill_to_detail, True) + + # test that disable_drill_to_detail can be set to False + extra = database.get_extra() + extra["disable_drill_to_detail"] = False + database.extra = json.dumps(extra) + self.assertEqual(database.disable_drill_to_detail, False) + + # test that disable_drill_to_detail is not broken with bad values + extra = database.get_extra() + extra["disable_drill_to_detail"] = "trash value" + database.extra = json.dumps(extra) + self.assertEqual(database.disable_drill_to_detail, False) + def test_explore_database_id(self): database = superset.utils.database.get_example_database() explore_database = superset.utils.database.get_example_database() diff --git a/tests/integration_tests/databases/api_tests.py b/tests/integration_tests/databases/api_tests.py index 0f9dc03723..249b953960 100644 --- a/tests/integration_tests/databases/api_tests.py +++ b/tests/integration_tests/databases/api_tests.py @@ -204,6 +204,7 @@ class TestDatabaseApi(SupersetTestCase): "created_by", "database_name", "disable_data_preview", + "disable_drill_to_detail", "engine_information", "explore_database_id", "expose_in_sqllab",