feat: Enable drilling in embedded#34319
Conversation
There was a problem hiding this comment.
Review by Korbit AI
Korbit automatically attempts to detect when you fix issues in new commits.
| Category | Issue | Status |
|---|---|---|
| Missing Required Field Specification in Schema ▹ view | ||
| Missing Null Safety Checks in post_dump ▹ view | 🧠 Not in scope | |
| Unmemoized Length Calculation ▹ view | 🧠 Not in scope | |
| Undocumented mock dataset structure ▹ view | 🧠 Not in standard | |
| Unclear embedded context comment ▹ view | 🧠 Not in standard | |
| Missing Column Filtering Logic ▹ view | 🧠 Incorrect | |
| Feature Flag Not Alphabetically Ordered ▹ view | 🧠 Not in standard | |
| Incomplete State Handling in Embedded Mode ▹ view | 🧠 Not in scope | |
| Insufficient Error Context ▹ view | 🧠 Not in scope | |
| Unjustified CSRF Protection Bypass ▹ view | 🧠 Not in scope |
Files scanned
| File Path | Reviewed |
|---|---|
| superset-frontend/src/features/datasets/metadataBar/DatasetMetadataBar.skipped-stories.tsx | ✅ |
| superset-frontend/packages/superset-ui-core/src/utils/featureFlags.ts | ✅ |
| superset-frontend/src/components/Chart/DrillDetail/DrillDetailModal.tsx | ✅ |
| superset-frontend/src/features/datasets/metadataBar/useDatasetMetadataBar.tsx | ✅ |
| superset-frontend/src/components/Chart/DrillDetail/DrillDetailMenuItems.tsx | ✅ |
| superset/views/datasource/views.py | ✅ |
| superset-frontend/src/components/Chart/DrillDetail/DrillDetailPane.tsx | ✅ |
| superset-frontend/src/components/Chart/DrillBy/DrillByMenuItems.tsx | ✅ |
| superset/datasets/schemas.py | ✅ |
| superset-frontend/src/components/Chart/ChartContextMenu/ChartContextMenu.tsx | ✅ |
| superset-frontend/src/components/Chart/DrillBy/DrillByModal.tsx | ✅ |
| superset/datasets/api.py | ✅ |
| superset/config.py | ✅ |
| superset/security/manager.py | ✅ |
Explore our documentation to understand the languages and file types we support and the files we ignore.
Check out our docs on how you can make Korbit work best for you and your team.
| const showSearch = | ||
| loadDrillByOptions || columns.length > SHOW_COLUMNS_SEARCH_THRESHOLD; | ||
| const columns = dataset ? ensureIsArray(dataset.columns) : []; | ||
| const showSearch = columns.length > SHOW_COLUMNS_SEARCH_THRESHOLD; |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| SupersetClient.configure({ csrfToken: '1234' }).init(); | ||
| const { metadataBar } = useDatasetMetadataBar({ datasetId: 1 }); | ||
|
|
||
| const mockDataset = { |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| }, [datasetId, datasetProps]); | ||
|
|
||
| const metadataBar = useMemo(() => { | ||
| // Short-circuit for embedded users - they don't need metadata bar |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| const ref = useRef<InputRef>(null); | ||
| const showSearch = | ||
| loadDrillByOptions || columns.length > SHOW_COLUMNS_SEARCH_THRESHOLD; | ||
| const columns = dataset ? ensureIsArray(dataset.columns) : []; |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| EmbeddableCharts = 'EMBEDDABLE_CHARTS', | ||
| EmbeddedSuperset = 'EMBEDDED_SUPERSET', | ||
| EnableAdvancedDataTypes = 'ENABLE_ADVANCED_DATA_TYPES', | ||
| DrillingInEmbedded = 'DRILLING_IN_EMBEDDED', |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| get_drill_info_schema = { | ||
| "type": "object", | ||
| "properties": { | ||
| "dashboard_id": {"type": "integer"}, | ||
| }, | ||
| } |
There was a problem hiding this comment.
Missing Required Field Specification in Schema 
Tell me more
What is the issue?
The schema for dashboard_id in get_drill_info_schema lacks the 'required' field specification
Why this matters
Without specifying if dashboard_id is required, the API may accept invalid requests without this crucial parameter, potentially causing drill functionality to fail
Suggested change ∙ Feature Preview
Add the 'required' field to explicitly define whether dashboard_id is mandatory:
get_drill_info_schema = {
"type": "object",
"properties": {
"dashboard_id": {"type": "integer"},
},
"required": ["dashboard_id"]
}Provide feedback to improve future suggestions
💬 Looking for more details? Reply to this comment to chat with Korbit.
There was a problem hiding this comment.
The parameter is only required for limited roles -- it works for admins or users with proper dataset acces without this param.
There was a problem hiding this comment.
I see - since dashboard_id is only required for limited roles but optional for admins/users with dataset access, keeping it as an optional parameter in the schema makes sense. Thanks for clarifying.
| @post_dump(pass_original=True) | ||
| def post_dump( | ||
| self, serialized: dict[str, Any], obj: SqlaTable, **kwargs: Any | ||
| ) -> dict[str, Any]: |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| WTF_CSRF_EXEMPT_LIST = [ | ||
| "superset.views.core.log", | ||
| "superset.views.core.explore_json", | ||
| "superset.charts.data.api.data", | ||
| "superset.dashboards.api.cache_dashboard_screenshot", | ||
| "superset.views.core.explore_json", | ||
| "superset.views.core.log", | ||
| "superset.views.datasource.views.samples", | ||
| ] |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| // short circuit if the user is embedded as explore is not available | ||
| if (isEmbedded()) return; |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| } catch (error) { | ||
| logging.error('Failed to load dataset:', error); | ||
| supersetGetCache.delete(`/api/v1/dataset/${datasetId}/drill_info/`); |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
|
@Vitor-Avila Do we really need a new feature flag? Should this be a dashboard property such cross filters? |
Hmm open to suggestions here. We could potentially fully remove the FF and rely solely on validating if the role has the required perms to D2D. Would you say that's better? |
Definitely. Every time we introduce a new feature flag we increase the complexity and maintainability of the codebase. In this case, permissions seem the natural way. @rusackas pointed out that we could also have readonly embedded dashboards, which in this case a configuration like we have for cross-filtering would be more appropriate. Either way works for me and it's better than adding a new feature flag. |
Awesome -- I'll work on moving to perm-based validation only and remove the FF. I would prefer on that because then we can potentially scope it by user, as opposed to by dashboard. Users can decide what's the desired perms for their embedded users, and if they need it dynamically they could have two roles and dynamically change the role assigned. In the future, if drilling can be disabled at the dashboard level, we can then respect that too in the embedded context (like a dashboard-level override) that could potentially be controlled via the embedded SDK too. |
5382fd6 to
6edf7e4
Compare
|
@michael-s-molina I've changed the approach here to remove the FF. Could you please take a look? 🙏 |
9b09030 to
c994fa2
Compare
1b09b50 to
59fbd99
Compare
| useEffect(() => { | ||
| async function loadOptions() { | ||
| const datasetId = Number(formData.datasource.split('__')[0]); | ||
| try { | ||
| setIsLoadingColumns(true); | ||
| let response: JsonResponse; | ||
| if (loadDrillByOptions) { | ||
| response = await loadDrillByOptions(datasetId, formData); | ||
| } else { | ||
| response = await cachedSupersetGet({ | ||
| endpoint: `/api/v1/dataset/${datasetId}?q=${queryString}`, | ||
| }); | ||
| } | ||
| const { json } = response; | ||
| const { result } = json; | ||
| setDataset(result); | ||
| setColumns( | ||
| ensureIsArray(result.columns) | ||
| .filter(column => column.groupby) | ||
| .filter( | ||
| column => | ||
| !ensureIsArray( | ||
| formData[drillByConfig?.groupbyFieldName ?? ''], | ||
| ).includes(column.column_name) && | ||
| column.column_name !== formData.x_axis && | ||
| ensureIsArray(excludedColumns)?.every( | ||
| excludedCol => excludedCol.column_name !== column.column_name, | ||
| ), | ||
| ), | ||
| ); | ||
| } catch (error) { | ||
| logging.error(error); | ||
| supersetGetCache.delete(`/api/v1/dataset/${datasetId}`); | ||
| addDangerToast(t('Failed to load dimensions for drill by')); | ||
| } finally { | ||
| setIsLoadingColumns(false); | ||
| } | ||
| } | ||
| if (handlesDimensionContextMenu && hasDrillBy) { | ||
| loadOptions(); | ||
| } | ||
| }, [ | ||
| addDangerToast, | ||
| drillByConfig?.groupbyFieldName, | ||
| excludedColumns, | ||
| formData, | ||
| handlesDimensionContextMenu, | ||
| hasDrillBy, | ||
| ]); | ||
|
|
There was a problem hiding this comment.
Logic moved to ChartContextMenu
| useEffect(() => { | ||
| if (!datasetProps && datasetId) { | ||
| cachedSupersetGet({ | ||
| endpoint: `/api/v1/dataset/${datasetId}`, | ||
| }) | ||
| .then(({ json: { result } }) => { | ||
| setResult(result); | ||
| setStatus(ResourceStatus.Complete); | ||
| }) | ||
| .catch(() => { | ||
| setStatus(ResourceStatus.Error); | ||
| }); | ||
| } | ||
| }, [datasetId, datasetProps]); |
There was a problem hiding this comment.
Dataset fetched in ChartContextMenu and passed through
| "superset.views.core.explore_json", | ||
| "superset.views.core.log", | ||
| "superset.views.datasource.views.samples", |
There was a problem hiding this comment.
Just really adding "superset.views.datasource.views.samples". Moved the other two to keep the list alphabetically sorted
| if clone_user.roles: | ||
| for role in clone_user.roles: | ||
| pvms.extend(role.permissions) |
There was a problem hiding this comment.
Moving away from doing temp_user.roles = clone_user.roles because now we support pvms_to_remove and we don't want to remove pvms from the copied role.
| if clone_user: | ||
| temp_user.roles = clone_user.roles | ||
|
|
There was a problem hiding this comment.
this was redundant here, unsure if there was a purpose but tests still work without it
| - [32317](https://github.com/apache/superset/pull/32317) The horizontal filter bar feature is now out of testing/beta development and its feature flag `HORIZONTAL_FILTER_BAR` has been removed. | ||
| - [31590](https://github.com/apache/superset/pull/31590) Marks the begining of intricate work around supporting dynamic Theming, and breaks support for [THEME_OVERRIDES](https://github.com/apache/superset/blob/732de4ac7fae88e29b7f123b6cbb2d7cd411b0e4/superset/config.py#L671) in favor of a new theming system based on AntD V5. Likely this will be in disrepair until settling over the 5.x lifecycle. | ||
| - [32432](https://github.com/apache/superset/pull/31260) Moves the List Roles FAB view to the frontend and requires `FAB_ADD_SECURITY_API` to be enabled in the configuration and `superset init` to be executed. | ||
| - [34319](https://github.com/apache/superset/pull/34319) Drill to Detail and Drill By is now supported in Embedded mode, and also with the `DASHBOARD_RBAC` FF. If you don't want to expose these features in Embedded / `DASHBOARD_RBAC`, make sure the roles used for Embedded / `DASHBOARD_RBAC`don't have the required permissions to perform D2D actions. |
There was a problem hiding this comment.
Can we highlight what the roles are to turn this on/off?
There was a problem hiding this comment.
@eschutho I think these are configureable by deployment, so unsure if we could highlight it. IIRC the embedded role is set in superset_config.py via the GUEST_ROLE_NAME config, then for DASHBOARD_RBAC access users can use any role they had created in the Superset UI.
Let me know if you want me to rephrase this in a better way.
| self, dataset: "BaseDatasource", dashboard: "Dashboard" | ||
| ) -> bool: | ||
| """ | ||
| Return True if an embedded user or DASHBOARD_RBAC user can drill a dataset. |
There was a problem hiding this comment.
@Vitor-Avila since drilling exposes more data, is there a way to disable this feature for embedded users if they don't want to turn it on?
There was a problem hiding this comment.
@eschutho initially I had a FF to gate this, but was advised against here : #34319 (comment)
This will still be controlled by regular role perm. If the embedded role has can_drill_info on Dataset they'll have access to this endpoint, otherwise they won't have it. Then the UI also checks for the regular permissions to show (or not) the Drill by and Drill to Detail options.
betodealmeida
left a comment
There was a problem hiding this comment.
This is such a great PR, from the summary to the tests, passing through the code! Approved, but left a few minor comments.
|
|
||
| fetchDataset(); | ||
| }, [ | ||
| visible, |
There was a problem hiding this comment.
Don't we need to add dataset here as well?
There was a problem hiding this comment.
I've thought so too and checked with Claude and it advised not to, because this block also does setDataset(result) so if dataset was a dependency it would re-render one more time as the dataset value would change with setDataset(result).
From my manual tests it's working properly.
superset/security/manager.py
Outdated
| and slc in dashboard_.slices | ||
| and slc.datasource == datasource | ||
| ) | ||
| or ( |
There was a problem hiding this comment.
Oh my, what is this monster expression stating on line 2379?!? 😱
We should break this down into methods that are self-explanatotry, like:
return (
self.is_d2d_allowed(form_data) or
self.is_dashbaord_rbac(form_data) or
...
)(Not saying you should do it, but you could start by moving the code you're adding into a method and call that instead here.)
There was a problem hiding this comment.
Great idea! Will do with this new block and then open a dedicated follow up later
SUMMARY
This PR exposes drilling capabilities to embedded users. Highlights:
New API endpoint
A new API endpoint was created for drilling:
/api/v1/dataset/{{pk}}/drill_info/. Previously, the drilling context (columns to drill, dataset info to display in theuseDatasetMetadataBaretc) were loaded from/api/v1/dataset/{{pk}}. Exposing this entire endpoint to embedded users is not ideal, as it exposes a lot of metadata.Server-side validations
groupby=False. This would allow embedded users to find out names of other columns that should not be exposed, so the list of columns is now filtered in the backend.groupby=False.Bonus
DASHBOARD_RBAC. Full fix might require migrating/datasource/samplesto/api/v1/.ChartContextMenuand it's passed toDrillDetail,DrillByanduseDatasetMetadataBar.select_columns, to filter the returned columns at the SQL query level (not at the response level) which also improves performance.BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF
Embedded experience
Screen.Recording.2025-07-28.at.4.49.39.PM.mov
Highlights:
Non-embedded experience
Screen.Recording.2025-07-29.at.1.13.04.AM.mov
Highlights:
TESTING INSTRUCTIONS
Test coverage added. For manual testing:
ADDITIONAL INFORMATION