From dc8b72d6b84f1008f89e99b34438d798a8d2b94c Mon Sep 17 00:00:00 2001
From: Alexander Polyankin <alexander.polyankin@metabase.com>
Date: Tue, 12 Apr 2022 15:20:28 +0300
Subject: [PATCH] Make event lines render for native queries (#21611)

---
 .../metabase/visualizations/lib/timelines.js  | 69 +++++++++--------
 .../collections/timelines.cy.spec.js          | 75 ++++++++++++++++++-
 .../visualizations/line.cy.spec.js            | 42 -----------
 3 files changed, 112 insertions(+), 74 deletions(-)

diff --git a/frontend/src/metabase/visualizations/lib/timelines.js b/frontend/src/metabase/visualizations/lib/timelines.js
index 2a6beade5f5..8f47faa26bc 100644
--- a/frontend/src/metabase/visualizations/lib/timelines.js
+++ b/frontend/src/metabase/visualizations/lib/timelines.js
@@ -14,10 +14,6 @@ function getAxis(chart) {
   return chart.svg().select(".axis.x");
 }
 
-function getBrush(chart) {
-  return chart.svg().select(".brush");
-}
-
 function getScale(chart) {
   return chart.x();
 }
@@ -101,6 +97,21 @@ function hasEventText(events, eventIndex, eventPoints) {
   }
 }
 
