diff --git a/frontend/src/metabase/components/CreateDashboardModal.jsx b/frontend/src/metabase/components/CreateDashboardModal.jsx index 113e24d99005b8b94ae13ec97781891facd15c06..1f04716e053838f6a08eb875dca2f2f7ff121846 100644 --- a/frontend/src/metabase/components/CreateDashboardModal.jsx +++ b/frontend/src/metabase/components/CreateDashboardModal.jsx @@ -34,7 +34,7 @@ export default class CreateDashboardModal extends Component { onSaved = dashboard => { const { onClose, onChangeLocation } = this.props; - onChangeLocation(Urls.dashboard(dashboard)); + onChangeLocation(Urls.dashboard(dashboard, { editMode: true })); if (onClose) { onClose(); } diff --git a/frontend/src/metabase/containers/AddToDashSelectDashModal.jsx b/frontend/src/metabase/containers/AddToDashSelectDashModal.jsx index 9cbc8730fad71036422c568f20604a9e4b98d615..4c6cf8c0ec0c385af3fe2c627b9546e29a44659d 100644 --- a/frontend/src/metabase/containers/AddToDashSelectDashModal.jsx +++ b/frontend/src/metabase/containers/AddToDashSelectDashModal.jsx @@ -35,8 +35,13 @@ export default class AddToDashSelectDashModal extends Component { }; navigateToDashboard = dashboard => { - this.props.onChangeLocation( - Urls.dashboard(dashboard, { addCardWithId: this.props.card.id }), + const { card, onChangeLocation } = this.props; + + onChangeLocation( + Urls.dashboard(dashboard, { + editMode: true, + addCardWithId: card.id, + }), ); }; diff --git a/frontend/src/metabase/dashboard/components/Dashboard/Dashboard.jsx b/frontend/src/metabase/dashboard/components/Dashboard/Dashboard.jsx index 4539e610e4be779a41c7bbc83e4928e9fa8cba7e..c469fb78b9cb9cbdfdaa40146d6c58a12066858c 100644 --- a/frontend/src/metabase/dashboard/components/Dashboard/Dashboard.jsx +++ b/frontend/src/metabase/dashboard/components/Dashboard/Dashboard.jsx @@ -49,7 +49,8 @@ export default class Dashboard extends Component { parameterValues: PropTypes.object, editingParameter: PropTypes.object, - addCardOnLoad: PropTypes.func, + editingOnLoad: PropTypes.bool, + addCardOnLoad: PropTypes.number, addCardToDashboard: PropTypes.func.isRequired, addParameter: PropTypes.func, archiveDashboard: PropTypes.func.isRequired, @@ -131,6 +132,7 @@ export default class Dashboard extends Component { async loadDashboard(dashboardId) { const { + editingOnLoad, addCardOnLoad, addCardToDashboard, fetchDashboard, @@ -146,10 +148,10 @@ export default class Dashboard extends Component { try { await fetchDashboard(dashboardId, location.query); - if (addCardOnLoad != null) { - // if we destructure this.props.dashboard, for some reason - // if will render dashboards as empty + if (editingOnLoad) { this.setEditing(this.props.dashboard); + } + if (addCardOnLoad != null) { addCardToDashboard({ dashId: dashboardId, cardId: addCardOnLoad }); } } catch (error) { diff --git a/frontend/src/metabase/dashboard/containers/DashboardApp.jsx b/frontend/src/metabase/dashboard/containers/DashboardApp.jsx index ccf05bac6c9712ed450e31fd151359df8ca21f38..641a70530960e95b63aebd90737586b88fa42bc7 100644 --- a/frontend/src/metabase/dashboard/containers/DashboardApp.jsx +++ b/frontend/src/metabase/dashboard/containers/DashboardApp.jsx @@ -91,15 +91,25 @@ export default class DashboardApp extends Component { UNSAFE_componentWillMount() { const options = parseHashOptions(window.location.hash); - if (options.add) { - this.setState({ addCardOnLoad: parseInt(options.add) }); + + if (options) { + this.setState({ + editingOnLoad: options.edit, + addCardOnLoad: options.add && parseInt(options.add), + }); } } render() { + const { editingOnLoad, addCardOnLoad } = this.state; + return ( <div className="shrink-below-content-size full-height"> - <Dashboard addCardOnLoad={this.state.addCardOnLoad} {...this.props} /> + <Dashboard + editingOnLoad={editingOnLoad} + addCardOnLoad={addCardOnLoad} + {...this.props} + /> {/* For rendering modal urls */} {this.props.children} </div> diff --git a/frontend/src/metabase/dashboard/hoc/DashboardControls.jsx b/frontend/src/metabase/dashboard/hoc/DashboardControls.jsx index 886b70fed4e737be662e510a0aa0b290942f9563..05e8b0b3cb8695728008fcf084f40e086c7ea415 100644 --- a/frontend/src/metabase/dashboard/hoc/DashboardControls.jsx +++ b/frontend/src/metabase/dashboard/hoc/DashboardControls.jsx @@ -114,10 +114,12 @@ export default (ComposedComponent: React.Class) => delete options.night; // DEPRECATED: options.night - // Delete the "add card to dashboard" parameter if it's present because we don't - // want to add the card again on page refresh. The `add` parameter is already handled in - // DashboardApp before this method is called. + // Delete the "add card to dashboard" and "editing mode" parameters + // if they are present because we do not want to add the card again on + // page refresh. The parameters are already handled in DashboardApp + // before this method is called. delete options.add; + delete options.edit; let hash = stringifyHashOptions(options); hash = hash ? "#" + hash : ""; diff --git a/frontend/src/metabase/lib/urls.js b/frontend/src/metabase/lib/urls.js index b563d606bc18949efd6e6ba29e0f11183d077bd2..66125c63a1b47f35a507485d16bae7b11cd7ee0c 100644 --- a/frontend/src/metabase/lib/urls.js +++ b/frontend/src/metabase/lib/urls.js @@ -3,6 +3,7 @@ import { serializeCardForUrl } from "metabase/lib/card"; import { SAVED_QUESTIONS_VIRTUAL_DB_ID } from "metabase/lib/saved-questions"; import MetabaseSettings from "metabase/lib/settings"; import Question from "metabase-lib/lib/Question"; +import { stringifyHashOptions } from "metabase/lib/browser"; function appendSlug(path, slug) { return slug ? `${path}-${slug}` : path; @@ -97,13 +98,15 @@ export function newQuestion({ mode, ...options } = {}) { } } -export function dashboard(dashboard, { addCardWithId } = {}) { +export function dashboard(dashboard, { addCardWithId, editMode } = {}) { + const options = { + ...(addCardWithId ? { add: addCardWithId } : {}), + ...(editMode ? { edit: editMode } : {}), + }; + const path = appendSlug(dashboard.id, slugg(dashboard.name)); - return addCardWithId != null - ? // NOTE: no-color-literals rule thinks #add is a color, oops - // eslint-disable-next-line no-color-literals - `/dashboard/${path}#add=${addCardWithId}` - : `/dashboard/${path}`; + const hash = stringifyHashOptions(options); + return hash ? `/dashboard/${path}#${hash}` : `/dashboard/${path}`; } function prepareModel(item) { diff --git a/frontend/test/metabase/scenarios/dashboard/dashboard.cy.spec.js b/frontend/test/metabase/scenarios/dashboard/dashboard.cy.spec.js index a515640646ea28d0ac72faed261c1f795cb33be6..5f73c986482c54b5cde594c1630630f0e0cd5dfc 100644 --- a/frontend/test/metabase/scenarios/dashboard/dashboard.cy.spec.js +++ b/frontend/test/metabase/scenarios/dashboard/dashboard.cy.spec.js @@ -32,6 +32,7 @@ describe("scenarios > dashboard", () => { cy.findByLabelText("Description").type("Desc"); cy.findByText("Create").click(); cy.findByText("This dashboard is looking empty."); + cy.findByText("You're editing this dashboard."); // See it as a listed dashboard cy.visit("/collection/root?type=dashboard");