From 9d8201f83d124ce043f03d7b19899aaa9be4b187 Mon Sep 17 00:00:00 2001
From: "metabase-bot[bot]"
 <109303359+metabase-bot[bot]@users.noreply.github.com>
Date: Wed, 3 Apr 2024 13:11:48 +0000
Subject: [PATCH] Fix row id mapping in object detail modal displaying (#40874)
 (#40951)

* Map row id on object detail id

* Add a fallback

* Add e2e test

* Add a limit

* Provide a fix for #34070

* Simplify test

Co-authored-by: Uladzimir Havenchyk <125459446+uladzimirdev@users.noreply.github.com>
---
 .../object_detail.cy.spec.js                  | 107 +++++++++++++++++-
 .../src/metabase/query_builder/selectors.js   |  23 ++++
 .../TableInteractive/TableInteractive.jsx     |  21 +++-
 3 files changed, 141 insertions(+), 10 deletions(-)

diff --git a/e2e/test/scenarios/visualizations-tabular/object_detail.cy.spec.js b/e2e/test/scenarios/visualizations-tabular/object_detail.cy.spec.js
index 5329b04889f..007d0119001 100644
--- a/e2e/test/scenarios/visualizations-tabular/object_detail.cy.spec.js
+++ b/e2e/test/scenarios/visualizations-tabular/object_detail.cy.spec.js
@@ -12,10 +12,19 @@ import {
   getTableId,
   visitPublicQuestion,
   visitPublicDashboard,
+  createQuestion,
 } from "e2e/support/helpers";
 
-const { ORDERS, ORDERS_ID, PRODUCTS, PRODUCTS_ID, PEOPLE, PEOPLE_ID } =
-  SAMPLE_DATABASE;
+const {
+  ORDERS,
+  ORDERS_ID,
+  PRODUCTS,
+  PRODUCTS_ID,
+  PEOPLE,
+  PEOPLE_ID,
+  REVIEWS,
+  REVIEWS_ID,
+} = SAMPLE_DATABASE;
 
 const FIRST_ORDER_ID = 9676;
 const SECOND_ORDER_ID = 10874;
@@ -45,7 +54,7 @@ describe("scenarios > question > object details", { tags: "@slow" }, () => {
     cy.signInAsAdmin();
   });
 
-  it("shows correct object detail card for questions with joins (metabase #27094)", () => {
+  it("shows correct object detail card for questions with joins (metabase#27094)", () => {
     const questionDetails = {
       name: "14775",
       query: {
@@ -74,6 +83,96 @@ describe("scenarios > question > object details", { tags: "@slow" }, () => {
     });
   });
 
+  it("shows correct object detail card for questions with joins after clicking on view details (metabase#39477)", () => {
+    const questionDetails = {
+      name: "39477",
+      query: {
+        "source-table": ORDERS_ID,
+        joins: [
+          {
+            fields: "all",
+            "source-table": PRODUCTS_ID,
+            condition: [
+              "=",
+              ["field-id", ORDERS.PRODUCT_ID],
+              ["joined-field", "Products", ["field-id", PRODUCTS.ID]],
+            ],
+            alias: "Products",
+          },
+        ],
+        limit: 5,
+      },
+    };
+
+    createQuestion(questionDetails, { visitQuestion: true });
+
+    cy.log("check click on 1st row");
+
+    cy.get(".cellData").contains("37.65").realHover();
+    cy.findByTestId("detail-shortcut").findByRole("button").click();
+
+    cy.findByTestId("object-detail").within(() => {
+      cy.get("h2").should("contain", "Order").should("contain", 1);
+      cy.findByTestId("object-detail-close-button").click();
+    });
+
+    cy.log("check click on 3rd row");
+
+    cy.get(".cellData").contains("52.72").realHover();
+    cy.findByTestId("detail-shortcut").findByRole("button").click();
+
+    cy.findByTestId("object-detail").within(() => {
+      cy.get("h2").should("contain", "Order").should("contain", 3);
+      cy.findByText("52.72");
+    });
+  });
+
+  it("applies correct filter (metabase#34070)", () => {
+    const questionDetails = {
+      name: "34070",
+      query: {
+        "source-table": PRODUCTS_ID,
+        joins: [
+          {
+            fields: "all",
+            alias: "Products",
+            condition: [
+              "=",
+              [
+                "field",
+                PRODUCTS.ID,
+                {
+                  "base-type": "type/BigInteger",
+                },
+              ],
+              [
+                "field",
+                REVIEWS.PRODUCT_ID,
+                {
+                  "base-type": "type/BigInteger",
+                  "join-alias": "Products",
+                },
+              ],
+            ],
+            "source-table": REVIEWS_ID,
+          },
+        ],
+        limit: 10,
+      },
+    };
+
+    createQuestion(questionDetails, { visitQuestion: true });
+
+    cy.get(".cellData").contains("4966277046676").realHover();
+    cy.findByTestId("detail-shortcut").findByRole("button").click();
+
+    cy.findByRole("dialog").findByTestId("fk-relation-orders").click();
+
+    cy.findByTestId("qb-filters-panel")
+      .findByText("Product ID is 3")
+      .should("be.visible");
+  });
+
   it("handles browsing records by PKs", () => {
     cy.createQuestion(TEST_QUESTION, { visitQuestion: true });
     drillPK({ id: FIRST_ORDER_ID });
@@ -212,7 +311,7 @@ describe("scenarios > question > object details", { tags: "@slow" }, () => {
     cy.findByTestId("object-detail").findByText("Searsboro").click();
   });
 
-  it("should work with non-numeric IDs (metabse#22768)", () => {
+  it("should work with non-numeric IDs (metabase#22768)", () => {
     cy.request("PUT", `/api/field/${PRODUCTS.ID}`, {
       semantic_type: null,
     });
diff --git a/frontend/src/metabase/query_builder/selectors.js b/frontend/src/metabase/query_builder/selectors.js
index a2c1ef2130c..d9fb7bf4fc4 100644
--- a/frontend/src/metabase/query_builder/selectors.js
+++ b/frontend/src/metabase/query_builder/selectors.js
@@ -182,6 +182,29 @@ export const getPKRowIndexMap = createSelector(
   },
 );
 
+// it's very similar to `getPKRowIndexMap` but it is required for covering "view details" click
+// we don't have objectId there, only rowId, mapping from `getPKRowIndexMap` is opposite
+// if rows are showing the same PK, only last one will have the entry in the map
+// and we'll not know which object to show
+export const getRowIndexToPKMap = createSelector(
+  [getFirstQueryResult, getPKColumnIndex],
+  (result, PKColumnIndex) => {
+    if (!result || !Number.isSafeInteger(PKColumnIndex)) {
+      return {};
+    }
+    const { rows } = result.data;
+    if (PKColumnIndex < 0) {
+      return rows.map((_, index) => index);
+    }
+    const map = {};
+    rows.forEach((row, index) => {
+      const PKValue = row[PKColumnIndex];
+      map[index] = PKValue;
+    });
+    return map;
+  },
+);
+
 export const getQueryStartTime = state => state.qb.queryStartTime;
 
 export const getDatabaseId = createSelector(
diff --git a/frontend/src/metabase/visualizations/components/TableInteractive/TableInteractive.jsx b/frontend/src/metabase/visualizations/components/TableInteractive/TableInteractive.jsx
index 6108edc4a0e..bc18e35ce3b 100644
--- a/frontend/src/metabase/visualizations/components/TableInteractive/TableInteractive.jsx
+++ b/frontend/src/metabase/visualizations/components/TableInteractive/TableInteractive.jsx
@@ -19,7 +19,10 @@ import Tooltip from "metabase/core/components/Tooltip";
 import { getScrollBarSize } from "metabase/lib/dom";
 import { formatValue } from "metabase/lib/formatting";
 import { zoomInRow } from "metabase/query_builder/actions";
-import { getQueryBuilderMode } from "metabase/query_builder/selectors";
+import {
+  getRowIndexToPKMap,
+  getQueryBuilderMode,
+} from "metabase/query_builder/selectors";
 import { EmotionCacheProvider } from "metabase/styled-components/components/EmotionCacheProvider";
 import { Icon, DelayGroup } from "metabase/ui";
 import {
@@ -75,6 +78,7 @@ function pickRowsToMeasure(rows, columnIndex, count = 10) {
 
 const mapStateToProps = state => ({
   queryBuilderMode: getQueryBuilderMode(state),
+  rowIndexToPkMap: getRowIndexToPKMap(state),
 });
 
 const mapDispatchToProps = dispatch => ({
@@ -477,11 +481,16 @@ class TableInteractive extends Component {
     }
   }
 
-  pkClick = rowIndex => {
-    const objectId = this.state.IDColumn
-      ? this.props.data.rows[rowIndex][this.state.IDColumnIndex]
-      : rowIndex;
-    return e => this.props.onZoomRow(objectId);
+  pkClick = rowIndex => () => {
+    let objectId;
+
+    if (this.state.IDColumn) {
+      objectId = this.props.data.rows[rowIndex][this.state.IDColumnIndex];
+    } else {
+      objectId = this.props.rowIndexToPkMap[rowIndex] ?? rowIndex;
+    }
+
+    this.props.onZoomRow(objectId);
   };
 
   onKeyDown = event => {
-- 
GitLab