From 07095e13184107a60d6395f2c363b846a2dae767 Mon Sep 17 00:00:00 2001
From: Alexander Polyankin <alexander.polyankin@metabase.com>
Date: Fri, 8 Apr 2022 20:03:57 +0300
Subject: [PATCH] Handle names for default timelines (#21514)

---
 .../src/metabase-types/api/mocks/timeline.ts  |  1 +
 frontend/src/metabase-types/api/timeline.ts   |  1 +
 .../entities/timeline-events/forms.js         |  4 +-
 frontend/src/metabase/entities/timelines.js   |  2 +-
 .../src/metabase/entities/timelines/forms.js  |  6 +-
 frontend/src/metabase/lib/timelines.ts        | 27 +++++--
 .../src/metabase/query_builder/selectors.js   |  3 +-
 frontend/src/metabase/schema.js               |  1 +
 .../EditTimelineModal/EditTimelineModal.tsx   |  8 +-
 .../NewTimelineModal/NewTimelineModal.tsx     |  1 +
 .../components/TimelineCard/TimelineCard.tsx  |  3 +-
 .../TimelineDetailsModal.tsx                  |  3 +-
 .../TimelineDetailsModal.unit.spec.tsx        | 32 ++++++++
 .../TimelineListModal/TimelineListModal.tsx   |  8 +-
 .../TimelineDetailsModal.tsx                  |  4 +-
 .../components/TimelineCard/TimelineCard.tsx  |  5 +-
 .../__support__/e2e/commands/api/timeline.js  |  2 +
 .../collections/timelines.cy.spec.js          | 77 +++++++++++++++++--
 .../scenarios/question/timelines.cy.spec.js   |  4 +-
 src/metabase/api/timeline.clj                 |  2 +-
 20 files changed, 161 insertions(+), 33 deletions(-)

diff --git a/frontend/src/metabase-types/api/mocks/timeline.ts b/frontend/src/metabase-types/api/mocks/timeline.ts
index e7898fbd43f..b0ed0639bba 100644
--- a/frontend/src/metabase-types/api/mocks/timeline.ts
+++ b/frontend/src/metabase-types/api/mocks/timeline.ts
@@ -7,6 +7,7 @@ export const createMockTimeline = (opts?: Partial<Timeline>): Timeline => ({
   name: "Events",
   description: "A timeline of events",
   icon: "star",
+  default: false,
   archived: false,
   ...opts,
 });
diff --git a/frontend/src/metabase-types/api/timeline.ts b/frontend/src/metabase-types/api/timeline.ts
index 3a6645ab7c5..2aa4437f669 100644
--- a/frontend/src/metabase-types/api/timeline.ts
+++ b/frontend/src/metabase-types/api/timeline.ts
@@ -7,6 +7,7 @@ export interface Timeline {
   name: string;
   description: string | null;
   icon: string;
+  default: boolean;
   archived: boolean;
   collection?: Collection;
   events?: TimelineEvent[];
diff --git a/frontend/src/metabase/entities/timeline-events/forms.js b/frontend/src/metabase/entities/timeline-events/forms.js
index d9111977b70..7a09270a51c 100644
--- a/frontend/src/metabase/entities/timeline-events/forms.js
+++ b/frontend/src/metabase/entities/timeline-events/forms.js
@@ -1,5 +1,5 @@
 import { t } from "ttag";
-import { getTimelineIcons } from "metabase/lib/timelines";
+import { getTimelineIcons, getTimelineName } from "metabase/lib/timelines";
 import validate from "metabase/lib/validate";
 
 const createForm = ({ timelines }) => {
@@ -37,7 +37,7 @@ const createForm = ({ timelines }) => {
       name: "timeline_id",
       title: t`Timeline`,
       type: timelines.length > 1 ? "select" : "hidden",
-      options: timelines.map(t => ({ name: t.name, value: t.id })),
+      options: timelines.map(t => ({ name: getTimelineName(t), value: t.id })),
     },
     {
       name: "source",
diff --git a/frontend/src/metabase/entities/timelines.js b/frontend/src/metabase/entities/timelines.js
index 355365d0472..87aa80221be 100644
--- a/frontend/src/metabase/entities/timelines.js
+++ b/frontend/src/metabase/entities/timelines.js
@@ -37,7 +37,7 @@ const Timelines = createEntity({
     setArchived: ({ id }, archived, opts) =>
       Timelines.actions.update(
         { id },
-        { archived },
+        { archived, default: false },
         undo(opts, t`timeline`, archived ? t`archived` : t`unarchived`),
       ),
   },
diff --git a/frontend/src/metabase/entities/timelines/forms.js b/frontend/src/metabase/entities/timelines/forms.js
index 2f41dbca78b..d0fee6e135a 100644
--- a/frontend/src/metabase/entities/timelines/forms.js
+++ b/frontend/src/metabase/entities/timelines/forms.js
@@ -6,7 +6,7 @@ const createForm = () => {
   return [
     {
       name: "name",
-      title: t`Timeline name`,
+      title: t`Name`,
       placeholder: t`Product releases`,
       autoFocus: true,
       validate: validate.required().maxLength(255),
@@ -28,6 +28,10 @@ const createForm = () => {
       name: "collection_id",
       type: "hidden",
     },
+    {
+      name: "default",
+      type: "hidden",
+    },
   ];
 };
 
diff --git a/frontend/src/metabase/lib/timelines.ts b/frontend/src/metabase/lib/timelines.ts
index f02262894f0..81207c91431 100644
--- a/frontend/src/metabase/lib/timelines.ts
+++ b/frontend/src/metabase/lib/timelines.ts
@@ -2,14 +2,10 @@ import { t } from "ttag";
 import { Collection, Timeline } from "metabase-types/api";
 import { canonicalCollectionId } from "metabase/collections/utils";
 
-export const getDefaultTimeline = (
-  collection: Collection,
-): Partial<Timeline> => {
-  return {
-    name: t`${collection.name} events`,
-    collection_id: canonicalCollectionId(collection.id),
-    icon: getDefaultTimelineIcon(),
-  };
+export const getTimelineName = (timeline: Timeline) => {
+  return timeline.default && timeline.collection
+    ? getDefaultTimelineName(timeline.collection)
+    : timeline.name;
 };
 
 export const getTimelineIcons = () => {
@@ -23,6 +19,21 @@ export const getTimelineIcons = () => {
   ];
 };
 
+export const getDefaultTimeline = (
+  collection: Collection,
+): Partial<Timeline> => {
+  return {
+    name: getDefaultTimelineName(collection),
+    collection_id: canonicalCollectionId(collection.id),
+    icon: getDefaultTimelineIcon(),
+    default: true,
+  };
+};
+
+export const getDefaultTimelineName = (collection: Collection) => {
+  return t`${collection.name} events`;
+};
+
 export const getDefaultTimelineIcon = () => {
   return "star";
 };
diff --git a/frontend/src/metabase/query_builder/selectors.js b/frontend/src/metabase/query_builder/selectors.js
index b0f02a614fb..2d27480f233 100644
--- a/frontend/src/metabase/query_builder/selectors.js
+++ b/frontend/src/metabase/query_builder/selectors.js
@@ -27,6 +27,7 @@ import Timelines from "metabase/entities/timelines";
 import { getMetadata } from "metabase/selectors/metadata";
 import { getAlerts } from "metabase/alert/selectors";
 import { parseTimestamp } from "metabase/lib/time";
+import { getTimelineName } from "metabase/lib/timelines";
 import {
   getXValues,
   isTimeseries,
@@ -674,7 +675,7 @@ export const getTransformedTimelines = createSelector(
             .value(),
         ),
       )
-      .sortBy(timeline => timeline.name)
+      .sortBy(getTimelineName)
       .sortBy(timeline => timeline.collection?.personal_owner_id != null) // personal collections last
       .value();
   },
diff --git a/frontend/src/metabase/schema.js b/frontend/src/metabase/schema.js
index 82e49b2bfa7..d6935db9ba0 100644
--- a/frontend/src/metabase/schema.js
+++ b/frontend/src/metabase/schema.js
@@ -86,6 +86,7 @@ MetricSchema.define({
 });
 
 TimelineSchema.define({
+  collection: CollectionSchema,
   events: [TimelineEventSchema],
 });
 
diff --git a/frontend/src/metabase/timelines/collections/components/EditTimelineModal/EditTimelineModal.tsx b/frontend/src/metabase/timelines/collections/components/EditTimelineModal/EditTimelineModal.tsx
index f6cab3faab0..3be1a1a0d6e 100644
--- a/frontend/src/metabase/timelines/collections/components/EditTimelineModal/EditTimelineModal.tsx
+++ b/frontend/src/metabase/timelines/collections/components/EditTimelineModal/EditTimelineModal.tsx
@@ -1,4 +1,4 @@
-import React, { useCallback } from "react";
+import React, { useCallback, useMemo } from "react";
 import { t } from "ttag";
 import Form from "metabase/containers/Form";
 import forms from "metabase/entities/timelines/forms";
@@ -23,6 +23,10 @@ const EditTimelineModal = ({
   onCancel,
   onClose,
 }: EditTimelineModalProps): JSX.Element => {
+  const initialValues = useMemo(() => {
+    return { ...timeline, default: false };
+  }, [timeline]);
+
   const handleSubmit = useCallback(
     async (values: Partial<Timeline>) => {
       await onSubmit(values, collection);
@@ -40,7 +44,7 @@ const EditTimelineModal = ({
       <ModalBody>
         <Form
           form={forms.details}
-          initialValues={timeline}
+          initialValues={initialValues}
           isModal={true}
           onSubmit={handleSubmit}
           onClose={onCancel}
diff --git a/frontend/src/metabase/timelines/collections/components/NewTimelineModal/NewTimelineModal.tsx b/frontend/src/metabase/timelines/collections/components/NewTimelineModal/NewTimelineModal.tsx
index 335d083e58d..2319f8dd973 100644
--- a/frontend/src/metabase/timelines/collections/components/NewTimelineModal/NewTimelineModal.tsx
+++ b/frontend/src/metabase/timelines/collections/components/NewTimelineModal/NewTimelineModal.tsx
@@ -25,6 +25,7 @@ const NewTimelineModal = ({
     return {
       collection_id: canonicalCollectionId(collection.id),
       icon: getDefaultTimelineIcon(),
+      default: false,
     };
   }, [collection]);
 
diff --git a/frontend/src/metabase/timelines/collections/components/TimelineCard/TimelineCard.tsx b/frontend/src/metabase/timelines/collections/components/TimelineCard/TimelineCard.tsx
index 3c5b333ab0a..837bce6d77c 100644
--- a/frontend/src/metabase/timelines/collections/components/TimelineCard/TimelineCard.tsx
+++ b/frontend/src/metabase/timelines/collections/components/TimelineCard/TimelineCard.tsx
@@ -1,6 +1,7 @@
 import React, { memo } from "react";
 import { t, msgid, ngettext } from "ttag";
 import * as Urls from "metabase/lib/urls";
+import { getTimelineName } from "metabase/lib/timelines";
 import EntityMenu from "metabase/components/EntityMenu";
 import { Collection, Timeline } from "metabase-types/api";
 import {
@@ -35,7 +36,7 @@ const TimelineCard = ({
     <CardRoot to={!timeline.archived ? timelineUrl : ""}>
       <CardIcon name={timeline.icon} />
       <CardBody>
-        <CardTitle>{timeline.name}</CardTitle>
+        <CardTitle>{getTimelineName(timeline)}</CardTitle>
         {timeline.description && (
           <CardDescription>{timeline.description}</CardDescription>
         )}
diff --git a/frontend/src/metabase/timelines/collections/components/TimelineDetailsModal/TimelineDetailsModal.tsx b/frontend/src/metabase/timelines/collections/components/TimelineDetailsModal/TimelineDetailsModal.tsx
index 3c02d56c7ec..18f5314d0ae 100644
--- a/frontend/src/metabase/timelines/collections/components/TimelineDetailsModal/TimelineDetailsModal.tsx
+++ b/frontend/src/metabase/timelines/collections/components/TimelineDetailsModal/TimelineDetailsModal.tsx
@@ -2,6 +2,7 @@ import React, { useCallback, useMemo, useState } from "react";
 import { t } from "ttag";
 import _ from "underscore";
 import { parseTimestamp } from "metabase/lib/time";
+import { getTimelineName } from "metabase/lib/timelines";
 import { SEARCH_DEBOUNCE_DURATION } from "metabase/lib/constants";
 import * as Urls from "metabase/lib/urls";
 import { useDebouncedValue } from "metabase/hooks/use-debounced-value";
@@ -42,7 +43,7 @@ const TimelineDetailsModal = ({
   onClose,
   onGoBack,
 }: TimelineDetailsModalProps): JSX.Element => {
-  const title = isArchive ? t`Archived events` : timeline.name;
+  const title = isArchive ? t`Archived events` : getTimelineName(timeline);
   const [inputText, setInputText] = useState("");
 
   const searchText = useDebouncedValue(
diff --git a/frontend/src/metabase/timelines/collections/components/TimelineDetailsModal/TimelineDetailsModal.unit.spec.tsx b/frontend/src/metabase/timelines/collections/components/TimelineDetailsModal/TimelineDetailsModal.unit.spec.tsx
index f9976b1dce9..5615364ecc7 100644
--- a/frontend/src/metabase/timelines/collections/components/TimelineDetailsModal/TimelineDetailsModal.unit.spec.tsx
+++ b/frontend/src/metabase/timelines/collections/components/TimelineDetailsModal/TimelineDetailsModal.unit.spec.tsx
@@ -11,6 +11,38 @@ import TimelineDetailsModal, {
 } from "./TimelineDetailsModal";
 
 describe("TimelineDetailsModal", () => {
+  it("should use the collection's name for default timelines", () => {
+    const props = getProps({
+      timeline: createMockTimeline({
+        name: "Metrics events",
+        default: true,
+        collection: createMockCollection({
+          name: "Analytics",
+        }),
+      }),
+    });
+
+    render(<TimelineDetailsModal {...props} />);
+
+    expect(screen.getByText("Analytics events")).toBeInTheDocument();
+  });
+
+  it("should use the timeline's name for non-default timelines", () => {
+    const props = getProps({
+      timeline: createMockTimeline({
+        name: "Metrics events",
+        default: false,
+        collection: createMockCollection({
+          name: "Analytics",
+        }),
+      }),
+    });
+
+    render(<TimelineDetailsModal {...props} />);
+
+    expect(screen.getByText("Metrics events")).toBeInTheDocument();
+  });
+
   it("should search a list of events", async () => {
     const props = getProps({
       timeline: createMockTimeline({
diff --git a/frontend/src/metabase/timelines/collections/components/TimelineListModal/TimelineListModal.tsx b/frontend/src/metabase/timelines/collections/components/TimelineListModal/TimelineListModal.tsx
index b5d8479a257..60bf887a031 100644
--- a/frontend/src/metabase/timelines/collections/components/TimelineListModal/TimelineListModal.tsx
+++ b/frontend/src/metabase/timelines/collections/components/TimelineListModal/TimelineListModal.tsx
@@ -2,6 +2,10 @@ import React, { useCallback } from "react";
 import { t } from "ttag";
 import _ from "underscore";
 import * as Urls from "metabase/lib/urls";
+import {
+  getDefaultTimelineName,
+  getTimelineName,
+} from "metabase/lib/timelines";
 import EntityMenu from "metabase/components/EntityMenu";
 import { Collection, Timeline } from "metabase-types/api";
 import ModalHeader from "metabase/timelines/common/components/ModalHeader";
@@ -75,7 +79,7 @@ const getTitle = (
   } else if (timelines.length) {
     return t`Events`;
   } else {
-    return t`${collection.name} events`;
+    return getDefaultTimelineName(collection);
   }
 };
 
@@ -102,7 +106,7 @@ const getMenuItems = (
 
 const getSortedTimelines = (timelines: Timeline[]) => {
   return _.chain(timelines)
-    .sortBy(timeline => timeline.name)
+    .sortBy(getTimelineName)
     .sortBy(timeline => timeline.collection?.personal_owner_id != null) // personal collections last
     .value();
 };
diff --git a/frontend/src/metabase/timelines/collections/containers/TimelineDetailsModal/TimelineDetailsModal.tsx b/frontend/src/metabase/timelines/collections/containers/TimelineDetailsModal/TimelineDetailsModal.tsx
index 7981b2b6715..e810aa08e6a 100644
--- a/frontend/src/metabase/timelines/collections/containers/TimelineDetailsModal/TimelineDetailsModal.tsx
+++ b/frontend/src/metabase/timelines/collections/containers/TimelineDetailsModal/TimelineDetailsModal.tsx
@@ -24,7 +24,9 @@ const timelineProps = {
 };
 
 const timelinesProps = {
-  query: { include: "events" },
+  query: (state: State, props: ModalProps) => ({
+    collectionId: Urls.extractCollectionId(props.params.slug),
+  }),
   LoadingAndErrorWrapper,
 };
 
diff --git a/frontend/src/metabase/timelines/questions/components/TimelineCard/TimelineCard.tsx b/frontend/src/metabase/timelines/questions/components/TimelineCard/TimelineCard.tsx
index 004c22c0096..40e5c29fb1d 100644
--- a/frontend/src/metabase/timelines/questions/components/TimelineCard/TimelineCard.tsx
+++ b/frontend/src/metabase/timelines/questions/components/TimelineCard/TimelineCard.tsx
@@ -7,6 +7,7 @@ import React, {
   useEffect,
 } from "react";
 import _ from "underscore";
+import { getTimelineName } from "metabase/lib/timelines";
 import Ellipsified from "metabase/components/Ellipsified";
 import { Timeline, TimelineEvent } from "metabase-types/api";
 import EventCard from "../EventCard";
@@ -74,7 +75,9 @@ const TimelineCard = ({
           onChange={handleCheckboxChange}
         />
         <CardLabel>
-          <Ellipsified tooltipMaxWidth="100%">{timeline.name}</Ellipsified>
+          <Ellipsified tooltipMaxWidth="100%">
+            {getTimelineName(timeline)}
+          </Ellipsified>
         </CardLabel>
         <CardIcon name={isExpanded ? "chevronup" : "chevrondown"} />
       </CardHeader>
diff --git a/frontend/test/__support__/e2e/commands/api/timeline.js b/frontend/test/__support__/e2e/commands/api/timeline.js
index ab614e70c8f..915401d501a 100644
--- a/frontend/test/__support__/e2e/commands/api/timeline.js
+++ b/frontend/test/__support__/e2e/commands/api/timeline.js
@@ -5,6 +5,7 @@ Cypress.Commands.add(
     description,
     icon = "star",
     collection_id,
+    default: is_default = false,
     archived = false,
   } = {}) => {
     return cy.request("POST", "/api/timeline", {
@@ -12,6 +13,7 @@ Cypress.Commands.add(
       description,
       icon,
       collection_id,
+      default: is_default,
       archived,
     });
   },
diff --git a/frontend/test/metabase/scenarios/collections/timelines.cy.spec.js b/frontend/test/metabase/scenarios/collections/timelines.cy.spec.js
index dc1073c74dc..61d6cb07813 100644
--- a/frontend/test/metabase/scenarios/collections/timelines.cy.spec.js
+++ b/frontend/test/metabase/scenarios/collections/timelines.cy.spec.js
@@ -10,6 +10,13 @@ import {
 describe("scenarios > collections > timelines", () => {
   beforeEach(() => {
     restore();
+    cy.intercept("PUT", "/api/collection/*").as("updateCollection");
+    cy.intercept("POST", "/api/timeline").as("createTimeline");
+    cy.intercept("PUT", "/api/timeline/*").as("updateTimeline");
+    cy.intercept("DELETE", "/api/timeline/*").as("deleteTimeline");
+    cy.intercept("POST", "/api/timeline-event").as("createEvent");
+    cy.intercept("PUT", "/api/timeline-event/*").as("updateEvent");
+    cy.intercept("DELETE", "/api/timeline-event/*").as("deleteEvent");
   });
 
   describe("as admin", () => {
@@ -25,6 +32,7 @@ describe("scenarios > collections > timelines", () => {
       cy.findByLabelText("Event name").type("RC1");
       cy.findByLabelText("Date").type("10/20/2020");
       cy.button("Create").click();
+      cy.wait("@createEvent");
 
       cy.findByText("RC1").should("be.visible");
       cy.findByText("October 20, 2020").should("be.visible");
@@ -36,6 +44,7 @@ describe("scenarios > collections > timelines", () => {
       cy.findByText("Star").click();
       cy.findByText("Balloons").click();
       cy.button("Create").click();
+      cy.wait("@createEvent");
 
       cy.findByText("RC2").should("be.visible");
       cy.findByText("May 12, 2021").should("be.visible");
@@ -51,6 +60,7 @@ describe("scenarios > collections > timelines", () => {
       cy.findByLabelText("Event name").type("RC1");
       cy.findByLabelText("Date").type("10/20/2020");
       cy.button("Create").click();
+      cy.wait("@createEvent");
 
       cy.findByText("RC1").should("be.visible");
     });
@@ -85,6 +95,7 @@ describe("scenarios > collections > timelines", () => {
       cy.findByText("15").click();
       cy.findByText("Done").click();
       cy.findByText("Create").click();
+      cy.wait("@createEvent");
 
       cy.findByText("Our analytics events").should("be.visible");
       cy.findByText("RC1").should("be.visible");
@@ -102,6 +113,7 @@ describe("scenarios > collections > timelines", () => {
       cy.findByText("Markdown supported").should("be.visible");
       cy.findByLabelText("Description").type("*1.0-rc1* release");
       cy.findByText("Create").click();
+      cy.wait("@createEvent");
 
       cy.findByText("Our analytics events").should("be.visible");
       cy.findByText("RC1").should("be.visible");
@@ -126,6 +138,7 @@ describe("scenarios > collections > timelines", () => {
         .type("20");
       cy.findByText("Done").click();
       cy.findByText("Create").click();
+      cy.wait("@createEvent");
 
       cy.findByText("Our analytics events").should("be.visible");
       cy.findByText("RC1").should("be.visible");
@@ -144,6 +157,7 @@ describe("scenarios > collections > timelines", () => {
       cy.findByText("Add time").click();
       cy.findByText("Done").click();
       cy.findByText("Create").click();
+      cy.wait("@createEvent");
 
       cy.findByText("Our analytics events").should("be.visible");
       cy.findByText("RC1").should("be.visible");
@@ -160,6 +174,7 @@ describe("scenarios > collections > timelines", () => {
         .clear()
         .type("RC2");
       cy.button("Update").click();
+      cy.wait("@updateEvent");
 
       cy.findByText("RC2").should("be.visible");
     });
@@ -175,6 +190,7 @@ describe("scenarios > collections > timelines", () => {
       openMenu("RC1");
       cy.findByText("Edit event").click();
       cy.findByText("Archive event").click();
+      cy.wait("@updateEvent");
 
       cy.findByText("Releases").should("be.visible");
       cy.findByText("RC1").should("not.exist");
@@ -190,9 +206,11 @@ describe("scenarios > collections > timelines", () => {
 
       openMenu("RC1");
       cy.findByText("Archive event").click();
+      cy.wait("@updateEvent");
       cy.findByText("RC1").should("not.exist");
 
       cy.findByText("Undo").click();
+      cy.wait("@updateEvent");
       cy.findByText("RC1").should("be.visible");
     });
 
@@ -210,9 +228,11 @@ describe("scenarios > collections > timelines", () => {
       openMenu("RC1");
 
       cy.findByText("Unarchive event").click();
+      cy.wait("@updateEvent");
       cy.findByText("No events found").should("be.visible");
 
       cy.findByText("Undo").click();
+      cy.wait("@updateEvent");
       cy.findByText("RC1").should("be.visible");
     });
 
@@ -230,6 +250,7 @@ describe("scenarios > collections > timelines", () => {
       openMenu("RC1");
       cy.findByText("Delete event").click();
       cy.findByText("Delete").click();
+      cy.wait("@deleteEvent");
       cy.findByText("No events found").should("be.visible");
     });
 
@@ -262,8 +283,9 @@ describe("scenarios > collections > timelines", () => {
       cy.visit("/collection/root/timelines");
       openMenu("Releases");
       cy.findByText("New timeline").click();
-      cy.findByLabelText("Timeline name").type("Launches");
+      cy.findByLabelText("Name").type("Launches");
       cy.findByText("Create").click();
+      cy.wait("@createTimeline");
 
       cy.findByText("Launches").should("be.visible");
       cy.findByText("Add an event").should("be.visible");
@@ -278,10 +300,11 @@ describe("scenarios > collections > timelines", () => {
       cy.visit("/collection/root/timelines");
       openMenu("Releases");
       cy.findByText("Edit timeline details").click();
-      cy.findByLabelText("Timeline name")
+      cy.findByLabelText("Name")
         .clear()
         .type("Launches");
       cy.findByText("Update").click();
+      cy.wait("@updateTimeline");
 
       cy.findByText("Launches").should("be.visible");
     });
@@ -296,10 +319,12 @@ describe("scenarios > collections > timelines", () => {
       openMenu("Releases");
       cy.findByText("Edit timeline details").click();
       cy.findByText("Archive timeline and all events").click();
+      cy.wait("@updateTimeline");
       cy.findByText("Our analytics events").should("be.visible");
       cy.findByText("Add an event").should("be.visible");
 
       cy.findByText("Undo").click();
+      cy.wait("@updateTimeline");
       cy.findByText("Releases").should("be.visible");
       cy.findByText("RC1").should("be.visible");
       cy.findByText("RC2").should("be.visible");
@@ -348,16 +373,16 @@ describe("scenarios > collections > timelines", () => {
       openMenu("Releases");
       cy.findByText("Edit timeline details").click();
       cy.findByText("Archive timeline and all events").click();
+      cy.wait("@updateTimeline");
 
       openMenu("Our analytics events");
       cy.findByText("View archived timelines").click();
 
       openMenu("Releases");
       cy.findByText("Unarchive timeline").click();
+      cy.wait("@updateTimeline");
       cy.findByText("No timelines found");
-      cy.get(".Modal").within(() => {
-        cy.icon("chevronleft").click();
-      });
+      getModal().within(() => cy.icon("chevronleft").click());
       cy.findByText("Releases");
     });
 
@@ -371,6 +396,7 @@ describe("scenarios > collections > timelines", () => {
       openMenu("Releases");
       cy.findByText("Edit timeline details").click();
       cy.findByText("Archive timeline and all events").click();
+      cy.wait("@updateTimeline");
 
       openMenu("Our analytics events");
       cy.findByText("View archived timelines").click();
@@ -378,12 +404,43 @@ describe("scenarios > collections > timelines", () => {
       openMenu("Releases");
       cy.findByText("Delete timeline").click();
       cy.findByText("Delete").click();
+      cy.wait("@deleteTimeline");
       cy.findByText("No timelines found");
-      cy.get(".Modal").within(() => {
-        cy.icon("chevronleft").click();
-      });
+      getModal().within(() => cy.icon("chevronleft").click());
       cy.findByText("Our analytics events");
     });
+
+    it("should preserve collection names for default timelines", () => {
+      cy.visit("/");
+      cy.findByText("First collection").click();
+
+      cy.icon("calendar").click();
+      cy.findByText("Add an event").click();
+      cy.findByLabelText("Event name").type("RC1");
+      cy.findByLabelText("Date").type("10/20/2020");
+      cy.button("Create").click();
+      cy.findByText("First collection events");
+      cy.wait("@createTimeline");
+      cy.icon("close").click();
+
+      cy.icon("pencil").click();
+      cy.findByText("Edit this collection").click();
+      cy.findByLabelText("Name")
+        .clear()
+        .type("1st collection");
+      cy.button("Update").click();
+      cy.wait("@updateCollection");
+
+      cy.icon("calendar").click();
+      openMenu("1st collection events");
+      cy.findByText("Edit timeline details").click();
+      cy.findByLabelText("Name")
+        .clear()
+        .type("Releases");
+      cy.button("Update").click();
+      cy.wait("@updateTimeline");
+      cy.findByText("Releases");
+    });
   });
 
   describe("as readonly user", () => {
@@ -445,6 +502,10 @@ describeWithSnowplow("scenarios > collections > timelines", () => {
   });
 });
 
+const getModal = () => {
+  return cy.get(".Modal");
+};
+
 const openMenu = name => {
   return cy
     .findByText(name)
diff --git a/frontend/test/metabase/scenarios/question/timelines.cy.spec.js b/frontend/test/metabase/scenarios/question/timelines.cy.spec.js
index 23a045a8150..96082e5873a 100644
--- a/frontend/test/metabase/scenarios/question/timelines.cy.spec.js
+++ b/frontend/test/metabase/scenarios/question/timelines.cy.spec.js
@@ -65,9 +65,7 @@ describe("scenarios > collections > timelines", () => {
 
       cy.icon("calendar").click();
       cy.findByText("Releases");
-      sidebar().within(() => {
-        cy.icon("ellipsis").click();
-      });
+      sidebar().within(() => cy.icon("ellipsis").click());
       cy.findByTextEnsureVisible("Edit event").click();
 
       cy.findByLabelText("Event name")
diff --git a/src/metabase/api/timeline.clj b/src/metabase/api/timeline.clj
index 00ae9b3c5eb..3bb68956c4e 100644
--- a/src/metabase/api/timeline.clj
+++ b/src/metabase/api/timeline.clj
@@ -86,7 +86,7 @@
     (collection/check-allowed-to-change-collection existing timeline-updates)
     (db/update! Timeline id
       (u/select-keys-when timeline-updates
-        :present #{:description :icon :collection_id :archived}
+        :present #{:description :icon :collection_id :default :archived}
         :non-nil #{:name}))
     (when (and (some? archived) (not= current-archived archived))
       (db/update-where! TimelineEvent {:timeline_id id} :archived archived))
-- 
GitLab