-
Notifications
You must be signed in to change notification settings - Fork 17.7k
fix(explore): pre-populate SaveModal dashboard from chart metadata #41181
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -615,6 +615,172 @@ test('onTabChange correctly updates selectedTab via forceUpdate', () => { | |
| }); | ||
| }); | ||
|
|
||
| const ownerUser = { | ||
| userId: 1, | ||
| username: 'testuser', | ||
| firstName: 'Test', | ||
| lastName: 'User', | ||
| isActive: true, | ||
| isAnonymous: false, | ||
| permissions: {}, | ||
| roles: { Alpha: [['can_write', 'Dashboard']] as [string, string][] }, | ||
| groups: [], | ||
| }; | ||
|
|
||
| const makeMetadataDashboard = (id: number, title: string) => ({ | ||
| id, | ||
| dashboard_title: title, | ||
| owners: [{ id: 1, first_name: 'Test', last_name: 'User' }], | ||
| extra_owners: [], | ||
| roles: [], | ||
| url: `/superset/dashboard/${id}/`, | ||
| slug: null, | ||
| thumbnail_url: null, | ||
| published: true, | ||
| changed_by_name: 'Test User', | ||
| changed_by: { id: 1, first_name: 'Test', last_name: 'User' }, | ||
| changed_on: '2024-01-01', | ||
| charts: [], | ||
| }); | ||
|
|
||
| test('pre-populates dashboard from metadata.dashboards when dashboardId prop is absent', async () => { | ||
| const dashboardId = 5; | ||
| const dashboardTitle = 'Chart Dashboard'; | ||
|
|
||
| const myProps = { | ||
| ...defaultProps, | ||
| dashboardId: null, | ||
| metadata: { | ||
| dashboards: [{ id: dashboardId, dashboard_title: dashboardTitle }], | ||
| owners: ['Test User'], | ||
| created_on_humanized: '2 days ago', | ||
| changed_on_humanized: '1 day ago', | ||
| }, | ||
| user: ownerUser, | ||
| slice: { slice_id: 1, slice_name: 'My Chart', owners: [1] }, | ||
| dispatch: jest.fn(), | ||
| addDangerToast: jest.fn(), | ||
| }; | ||
|
|
||
| const component = new TestSaveModal(myProps); | ||
| const mockFull = makeMetadataDashboard(dashboardId, dashboardTitle); | ||
|
|
||
| component.loadDashboard = jest.fn().mockResolvedValue(mockFull); | ||
| component.loadTabs = jest.fn().mockResolvedValue([]); | ||
|
|
||
| const stateUpdates: any[] = []; | ||
| component.setState = jest.fn((update: any) => { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: Change the Severity Level: Minor Why it matters? 🤔This is modified TypeScript code using (Use Cmd/Ctrl + Click for best experience) Prompt for AI Agent 🤖This is a comment left during a code review.
**Path:** superset-frontend/src/explore/components/SaveModal.test.tsx
**Line:** 672:672
**Comment:**
*Custom Rule: Change the `update` callback parameter from `any` to a precise union/type that matches the `setState` updater contract used in this test.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Declining — same reason as the adjacent comment: line 602 uses the identical |
||
| stateUpdates.push(update); | ||
| }); | ||
|
|
||
| try { | ||
| sessionStorage.clear(); | ||
| } catch (_) { | ||
| // ignore | ||
| } | ||
|
|
||
| await component.componentDidMount(); | ||
|
|
||
| expect(component.loadDashboard).toHaveBeenCalledWith(dashboardId); | ||
| expect(stateUpdates).toContainEqual({ | ||
| dashboard: { label: dashboardTitle, value: dashboardId }, | ||
| }); | ||
| expect(component.loadTabs).toHaveBeenCalledWith(dashboardId); | ||
| }); | ||
|
|
||
| test('skips non-editable dashboards and picks the first editable one from metadata', async () => { | ||
| const editableId = 7; | ||
| const editableTitle = 'Editable Dashboard'; | ||
|
|
||
| const myProps = { | ||
| ...defaultProps, | ||
| dashboardId: null, | ||
| metadata: { | ||
| dashboards: [ | ||
| { id: 6, dashboard_title: 'Not Mine' }, | ||
| { id: editableId, dashboard_title: editableTitle }, | ||
| ], | ||
| owners: ['Test User'], | ||
| created_on_humanized: '2 days ago', | ||
| changed_on_humanized: '1 day ago', | ||
| }, | ||
| user: ownerUser, | ||
| slice: { slice_id: 1, slice_name: 'My Chart', owners: [1] }, | ||
| dispatch: jest.fn(), | ||
| addDangerToast: jest.fn(), | ||
| }; | ||
|
|
||
| const component = new TestSaveModal(myProps); | ||
|
|
||
| const notMine = makeMetadataDashboard(6, 'Not Mine'); | ||
| notMine.owners = [{ id: 99, first_name: 'Other', last_name: 'Owner' }]; | ||
| const editable = makeMetadataDashboard(editableId, editableTitle); | ||
|
|
||
| component.loadDashboard = jest | ||
| .fn() | ||
| .mockImplementation((id: number) => | ||
| Promise.resolve(id === 6 ? notMine : editable), | ||
| ); | ||
| component.loadTabs = jest.fn().mockResolvedValue([]); | ||
|
|
||
| const stateUpdates: any[] = []; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: Replace this Severity Level: Minor Why it matters? 🤔This is another occurrence of (Use Cmd/Ctrl + Click for best experience) Prompt for AI Agent 🤖This is a comment left during a code review.
**Path:** superset-frontend/src/explore/components/SaveModal.test.tsx
**Line:** 726:726
**Comment:**
*Custom Rule: Replace this `any[]` declaration with a reusable typed alias for state updates to keep test typing strict and consistent.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix |
||
| component.setState = jest.fn((update: any) => { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: Type the mocked Severity Level: Minor Why it matters? 🤔The modified test code uses (Use Cmd/Ctrl + Click for best experience) Prompt for AI Agent 🤖This is a comment left during a code review.
**Path:** superset-frontend/src/explore/components/SaveModal.test.tsx
**Line:** 727:727
**Comment:**
*Custom Rule: Type the mocked `setState` callback argument with a specific updater type (or `Partial` state type) instead of `any`.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix |
||
| stateUpdates.push(update); | ||
| }); | ||
|
|
||
| try { | ||
| sessionStorage.clear(); | ||
| } catch (_) { | ||
| // ignore | ||
| } | ||
|
|
||
| await component.componentDidMount(); | ||
|
|
||
| expect(stateUpdates).toContainEqual({ | ||
| dashboard: { label: editableTitle, value: editableId }, | ||
| }); | ||
| expect(component.loadTabs).toHaveBeenCalledWith(editableId); | ||
| }); | ||
|
|
||
| test('does not use metadata fallback when dashboardId prop is set', async () => { | ||
| const propDashboardId = 3; | ||
| const propDashboardTitle = 'Prop Dashboard'; | ||
|
|
||
| const myProps = { | ||
| ...defaultProps, | ||
| dashboardId: propDashboardId, | ||
| metadata: { | ||
| dashboards: [{ id: 99, dashboard_title: 'Should Not Be Used' }], | ||
| owners: ['Test User'], | ||
| created_on_humanized: '2 days ago', | ||
| changed_on_humanized: '1 day ago', | ||
| }, | ||
| user: ownerUser, | ||
| slice: { slice_id: 1, slice_name: 'My Chart', owners: [1] }, | ||
| dispatch: jest.fn(), | ||
| addDangerToast: jest.fn(), | ||
| }; | ||
|
|
||
| const component = new TestSaveModal(myProps); | ||
| const mockFull = makeMetadataDashboard(propDashboardId, propDashboardTitle); | ||
|
|
||
| component.loadDashboard = jest.fn().mockResolvedValue(mockFull); | ||
| component.loadTabs = jest.fn().mockResolvedValue([]); | ||
|
|
||
| const stateUpdates: any[] = []; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: Replace this Severity Level: Minor Why it matters? 🤔The final PR state includes this (Use Cmd/Ctrl + Click for best experience) Prompt for AI Agent 🤖This is a comment left during a code review.
**Path:** superset-frontend/src/explore/components/SaveModal.test.tsx
**Line:** 770:770
**Comment:**
*Custom Rule: Replace this `any[]` usage with a concrete typed structure that reflects the expected dashboard selection state updates.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix |
||
| component.setState = jest.fn((update: any) => { | ||
| stateUpdates.push(update); | ||
| }); | ||
|
|
||
| await component.componentDidMount(); | ||
|
|
||
| expect(component.loadDashboard).toHaveBeenCalledWith(propDashboardId); | ||
| expect(component.loadDashboard).not.toHaveBeenCalledWith(99); | ||
| expect(stateUpdates).toContainEqual({ | ||
| dashboard: { label: propDashboardTitle, value: propDashboardId }, | ||
| }); | ||
| }); | ||
|
|
||
| test('chart placement logic finds row with available space', () => { | ||
| // Test case 1: Row has space (8 + 4 = 12 <= 12) | ||
| const positionJson1 = { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -54,7 +54,11 @@ import { | |
| isUserAdmin, | ||
| } from 'src/dashboard/util/permissionUtils'; | ||
| import { setSaveChartModalVisibility } from 'src/explore/actions/saveModalActions'; | ||
| import { SaveActionType, ChartStatusType } from 'src/explore/types'; | ||
| import { | ||
| SaveActionType, | ||
| ChartStatusType, | ||
| ExplorePageInitialData, | ||
| } from 'src/explore/types'; | ||
| import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes'; | ||
| import { | ||
| removeChartState, | ||
|
|
@@ -81,6 +85,7 @@ interface SaveModalProps extends RouteComponentProps { | |
| isVisible: boolean; | ||
| dispatch: Dispatch; | ||
| theme: SupersetTheme; | ||
| metadata?: ExplorePageInitialData['metadata']; | ||
| } | ||
|
|
||
| type SaveModalState = { | ||
|
|
@@ -162,6 +167,35 @@ class SaveModal extends Component<SaveModalProps, SaveModalState> { | |
| t('An error occurred while loading dashboard information.'), | ||
| ); | ||
| } | ||
| } else { | ||
| const metadataDashboards = this.props.metadata?.dashboards; | ||
| if (metadataDashboards?.length) { | ||
| // Fallback: the chart is already on one or more dashboards (from Explore API | ||
| // metadata). Pre-populate with the first dashboard the user can edit so the | ||
| // "Save & go to dashboard" button works out of the box. | ||
| try { | ||
| let editable: Dashboard | undefined; | ||
| for (const { id } of metadataDashboards) { | ||
| // eslint-disable-next-line no-await-in-loop | ||
| const result = await this.loadDashboard(id).catch(() => null); | ||
| if (result && canUserEditDashboard(result, this.props.user)) { | ||
| editable = result as Dashboard; | ||
| break; | ||
| } | ||
| } | ||
|
Comment on lines
+178
to
+185
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: The metadata fallback fetches dashboards sequentially using Severity Level: Major
|
||
| if (editable) { | ||
| this.setState({ | ||
| dashboard: { | ||
| label: editable.dashboard_title, | ||
| value: editable.id, | ||
| }, | ||
| }); | ||
| await this.loadTabs(editable.id); | ||
| } | ||
| } catch (error) { | ||
| logging.warn(error); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -826,6 +860,7 @@ interface StateProps { | |
| dashboards: any; | ||
| alert: any; | ||
| isVisible: boolean; | ||
| metadata?: ExplorePageInitialData['metadata']; | ||
| } | ||
|
|
||
| function mapStateToProps({ | ||
|
|
@@ -841,6 +876,7 @@ function mapStateToProps({ | |
| dashboards: saveModal.dashboards, | ||
| alert: saveModal.saveModalAlert, | ||
| isVisible: saveModal.isVisible, | ||
| metadata: explore.metadata, | ||
| }; | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: Replace
any[]with a specific state update collection type (for example, a typed partial state shape) so the test assertions stay type-safe. [custom_rule]Severity Level: Minor⚠️
Why it matters? 🤔
The file is TypeScript (
.tsx), and this line usesany[], which directly violates the rule against usinganyin modified TypeScript/TSX code. A concrete type or a narrower reusable type should be used instead.(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Declining — line 602 in this same file already uses the identical
setState = function (this: any, stateUpdate: any)pattern. Adding specific types only to the new test helper would be inconsistent.