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