Repository: metabase/metabase
Pull request #58423: Fix static question embed download when parameters have multiple values
Author: login: WiNloSt
Labels: name: .Team/Embedding
name: triple-backport
Created: 2025-05-23T15:04:58Z | Merged: 2025-05-27T05:42:29Z

Closes https://github.com/metabase/metabase/issues/52430, closes #47554, closes EMB-78

### Description

We mistakenly dropped parameter values when a single parameter has multiple values.

This happen because a parameter could be like this `param=1&param=2` and by using `Object.fromEntries`, we'll only get the last parameter value. We need to perform a merge manually.

### How to verify

Follow the repro step from #52430


### Checklist

- [x] Tests have been added/updated to cover changes in this PR (I modify existing test to also test this scenario)


Linked issue #47554: Exporting from embedded question with multiple variable selections exports only one selection
### Describe the bug

When exporting data from embedded question in Metabase, if a variable is set to allow multiple selections, only one of the selected options is included in the export. This behavior is unexpected and inconsistent with the selected state in the dashboard where multiple options are selected.

### To Reproduce

1. create question with variable. doesn't need complecated. I made fugafuga with a string.
```
select *
from hogehoge
where 
1 = 1
[[and {{fugafuga}}]]
```
2. embed it
3. filter with 2 or more selection
4. export it in anyway


### Expected behavior

_No response_

### Logs

_No response_

### Information about your Metabase installation

```JSON
Mac
Google Chrome
Metabase v0.50.19
Database: MySQL
Internal DB: H2
```


### Severity

blocking some users

### Additional context

_No response_
Linked issue comments: user: login: WiNloSt
created_at: 2024-09-03T15:15:29Z
body: For engineers troubleshooting this.

Tested with Postgres on master @ ee7bc42d73da2d172145c62eca3f4edcfb96bb40
- DB: H2 Sample, Postgres Sample
- Question: `select * from products where 1 = 1 [[and {{category}}]]`
- Parameter `Field Filter` maps to `Category` with filter type `string`.
- Export as `CSV`, `JSON`

Couldn't reproduce the issue.

I haven't tested the reported version, so it's worth taking a look.
author_association: MEMBER
user: login: star-tsukuda-x
created_at: 2024-09-05T02:28:56Z
body: Thank you, @WiNloSt for investigating this. I will try it with the latest version next Tuesday.
author_association: NONE
user: login: star-tsukuda-x
created_at: 2024-09-11T00:43:39Z
body: it's still happen on my environment with v0.50.24 (newest).
author_association: NONE
user: login: star-tsukuda-x
created_at: 2024-09-11T00:48:47Z
body: the prerequisites might be different to reproduce the issue correctly.

Let me share the conditions again:

```
select *
from hogehoge
where 
1 = 1
[[and {{fugafuga}}]]
[[and {{piyopiyo}}]]
```

In this case, please specify fugafuga and piyopiyo as field filters in Metabase, mapped to some fields in the hogehoge table.
Then, publish a static embedding, setting fugafuga as locked and piyopiyo as editable.
On the embedding server, the payload is created like this:
```
{
  resource: { question: question_num },
  params: {
    "fugafuga" => fugafuga
  },
  exp: 1.days.from_now.to_i
}
```

The URL is `/embed/question/#{token}#bordered=true&titled=true&hide_parameters=fugafuga`.

However, when piyopiyo is selected multiple times in the embedded state, the export output contains only one of the selected values, not all.

By the way, the same issue occurs even if piyopiyo is not a string type.
author_association: NONE
user: login: felipedsc
created_at: 2024-11-06T15:55:46Z
body: Still happening on 0.51.2
author_association: NONE
user: login: albertmatyi
created_at: 2025-05-16T13:21:24Z
body: Still happening in metabase:v0.54.1.x
author_association: NONE
user: login: WiNloSt
created_at: 2025-05-26T08:45:35Z
body: I think this is the same issue as #52430 and [the fix](https://github.com/metabase/metabase/pull/58423) should land soon.

