Skip to content
Snippets Groups Projects
Unverified Commit 23453f57 authored by Kamil Mielnik's avatar Kamil Mielnik Committed by GitHub
Browse files

Show custom warning message when leaving editing dashboard via Cancel button (#34405)

* Show a warning modal when clicking "Cancel" in dashboard

* Update dashboard test

* Update question settings test

* Add missing "Edit dashboard" click

* Add tests for leaving dashboard editing via Cancel button
parent d7576fc8
No related branches found
No related tags found
No related merge requests found
...@@ -336,6 +336,9 @@ describe("scenarios > dashboard", () => { ...@@ -336,6 +336,9 @@ describe("scenarios > dashboard", () => {
cy.log("Should revert the title change if editing is cancelled"); cy.log("Should revert the title change if editing is cancelled");
cy.findByTestId("dashboard-name-heading").clear().type(newTitle).blur(); cy.findByTestId("dashboard-name-heading").clear().type(newTitle).blur();
cy.findByTestId("edit-bar").button("Cancel").click(); cy.findByTestId("edit-bar").button("Cancel").click();
modal().within(() => {
cy.button("Leave anyway").click();
});
cy.findByTestId("edit-bar").should("not.exist"); cy.findByTestId("edit-bar").should("not.exist");
cy.get("@updateDashboardSpy").should("not.have.been.called"); cy.get("@updateDashboardSpy").should("not.have.been.called");
cy.findByDisplayValue(originalDashboardName); cy.findByDisplayValue(originalDashboardName);
......
...@@ -5,6 +5,7 @@ import { ...@@ -5,6 +5,7 @@ import {
openNavigationSidebar, openNavigationSidebar,
visitQuestionAdhoc, visitQuestionAdhoc,
popover, popover,
modal,
sidebar, sidebar,
moveColumnDown, moveColumnDown,
} from "e2e/support/helpers"; } from "e2e/support/helpers";
...@@ -427,6 +428,9 @@ describe("scenarios > question > settings", () => { ...@@ -427,6 +428,9 @@ describe("scenarios > question > settings", () => {
cy.contains("Orders in a dashboard").click(); cy.contains("Orders in a dashboard").click();
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
cy.findByText("Cancel").click(); cy.findByText("Cancel").click();
modal().within(() => {
cy.button("Leave anyway").click();
});
// create a new question to see if the "add to a dashboard" modal is still there // create a new question to see if the "add to a dashboard" modal is still there
openNavigationSidebar(); openNavigationSidebar();
......
...@@ -161,6 +161,8 @@ describe("DashboardApp", function () { ...@@ -161,6 +161,8 @@ describe("DashboardApp", function () {
screen.queryAllByTestId("loading-spinner"), screen.queryAllByTestId("loading-spinner"),
); );
userEvent.click(screen.getByLabelText("Edit dashboard"));
history.goBack(); history.goBack();
expect( expect(
...@@ -197,6 +199,41 @@ describe("DashboardApp", function () { ...@@ -197,6 +199,41 @@ describe("DashboardApp", function () {
), ),
).toBeInTheDocument(); ).toBeInTheDocument();
}); });
it("does not show custom warning modal when leaving with no changes via Cancel button", async () => {
await setup();
userEvent.click(screen.getByLabelText("Edit dashboard"));
userEvent.click(screen.getByRole("button", { name: "Cancel" }));
expect(
screen.queryByText("Changes were not saved"),
).not.toBeInTheDocument();
expect(
screen.queryByText(
"Navigating away from here will cause you to lose any changes you have made.",
),
).not.toBeInTheDocument();
});
it("shows custom warning modal when leaving with unsaved changes via Cancel button", async () => {
await setup();
userEvent.click(screen.getByLabelText("Edit dashboard"));
userEvent.click(screen.getByTestId("dashboard-name-heading"));
userEvent.type(screen.getByTestId("dashboard-name-heading"), "a");
userEvent.tab(); // need to click away from the input to trigger the isDirty flag
userEvent.click(screen.getByRole("button", { name: "Cancel" }));
expect(screen.getByText("Changes were not saved")).toBeInTheDocument();
expect(
screen.getByText(
"Navigating away from here will cause you to lose any changes you have made.",
),
).toBeInTheDocument();
});
}); });
describe("empty dashboard", () => { describe("empty dashboard", () => {
......
...@@ -10,6 +10,8 @@ import { trackExportDashboardToPDF } from "metabase/dashboard/analytics"; ...@@ -10,6 +10,8 @@ import { trackExportDashboardToPDF } from "metabase/dashboard/analytics";
import { getIsNavbarOpen } from "metabase/selectors/app"; import { getIsNavbarOpen } from "metabase/selectors/app";
import ActionButton from "metabase/components/ActionButton"; import ActionButton from "metabase/components/ActionButton";
import ConfirmContent from "metabase/components/ConfirmContent";
import Modal from "metabase/components/Modal";
import Button from "metabase/core/components/Button"; import Button from "metabase/core/components/Button";
import { Icon } from "metabase/core/components/Icon"; import { Icon } from "metabase/core/components/Icon";
import Tooltip from "metabase/core/components/Tooltip"; import Tooltip from "metabase/core/components/Tooltip";
...@@ -81,7 +83,7 @@ class DashboardHeader extends Component { ...@@ -81,7 +83,7 @@ class DashboardHeader extends Component {
} }
state = { state = {
modal: null, showCancelWarning: false,
}; };
static propTypes = { static propTypes = {
...@@ -89,6 +91,7 @@ class DashboardHeader extends Component { ...@@ -89,6 +91,7 @@ class DashboardHeader extends Component {
fetchPulseFormInput: PropTypes.func.isRequired, fetchPulseFormInput: PropTypes.func.isRequired,
formInput: PropTypes.object.isRequired, formInput: PropTypes.object.isRequired,
isAdmin: PropTypes.bool, isAdmin: PropTypes.bool,
isDirty: PropTypes.bool,
isEditing: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]) isEditing: PropTypes.oneOfType([PropTypes.bool, PropTypes.object])
.isRequired, .isRequired,
isFullscreen: PropTypes.bool.isRequired, isFullscreen: PropTypes.bool.isRequired,
...@@ -151,6 +154,10 @@ class DashboardHeader extends Component { ...@@ -151,6 +154,10 @@ class DashboardHeader extends Component {
this.props.onEditingChange(dashboard); this.props.onEditingChange(dashboard);
} }
handleCancelWarningClose = () => {
this.setState({ showCancelWarning: false });
};
handleToggleBookmark() { handleToggleBookmark() {
const { createBookmark, deleteBookmark, isBookmarked } = this.props; const { createBookmark, deleteBookmark, isBookmarked } = this.props;
...@@ -206,10 +213,20 @@ class DashboardHeader extends Component { ...@@ -206,10 +213,20 @@ class DashboardHeader extends Component {
this.onDoneEditing(); this.onDoneEditing();
} }
async onCancel() { onRequestCancel = () => {
const { isDirty, isEditing } = this.props;
if (isDirty && isEditing) {
this.setState({ showCancelWarning: true });
} else {
this.onCancel();
}
};
onCancel = () => {
this.onRevert(); this.onRevert();
this.onDoneEditing(); this.onDoneEditing();
} };
getEditWarning(dashboard) { getEditWarning(dashboard) {
if (dashboard.embedding_params) { if (dashboard.embedding_params) {
...@@ -234,7 +251,7 @@ class DashboardHeader extends Component { ...@@ -234,7 +251,7 @@ class DashboardHeader extends Component {
data-metabase-event="Dashboard;Cancel Edits" data-metabase-event="Dashboard;Cancel Edits"
key="cancel" key="cancel"
className="Button Button--small mr1" className="Button Button--small mr1"
onClick={() => this.onCancel()} onClick={this.onRequestCancel}
> >
{t`Cancel`} {t`Cancel`}
</Button>, </Button>,
...@@ -502,31 +519,47 @@ class DashboardHeader extends Component { ...@@ -502,31 +519,47 @@ class DashboardHeader extends Component {
setSidebar, setSidebar,
isHomepageDashboard, isHomepageDashboard,
} = this.props; } = this.props;
const { showCancelWarning } = this.state;
const hasLastEditInfo = dashboard["last-edit-info"] != null; const hasLastEditInfo = dashboard["last-edit-info"] != null;
return ( return (
<DashboardHeaderComponent <>
headerClassName="wrapper" <DashboardHeaderComponent
objectType="dashboard" headerClassName="wrapper"
analyticsContext="Dashboard" objectType="dashboard"
location={this.props.location} analyticsContext="Dashboard"
dashboard={dashboard} location={this.props.location}
isEditing={isEditing} dashboard={dashboard}
isBadgeVisible={!isEditing && !isFullscreen && isAdditionalInfoVisible} isEditing={isEditing}
isLastEditInfoVisible={hasLastEditInfo && isAdditionalInfoVisible} isBadgeVisible={
isEditingInfo={isEditing} !isEditing && !isFullscreen && isAdditionalInfoVisible
isNavBarOpen={this.props.isNavBarOpen} }
headerButtons={this.getHeaderButtons()} isLastEditInfoVisible={hasLastEditInfo && isAdditionalInfoVisible}
editWarning={this.getEditWarning(dashboard)} isEditingInfo={isEditing}
editingTitle={t`You're editing this dashboard.`.concat( isNavBarOpen={this.props.isNavBarOpen}
isHomepageDashboard headerButtons={this.getHeaderButtons()}
? t` Remember that this dashboard is set as homepage.` editWarning={this.getEditWarning(dashboard)}
: "", editingTitle={t`You're editing this dashboard.`.concat(
)} isHomepageDashboard
editingButtons={this.getEditingButtons()} ? t` Remember that this dashboard is set as homepage.`
setDashboardAttribute={setDashboardAttribute} : "",
onLastEditInfoClick={() => setSidebar({ name: SIDEBAR_NAME.info })} )}
/> editingButtons={this.getEditingButtons()}
setDashboardAttribute={setDashboardAttribute}
onLastEditInfoClick={() => setSidebar({ name: SIDEBAR_NAME.info })}
/>
<Modal isOpen={showCancelWarning}>
<ConfirmContent
title={t`Changes were not saved`}
message={t`Navigating away from here will cause you to lose any changes you have made.`}
confirmButtonText={t`Leave anyway`}
cancelButtonText={t`Cancel`}
onClose={this.handleCancelWarningClose}
onAction={this.onCancel}
/>
</Modal>
</>
); );
} }
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment