Repository: metabase/metabase
Pull request #55362: Setting shouldUpdateUrl to false when calling popState in QB
Author: login: npfitz
Labels: name: backport
description: Automatically create PR on current release branch on merge
name: .Team/AdminWebapp
description: DEPRECATED  Please use .Team/UXWest instead
Created: 2025-03-18T17:49:47Z | Merged: 2025-03-24T12:56:38Z

Closes https://github.com/metabase/metabase/issues/55162
Closes https://github.com/metabase/metabase/issues/55158

### Description
Bad state was happening because when we used the back button to navigate away from the dataset editor, we would end up partially re-directing back to the same metadata url when running the query. This happened because we were telling our thunks to update the URL, and it was building it from outdated state.

One thing this PR does that I'm not sure about is that it changes the default value for `queryBuilderMode` in the redux store. I didn't notice any issues, but it's certainly worth calling out.

### How to verify
Follow the same steps in the linked bug.

1. Open a model
2. Click "Edit metadata"
3. Click "Cancel"
4. Click "Edit metadata"
5. Hit back button in your browser

You should not be in a good state where the URL doesn't end in `/metadata`, and the app bar is still present.

### Checklist

- [x] Tests have been added/updated to cover changes in this PR



Diff:
diff --git a/e2e/support/helpers/e2e-models-metadata-helpers.js b/e2e/support/helpers/e2e-models-metadata-helpers.js
index 1677e1b81dd9..964856d66a4e 100644
--- a/e2e/support/helpers/e2e-models-metadata-helpers.js
+++ b/e2e/support/helpers/e2e-models-metadata-helpers.js
@@ -4,6 +4,10 @@ import {
   tableInteractive,
 } from "e2e/support/helpers";
 
