From 9125f5d2e92c81464eb38f881ae1a04a5c86c944 Mon Sep 17 00:00:00 2001
From: Anton Kulyk <kuliks.anton@gmail.com>
Date: Tue, 10 Jan 2023 19:50:39 +0000
Subject: [PATCH] Remove redundant components (#27607)

* Delete `QuestionHistoryModal`

* Delete `HistoryModal`

* Delete `AdminEmptyText`

* Delete `EditWarning`

* Delete `Expandable`

* Remove a bunch of question loaders

* Delete `CandidateListLoader`

* Delete `QuestionName`

* Revert "Remove a bunch of question loaders"

This reverts commit 209dbac68dec92083d6e3bdf34ff42ace76a3870.

* Remove `QuestionAndResultLoader`
---
 .../containers/MetadataEditorApp.jsx          |  13 +--
 .../GroupMembersTable/GroupMembersTable.tsx   |   6 +-
 .../metabase/components/AdminEmptyText.jsx    |  12 --
 .../metabase/components/DirectionalButton.jsx |  20 ----
 .../DrawerSection/DrawerSection.info.js       |  78 -------------
 .../DrawerSection/DrawerSection.jsx           | 100 ----------------
 .../DrawerSection/DrawerSection.styled.jsx    |  51 ---------
 .../src/metabase/components/EditWarning.jsx   |  18 ---
 .../src/metabase/components/Expandable.jsx    |  45 --------
 .../src/metabase/components/HistoryModal.jsx  |  74 ------------
 .../components/HistoryModal.unit.spec.js      | 108 ------------------
 .../containers/CandidateListLoader.jsx        | 100 ----------------
 .../src/metabase/containers/HistoryModal.jsx  |  40 -------
 .../containers/QuestionAndResultLoader.jsx    |  43 -------
 .../src/metabase/containers/QuestionName.jsx  |  10 --
 .../metabase/containers/QuestionSelect.jsx    |   8 --
 .../metabase/containers/QuestionSelect.tsx    |  16 +++
 .../components/DashboardHeader.styled.tsx     |   7 ++
 .../dashboard/components/DashboardHeader.tsx  |   8 +-
 .../internal/components/QuestionApp.jsx       |  41 -------
 .../query_builder/components/QueryModals.jsx  |  12 --
 .../src/metabase/query_builder/constants.js   |   1 -
 .../containers/QuestionHistoryModal.jsx       |  33 ------
 23 files changed, 35 insertions(+), 809 deletions(-)
 delete mode 100644 frontend/src/metabase/components/AdminEmptyText.jsx
 delete mode 100644 frontend/src/metabase/components/DirectionalButton.jsx
 delete mode 100644 frontend/src/metabase/components/DrawerSection/DrawerSection.info.js
 delete mode 100644 frontend/src/metabase/components/DrawerSection/DrawerSection.jsx
 delete mode 100644 frontend/src/metabase/components/DrawerSection/DrawerSection.styled.jsx
 delete mode 100644 frontend/src/metabase/components/EditWarning.jsx
 delete mode 100644 frontend/src/metabase/components/Expandable.jsx
 delete mode 100644 frontend/src/metabase/components/HistoryModal.jsx
 delete mode 100644 frontend/src/metabase/components/HistoryModal.unit.spec.js
 delete mode 100644 frontend/src/metabase/containers/CandidateListLoader.jsx
 delete mode 100644 frontend/src/metabase/containers/HistoryModal.jsx
 delete mode 100644 frontend/src/metabase/containers/QuestionAndResultLoader.jsx
 delete mode 100644 frontend/src/metabase/containers/QuestionName.jsx
 delete mode 100644 frontend/src/metabase/containers/QuestionSelect.jsx
 create mode 100644 frontend/src/metabase/containers/QuestionSelect.tsx
 delete mode 100644 frontend/src/metabase/internal/components/QuestionApp.jsx
 delete mode 100644 frontend/src/metabase/query_builder/containers/QuestionHistoryModal.jsx

diff --git a/frontend/src/metabase/admin/datamodel/containers/MetadataEditorApp.jsx b/frontend/src/metabase/admin/datamodel/containers/MetadataEditorApp.jsx
index ffdfe00818a..5511b5e5a57 100644
--- a/frontend/src/metabase/admin/datamodel/containers/MetadataEditorApp.jsx
+++ b/frontend/src/metabase/admin/datamodel/containers/MetadataEditorApp.jsx
@@ -8,7 +8,6 @@ import _ from "underscore";
 import { t } from "ttag";
 import * as MetabaseAnalytics from "metabase/lib/analytics";
 
-import AdminEmptyText from "metabase/components/AdminEmptyText";
 import {
   metrics as Metrics,
   databases as Databases,
@@ -112,13 +111,11 @@ class MetadataEditorInner extends Component {
           ) : (
             <div style={{ paddingTop: "10rem" }} className="full text-centered">
               {!loading && (
-                <AdminEmptyText
-                  message={
-                    hasLoadedDatabase
-                      ? t`Select any table to see its schema and add or edit metadata.`
-                      : t`The page you asked for couldn't be found.`
-                  }
-                />
+                <h2 className="text-medium">
+                  {hasLoadedDatabase
+                    ? t`Select any table to see its schema and add or edit metadata.`
+                    : t`The page you asked for couldn't be found.`}
+                </h2>
               )}
             </div>
           )}
diff --git a/frontend/src/metabase/admin/people/components/GroupMembersTable/GroupMembersTable.tsx b/frontend/src/metabase/admin/people/components/GroupMembersTable/GroupMembersTable.tsx
index 8df78ce0dc0..37407948346 100644
--- a/frontend/src/metabase/admin/people/components/GroupMembersTable/GroupMembersTable.tsx
+++ b/frontend/src/metabase/admin/people/components/GroupMembersTable/GroupMembersTable.tsx
@@ -1,12 +1,10 @@
 /* eslint-disable react/prop-types */
 import React, { useMemo } from "react";
-import PropTypes from "prop-types";
 import { t } from "ttag";
 
 import { isAdminGroup, isDefaultGroup } from "metabase/lib/groups";
 import { getFullName } from "metabase/lib/user";
 import Icon from "metabase/components/Icon";
-import AdminEmptyText from "metabase/components/AdminEmptyText";
 import AdminContentTable from "metabase/components/AdminContentTable";
 import PaginationControls from "metabase/components/PaginationControls";
 
@@ -127,9 +125,7 @@ function GroupMembersTable({
       )}
       {!hasMembers && (
         <div className="mt4 pt4 flex layout-centered">
-          <AdminEmptyText
-            message={t`A group is only as good as its members.`}
-          />
+          <h2 className="text-medium">{t`A group is only as good as its members.`}</h2>
         </div>
       )}
     </React.Fragment>
diff --git a/frontend/src/metabase/components/AdminEmptyText.jsx b/frontend/src/metabase/components/AdminEmptyText.jsx
deleted file mode 100644
index 6df1372edec..00000000000
--- a/frontend/src/metabase/components/AdminEmptyText.jsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import React from "react";
-import PropTypes from "prop-types";
-
-const AdminEmptyText = ({ message }) => (
-  <h2 className="text-medium">{message}</h2>
-);
-
-AdminEmptyText.propTypes = {
-  message: PropTypes.string.isRequired,
-};
-
-export default AdminEmptyText;
diff --git a/frontend/src/metabase/components/DirectionalButton.jsx b/frontend/src/metabase/components/DirectionalButton.jsx
deleted file mode 100644
index d29d527c1c8..00000000000
--- a/frontend/src/metabase/components/DirectionalButton.jsx
+++ /dev/null
@@ -1,20 +0,0 @@
-/* eslint-disable react/prop-types */
-import React from "react";
-import Icon from "metabase/components/Icon";
-
-import { color } from "metabase/lib/colors";
-
-const DirectionalButton = ({ direction = "left", onClick }) => (
-  <div
-    className="shadowed cursor-pointer text-brand-hover text-medium flex align-center circle p2 bg-white transition-background transition-color"
-    onClick={onClick}
-    style={{
-      border: `1px solid ${color("border")}`,
-      boxShadow: `0 2px 4px 0 ${color("shadow")}`,
-    }}
-  >
-    <Icon name={`arrow_${direction}`} />
-  </div>
-);
-
-export default DirectionalButton;
diff --git a/frontend/src/metabase/components/DrawerSection/DrawerSection.info.js b/frontend/src/metabase/components/DrawerSection/DrawerSection.info.js
deleted file mode 100644
index c11cc6b63ac..00000000000
--- a/frontend/src/metabase/components/DrawerSection/DrawerSection.info.js
+++ /dev/null
@@ -1,78 +0,0 @@
-import React from "react";
-
-import moment from "moment-timezone";
-import styled from "@emotion/styled";
-import Timeline from "metabase/components/Timeline";
-import { color } from "metabase/lib/colors";
-import DrawerSection from "./DrawerSection";
-
-export const component = DrawerSection;
-export const category = "layout";
-export const description = `
-  This component is similar to the CollapseSection component,
-  but instead of expanding downward, it expands upward.
-  The header situates itself at the bottom of the remaining space
-  in a parent component and when opened fills the remaining space
-  with what child components you have given it.
-
-  For this to work properly, the containing element (here, Container)
-  must handle overflow when the DrawerSection is open and have a display
-  of "flex" (plus a flex-direction of "column") so that the DrawerSection
-  can properly use the remaining space in the Container component.
-`;
-
-const Container = styled.div`
-  line-height: 1.5;
-  width: 350px;
-  border: 1px dashed ${color("bg-dark")};
-  border-radius: 0.5rem;
-  padding: 1rem;
-  height: 32rem;
-
-  overflow-y: auto;
-  display: flex;
-  flex-direction: column;
-`;
-
-const TextArea = styled.textarea`
-  width: 100%;
-  flex-shrink: 0;
-`;
-
-const items = [
-  {
-    icon: "verified",
-    title: "John Someone verified this",
-    description: "idk lol",
-    timestamp: moment().subtract(1, "day").valueOf(),
-    numComments: 5,
-  },
-  {
-    icon: "pencil",
-    title: "Foo edited this",
-    description: "Did a thing.",
-    timestamp: moment().subtract(1, "week").valueOf(),
-  },
-  {
-    icon: "close",
-    title: "foo foo foo",
-    timestamp: moment().subtract(2, "month").valueOf(),
-  },
-  {
-    icon: "number",
-    title: "bar bar bar",
-    timestamp: moment().subtract(1, "year").valueOf(),
-    numComments: 123,
-  },
-];
-
-export const examples = {
-  "Constrained container": (
-    <Container>
-      <TextArea placeholder="an element with variable height" />
-      <DrawerSection header="foo">
-        <Timeline items={items} />
-      </DrawerSection>
-    </Container>
-  ),
-};
diff --git a/frontend/src/metabase/components/DrawerSection/DrawerSection.jsx b/frontend/src/metabase/components/DrawerSection/DrawerSection.jsx
deleted file mode 100644
index 444eaf4446e..00000000000
--- a/frontend/src/metabase/components/DrawerSection/DrawerSection.jsx
+++ /dev/null
@@ -1,100 +0,0 @@
-import React, { useState } from "react";
-import PropTypes from "prop-types";
-import _ from "underscore";
-
-import Icon from "metabase/components/Icon";
-import {
-  Container,
-  Transformer,
-  Children,
-  Header,
-} from "./DrawerSection.styled";
-
-export const STATES = {
-  closed: "closed",
-  open: "open",
-};
-
-DrawerSection.propTypes = {
-  header: PropTypes.node.isRequired,
-  children: PropTypes.node,
-  state: PropTypes.oneOf([STATES.closed, STATES.open]),
-  onStateChange: PropTypes.func,
-};
-
-function DrawerSection({ header, children, state, onStateChange }) {
-  return _.isFunction(onStateChange) ? (
-    <ControlledDrawerSection
-      header={header}
-      state={state}
-      onStateChange={onStateChange}
-    >
-      {children}
-    </ControlledDrawerSection>
-  ) : (
-    <UncontrolledDrawerSection header={header} initialState={state}>
-      {children}
-    </UncontrolledDrawerSection>
-  );
-}
-
-UncontrolledDrawerSection.propTypes = {
-  header: PropTypes.node.isRequired,
-  children: PropTypes.node,
-  initialState: PropTypes.oneOf([STATES.closed, STATES.open]),
-};
-
-function UncontrolledDrawerSection({ header, children, initialState }) {
-  const [state, setState] = useState(initialState);
-
-  return (
-    <ControlledDrawerSection
-      header={header}
-      state={state}
-      onStateChange={setState}
-    >
-      {children}
-    </ControlledDrawerSection>
-  );
-}
-
-ControlledDrawerSection.propTypes = {
-  header: PropTypes.node.isRequired,
-  children: PropTypes.node,
-  state: PropTypes.oneOf([STATES.closed, STATES.open]),
-  onStateChange: PropTypes.func.isRequired,
-};
-
-function ControlledDrawerSection({ header, children, state, onStateChange }) {
-  const isOpen = state === STATES.open;
-
-  const toggleState = () => {
-    if (state === STATES.open) {
-      onStateChange(STATES.closed);
-    } else {
-      onStateChange(STATES.open);
-    }
-  };
-
-  return (
-    <Container isOpen={isOpen}>
-      <Transformer isOpen={isOpen}>
-        <Header
-          isOpen={isOpen}
-          onClick={toggleState}
-          onKeyDown={e => e.key === "Enter" && toggleState()}
-        >
-          {header}
-          <Icon
-            className="mr1"
-            name={isOpen ? "chevrondown" : "chevronup"}
-            size={12}
-          />
-        </Header>
-        <Children isOpen={isOpen}>{children}</Children>
-      </Transformer>
-    </Container>
-  );
-}
-
-export default DrawerSection;
diff --git a/frontend/src/metabase/components/DrawerSection/DrawerSection.styled.jsx b/frontend/src/metabase/components/DrawerSection/DrawerSection.styled.jsx
deleted file mode 100644
index d42ce35544a..00000000000
--- a/frontend/src/metabase/components/DrawerSection/DrawerSection.styled.jsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import styled from "@emotion/styled";
-
-import { color } from "metabase/lib/colors";
-
-const HEADER_HEIGHT = "49px";
-
-export const Container = styled.div`
-  min-height: ${HEADER_HEIGHT};
-  height: ${props => (props.isOpen ? "auto" : "100%")};
-  overflow: ${props => (props.isOpen ? "unset" : "hidden")};
-  position: relative;
-  width: 100%;
-`;
-
-export const Transformer = styled.div`
-  height: 100%;
-  position: relative;
-  width: 100%;
-
-  will-change: transform;
-  transform: ${props =>
-    props.isOpen
-      ? "translateY(0)"
-      : `translateY(calc(100% - ${HEADER_HEIGHT}))`};
-  transition: transform 0.2s ease-in-out;
-`;
-
-export const Children = styled.div`
-  display: ${props => (props.isOpen ? "block" : "none")};
-  padding: 0 1.5rem;
-`;
-
-export const Header = styled.div`
-  height: ${HEADER_HEIGHT};
-  cursor: pointer;
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  border-top: 1px solid ${color("border")};
-  font-weight: 700;
-  padding: 0 1.5rem;
-
-  &:hover {
-    color: ${color("brand")};
-  }
-`;
-
-Header.defaultProps = {
-  role: "button",
-  tabIndex: "0",
-};
diff --git a/frontend/src/metabase/components/EditWarning.jsx b/frontend/src/metabase/components/EditWarning.jsx
deleted file mode 100644
index ea1d4c17b32..00000000000
--- a/frontend/src/metabase/components/EditWarning.jsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import React from "react";
-import PropTypes from "prop-types";
-
-export default function EditWarning({ title }) {
-  if (title) {
-    return (
-      <div className="EditHeader wrapper py1 flex align-center">
-        <span className="EditHeader-title">{title}</span>
-      </div>
-    );
-  } else {
-    return null;
-  }
-}
-
-EditWarning.propTypes = {
-  title: PropTypes.string.isRequired,
-};
diff --git a/frontend/src/metabase/components/Expandable.jsx b/frontend/src/metabase/components/Expandable.jsx
deleted file mode 100644
index 9b70f1183b1..00000000000
--- a/frontend/src/metabase/components/Expandable.jsx
+++ /dev/null
@@ -1,45 +0,0 @@
-import React, { Component } from "react";
-import PropTypes from "prop-types";
-
-const Expandable = ComposedComponent =>
-  class extends Component {
-    static displayName =
-      "Expandable[" +
-      (ComposedComponent.displayName || ComposedComponent.name) +
-      "]";
-
-    constructor(props, context) {
-      super(props, context);
-      this.state = {
-        expanded: false,
-      };
-      this.expand = () => this.setState({ expanded: true });
-    }
-
-    static propTypes = {
-      items: PropTypes.array.isRequired,
-      initialItemLimit: PropTypes.number.isRequired,
-    };
-    static defaultProps = {
-      initialItemLimit: 4,
-    };
-
-    render() {
-      let { expanded } = this.state;
-      let { items, initialItemLimit } = this.props;
-      if (items.length > initialItemLimit && !expanded) {
-        items = items.slice(0, initialItemLimit - 1);
-      }
-      expanded = items.length >= this.props.items.length;
-      return (
-        <ComposedComponent
-          {...this.props}
-          isExpanded={expanded}
-          onExpand={this.expand}
-          items={items}
-        />
-      );
-    }
-  };
-
-export default Expandable;
diff --git a/frontend/src/metabase/components/HistoryModal.jsx b/frontend/src/metabase/components/HistoryModal.jsx
deleted file mode 100644
index 29c3e1fc4ba..00000000000
--- a/frontend/src/metabase/components/HistoryModal.jsx
+++ /dev/null
@@ -1,74 +0,0 @@
-import React, { Component } from "react";
-import PropTypes from "prop-types";
-import { t } from "ttag";
-import moment from "moment-timezone";
-import ActionButton from "metabase/components/ActionButton";
-import ModalContent from "metabase/components/ModalContent";
-import {
-  isValidRevision,
-  getRevisionDescription,
-} from "metabase/lib/revisions";
-
-function formatDate(date) {
-  const m = moment(date);
-  if (m.isSame(moment(), "day")) {
-    return t`Today, ` + m.format("h:mm a");
-  } else if (m.isSame(moment().subtract(1, "day"), "day")) {
-    return t`Yesterday, ` + m.format("h:mm a");
-  } else {
-    return m.format("MMM D YYYY, h:mm a");
-  }
-}
-
-export default class HistoryModal extends Component {
-  static propTypes = {
-    revisions: PropTypes.array,
-    onRevert: PropTypes.func,
-    onClose: PropTypes.func.isRequired,
-  };
-
-  render() {
-    const { revisions, onRevert, onClose } = this.props;
-    const cellClassName = "p1 border-bottom";
-
-    return (
-      <ModalContent title={t`Revision history`} onClose={onClose}>
-        <table className="full">
-          <thead>
-            <tr>
-              <th className={cellClassName}>{t`When`}</th>
-              <th className={cellClassName}>{t`Who`}</th>
-              <th className={cellClassName}>{t`What`}</th>
-              <th className={cellClassName} />
-            </tr>
-          </thead>
-          <tbody>
-            {revisions.filter(isValidRevision).map((revision, index) => (
-              <tr key={revision.id} data-testid="revision-history-row">
-                <td className={cellClassName}>
-                  {formatDate(revision.timestamp)}
-                </td>
-                <td className={cellClassName}>{revision.user.common_name}</td>
-                <td className={cellClassName}>
-                  <span>{getRevisionDescription(revision)}</span>
-                </td>
-                <td className={cellClassName}>
-                  {index !== 0 && (
-                    <ActionButton
-                      actionFn={() => onRevert(revision)}
-                      className="Button Button--small Button--danger text-uppercase"
-                      normalText={t`Revert`}
-                      activeText={t`Reverting…`}
-                      failedText={t`Revert failed`}
-                      successText={t`Reverted`}
-                    />
-                  )}
-                </td>
-              </tr>
-            ))}
-          </tbody>
-        </table>
-      </ModalContent>
-    );
-  }
-}
diff --git a/frontend/src/metabase/components/HistoryModal.unit.spec.js b/frontend/src/metabase/components/HistoryModal.unit.spec.js
deleted file mode 100644
index 9bd76afa2bb..00000000000
--- a/frontend/src/metabase/components/HistoryModal.unit.spec.js
+++ /dev/null
@@ -1,108 +0,0 @@
-import React from "react";
-import { fireEvent, render, screen } from "@testing-library/react";
-import HistoryModal from "./HistoryModal";
-
-function getRevision({
-  isCreation = false,
-  isReversion = false,
-  userName = "John",
-  timestamp = "2016-05-08T02:02:07.441Z",
-  ...rest
-} = {}) {
-  return {
-    id: Math.random(),
-    is_reversion: isReversion,
-    is_creation: isCreation,
-    user: {
-      common_name: userName,
-    },
-    timestamp,
-    diff: null,
-    ...rest,
-  };
-}
-
-function getSimpleChangeRevision({ field, before, after, ...rest }) {
-  return getRevision({
-    ...rest,
-    diff: {
-      before: {
-        [field]: before,
-      },
-      after: {
-        [field]: after,
-      },
-    },
-  });
-}
-
-const CHANGE_EVENT_REVISION = getSimpleChangeRevision({
-  field: "archived",
-  before: false,
-  after: true,
-  description: 'changed archived from "false" to "true"',
-});
-
-const REVISIONS = [
-  getSimpleChangeRevision({ isReversion: true }),
-  CHANGE_EVENT_REVISION,
-  getSimpleChangeRevision({
-    field: "description",
-    before: null,
-    after: "Very helpful dashboard",
-    description: 'changed description from "null" to "Very helpful dashboard"',
-  }),
-  getRevision({ isCreation: true }),
-];
-
-function setup({ revisions = REVISIONS } = {}) {
-  const onRevert = jest.fn().mockResolvedValue({});
-  const onClose = jest.fn();
-  render(
-    <HistoryModal
-      revisions={revisions}
-      onRevert={onRevert}
-      onClose={onClose}
-    />,
-  );
-  return { onRevert, onClose };
-}
-
-describe("HistoryModal", () => {
-  it("displays revisions", () => {
-    setup();
-    expect(screen.getByText("created this")).toBeInTheDocument();
-    expect(screen.getByText("added a description")).toBeInTheDocument();
-    expect(screen.getByText("archived this")).toBeInTheDocument();
-    expect(
-      screen.getByText("reverted to an earlier revision"),
-    ).toBeInTheDocument();
-    expect(screen.getAllByTestId("revision-history-row")).toHaveLength(4);
-  });
-
-  it("does not display invalid revisions", () => {
-    setup({
-      revisions: [getRevision({ diff: { before: null, after: null } })],
-    });
-    expect(
-      screen.queryByTestId("revision-history-row"),
-    ).not.toBeInTheDocument();
-  });
-
-  it("calls onClose when close icon is clicked", () => {
-    const { onClose } = setup();
-    fireEvent.click(screen.queryByLabelText("close icon"));
-    expect(onClose).toHaveBeenCalledTimes(1);
-  });
-
-  it("calls onRevert with a revision object when Revert button is clicked", () => {
-    const { onRevert } = setup({
-      revisions: [getRevision({ isCreation: true }), CHANGE_EVENT_REVISION],
-    });
-
-    fireEvent.click(screen.queryByRole("button", { name: "Revert" }));
-
-    expect(onRevert).toHaveBeenCalledTimes(1);
-    expect(onRevert).toHaveBeenCalledWith(CHANGE_EVENT_REVISION);
-  });
-});
diff --git a/frontend/src/metabase/containers/CandidateListLoader.jsx b/frontend/src/metabase/containers/CandidateListLoader.jsx
deleted file mode 100644
index 193ea9b1dfe..00000000000
--- a/frontend/src/metabase/containers/CandidateListLoader.jsx
+++ /dev/null
@@ -1,100 +0,0 @@
-/* eslint-disable react/prop-types */
-import React from "react";
-import _ from "underscore";
-
-import { MetabaseApi, AutoApi } from "metabase/services";
-
-const CANDIDATES_POLL_INTERVAL = 2000;
-// ensure this is 1 second offset from CANDIDATES_POLL_INTERVAL due to
-// concurrency issue in candidates endpoint
-const CANDIDATES_TIMEOUT = 11000;
-
-class CandidateListLoader extends React.Component {
-  state = {
-    databaseId: null,
-    isSample: null,
-    candidates: null,
-    sampleCandidates: null,
-  };
-
-  async UNSAFE_componentWillMount() {
-    // If we get passed in a database id, just use that.
-    // Don't fall back to the sample database
-    if (this.props.databaseId) {
-      this.setState({ databaseId: this.props.databaseId }, () => {
-        this._loadCandidates();
-      });
-    } else {
-      // Otherwise, it's a fresh start. Grab the last added database
-      const [sampleDbs, otherDbs] = _.partition(
-        await MetabaseApi.db_list(),
-        db => db.is_sample,
-      );
-      if (otherDbs.length > 0) {
-        this.setState({ databaseId: otherDbs[0].id, isSample: false }, () => {
-          this._loadCandidates();
-        });
-        // If things are super slow for whatever reason,
-        // just load candidates for sample database
-        this._sampleTimeout = setTimeout(async () => {
-          this._sampleTimeout = null;
-          this.setState({
-            sampleCandidates: await AutoApi.db_candidates({
-              id: sampleDbs[0].id,
-            }),
-          });
-        }, CANDIDATES_TIMEOUT);
-      } else {
-        this.setState({ databaseId: sampleDbs[0].id, isSample: true }, () => {
-          this._loadCandidates();
-        });
-      }
-    }
-    this._pollTimer = setInterval(
-      this._loadCandidates,
-      CANDIDATES_POLL_INTERVAL,
-    );
-  }
-  componentWillUnmount() {
-    this._clearTimers();
-  }
-  _clearTimers() {
-    if (this._pollTimer != null) {
-      clearInterval(this._pollTimer);
-      this._pollTimer = null;
-    }
-    if (this._sampleTimeout != null) {
-      clearInterval(this._sampleTimeout);
-      this._sampleTimeout = null;
-    }
-  }
-  _loadCandidates = async () => {
-    try {
-      const { databaseId } = this.state;
-      if (databaseId != null) {
-        const database = await MetabaseApi.db_get({
-          dbId: databaseId,
-        });
-        const candidates = await AutoApi.db_candidates({
-          id: databaseId,
-        });
-        if (candidates && candidates.length > 0) {
-          this._clearTimers();
-          this.setState({ candidates, isSample: database.is_sample });
-        }
-      }
-    } catch (e) {
-      console.log(e);
-    }
-  };
-  render() {
-    const { candidates, sampleCandidates, isSample } = this.state;
-    return this.props.children({
-      candidates,
-      sampleCandidates,
-      isSample,
-    });
-  }
-}
-
-export default CandidateListLoader;
diff --git a/frontend/src/metabase/containers/HistoryModal.jsx b/frontend/src/metabase/containers/HistoryModal.jsx
deleted file mode 100644
index 8108ed3b816..00000000000
--- a/frontend/src/metabase/containers/HistoryModal.jsx
+++ /dev/null
@@ -1,40 +0,0 @@
-/* eslint-disable react/prop-types */
-import React from "react";
-import PropTypes from "prop-types";
-
-import HistoryModal from "metabase/components/HistoryModal";
-import Revision from "metabase/entities/revisions";
-
-class HistoryModalContainer extends React.Component {
-  static propTypes = {
-    canRevert: PropTypes.bool.isRequired,
-  };
-
-  onRevert = async revision => {
-    const { onReverted, reload } = this.props;
-    await revision.revert();
-    if (onReverted) {
-      onReverted();
-    }
-    await reload();
-  };
-
-  render() {
-    const { revisions, canRevert, onClose } = this.props;
-    return (
-      <HistoryModal
-        revisions={revisions}
-        onRevert={canRevert ? this.onRevert : null}
-        onClose={onClose}
-      />
-    );
-  }
-}
-
-export default Revision.loadList({
-  query: (state, props) => ({
-    model_type: props.modelType,
-    model_id: props.modelId,
-  }),
-  wrapped: true,
-})(HistoryModalContainer);
diff --git a/frontend/src/metabase/containers/QuestionAndResultLoader.jsx b/frontend/src/metabase/containers/QuestionAndResultLoader.jsx
deleted file mode 100644
index 7fe65d2085e..00000000000
--- a/frontend/src/metabase/containers/QuestionAndResultLoader.jsx
+++ /dev/null
@@ -1,43 +0,0 @@
-/* eslint-disable react/prop-types */
-import React from "react";
-
-import QuestionLoader from "metabase/containers/QuestionLoader";
-import QuestionResultLoader from "metabase/containers/QuestionResultLoader";
-
-/*
- * QuestionAndResultLoader
- *
- * Load a question and also run the query to get the result. Useful when you want
- * to load both a question and its visualization at the same time.
- *
- * @example
- *
- * import QuestionAndResultLoader from 'metabase/containers/QuestionAndResultLoader'
- *
- * const MyNewFeature = ({ params, location }) =>
- * <QuestionAndResultLoader question={question}>
- * { ({ question, result, cancel, reload }) =>
- *   <div>
- *   </div>
- * </QuestionAndResultLoader>
- *
- */
-const QuestionAndResultLoader = ({ questionId, questionHash, children }) => (
-  <QuestionLoader questionId={questionId} questionHash={questionHash}>
-    {({ loading: questionLoading, error: questionError, ...questionProps }) => (
-      <QuestionResultLoader question={questionProps.question}>
-        {({ loading: resultLoading, error: resultError, ...resultProps }) =>
-          children &&
-          children({
-            ...questionProps,
-            ...resultProps,
-            loading: resultLoading || questionLoading,
-            error: resultError || questionError,
-          })
-        }
-      </QuestionResultLoader>
-    )}
-  </QuestionLoader>
-);
-
-export default QuestionAndResultLoader;
diff --git a/frontend/src/metabase/containers/QuestionName.jsx b/frontend/src/metabase/containers/QuestionName.jsx
deleted file mode 100644
index ffa8f1efca5..00000000000
--- a/frontend/src/metabase/containers/QuestionName.jsx
+++ /dev/null
@@ -1,10 +0,0 @@
-/* eslint-disable react/prop-types */
-import React from "react";
-
-import Question from "metabase/entities/questions";
-
-// TODO: remove this in favor of using Question.Name directly
-
-const QuestionName = ({ questionId }) => <Question.Name id={questionId} />;
-
-export default QuestionName;
diff --git a/frontend/src/metabase/containers/QuestionSelect.jsx b/frontend/src/metabase/containers/QuestionSelect.jsx
deleted file mode 100644
index c40052e69ab..00000000000
--- a/frontend/src/metabase/containers/QuestionSelect.jsx
+++ /dev/null
@@ -1,8 +0,0 @@
-import ItemSelect from "./ItemSelect";
-
-import QuestionPicker from "./QuestionPicker";
-import QuestionName from "./QuestionName";
-
-const QuestionSelect = ItemSelect(QuestionPicker, QuestionName, "question");
-
-export default QuestionSelect;
diff --git a/frontend/src/metabase/containers/QuestionSelect.tsx b/frontend/src/metabase/containers/QuestionSelect.tsx
new file mode 100644
index 00000000000..c14afaa3220
--- /dev/null
+++ b/frontend/src/metabase/containers/QuestionSelect.tsx
@@ -0,0 +1,16 @@
+import React from "react";
+
+import Question from "metabase/entities/questions";
+
+import type { CardId } from "metabase-types/api";
+
+import ItemSelect from "./ItemSelect";
+import QuestionPicker from "./QuestionPicker";
+
+const QuestionName = ({ questionId }: { questionId: CardId }) => (
+  <Question.Name id={questionId} />
+);
+
+const QuestionSelect = ItemSelect(QuestionPicker, QuestionName, "question");
+
+export default QuestionSelect;
diff --git a/frontend/src/metabase/dashboard/components/DashboardHeader.styled.tsx b/frontend/src/metabase/dashboard/components/DashboardHeader.styled.tsx
index e67f1729d8a..b72ec2a4cc7 100644
--- a/frontend/src/metabase/dashboard/components/DashboardHeader.styled.tsx
+++ b/frontend/src/metabase/dashboard/components/DashboardHeader.styled.tsx
@@ -72,6 +72,13 @@ export const HeaderLastEditInfoLabel = styled(LastEditInfoLabel)`
   }
 `;
 
+export const EditWarning = styled.div`
+  display: flex;
+  align-items: center;
+  padding-top: 0.5rem;
+  padding-bottom: 0.5rem;
+`;
+
 interface HeaderContentProps {
   showSubHeader: boolean;
   hasSubHeader: boolean;
diff --git a/frontend/src/metabase/dashboard/components/DashboardHeader.tsx b/frontend/src/metabase/dashboard/components/DashboardHeader.tsx
index e18df848f9b..43e50d1f0a3 100644
--- a/frontend/src/metabase/dashboard/components/DashboardHeader.tsx
+++ b/frontend/src/metabase/dashboard/components/DashboardHeader.tsx
@@ -14,9 +14,9 @@ import { getScrollY } from "metabase/lib/dom";
 import { Dashboard } from "metabase-types/api";
 
 import EditBar from "metabase/components/EditBar";
-import EditWarning from "metabase/components/EditWarning";
 import HeaderModal from "metabase/components/HeaderModal";
 import {
+  EditWarning,
   HeaderRoot,
   HeaderBadges,
   HeaderContent,
@@ -122,7 +122,11 @@ const DashboardHeader = ({
           buttons={editingButtons}
         />
       )}
-      {editWarning && <EditWarning title={editWarning} />}
+      {editWarning && (
+        <EditWarning className="wrapper">
+          <span>{editWarning}</span>
+        </EditWarning>
+      )}
       <HeaderModal
         isOpen={!!headerModalMessage}
         height={headerHeight}
diff --git a/frontend/src/metabase/internal/components/QuestionApp.jsx b/frontend/src/metabase/internal/components/QuestionApp.jsx
deleted file mode 100644
index 3844eabe1a6..00000000000
--- a/frontend/src/metabase/internal/components/QuestionApp.jsx
+++ /dev/null
@@ -1,41 +0,0 @@
-/* eslint-disable react/prop-types */
-import React from "react";
-import { Route } from "react-router";
-
-import QuestionAndResultLoader from "metabase/containers/QuestionAndResultLoader";
-import Visualization from "metabase/visualizations/components/Visualization";
-
-export default class QuestionApp extends React.Component {
-  render() {
-    const { location, params } = this.props;
-    if (!location.hash && !params.questionId) {
-      return (
-        <div className="p4 text-centered flex-full">
-          Visit <strong>/_internal/question/:id</strong> or{" "}
-          <strong>/_internal/question#:hash</strong>.
-        </div>
-      );
-    }
-    return (
-      <div style={{ height: 500 }}>
-        <QuestionAndResultLoader
-          questionHash={location.hash}
-          questionId={params.questionId ? parseInt(params.questionId) : null}
-        >
-          {({ question, rawSeries }) =>
-            rawSeries && (
-              <Visualization className="flex-full" rawSeries={rawSeries} />
-            )
-          }
-        </QuestionAndResultLoader>
-      </div>
-    );
-  }
-}
-
-QuestionApp.routes = (
-  <React.Fragment>
-    <Route path="question" component={QuestionApp} />
-    <Route path="question/:questionId" component={QuestionApp} />
-  </React.Fragment>
-);
diff --git a/frontend/src/metabase/query_builder/components/QueryModals.jsx b/frontend/src/metabase/query_builder/components/QueryModals.jsx
index bf79fef66c5..eccba94258d 100644
--- a/frontend/src/metabase/query_builder/components/QueryModals.jsx
+++ b/frontend/src/metabase/query_builder/components/QueryModals.jsx
@@ -20,7 +20,6 @@ import CollectionMoveModal from "metabase/containers/CollectionMoveModal";
 import ArchiveQuestionModal from "metabase/questions/containers/ArchiveQuestionModal";
 import QuestionEmbedWidget from "metabase/query_builder/containers/QuestionEmbedWidget";
 
-import QuestionHistoryModal from "metabase/query_builder/containers/QuestionHistoryModal";
 import { CreateAlertModalContent } from "metabase/query_builder/components/AlertModals";
 import { ImpossibleToCreateModelModal } from "metabase/query_builder/components/ImpossibleToCreateModelModal";
 import NewDatasetModal from "metabase/query_builder/components/NewDatasetModal";
@@ -186,17 +185,6 @@ class QueryModals extends React.Component {
           onClose={onCloseModal}
         />
       </Modal>
-    ) : modal === MODAL_TYPES.HISTORY ? (
-      <Modal onClose={onCloseModal}>
-        <QuestionHistoryModal
-          questionId={this.props.card.id}
-          onClose={onCloseModal}
-          onReverted={() => {
-            this.props.reloadCard();
-            onCloseModal();
-          }}
-        />
-      </Modal>
     ) : modal === MODAL_TYPES.MOVE ? (
       <Modal onClose={onCloseModal}>
         <CollectionMoveModal
diff --git a/frontend/src/metabase/query_builder/constants.js b/frontend/src/metabase/query_builder/constants.js
index f0f45732e5c..4878f8a8c4a 100644
--- a/frontend/src/metabase/query_builder/constants.js
+++ b/frontend/src/metabase/query_builder/constants.js
@@ -10,7 +10,6 @@ export const MODAL_TYPES = {
   SAVE_QUESTION_BEFORE_ALERT: "save-question-before-alert",
   SAVE_QUESTION_BEFORE_EMBED: "save-question-before-embed",
   FILTERS: "filters",
-  HISTORY: "history",
   EMBED: "embed",
   TURN_INTO_DATASET: "turn-into-dataset",
   CAN_NOT_CREATE_MODEL: "can-not-create-model",
diff --git a/frontend/src/metabase/query_builder/containers/QuestionHistoryModal.jsx b/frontend/src/metabase/query_builder/containers/QuestionHistoryModal.jsx
deleted file mode 100644
index ae6a8d2e692..00000000000
--- a/frontend/src/metabase/query_builder/containers/QuestionHistoryModal.jsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import React from "react";
-import PropTypes from "prop-types";
-
-import Questions from "metabase/entities/questions";
-import HistoryModal from "metabase/containers/HistoryModal";
-
-class QuestionHistoryModalInner extends React.Component {
-  static propTypes = {
-    question: PropTypes.object.isRequired,
-    questionId: PropTypes.number.isRequired,
-    onClose: PropTypes.func.isRequired,
-    onReverted: PropTypes.func.isRequired,
-  };
-
-  render() {
-    const { question, onClose, onReverted } = this.props;
-    return (
-      <HistoryModal
-        modelType="card"
-        modelId={question.id}
-        canRevert={question.can_write}
-        onClose={onClose}
-        onReverted={onReverted}
-      />
-    );
-  }
-}
-
-const QuestionHistoryModal = Questions.load({
-  id: (state, props) => props.questionId,
-})(QuestionHistoryModalInner);
-
-export default QuestionHistoryModal;
-- 
GitLab