Not sure why I couldn't repro that in the first place 🙏 
author_association: MEMBER
user: login: github-actions[bot]
created_at: 2025-05-27T08:02:44Z
body: 🚀 This should also be released by [v0.54.10](https://github.com/metabase/metabase/milestone/340)
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-05-27T08:05:32Z
body: 🚀 This should also be released by [v0.53.17](https://github.com/metabase/metabase/milestone/339)
author_association: CONTRIBUTOR

Diff:
diff --git a/e2e/test/scenarios/embedding/embed-resource-downloads.cy.spec.ts b/e2e/test/scenarios/embedding/embed-resource-downloads.cy.spec.ts
index 9f1a6cc3688e..fb4664eb02c9 100644
--- a/e2e/test/scenarios/embedding/embed-resource-downloads.cy.spec.ts
+++ b/e2e/test/scenarios/embedding/embed-resource-downloads.cy.spec.ts
@@ -6,7 +6,7 @@ import {
 } from "e2e/support/cypress_sample_instance_data";
 import { createMockParameter } from "metabase-types/api/mocks";
 
-const { PRODUCTS, PRODUCTS_ID } = SAMPLE_DATABASE;
+const { PRODUCTS, PRODUCTS_ID, PEOPLE } = SAMPLE_DATABASE;
 
 /** These tests are about the `downloads` flag for static embeds, both dashboards and questions.
  *  Unless the product changes, these should test the same things as `public-resource-downloads.cy.spec.ts`
@@ -297,6 +297,8 @@ H.describeWithSnowplowEE(
       });
 
       describe("with native question parameters", () => {
+        const FILTER_VALUES = ["NY", "NH"];
+
         beforeEach(() => {
           cy.signInAsAdmin();
 
@@ -308,29 +310,37 @@ H.describeWithSnowplowEE(
               name: "Native question with a parameter",
               native: {
                 "template-tags": {
-                  num: {
+                  state: {
                     id: "f7672b4d-1e84-1fa8-bf02-b5e584cd4535",
-                    name: "num",
-                    "display-name": "Num",
-                    type: "number",
+                    name: "state",
+                    "display-name": "State",
+                    type: "dimension",
+                    options: {
+                      "case-sensitive": false,
+                    },
+                    dimension: ["field", PEOPLE.STATE, null],
                     default: null,
+                    "widget-type": "string/contains",
                   },
                 },
-                query: "select {{num}}",
+                query: "select id, email, state from people where {{state}}",
               },
               parameters: [
                 {
                   id: "f7672b4d-1e84-1fa8-bf02-b5e584cd4535",
-                  type: "number/=",
-                  target: ["variable", ["template-tag", "num"]],
-                  name: "Num",
-                  slug: "num",
+                  type: "string/contains",
+                  options: {
+                    "case-sensitive": false,
+                  },
+                  target: ["dimension", ["template-tag", "state"]],
+                  name: "State",
+                  slug: "state",
                   default: null,
                 },
               ],
               enable_embedding: true,
               embedding_params: {
-                num: "enabled",
+                state: "enabled",
               },
             },
             {
@@ -338,18 +348,16 @@ H.describeWithSnowplowEE(
               wrapId: true,
             },
           );
-
           cy.signOut();
         });
 
-        it("should be able to download a static embedded dashcard as CSV", () => {
-          const value = 9999;
+        it("should be able to download a static embedded question as CSV with correct parameters when field filters has multiple values (metabase#52430)", () => {
           cy.get("@questionId").then((questionId) => {
             H.visitEmbeddedPage(
               {
                 resource: { question: Number(questionId) },
                 params: {
-                  num: value,
+                  state: FILTER_VALUES,
                 },
               },
               {
@@ -362,16 +370,32 @@ H.describeWithSnowplowEE(
 
           waitLoading();
 
-          H.main().findByText(value).should("exist");
-
-          cy.findByRole("button", { name: "Download results" }).click();
+          const FIRST_ROW = [
+            5,
+            "leffler.dominique@hotmail.com",
+            FILTER_VALUES[0],
+          ];
 
-          H.popover().within(() => {
-            cy.findByText(".csv").click();
-            cy.findByTestId("download-results-button").click();
+          H.assertTableData({
+            columns: ["ID", "EMAIL", "STATE"],
+            firstRows: [FIRST_ROW],
           });
 
-          cy.verifyDownload(".csv", { contains: true });
+          H.downloadAndAssert(
+            {
+              isDashboard: false,
+              isEmbed: true,
+              enableFormatting: true,
+              fileType: "csv",
+              downloadUrl: "/api/embed/card/*/query/csv*",
+              downloadMethod: "GET",
+            },
+            (sheet) => {
+              expect(sheet["A2"].v).to.eq(FIRST_ROW[0]);
+              expect(sheet["B2"].v).to.eq(FIRST_ROW[1]);
+              expect(sheet["C2"].v).to.eq(FIRST_ROW[2]);
+            },
+          );
 
           H.expectUnstructuredSnowplowEvent({
             event: "download_results_clicked",
diff --git a/frontend/src/metabase/redux/downloads.ts b/frontend/src/metabase/redux/downloads.ts
index ccee7a77f0b1..a94292ac515b 100644
--- a/frontend/src/metabase/redux/downloads.ts
+++ b/frontend/src/metabase/redux/downloads.ts
@@ -235,11 +235,24 @@ const getDatasetParams = ({
     if (resource === "question" && token) {
       const params = new URLSearchParams(window.location.search);
 
+      const convertSearchParamsToObject = (params: URLSearchParams) => {
+        const object: Record<string, string[]> = {};
+        for (const [key, value] of params.entries()) {
+          if (object[key]) {
+            object[key] = [...object[key], value];
+          } else {
+            object[key] = [value];
+          }
+        }
+
+        return object;
+      };
+
       return {
         method: "GET",
         url: Urls.embedCard(token, type),
         params: new URLSearchParams({
-          parameters: JSON.stringify(Object.fromEntries(params)),
+          parameters: JSON.stringify(convertSearchParamsToObject(params)),
           ..._.mapObject(exportParams, (value) => String(value)),
         }),
       };


Reviews:
user: login: WiNloSt
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-23T15:07:48Z
user: login: WiNloSt
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-23T15:24:30Z
user: login: heypoom
body: The test case and the fix makes sense!
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-26T10:03:29Z
user: login: WiNloSt
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-26T10:06:08Z
user: login: WiNloSt
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-26T10:07:48Z
user: login: heypoom
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-26T10:08:49Z
user: login: WiNloSt
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-26T10:58:47Z
user: login: WiNloSt
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-26T11:02:51Z

Review comments:
diff_hunk: @@ -362,16 +368,32 @@ H.describeWithSnowplowEE(
 
           waitLoading();
 
-          H.main().findByText(value).should("exist");
+          const FIRST_ROW = [
+            5,
+            "leffler.dominique@hotmail.com",
+            FILTER_VALUES[0],
+          ];
 
-          cy.findByRole("button", { name: "Download results" }).click();
-
-          H.popover().within(() => {
-            cy.findByText(".csv").click();
-            cy.findByTestId("download-results-button").click();
+          H.assertTableData({
path: e2e/test/scenarios/embedding/embed-resource-downloads.cy.spec.ts
user: login: WiNloSt
body: assert both the first line of the table, and the first line of the CSV data, that it matches. You can verify this by reverting the fixed code (1 LoC) and the test should fail.
created_at: 2025-05-23T15:05:39Z
author_association: MEMBER
diff_hunk: @@ -194,7 +194,7 @@ export interface TemplateTag {
   dimension?: LocalFieldReference;
   "widget-type"?: string;
   required?: boolean;
-  default?: string | null;
+  default?: string | string[] | null;
path: frontend/src/metabase-types/api/dataset.ts
user: login: WiNloSt
body: I see the actual API call, it could be `string[]` as well. I think we might not use the type before, so it's outdated.
created_at: 2025-05-23T15:06:03Z
author_association: MEMBER
diff_hunk: @@ -235,11 +235,24 @@ const getDatasetParams = ({
     if (resource === "question" && token) {
       const params = new URLSearchParams(window.location.search);
 
+      const convertSearchParamsToObject = (params: URLSearchParams) => {
+        const object: Record<string, string | string[]> = {};
+        for (const [key, value] of params.entries()) {
+          if (object[key]) {
+            object[key] = [...object[key], value];
+          } else {
+            object[key] = [value];
+          }
+        }
+
+        return object;
+      };
+
       return {
         method: "GET",
         url: Urls.embedCard(token, type),
         params: new URLSearchParams({
-          parameters: JSON.stringify(Object.fromEntries(params)),
+          parameters: JSON.stringify(convertSearchParamsToObject(params)),
path: frontend/src/metabase/redux/downloads.ts
user: login: WiNloSt
body: We mistakenly dropped parameter values when a single parameter has multiple values.

This happens because a parameter could be like this `param=1&param=2` and by using `Object.fromEntries`, we'll only get the last parameter value. We need to perform a merge manually.
created_at: 2025-05-23T15:06:37Z
author_association: MEMBER
diff_hunk: @@ -194,7 +194,7 @@ export interface TemplateTag {
   dimension?: LocalFieldReference;
   "widget-type"?: string;
   required?: boolean;
-  default?: string | null;
+  default?: string | string[] | null;
path: frontend/src/metabase-types/api/dataset.ts
user: login: WiNloSt
body: Unfortunately, this failed type in another location. And I don't wanna mess around with query builder code.
created_at: 2025-05-23T15:24:30Z
author_association: MEMBER
diff_hunk: @@ -362,16 +370,32 @@ H.describeWithSnowplowEE(
 
           waitLoading();
 
-          H.main().findByText(value).should("exist");
-
-          cy.findByRole("button", { name: "Download results" }).click();
+          const FIRST_ROW = [
+            5,
+            "leffler.dominique@hotmail.com",
+            FILTER_VALUES[0],
+          ];
 
-          H.popover().within(() => {
-            cy.findByText(".csv").click();
-            cy.findByTestId("download-results-button").click();
+          H.assertTableData({
+            columns: ["ID", "EMAIL", "STATE"],
+            firstRows: [FIRST_ROW],
           });
 
-          cy.verifyDownload(".csv", { contains: true });
+          H.downloadAndAssert(
+            {
+              isDashboard: false,
+              isEmbed: true,
+              enableFormatting: true,
+              fileType: "csv",
+              downloadUrl: "/api/embed/card/*/query/csv*",
+              downloadMethod: "GET",
+            },
+            (sheet) => {
+              expect(sheet["A2"].v).to.eq(FIRST_ROW[0]);
+              expect(sheet["B2"].w).to.eq(FIRST_ROW[1]);
+              expect(sheet["C2"].w).to.eq(FIRST_ROW[2]);
path: e2e/test/scenarios/embedding/embed-resource-downloads.cy.spec.ts
user: login: heypoom
body: What is `v` and `w` here? Maybe worth adding an inline comment.
created_at: 2025-05-26T10:01:29Z
author_association: MEMBER
diff_hunk: @@ -235,11 +235,24 @@ const getDatasetParams = ({
     if (resource === "question" && token) {
       const params = new URLSearchParams(window.location.search);
 
+      const convertSearchParamsToObject = (params: URLSearchParams) => {
+        const object: Record<string, string[]> = {};
+        for (const [key, value] of params.entries()) {
+          if (object[key]) {
+            object[key] = [...object[key], value];
+          } else {
+            object[key] = [value];
+          }
+        }
+
+        return object;
+      };
path: frontend/src/metabase/redux/downloads.ts
user: login: heypoom
body: I feel like this inline function could've been moved outside of this function, it seems pretty useful on its own. Also because it could've been unit-tested on its own.
created_at: 2025-05-26T10:02:48Z
author_association: MEMBER
diff_hunk: @@ -362,16 +370,32 @@ H.describeWithSnowplowEE(
 
           waitLoading();
 
-          H.main().findByText(value).should("exist");
-
-          cy.findByRole("button", { name: "Download results" }).click();
+          const FIRST_ROW = [
+            5,
+            "leffler.dominique@hotmail.com",
+            FILTER_VALUES[0],
+          ];
 
-          H.popover().within(() => {
-            cy.findByText(".csv").click();
-            cy.findByTestId("download-results-button").click();
+          H.assertTableData({
+            columns: ["ID", "EMAIL", "STATE"],
+            firstRows: [FIRST_ROW],
           });
 
-          cy.verifyDownload(".csv", { contains: true });
+          H.downloadAndAssert(
+            {
+              isDashboard: false,
+              isEmbed: true,
+              enableFormatting: true,
+              fileType: "csv",
+              downloadUrl: "/api/embed/card/*/query/csv*",
+              downloadMethod: "GET",
+            },
+            (sheet) => {
+              expect(sheet["A2"].v).to.eq(FIRST_ROW[0]);
+              expect(sheet["B2"].w).to.eq(FIRST_ROW[1]);
+              expect(sheet["C2"].w).to.eq(FIRST_ROW[2]);
path: e2e/test/scenarios/embedding/embed-resource-downloads.cy.spec.ts
user: login: WiNloSt
body: Dude, I have no idea tbh lol. I just copied and pasted the assertion. Let me double check. Btw good eyes!
created_at: 2025-05-26T10:06:08Z
author_association: MEMBER
diff_hunk: @@ -235,11 +235,24 @@ const getDatasetParams = ({
     if (resource === "question" && token) {
       const params = new URLSearchParams(window.location.search);
 
+      const convertSearchParamsToObject = (params: URLSearchParams) => {
+        const object: Record<string, string[]> = {};
+        for (const [key, value] of params.entries()) {
+          if (object[key]) {
+            object[key] = [...object[key], value];
+          } else {
+            object[key] = [value];
+          }
+        }
+
+        return object;
+      };
path: frontend/src/metabase/redux/downloads.ts
user: login: WiNloSt
body: well yeah, it was outside for a sec (literally minutes), but I have a small comment that this function is only used for static embed question. Then I realized by moving this inside, it's clear that this function only needed here without exposing it to more potential callers.

I don't know, it can go both ways, I think limiting the function scope is safer in this case to prevent any potential misuse of the function. 
created_at: 2025-05-26T10:07:47Z
author_association: MEMBER
diff_hunk: @@ -235,11 +235,24 @@ const getDatasetParams = ({
     if (resource === "question" && token) {
       const params = new URLSearchParams(window.location.search);
 
+      const convertSearchParamsToObject = (params: URLSearchParams) => {
+        const object: Record<string, string[]> = {};
+        for (const [key, value] of params.entries()) {
+          if (object[key]) {
+            object[key] = [...object[key], value];
+          } else {
+            object[key] = [value];
+          }
+        }
+
+        return object;
+      };
path: frontend/src/metabase/redux/downloads.ts
user: login: heypoom
body: That works! Let's keep it in here if it wouldn't be useful in other places.
created_at: 2025-05-26T10:08:49Z
author_association: MEMBER
diff_hunk: @@ -362,16 +370,32 @@ H.describeWithSnowplowEE(
 
           waitLoading();
 
-          H.main().findByText(value).should("exist");
-
-          cy.findByRole("button", { name: "Download results" }).click();
+          const FIRST_ROW = [
+            5,
+            "leffler.dominique@hotmail.com",
+            FILTER_VALUES[0],
+          ];
 
-          H.popover().within(() => {
-            cy.findByText(".csv").click();
-            cy.findByTestId("download-results-button").click();
+          H.assertTableData({
+            columns: ["ID", "EMAIL", "STATE"],
+            firstRows: [FIRST_ROW],
           });
 
-          cy.verifyDownload(".csv", { contains: true });
+          H.downloadAndAssert(
+            {
+              isDashboard: false,
+              isEmbed: true,
+              enableFormatting: true,
+              fileType: "csv",
+              downloadUrl: "/api/embed/card/*/query/csv*",
+              downloadMethod: "GET",
+            },
+            (sheet) => {
+              expect(sheet["A2"].v).to.eq(FIRST_ROW[0]);
+              expect(sheet["B2"].w).to.eq(FIRST_ROW[1]);
+              expect(sheet["C2"].w).to.eq(FIRST_ROW[2]);
path: e2e/test/scenarios/embedding/embed-resource-downloads.cy.spec.ts
user: login: WiNloSt
body: ah, forgot to comment about this earlier. So, we're [using xlsx](https://docs.sheetjs.com/docs/csf/cell)
![image](https://github.com/user-attachments/assets/f6a383cd-e1b0-4acf-be3f-3137b631aa75)

`v` is the actual value of the cell, `w` is the formatted text, i.e. what we actually see. From what I can tell, we should use `v`, as it's the actual value.

created_at: 2025-05-26T10:58:47Z
author_association: MEMBER
diff_hunk: @@ -362,16 +370,32 @@ H.describeWithSnowplowEE(
 
           waitLoading();
 
-          H.main().findByText(value).should("exist");
-
-          cy.findByRole("button", { name: "Download results" }).click();
+          const FIRST_ROW = [
+            5,
+            "leffler.dominique@hotmail.com",
+            FILTER_VALUES[0],
+          ];
 
-          H.popover().within(() => {
-            cy.findByText(".csv").click();
-            cy.findByTestId("download-results-button").click();
+          H.assertTableData({
+            columns: ["ID", "EMAIL", "STATE"],
+            firstRows: [FIRST_ROW],
           });
 
-          cy.verifyDownload(".csv", { contains: true });
+          H.downloadAndAssert(
+            {
+              isDashboard: false,
+              isEmbed: true,
+              enableFormatting: true,
+              fileType: "csv",
+              downloadUrl: "/api/embed/card/*/query/csv*",
+              downloadMethod: "GET",
+            },
+            (sheet) => {
+              expect(sheet["A2"].v).to.eq(FIRST_ROW[0]);
+              expect(sheet["B2"].w).to.eq(FIRST_ROW[1]);
+              expect(sheet["C2"].w).to.eq(FIRST_ROW[2]);
path: e2e/test/scenarios/embedding/embed-resource-downloads.cy.spec.ts
user: login: WiNloSt
body: Fixed in 6631fa9493f09002a27eec5c478e68900de1b833

created_at: 2025-05-26T11:02:51Z
author_association: MEMBER

PR comments:
user: login: trunk-io[bot]
created_at: 2025-05-23T16:16:08Z
body: <!-- Trunk Test Analytics -->


[![Static Badge](https://raster.shields.io/badge/14754_tests-9489c12d-purple)](https://app.trunk.io/metabase/flaky-tests/pr/58423?repo=metabase/metabase&commitHash=9489c12da6d5fad00f709fbf8df09c8cedcf6cc5&utm_source=gh-comment) &ensp; [![Static Badge](https://raster.shields.io/badge/6-failed-crimson)](https://app.trunk.io/metabase/flaky-tests/pr/58423?repo=metabase/metabase&commitHash=9489c12da6d5fad00f709fbf8df09c8cedcf6cc5&utm_source=gh-comment&runConclusion=FAILURE) &ensp; [![Static Badge](https://raster.shields.io/badge/3-flaky-red)](https://app.trunk.io/metabase/flaky-tests/pr/58423?repo=metabase/metabase&commitHash=9489c12da6d5fad00f709fbf8df09c8cedcf6cc5&utm_source=gh-comment&runConclusion=FAILURE) &ensp; [![Static Badge](https://raster.shields.io/badge/0-quarantined-yellow)](https://app.trunk.io/metabase/flaky-tests/pr/58423?repo=metabase/metabase&commitHash=9489c12da6d5fad00f709fbf8df09c8cedcf6cc5&utm_source=gh-comment&runConclusion=QUARANTINED)


| Failed Test | Failure Summary | Logs |
| --- | --- | --- |
| <code>scenarios > question > download download format preference should remember the download format on dashboards</code> | The expected HTML element did not contain the expected string '.xlsx' after a 4000ms timeout. | [Logs ↗︎](https://app.trunk.io/metabase/flaky-tests/pr/58423?repo=metabase/metabase&commitHash=9489c12da6d5fad00f709fbf8df09c8cedcf6cc5&utm_source=gh-comment&runConclusion=FAILURE&runId=14148dd5-3dcb-5aee-acd0-6e26d44b5ccf) |
| <code>scenarios > question > download download format preference should remember the selected format across page reloads</code> | The expected HTML element did not contain the expected string '.xlsx' after a 4-second timeout. | [Logs ↗︎](https://app.trunk.io/metabase/flaky-tests/pr/58423?repo=metabase/metabase&commitHash=9489c12da6d5fad00f709fbf8df09c8cedcf6cc5&utm_source=gh-comment&runConclusion=FAILURE&runId=3ed05b29-bf65-5f82-b32f-fdec68bd8bea) |
| <code>scenarios > question > download download format preference should remember the selected format across page reloads</code> | The expected HTML element did not contain the expected string '.xlsx' after a 4-second timeout. | [Logs ↗︎](https://app.trunk.io/metabase/flaky-tests/pr/58423?repo=metabase/metabase&commitHash=9489c12da6d5fad00f709fbf8df09c8cedcf6cc5&utm_source=gh-comment&runConclusion=FAILURE&runId=5cff394e-5c97-505a-b006-bb6f056977a1) |
| <code>scenarios > question > download download format preference should remember the download format on dashboards</code> | The expected HTML element did not contain the expected string '.xlsx' after a 4000ms timeout. | [Logs ↗︎](https://app.trunk.io/metabase/flaky-tests/pr/58423?repo=metabase/metabase&commitHash=9489c12da6d5fad00f709fbf8df09c8cedcf6cc5&utm_source=gh-comment&runConclusion=FAILURE&runId=5ea7e137-aac0-5584-a477-e31997b8d470) |

[... and 2 more](https://app.trunk.io/metabase/flaky-tests/pr/58423?repo=metabase/metabase&commitHash=9489c12da6d5fad00f709fbf8df09c8cedcf6cc5&utm_source=gh-comment&runConclusion=FAILURE)


| Flaky Test | Failure Summary | Logs |
| --- | --- | --- |
| <code>issue 16170 replace missing values with "Zero" should work on Mongo (metabase\#16170)</code> | Unable to find an element with data-testid "echarts-tooltip" within 4 seconds. | [Logs ↗︎](https://app.trunk.io/metabase/flaky-tests/pr/58423?repo=metabase/metabase&commitHash=9489c12da6d5fad00f709fbf8df09c8cedcf6cc5&utm_source=gh-comment&runConclusion=FAILURE&runId=3cbd9ac7-d9d1-5916-af68-7baf02c354ba) |
| <code>scenarios > dashboard > visualizer > basics should rename a dashboard card</code> | The expected element was found in the DOM, but it was not expected to be there. | [Logs ↗︎](https://app.trunk.io/metabase/flaky-tests/pr/58423?repo=metabase/metabase&commitHash=9489c12da6d5fad00f709fbf8df09c8cedcf6cc5&utm_source=gh-comment&runConclusion=FAILURE&runId=9fa65b5e-42ac-5b32-bcc2-bc577c8744c1) |
| <code>scenarios > dashboard > visualizer > basics should rename a dashboard card</code> | The expected element was found in the DOM, but it was not expected to be there. | [Logs ↗︎](https://app.trunk.io/metabase/flaky-tests/pr/58423?repo=metabase/metabase&commitHash=9489c12da6d5fad00f709fbf8df09c8cedcf6cc5&utm_source=gh-comment&runConclusion=FAILURE&runId=aef63ff9-15e0-5a38-a2e9-244fe3427406) |
<sub>

[View Full Report ↗︎](https://app.trunk.io/metabase/flaky-tests/pr/58423?repo=metabase/metabase&commitHash=9489c12da6d5fad00f709fbf8df09c8cedcf6cc5&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-05-26T13:24:53Z
body: 
  ## e2e tests failed on `9489c12da6d5fad00f709fbf8df09c8cedcf6cc5-3`

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

  File | Test Name
  ---- | ---------
  `downloads.cy.spec.js` | (_flaky_) scenarios > question > download > download format preference > should remember the selected format across page reloads
`downloads.cy.spec.js` | (_flaky_) scenarios > question > download > download format preference > should remember the download format on dashboards

author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-05-27T05:43:39Z
body: @WiNloSt Manual conflict resolution is required on #58507
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-05-27T05:43:48Z
body: @WiNloSt Manual conflict resolution is required on #58508
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-05-27T05:43:57Z
body: @WiNloSt Manual conflict resolution is required on #58509
author_association: CONTRIBUTOR