+export function datasetEditBar() {
+  return cy.findByTestId("dataset-edit-bar");
+}
+
 export function saveMetadataChanges() {
   interceptIfNotPreviouslyDefined({
     method: "POST",
diff --git a/e2e/test/scenarios/models/models-metadata.cy.spec.js b/e2e/test/scenarios/models/models-metadata.cy.spec.js
index 5ba58e56e2de..97b5002121ed 100644
--- a/e2e/test/scenarios/models/models-metadata.cy.spec.js
+++ b/e2e/test/scenarios/models/models-metadata.cy.spec.js
@@ -25,10 +25,7 @@ describe("scenarios > models metadata", () => {
         type: "model",
       };
 
-      H.createQuestion(modelDetails).then(({ body: { id } }) => {
-        cy.visit(`/model/${id}`);
-        cy.wait("@dataset");
-      });
+      H.createQuestion(modelDetails, { visitQuestion: true, wrapId: true });
     });
 
     it("should edit GUI model metadata", () => {
@@ -64,9 +61,8 @@ describe("scenarios > models metadata", () => {
         .and("not.contain", "Subtotal");
     });
 
-    it("allows for canceling changes", () => {
-      H.openQuestionActions();
-      H.popover().findByTextEnsureVisible("Edit metadata").click();
+    it("allows for canceling changes, back navigation (metabase#55162)", () => {
+      H.openQuestionActions("Edit metadata");
 
       H.openColumnOptions("Subtotal");
       H.renameColumn("Subtotal", "Pre-tax");
@@ -78,6 +74,13 @@ describe("scenarios > models metadata", () => {
       cy.findAllByTestId("header-cell")
         .should("contain", "Subtotal")
         .and("not.contain", "Pre-tax");
+
+      // Ensure back navigation works correctly metabase#55162
+      H.openQuestionActions("Edit metadata");
+      cy.go("back");
+      cy.get("@questionId").then(id => {
+        cy.location("pathname").should("equal", `/model/${id}-gui-model`);
+      });
     });
 
     it("clears custom metadata when a model is turned back into a question", () => {
diff --git a/e2e/test/scenarios/question/notebook.cy.spec.js b/e2e/test/scenarios/question/notebook.cy.spec.js
index 6b1c8f1f50be..72a3b4a07639 100644
--- a/e2e/test/scenarios/question/notebook.cy.spec.js
+++ b/e2e/test/scenarios/question/notebook.cy.spec.js
@@ -1126,6 +1126,157 @@ describe("scenarios > question > notebook", { tags: "@slow" }, () => {
       locale: "en",
     });
   });
+
+  it("should support browser based navigation (metabase#55162)", () => {
+    cy.intercept(`/api/table/${PRODUCTS_ID}/fks`).as("tableFK");
+    H.createQuestion(
+      { query: { "source-table": PRODUCTS_ID }, name: "products" },
+      { visitQuestion: true, wrapId: true },
+    );
+
+    cy.get("@questionId").then(PRODUCT_QUESTION_ID => {
+      cy.findByRole("button", { name: /Editor/ }).click();
+      cy.findByRole("button", { name: /Visualization/ }).click();
+
+      cy.go("back");
+      cy.location("pathname").should(
+        "equal",
+        `/question/${PRODUCT_QUESTION_ID}-products/notebook`,
+      );
+
+      cy.go("back");
+      cy.location("pathname").should(
+        "equal",
+        `/question/${PRODUCT_QUESTION_ID}-products`,
+      );
+
+      cy.go("forward");
+      cy.location("pathname").should(
+        "equal",
+        `/question/${PRODUCT_QUESTION_ID}-products/notebook`,
+      );
+
+      H.openQuestionActions("Turn into a model");
+
+      H.modal()
+        .findByRole("button", { name: "Turn this into a model" })
+        .click();
+
+      cy.location("pathname").should(
+        "equal",
+        `/model/${PRODUCT_QUESTION_ID}-products/notebook`,
+      );
+
+      H.openQuestionActions("Edit metadata");
+      H.datasetEditBar().findByRole("button", { name: "Cancel" }).click();
+
+      cy.location("pathname").should(
+        "equal",
+        `/model/${PRODUCT_QUESTION_ID}-products`,
+      );
+
+      H.openQuestionActions("Edit metadata");
+
+      cy.go("back");
+      cy.location("pathname").should(
+        "equal",
+        `/model/${PRODUCT_QUESTION_ID}-products`,
+      );
+
+      cy.go("back");
+
+      cy.location("pathname").should(
+        "equal",
+        `/model/${PRODUCT_QUESTION_ID}-products/metadata`,
+      );
+
+      H.datasetEditBar().findByText("Query").click();
+
+      cy.go("back");
+
+      cy.location("pathname").should(
+        "equal",
+        `/model/${PRODUCT_QUESTION_ID}-products/metadata`,
+      );
+
+      cy.go("forward");
+
+      cy.location("pathname").should(
+        "equal",
+        `/model/${PRODUCT_QUESTION_ID}-products/query`,
+      );
+
+      H.datasetEditBar().findByRole("button", { name: "Cancel" }).click();
+
+      cy.go("back");
+
+      cy.location("pathname").should(
+        "equal",
+        `/model/${PRODUCT_QUESTION_ID}-products/query`,
+      );
+
+      cy.go("back");
+
+      // This should work, but doesn't (metabase#55486)
+      // cy.location("pathname").should(
+      //   "equal",
+      //   `/model/${PRODUCT_QUESTION_ID}-products/metadata`,
+      // );
+
+      H.datasetEditBar().findByRole("button", { name: "Cancel" }).click();
+
+      cy.findAllByTestId("row-id-cell")
+        .first()
+        .findByRole("button", { hidden: true })
+        .click({ force: true });
+
+      // Cannot navigate back and forth to details modal (metabase#55487)
+      // cy.go("back");
+
+      // cy.location("pathname").should(
+      //   "equal",
+      //   `/model/${PRODUCT_QUESTION_ID}-products`,
+      // );
+
+      // cy.go("forward");
+
+      // cy.location("pathname").should(
+      //   "equal",
+      //   `/model/${PRODUCT_QUESTION_ID}-products/1`,
+      // );
+
+      /**
+       * foreign key relation orders should work, but it consistently fails in CI
+       */
+      // cy.wait("@tableFK");
+
+      // H.modal().findByTestId("fk-relation-orders").click();
+
+      // cy.location("pathname").should("contain", "/question");
+      // cy.findByTestId("filter-pill").should("contain.text", "Product ID is 1");
+
+      cy.go("back");
+
+      cy.location("pathname").should(
+        "equal",
+        `/model/${PRODUCT_QUESTION_ID}-products`,
+      );
+
+      H.openQuestionActions("Turn back to saved question");
+
+      cy.location("pathname").should(
+        "equal",
+        `/question/${PRODUCT_QUESTION_ID}-products`,
+      );
+
+      // Going back returns us to a model URL, which it should not (metabase#55488)
+      // cy.go("back");
+      // cy.location("pathname").should(
+      //   "not.equal",
+      //   `/model/${PRODUCT_QUESTION_ID}-products`,
+      // );
+    });
+  });
 });
 
 function assertTableRowCount(expectedCount) {
diff --git a/frontend/src/metabase/query_builder/actions/navigation.ts b/frontend/src/metabase/query_builder/actions/navigation.ts
index 4a9ac89daa18..f810835add7f 100644
--- a/frontend/src/metabase/query_builder/actions/navigation.ts
+++ b/frontend/src/metabase/query_builder/actions/navigation.ts
@@ -61,7 +61,6 @@ export const popState = createThunkAction(
     const card = getCard(getState());
     if (location.state && location.state.card) {
       if (!equals(card, location.state.card)) {
-        const shouldUpdateUrl = location.state.card.type === "model";
         const isEmptyQuery = !location.state.card.dataset_query.database;
 
         if (isEmptyQuery) {
@@ -71,7 +70,7 @@ export const popState = createThunkAction(
           await dispatch(initializeQB(location, {}));
         } else {
           await dispatch(
-            setCardAndRun(location.state.card, { shouldUpdateUrl }),
+            setCardAndRun(location.state.card, { shouldUpdateUrl: false }),
           );
           await dispatch(setCurrentState(location.state));
           await dispatch(resetUIControls());
diff --git a/frontend/src/metabase/query_builder/defaults.ts b/frontend/src/metabase/query_builder/defaults.ts
index a47e9a870b47..2aa2ca10fa12 100644
--- a/frontend/src/metabase/query_builder/defaults.ts
+++ b/frontend/src/metabase/query_builder/defaults.ts
@@ -20,7 +20,7 @@ export const DEFAULT_UI_CONTROLS: QueryBuilderUIControls = {
   isNativeEditorOpen: false,
   initialChartSetting: {},
   isShowingRawTable: false, // table/viz toggle
-  queryBuilderMode: false, // "view" | "notebook" | "dataset"
+  queryBuilderMode: "view", // "view" | "notebook" | "dataset"
   previousQueryBuilderMode: false,
   snippetCollectionId: null,
   datasetEditorTab: "query", // "query" / "metadata"


Reviews:
user: login: ranquild
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T17:53:28Z
user: login: kamilmielnik
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T07:31:40Z
user: login: ranquild
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T17:06:58Z
user: login: kamilmielnik
body: :1st_place_medal: for reporting all these related navigation issues :rocket: 
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T08:06:25Z

Review comments:
diff_hunk: @@ -78,6 +80,14 @@ describe("scenarios > models metadata", () => {
       cy.findAllByTestId("header-cell")
         .should("contain", "Subtotal")
         .and("not.contain", "Pre-tax");
+
+      // Ensure back navigation works correctly metabase#55162
+      H.openQuestionActions();
+      H.popover().findByTextEnsureVisible("Edit metadata").click();
path: e2e/test/scenarios/models/models-metadata.cy.spec.js
user: login: kamilmielnik
body: ```suggestion
      H.openQuestionActions("Edit metadata");
```
created_at: 2025-03-19T07:29:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -78,6 +80,14 @@ describe("scenarios > models metadata", () => {
       cy.findAllByTestId("header-cell")
         .should("contain", "Subtotal")
         .and("not.contain", "Pre-tax");
+
+      // Ensure back navigation works correctly metabase#55162
path: e2e/test/scenarios/models/models-metadata.cy.spec.js
user: login: kamilmielnik
body: Let's also add the issue tag in test name - it should decrease probability of accidental removal.
created_at: 2025-03-19T07:29:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -25,10 +25,12 @@ describe("scenarios > models metadata", () => {
         type: "model",
       };
 
-      H.createQuestion(modelDetails).then(({ body: { id } }) => {
-        cy.visit(`/model/${id}`);
-        cy.wait("@dataset");
-      });
+      H.createQuestion(modelDetails, { wrapId: true }).then(
+        ({ body: { id } }) => {
+          cy.visit(`/model/${id}`);
+          cy.wait("@dataset");
+        },
+      );
path: e2e/test/scenarios/models/models-metadata.cy.spec.js
user: login: kamilmielnik
body: ```suggestion
      H.createQuestion(modelDetails, { visitQuestion: true, wrapId: true });
```
created_at: 2025-03-19T07:31:24Z
author_association: CONTRIBUTOR

PR comments:
user: login: trunk-io[bot]
created_at: 2025-03-18T19:13:48Z
body: <!-- Trunk Test Analytics -->


[![Static Badge](https://img.shields.io/badge/83_tests-179fa703-purple)](https://app.trunk.io/metabase/flaky-tests/pr/55362?repo=metabase/metabase&commitHash=179fa703cc73126955034f817e43ac0af7a92d60&utm_source=gh-comment) &ensp; [![Static Badge](https://img.shields.io/badge/1-failed-crimson)](https://app.trunk.io/metabase/flaky-tests/pr/55362?repo=metabase/metabase&commitHash=179fa703cc73126955034f817e43ac0af7a92d60&utm_source=gh-comment&runConclusion=FAILURE) &ensp; [![Static Badge](https://img.shields.io/badge/2-flaky-red)](https://app.trunk.io/metabase/flaky-tests/pr/55362?repo=metabase/metabase&commitHash=179fa703cc73126955034f817e43ac0af7a92d60&utm_source=gh-comment&runConclusion=FAILURE) &ensp; [![Static Badge](https://img.shields.io/badge/0-quarantined-yellow)](https://app.trunk.io/metabase/flaky-tests/pr/55362?repo=metabase/metabase&commitHash=179fa703cc73126955034f817e43ac0af7a92d60&utm_source=gh-comment&runConclusion=QUARANTINED)


| Failed Test | Failure Summary | Logs |
| --- | --- | --- |
| <code>scenarios > question > notebook should support browser based navigation (metabase\#55162)</code> | cy.wait() timed out waiting for the first request to the route 'tableFK' to occur, but no request was made. | [Logs ↗︎](https://app.trunk.io/metabase/flaky-tests/pr/55362?repo=metabase/metabase&commitHash=179fa703cc73126955034f817e43ac0af7a92d60&utm_source=gh-comment&runConclusion=FAILURE&runId=3aac92a8-850b-5522-b359-98e682a8e5e5) |


| Flaky Test | Failure Summary | Logs |
| --- | --- | --- |
| <code>scenarios > visualizations > line/bar chart > tooltips should be enterable and scollable to view all rows in long tooltips (metabase\#53586) (metab...</code> | The test timed out while trying to find an element with data-testid "echarts-tooltip". | [Logs ↗︎](https://app.trunk.io/metabase/flaky-tests/pr/55362?repo=metabase/metabase&commitHash=179fa703cc73126955034f817e43ac0af7a92d60&utm_source=gh-comment&runConclusion=FAILURE&runId=174d670a-037a-535e-896a-3c801d762761) |
| <code>issue 39487 calendar has constant size when using date range picker filter (metabase\#39487)</code> | The modal dialog was not found within 4 seconds, causing a timeout. | [Logs ↗︎](https://app.trunk.io/metabase/flaky-tests/pr/55362?repo=metabase/metabase&commitHash=179fa703cc73126955034f817e43ac0af7a92d60&utm_source=gh-comment&runConclusion=FAILURE&runId=b88d236e-db0b-54e8-a506-f6e09040d8c7) |
<sub>

[View Full Report ↗︎](https://app.trunk.io/metabase/flaky-tests/pr/55362?repo=metabase/metabase&commitHash=179fa703cc73126955034f817e43ac0af7a92d60&utm_source=gh-comment) ⋅ [Docs](https://docs.trunk.io/flaky-tests?utm_source=gh-comment)

</sub>
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-24T01:35:45Z
body: 
  ## e2e tests failed on `6fcafdb0c692525fbfd1fecaba386857729637a0-2`

  [e2e test run](https://github.com/metabase/metabase/actions/runs/14000782357?pr=55362)

  File | Test Name
  ---- | ---------
  `reproductions.cy.spec.ts` | (_flaky_) issue 39487 > calendar has constant size when using date range picker filter (metabase#39487)
`line-bar-tooltips.cy.spec.js` | (_flaky_) scenarios > visualizations > line/bar chart > tooltips > should be enterable and scollable to view all rows in long tooltips (metabase#53586) (metabase#48347)
`notebook.cy.spec.js` | scenarios > question > notebook > should support browser based navigation (metabase#55162)

author_association: CONTRIBUTOR