+function renderEventBrush({ chart }) {
+  const g = chart.g();
+  const margins = chart.margins();
+  const brush = g.selectAll(".event-brush").data([0]);
+  brush.exit().remove();
+
+  brush
+    .enter()
+    .insert("g", ":first-child")
+    .attr("class", "event-brush")
+    .attr("transform", `translate(${margins.left}, ${margins.top})`);
+
+  return brush;
+}
+
 function renderEventLines({
   chart,
   brush,
@@ -134,7 +145,7 @@ function renderEventTicks({
   onSelectTimelineEvents,
   onDeselectTimelineEvents,
 }) {
-  const eventAxis = axis.selectAll(".event-axis").data([eventGroups]);
+  const eventAxis = axis.selectAll(".event-axis").data([0]);
   const eventLines = brush.selectAll(".event-line").data(eventGroups);
   eventAxis.exit().remove();
 
@@ -216,40 +227,38 @@ export function renderEvents(
     onDeselectTimelineEvents,
   },
 ) {
-  if (!isTimeseries) {
+  const axis = getAxis(chart);
+
+  if (!axis || !isTimeseries) {
     return;
   }
 
-  const axis = getAxis(chart);
-  const brush = getBrush(chart);
   const scale = getScale(chart);
   const eventMapping = getEventMapping(events, scale);
   const eventPoints = getEventPoints(eventMapping);
   const eventGroups = getEventGroups(eventMapping);
 
-  if (brush) {
-    renderEventLines({
-      chart,
-      brush,
-      eventPoints,
-      eventGroups,
-      selectedEventIds,
-    });
-  }
+  const brush = renderEventBrush({ chart, eventGroups });
 
-  if (axis) {
-    renderEventTicks({
-      axis,
-      brush,
-      eventPoints,
-      eventGroups,
-      selectedEventIds,
-      onHoverChange,
-      onOpenTimelines,
-      onSelectTimelineEvents,
-      onDeselectTimelineEvents,
-    });
-  }
+  renderEventLines({
+    chart,
+    brush,
+    eventPoints,
+    eventGroups,
+    selectedEventIds,
+  });
+
+  renderEventTicks({
+    axis,
+    brush,
+    eventPoints,
+    eventGroups,
+    selectedEventIds,
+    onHoverChange,
+    onOpenTimelines,
+    onSelectTimelineEvents,
+    onDeselectTimelineEvents,
+  });
 }
 
 export function hasEventAxis({ timelineEvents = [], isTimeseries }) {
diff --git a/frontend/test/metabase-visual/collections/timelines.cy.spec.js b/frontend/test/metabase-visual/collections/timelines.cy.spec.js
index c29c59776f7..7d5a3a80c7a 100644
--- a/frontend/test/metabase-visual/collections/timelines.cy.spec.js
+++ b/frontend/test/metabase-visual/collections/timelines.cy.spec.js
@@ -1,4 +1,8 @@
-import { restore } from "__support__/e2e/cypress";
+import { restore, visitQuestionAdhoc } from "__support__/e2e/cypress";
+import { SAMPLE_DB_ID } from "__support__/e2e/cypress_data";
+import { SAMPLE_DATABASE } from "__support__/e2e/cypress_sample_database";
+
+const { ORDERS, ORDERS_ID } = SAMPLE_DATABASE;
 
 const EVENTS = [
   { name: "Event 1", timestamp: "2020-01-01", icon: "star" },
@@ -19,7 +23,7 @@ describe("timelines", () => {
     cy.percySnapshot();
   });
 
-  it("should display timeline events", () => {
+  it("should display timeline events in collections", () => {
     cy.createTimelineWithEvents({ events: EVENTS }).then(({ timeline }) => {
       cy.visit(`/collection/root/timelines/${timeline.id}`);
 
@@ -27,4 +31,71 @@ describe("timelines", () => {
       cy.percySnapshot();
     });
   });
+
+  it("should display timeline events with a structured question", () => {
+    cy.createTimelineWithEvents({
+      timeline: { name: "Releases" },
+      events: [
+        { name: "v20", timestamp: "2017-10-30T00:00:00Z", icon: "cloud" },
+        { name: "v21", timestamp: "2018-08-08T00:00:00Z", icon: "mail" },
+        { name: "RC1", timestamp: "2019-05-10T00:00:00Z", icon: "bell" },
+        { name: "RC2", timestamp: "2019-05-20T00:00:00Z", icon: "star" },
+      ],
+    });
+
+    visitQuestionAdhoc({
+      dataset_query: {
+        type: "query",
+        query: {
+          "source-table": ORDERS_ID,
+          aggregation: [["count"]],
+          breakout: [
+            ["field", ORDERS.CREATED_AT, { "temporal-unit": "month" }],
+          ],
+        },
+        database: SAMPLE_DB_ID,
+      },
+      display: "line",
+      visualization_settings: {
+        "graph.dimensions": ["CREATED_AT"],
+        "graph.metrics": ["count"],
+        "graph.show_values": true,
+      },
+    });
+
+    cy.findByLabelText("star icon").realHover();
+    cy.findByText("RC1");
+    cy.percySnapshot();
+  });
+
+  it("should display timeline events with a native question", () => {
+    cy.createTimelineWithEvents({
+      timeline: { name: "Releases" },
+      events: [
+        { name: "v20", timestamp: "2017-10-30T00:00:00Z", icon: "cloud" },
+        { name: "v21", timestamp: "2018-08-08T00:00:00Z", icon: "mail" },
+        { name: "RC1", timestamp: "2019-05-10T00:00:00Z", icon: "bell" },
+        { name: "RC2", timestamp: "2019-05-20T00:00:00Z", icon: "star" },
+      ],
+    });
+
+    visitQuestionAdhoc({
+      dataset_query: {
+        type: "native",
+        native: {
+          query: "SELECT ID, CREATED_AT FROM ORDERS LIMIT 25",
+        },
+        database: SAMPLE_DB_ID,
+      },
+      display: "line",
+      visualization_settings: {
+        "graph.dimensions": ["CREATED_AT"],
+        "graph.metrics": ["ID"],
+      },
+    });
+
+    cy.findByLabelText("star icon").realHover();
+    cy.findByText("RC1");
+    cy.percySnapshot();
+  });
 });
diff --git a/frontend/test/metabase-visual/visualizations/line.cy.spec.js b/frontend/test/metabase-visual/visualizations/line.cy.spec.js
index 74039680b9e..708cf04d4a2 100644
--- a/frontend/test/metabase-visual/visualizations/line.cy.spec.js
+++ b/frontend/test/metabase-visual/visualizations/line.cy.spec.js
@@ -191,46 +191,4 @@ describe("visual tests > visualizations > line", () => {
 
     cy.percySnapshot();
   });
-
-  it("with timeline events", () => {
-    cy.createTimelineWithEvents({
-      timeline: { name: "Releases" },
-      events: [
-        { name: "v20", timestamp: "2017-10-30T00:00:00Z", icon: "cloud" },
-        { name: "v21", timestamp: "2018-08-08T00:00:00Z", icon: "mail" },
-        { name: "RC1", timestamp: "2019-05-10T00:00:00Z", icon: "bell" },
-        { name: "RC2", timestamp: "2019-05-20T00:00:00Z", icon: "star" },
-      ],
-    });
-
-    visitQuestionAdhoc({
-      dataset_query: {
-        type: "query",
-        query: {
-          "source-table": ORDERS_ID,
-          aggregation: [["count"]],
-          breakout: [
-            [
-              "field",
-              ORDERS.CREATED_AT,
-              {
-                "temporal-unit": "month",
-              },
-            ],
-          ],
-        },
-        database: SAMPLE_DB_ID,
-      },
-      display: "line",
-      visualization_settings: {
-        "graph.dimensions": ["CREATED_AT"],
-        "graph.metrics": ["count"],
-        "graph.show_values": true,
-      },
-    });
-
-    cy.findByLabelText("star icon").realHover();
-    cy.findByText("RC1");
-    cy.percySnapshot();
-  });
 });
-- 
GitLab