From b912e4f911dd7ddcc6a28cf73bb3aa26936ec15a Mon Sep 17 00:00:00 2001 From: Alexander Polyankin <alexander.polyankin@metabase.com> Date: Tue, 7 Dec 2021 16:15:04 +0300 Subject: [PATCH] Convert homepage to TypeScript (#19204) --- .../home/homepage/{actions.js => actions.ts} | 6 +- ...tyled.jsx => CollectionSection.styled.tsx} | 2 +- ...ctionSection.jsx => CollectionSection.tsx} | 30 +++++----- ...pec.js => CollectionSection.unit.spec.tsx} | 17 +++--- .../CollectionSection/{index.js => index.ts} | 0 ....styled.jsx => DatabaseSection.styled.tsx} | 4 +- ...atabaseSection.jsx => DatabaseSection.tsx} | 40 ++++++------- ....spec.js => DatabaseSection.unit.spec.tsx} | 27 +++++---- .../DatabaseSection/{index.js => index.ts} | 0 ....styled.jsx => GreetingSection.styled.tsx} | 0 ...reetingSection.jsx => GreetingSection.tsx} | 14 ++--- ....spec.js => GreetingSection.unit.spec.tsx} | 8 ++- .../GreetingSection/{index.js => index.ts} | 0 ...omepage.styled.jsx => Homepage.styled.tsx} | 0 .../Homepage/{Homepage.jsx => Homepage.tsx} | 40 +++++++------ .../Homepage/{index.js => index.ts} | 0 ...{Section.styled.jsx => Section.styled.tsx} | 2 +- .../components/Section/{index.js => index.ts} | 0 ...ion.styled.jsx => StartSection.styled.tsx} | 10 +++- .../{StartSection.jsx => StartSection.tsx} | 48 +++++++--------- ...nit.spec.js => StartSection.unit.spec.tsx} | 54 ++++++++++-------- .../StartSection/{index.js => index.ts} | 0 ...tion.styled.jsx => XraySection.styled.tsx} | 4 +- .../{XraySection.jsx => XraySection.tsx} | 57 +++++++++---------- ...unit.spec.js => XraySection.unit.spec.tsx} | 35 ++++++++++-- .../XraySection/{index.js => index.ts} | 0 .../{HomepageApp.jsx => HomepageApp.tsx} | 5 +- .../HomepageApp/{index.js => index.ts} | 0 .../homepage/{selectors.js => selectors.ts} | 2 +- frontend/src/metabase/home/homepage/types.ts | 29 ++++++++++ jest.unit.conf.json | 4 +- 31 files changed, 251 insertions(+), 187 deletions(-) rename frontend/src/metabase/home/homepage/{actions.js => actions.ts} (87%) rename frontend/src/metabase/home/homepage/components/CollectionSection/{CollectionSection.styled.jsx => CollectionSection.styled.tsx} (100%) rename frontend/src/metabase/home/homepage/components/CollectionSection/{CollectionSection.jsx => CollectionSection.tsx} (81%) rename frontend/src/metabase/home/homepage/components/CollectionSection/{CollectionSection.unit.spec.js => CollectionSection.unit.spec.tsx} (81%) rename frontend/src/metabase/home/homepage/components/CollectionSection/{index.js => index.ts} (100%) rename frontend/src/metabase/home/homepage/components/DatabaseSection/{DatabaseSection.styled.jsx => DatabaseSection.styled.tsx} (100%) rename frontend/src/metabase/home/homepage/components/DatabaseSection/{DatabaseSection.jsx => DatabaseSection.tsx} (79%) rename frontend/src/metabase/home/homepage/components/DatabaseSection/{DatabaseSection.unit.spec.js => DatabaseSection.unit.spec.tsx} (84%) rename frontend/src/metabase/home/homepage/components/DatabaseSection/{index.js => index.ts} (100%) rename frontend/src/metabase/home/homepage/components/GreetingSection/{GreetingSection.styled.jsx => GreetingSection.styled.tsx} (100%) rename frontend/src/metabase/home/homepage/components/GreetingSection/{GreetingSection.jsx => GreetingSection.tsx} (79%) rename frontend/src/metabase/home/homepage/components/GreetingSection/{GreetingSection.unit.spec.js => GreetingSection.unit.spec.tsx} (67%) rename frontend/src/metabase/home/homepage/components/GreetingSection/{index.js => index.ts} (100%) rename frontend/src/metabase/home/homepage/components/Homepage/{Homepage.styled.jsx => Homepage.styled.tsx} (100%) rename frontend/src/metabase/home/homepage/components/Homepage/{Homepage.jsx => Homepage.tsx} (75%) rename frontend/src/metabase/home/homepage/components/Homepage/{index.js => index.ts} (100%) rename frontend/src/metabase/home/homepage/components/Section/{Section.styled.jsx => Section.styled.tsx} (100%) rename frontend/src/metabase/home/homepage/components/Section/{index.js => index.ts} (100%) rename frontend/src/metabase/home/homepage/components/StartSection/{StartSection.styled.jsx => StartSection.styled.tsx} (95%) rename frontend/src/metabase/home/homepage/components/StartSection/{StartSection.jsx => StartSection.tsx} (84%) rename frontend/src/metabase/home/homepage/components/StartSection/{StartSection.unit.spec.js => StartSection.unit.spec.tsx} (84%) rename frontend/src/metabase/home/homepage/components/StartSection/{index.js => index.ts} (100%) rename frontend/src/metabase/home/homepage/components/XraySection/{XraySection.styled.jsx => XraySection.styled.tsx} (100%) rename frontend/src/metabase/home/homepage/components/XraySection/{XraySection.jsx => XraySection.tsx} (72%) rename frontend/src/metabase/home/homepage/components/XraySection/{XraySection.unit.spec.js => XraySection.unit.spec.tsx} (82%) rename frontend/src/metabase/home/homepage/components/XraySection/{index.js => index.ts} (100%) rename frontend/src/metabase/home/homepage/containers/HomepageApp/{HomepageApp.jsx => HomepageApp.tsx} (92%) rename frontend/src/metabase/home/homepage/containers/HomepageApp/{index.js => index.ts} (100%) rename frontend/src/metabase/home/homepage/{selectors.js => selectors.ts} (88%) create mode 100644 frontend/src/metabase/home/homepage/types.ts diff --git a/frontend/src/metabase/home/homepage/actions.js b/frontend/src/metabase/home/homepage/actions.ts similarity index 87% rename from frontend/src/metabase/home/homepage/actions.js rename to frontend/src/metabase/home/homepage/actions.ts index e689063a9f5..c7003717d78 100644 --- a/frontend/src/metabase/home/homepage/actions.js +++ b/frontend/src/metabase/home/homepage/actions.ts @@ -3,7 +3,7 @@ import { updateSetting } from "metabase/admin/settings/settings"; export const HIDE_DATA = "metabase/home/homepage/HIDE_DATA"; export const hideData = createThunkAction(HIDE_DATA, function() { - return async function(dispatch) { + return async function(dispatch: any) { const setting = { key: "show-homepage-data", value: false }; await dispatch(updateSetting(setting)); }; @@ -11,7 +11,7 @@ export const hideData = createThunkAction(HIDE_DATA, function() { export const HIDE_XRAYS = "metabase/home/homepage/HIDE_XRAYS"; export const hideXrays = createThunkAction(HIDE_XRAYS, function() { - return async function(dispatch) { + return async function(dispatch: any) { const setting = { key: "show-homepage-xrays", value: false }; await dispatch(updateSetting(setting)); }; @@ -19,7 +19,7 @@ export const hideXrays = createThunkAction(HIDE_XRAYS, function() { export const HIDE_PIN_MESSAGE = "metabase/home/homepage/HIDE_PIN_MESSAGE"; export const hidePinMessage = createThunkAction(HIDE_PIN_MESSAGE, function() { - return async function(dispatch) { + return async function(dispatch: any) { const setting = { key: "show-homepage-pin-message", value: false }; await dispatch(updateSetting(setting)); }; diff --git a/frontend/src/metabase/home/homepage/components/CollectionSection/CollectionSection.styled.jsx b/frontend/src/metabase/home/homepage/components/CollectionSection/CollectionSection.styled.tsx similarity index 100% rename from frontend/src/metabase/home/homepage/components/CollectionSection/CollectionSection.styled.jsx rename to frontend/src/metabase/home/homepage/components/CollectionSection/CollectionSection.styled.tsx index 0c96efa2ee9..7b9c490799c 100644 --- a/frontend/src/metabase/home/homepage/components/CollectionSection/CollectionSection.styled.jsx +++ b/frontend/src/metabase/home/homepage/components/CollectionSection/CollectionSection.styled.tsx @@ -1,5 +1,5 @@ -import styled from "styled-components"; import { Link } from "react-router"; +import styled from "styled-components"; import { color } from "metabase/lib/colors"; import Icon from "metabase/components/Icon"; diff --git a/frontend/src/metabase/home/homepage/components/CollectionSection/CollectionSection.jsx b/frontend/src/metabase/home/homepage/components/CollectionSection/CollectionSection.tsx similarity index 81% rename from frontend/src/metabase/home/homepage/components/CollectionSection/CollectionSection.jsx rename to frontend/src/metabase/home/homepage/components/CollectionSection/CollectionSection.tsx index 06e6a71e69f..bb7bf30c915 100644 --- a/frontend/src/metabase/home/homepage/components/CollectionSection/CollectionSection.jsx +++ b/frontend/src/metabase/home/homepage/components/CollectionSection/CollectionSection.tsx @@ -1,26 +1,26 @@ import React from "react"; -import PropTypes from "prop-types"; import { t } from "ttag"; -import * as Urls from "metabase/lib/urls"; -import { ROOT_COLLECTION } from "metabase/entities/collections"; import CollectionList from "metabase/components/CollectionList"; +import { ROOT_COLLECTION } from "metabase/entities/collections"; +import * as Urls from "metabase/lib/urls"; +import { Collection, User } from "../../types"; import Section, { SectionHeader, SectionTitle } from "../Section"; import { + CollectionContent, CollectionLink, CollectionLinkIcon, CollectionLinkText, EmptyStateImage, EmptyStateRoot, EmptyStateTitle, - CollectionContent, } from "./CollectionSection.styled"; -const propTypes = { - user: PropTypes.object.isRequired, - collections: PropTypes.array.isRequired, -}; +interface Props { + user: User; + collections: Collection[]; +} -const CollectionSection = ({ user, collections }) => { +const CollectionSection = ({ user, collections }: Props) => { const showList = collections.some(c => c.id !== user.personal_collection_id); const collectionUrl = Urls.collection(ROOT_COLLECTION); @@ -47,13 +47,11 @@ const CollectionSection = ({ user, collections }) => { ); }; -CollectionSection.propTypes = propTypes; +interface EmptyStateProps { + user: User; +} -const emptyStatePropTypes = { - user: PropTypes.object.isRequired, -}; - -const EmptyState = ({ user }) => { +const EmptyState = ({ user }: EmptyStateProps) => { return ( <EmptyStateRoot> <EmptyStateImage @@ -69,6 +67,4 @@ const EmptyState = ({ user }) => { ); }; -EmptyState.propTypes = emptyStatePropTypes; - export default CollectionSection; diff --git a/frontend/src/metabase/home/homepage/components/CollectionSection/CollectionSection.unit.spec.js b/frontend/src/metabase/home/homepage/components/CollectionSection/CollectionSection.unit.spec.tsx similarity index 81% rename from frontend/src/metabase/home/homepage/components/CollectionSection/CollectionSection.unit.spec.js rename to frontend/src/metabase/home/homepage/components/CollectionSection/CollectionSection.unit.spec.tsx index 1e878b7e8b1..f9df43b7b25 100644 --- a/frontend/src/metabase/home/homepage/components/CollectionSection/CollectionSection.unit.spec.js +++ b/frontend/src/metabase/home/homepage/components/CollectionSection/CollectionSection.unit.spec.tsx @@ -1,5 +1,6 @@ import React from "react"; import { render, screen } from "@testing-library/react"; +import { Collection, User } from "../../types"; import CollectionSection from "./CollectionSection"; const CollectionListMock = () => <div>CollectionList</div>; @@ -35,12 +36,14 @@ describe("CollectionSection", () => { }); }); -const getUser = ({ - is_superuser = false, - personal_collection_id = "personal", -} = {}) => ({ - is_superuser, - personal_collection_id, +const getUser = (opts?: Partial<User>): User => ({ + first_name: "John", + is_superuser: false, + personal_collection_id: "personal", + ...opts, }); -const getCollection = ({ id = "root" } = {}) => ({ id }); +const getCollection = (opts?: Partial<Collection>): Collection => ({ + id: "root", + ...opts, +}); diff --git a/frontend/src/metabase/home/homepage/components/CollectionSection/index.js b/frontend/src/metabase/home/homepage/components/CollectionSection/index.ts similarity index 100% rename from frontend/src/metabase/home/homepage/components/CollectionSection/index.js rename to frontend/src/metabase/home/homepage/components/CollectionSection/index.ts diff --git a/frontend/src/metabase/home/homepage/components/DatabaseSection/DatabaseSection.styled.jsx b/frontend/src/metabase/home/homepage/components/DatabaseSection/DatabaseSection.styled.tsx similarity index 100% rename from frontend/src/metabase/home/homepage/components/DatabaseSection/DatabaseSection.styled.jsx rename to frontend/src/metabase/home/homepage/components/DatabaseSection/DatabaseSection.styled.tsx index e5d6cbc3f34..09fc3ca7026 100644 --- a/frontend/src/metabase/home/homepage/components/DatabaseSection/DatabaseSection.styled.jsx +++ b/frontend/src/metabase/home/homepage/components/DatabaseSection/DatabaseSection.styled.tsx @@ -1,7 +1,7 @@ -import styled from "styled-components"; import { Link } from "react-router"; -import { color } from "metabase/lib/colors"; +import styled from "styled-components"; import Icon from "metabase/components/Icon"; +import { color } from "metabase/lib/colors"; import { breakpointMinMedium, breakpointMinSmall, diff --git a/frontend/src/metabase/home/homepage/components/DatabaseSection/DatabaseSection.jsx b/frontend/src/metabase/home/homepage/components/DatabaseSection/DatabaseSection.tsx similarity index 79% rename from frontend/src/metabase/home/homepage/components/DatabaseSection/DatabaseSection.jsx rename to frontend/src/metabase/home/homepage/components/DatabaseSection/DatabaseSection.tsx index b9ee831ba9f..149192b953f 100644 --- a/frontend/src/metabase/home/homepage/components/DatabaseSection/DatabaseSection.jsx +++ b/frontend/src/metabase/home/homepage/components/DatabaseSection/DatabaseSection.tsx @@ -1,10 +1,10 @@ -import React from "react"; -import PropTypes from "prop-types"; +import React, { ReactNode } from "react"; import { t } from "ttag"; -import * as Urls from "metabase/lib/urls"; import Button from "metabase/components/Button"; -import Tooltip from "metabase/components/Tooltip"; import ModalWithTrigger from "metabase/components/ModalWithTrigger"; +import Tooltip from "metabase/components/Tooltip"; +import * as Urls from "metabase/lib/urls"; +import { Database, User } from "../../types"; import Section, { SectionCloseIcon, SectionHeader, @@ -19,14 +19,14 @@ import { ListRoot, } from "./DatabaseSection.styled"; -const propTypes = { - user: PropTypes.object.isRequired, - databases: PropTypes.array.isRequired, - showData: PropTypes.bool, - onHideData: PropTypes.func, -}; +interface Props { + user: User; + databases: Database[]; + showData?: boolean; + onHideData?: () => void; +} -const DatabaseSection = ({ user, databases, showData, onHideData }) => { +const DatabaseSection = ({ user, databases, showData, onHideData }: Props) => { const hasAddLink = user.is_superuser; const hasUserDatabase = databases.some(d => !d.is_sample); @@ -39,11 +39,11 @@ const DatabaseSection = ({ user, databases, showData, onHideData }) => { <SectionHeader> <SectionTitle>{t`Our data`}</SectionTitle> {hasAddLink && ( - <SectionRemoveModal onSubmit={onHideData}> + <HideSectionModal onSubmit={onHideData}> <Tooltip tooltip={t`Hide this section`}> <SectionCloseIcon name="close" /> </Tooltip> - </SectionRemoveModal> + </HideSectionModal> )} {hasAddLink && hasUserDatabase && ( <ActionLink to={Urls.newDatabase()}>{t`Add a database`}</ActionLink> @@ -70,14 +70,12 @@ const DatabaseSection = ({ user, databases, showData, onHideData }) => { ); }; -DatabaseSection.propTypes = propTypes; - -const modalPropTypes = { - children: PropTypes.node, - onSubmit: PropTypes.func, -}; +interface HideSectionModalProps { + children?: ReactNode; + onSubmit?: () => void; +} -const SectionRemoveModal = ({ children, onSubmit }) => { +const HideSectionModal = ({ children, onSubmit }: HideSectionModalProps) => { return ( <ModalWithTrigger title={t`Remove this section?`} @@ -91,6 +89,4 @@ const SectionRemoveModal = ({ children, onSubmit }) => { ); }; -SectionRemoveModal.propTypes = modalPropTypes; - export default DatabaseSection; diff --git a/frontend/src/metabase/home/homepage/components/DatabaseSection/DatabaseSection.unit.spec.js b/frontend/src/metabase/home/homepage/components/DatabaseSection/DatabaseSection.unit.spec.tsx similarity index 84% rename from frontend/src/metabase/home/homepage/components/DatabaseSection/DatabaseSection.unit.spec.js rename to frontend/src/metabase/home/homepage/components/DatabaseSection/DatabaseSection.unit.spec.tsx index d8cdb9e5ce3..5dbe44558a5 100644 --- a/frontend/src/metabase/home/homepage/components/DatabaseSection/DatabaseSection.unit.spec.js +++ b/frontend/src/metabase/home/homepage/components/DatabaseSection/DatabaseSection.unit.spec.tsx @@ -1,6 +1,7 @@ import React from "react"; import { render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; +import { Database, User } from "../../types"; import DatabaseSection from "./DatabaseSection"; describe("DatabaseSection", () => { @@ -41,18 +42,16 @@ describe("DatabaseSection", () => { it("should not be visible for regular users when there are no databases", () => { const user = getUser(); - const databases = []; - render(<DatabaseSection user={user} databases={databases} showData />); + render(<DatabaseSection user={user} databases={[]} showData />); expect(screen.queryByText("Our data")).not.toBeInTheDocument(); }); it("should be visible for admin users when there are no databases", () => { const user = getUser({ is_superuser: true }); - const databases = []; - render(<DatabaseSection user={user} databases={databases} showData />); + render(<DatabaseSection user={user} databases={[]} showData />); expect(screen.getByText("Our data")).toBeInTheDocument(); expect(screen.getByText("Add a database")).toBeInTheDocument(); @@ -60,13 +59,12 @@ describe("DatabaseSection", () => { it("should allow admins to hide the section", () => { const user = getUser({ is_superuser: true }); - const databases = []; const onHideData = jest.fn(); render( <DatabaseSection user={user} - databases={databases} + databases={[]} showData onHideData={onHideData} />, @@ -80,13 +78,12 @@ describe("DatabaseSection", () => { it("should not allow regular users to hide the section", () => { const user = getUser({ is_superuser: false }); - const databases = []; const onHideData = jest.fn(); render( <DatabaseSection user={user} - databases={databases} + databases={[]} showData onHideData={onHideData} />, @@ -96,6 +93,16 @@ describe("DatabaseSection", () => { }); }); -const getUser = ({ is_superuser = false } = {}) => ({ is_superuser }); +const getUser = (opts?: Partial<User>): User => ({ + first_name: "John", + is_superuser: false, + personal_collection_id: "personal", + ...opts, +}); -const getDatabase = ({ id = 1, name } = {}) => ({ id, name }); +const getDatabase = (opts?: Partial<Database>): Database => ({ + id: 1, + name: "Our database", + is_sample: false, + ...opts, +}); diff --git a/frontend/src/metabase/home/homepage/components/DatabaseSection/index.js b/frontend/src/metabase/home/homepage/components/DatabaseSection/index.ts similarity index 100% rename from frontend/src/metabase/home/homepage/components/DatabaseSection/index.js rename to frontend/src/metabase/home/homepage/components/DatabaseSection/index.ts diff --git a/frontend/src/metabase/home/homepage/components/GreetingSection/GreetingSection.styled.jsx b/frontend/src/metabase/home/homepage/components/GreetingSection/GreetingSection.styled.tsx similarity index 100% rename from frontend/src/metabase/home/homepage/components/GreetingSection/GreetingSection.styled.jsx rename to frontend/src/metabase/home/homepage/components/GreetingSection/GreetingSection.styled.tsx diff --git a/frontend/src/metabase/home/homepage/components/GreetingSection/GreetingSection.jsx b/frontend/src/metabase/home/homepage/components/GreetingSection/GreetingSection.tsx similarity index 79% rename from frontend/src/metabase/home/homepage/components/GreetingSection/GreetingSection.jsx rename to frontend/src/metabase/home/homepage/components/GreetingSection/GreetingSection.tsx index a27bfa21ef6..1ad178b441d 100644 --- a/frontend/src/metabase/home/homepage/components/GreetingSection/GreetingSection.jsx +++ b/frontend/src/metabase/home/homepage/components/GreetingSection/GreetingSection.tsx @@ -1,17 +1,17 @@ import React, { useMemo } from "react"; -import PropTypes from "prop-types"; import { t } from "ttag"; -import Greeting from "metabase/lib/greeting"; import MetabotLogo from "metabase/components/MetabotLogo"; import Tooltip from "metabase/components/Tooltip"; +import Greeting from "metabase/lib/greeting"; +import { User } from "../../types"; import Section from "../Section"; import { GreetingContent, GreetingTitle } from "./GreetingSection.styled"; -const propTypes = { - user: PropTypes.object.isRequired, -}; +interface Props { + user: User; +} -const GreetingSection = ({ user: { first_name } }) => { +const GreetingSection = ({ user: { first_name } }: Props) => { const greeting = useMemo(() => Greeting.sayHello(first_name), [first_name]); return ( @@ -29,6 +29,4 @@ const GreetingSection = ({ user: { first_name } }) => { ); }; -GreetingSection.propTypes = propTypes; - export default GreetingSection; diff --git a/frontend/src/metabase/home/homepage/components/GreetingSection/GreetingSection.unit.spec.js b/frontend/src/metabase/home/homepage/components/GreetingSection/GreetingSection.unit.spec.tsx similarity index 67% rename from frontend/src/metabase/home/homepage/components/GreetingSection/GreetingSection.unit.spec.js rename to frontend/src/metabase/home/homepage/components/GreetingSection/GreetingSection.unit.spec.tsx index 60990cfc86f..1e630be4352 100644 --- a/frontend/src/metabase/home/homepage/components/GreetingSection/GreetingSection.unit.spec.js +++ b/frontend/src/metabase/home/homepage/components/GreetingSection/GreetingSection.unit.spec.tsx @@ -1,5 +1,6 @@ import React from "react"; import { render, screen } from "@testing-library/react"; +import { User } from "../../types"; import GreetingSection from "./GreetingSection"; describe("GreetingSection", () => { @@ -12,4 +13,9 @@ describe("GreetingSection", () => { }); }); -const getUser = ({ first_name } = {}) => ({ first_name }); +const getUser = (opts?: Partial<User>): User => ({ + first_name: "John", + is_superuser: false, + personal_collection_id: "personal", + ...opts, +}); diff --git a/frontend/src/metabase/home/homepage/components/GreetingSection/index.js b/frontend/src/metabase/home/homepage/components/GreetingSection/index.ts similarity index 100% rename from frontend/src/metabase/home/homepage/components/GreetingSection/index.js rename to frontend/src/metabase/home/homepage/components/GreetingSection/index.ts diff --git a/frontend/src/metabase/home/homepage/components/Homepage/Homepage.styled.jsx b/frontend/src/metabase/home/homepage/components/Homepage/Homepage.styled.tsx similarity index 100% rename from frontend/src/metabase/home/homepage/components/Homepage/Homepage.styled.jsx rename to frontend/src/metabase/home/homepage/components/Homepage/Homepage.styled.tsx diff --git a/frontend/src/metabase/home/homepage/components/Homepage/Homepage.jsx b/frontend/src/metabase/home/homepage/components/Homepage/Homepage.tsx similarity index 75% rename from frontend/src/metabase/home/homepage/components/Homepage/Homepage.jsx rename to frontend/src/metabase/home/homepage/components/Homepage/Homepage.tsx index 7ac38598adf..b0ee92351f3 100644 --- a/frontend/src/metabase/home/homepage/components/Homepage/Homepage.jsx +++ b/frontend/src/metabase/home/homepage/components/Homepage/Homepage.tsx @@ -1,25 +1,31 @@ import React, { Fragment } from "react"; -import PropTypes from "prop-types"; -import GreetingSection from "../GreetingSection"; +import { + Collection, + Dashboard, + Database, + DatabaseCandidate, + User, +} from "../../types"; import CollectionSection from "../CollectionSection"; import DatabaseSection from "../DatabaseSection"; +import GreetingSection from "../GreetingSection"; import StartSection from "../StartSection"; import XraySection from "../XraySection"; import { LandingRoot } from "./Homepage.styled"; -const propTypes = { - user: PropTypes.object.isRequired, - databases: PropTypes.array, - collections: PropTypes.array, - dashboards: PropTypes.array, - databaseCandidates: PropTypes.array, - showData: PropTypes.bool, - showXrays: PropTypes.bool, - showPinMessage: PropTypes.bool, - onHideData: PropTypes.func, - onHideXrays: PropTypes.func, - onHidePinMessage: PropTypes.func, -}; +interface Props { + user: User; + databases?: Database[]; + collections?: Collection[]; + dashboards?: Dashboard[]; + databaseCandidates?: DatabaseCandidate[]; + showData?: boolean; + showXrays?: boolean; + showPinMessage?: boolean; + onHideData?: () => void; + onHideXrays?: () => void; + onHidePinMessage?: () => void; +} const Homepage = ({ user, @@ -33,7 +39,7 @@ const Homepage = ({ onHideData, onHideXrays, onHidePinMessage, -}) => { +}: Props) => { return ( <LandingRoot> <GreetingSection user={user} /> @@ -66,6 +72,4 @@ const Homepage = ({ ); }; -Homepage.propTypes = propTypes; - export default Homepage; diff --git a/frontend/src/metabase/home/homepage/components/Homepage/index.js b/frontend/src/metabase/home/homepage/components/Homepage/index.ts similarity index 100% rename from frontend/src/metabase/home/homepage/components/Homepage/index.js rename to frontend/src/metabase/home/homepage/components/Homepage/index.ts diff --git a/frontend/src/metabase/home/homepage/components/Section/Section.styled.jsx b/frontend/src/metabase/home/homepage/components/Section/Section.styled.tsx similarity index 100% rename from frontend/src/metabase/home/homepage/components/Section/Section.styled.jsx rename to frontend/src/metabase/home/homepage/components/Section/Section.styled.tsx index 3784f71d82a..c59410ebc77 100644 --- a/frontend/src/metabase/home/homepage/components/Section/Section.styled.jsx +++ b/frontend/src/metabase/home/homepage/components/Section/Section.styled.tsx @@ -1,6 +1,6 @@ import styled from "styled-components"; -import { color } from "metabase/lib/colors"; import Icon from "metabase/components/Icon"; +import { color } from "metabase/lib/colors"; export const SectionHeader = styled.div` display: flex; diff --git a/frontend/src/metabase/home/homepage/components/Section/index.js b/frontend/src/metabase/home/homepage/components/Section/index.ts similarity index 100% rename from frontend/src/metabase/home/homepage/components/Section/index.js rename to frontend/src/metabase/home/homepage/components/Section/index.ts diff --git a/frontend/src/metabase/home/homepage/components/StartSection/StartSection.styled.jsx b/frontend/src/metabase/home/homepage/components/StartSection/StartSection.styled.tsx similarity index 95% rename from frontend/src/metabase/home/homepage/components/StartSection/StartSection.styled.jsx rename to frontend/src/metabase/home/homepage/components/StartSection/StartSection.styled.tsx index 0f2965e5800..e84f53a891d 100644 --- a/frontend/src/metabase/home/homepage/components/StartSection/StartSection.styled.jsx +++ b/frontend/src/metabase/home/homepage/components/StartSection/StartSection.styled.tsx @@ -1,13 +1,17 @@ -import styled from "styled-components"; import { Link } from "react-router"; -import { color } from "metabase/lib/colors"; +import styled from "styled-components"; import Icon from "metabase/components/Icon"; +import { color } from "metabase/lib/colors"; import { breakpointMinMedium, breakpointMinSmall, } from "metabase/styled-components/theme"; -export const ListRoot = styled.div` +interface ListRootProps { + hasMargin?: boolean; +} + +export const ListRoot = styled.div<ListRootProps>` display: grid; grid-template-columns: repeat(1, 1fr); gap: 1rem; diff --git a/frontend/src/metabase/home/homepage/components/StartSection/StartSection.jsx b/frontend/src/metabase/home/homepage/components/StartSection/StartSection.tsx similarity index 84% rename from frontend/src/metabase/home/homepage/components/StartSection/StartSection.jsx rename to frontend/src/metabase/home/homepage/components/StartSection/StartSection.tsx index f6ff201b2b8..2d8deb9f80e 100644 --- a/frontend/src/metabase/home/homepage/components/StartSection/StartSection.jsx +++ b/frontend/src/metabase/home/homepage/components/StartSection/StartSection.tsx @@ -1,11 +1,11 @@ import React from "react"; -import PropTypes from "prop-types"; import { jt, t } from "ttag"; +import ExternalLink from "metabase/components/ExternalLink"; +import Link from "metabase/components/Link"; +import { ROOT_COLLECTION } from "metabase/entities/collections"; import Settings from "metabase/lib/settings"; import * as Urls from "metabase/lib/urls"; -import { ROOT_COLLECTION } from "metabase/entities/collections"; -import Link from "metabase/components/Link"; -import ExternalLink from "metabase/components/ExternalLink"; +import { Dashboard, Database, User } from "../../types"; import Section, { SectionHeader, SectionTitle } from "../Section"; import { BannerCloseIcon, @@ -21,13 +21,13 @@ import { ListRoot, } from "./StartSection.styled"; -const propTypes = { - user: PropTypes.object.isRequired, - databases: PropTypes.array.isRequired, - dashboards: PropTypes.array.isRequired, - showPinMessage: PropTypes.bool, - onHidePinMessage: PropTypes.func, -}; +interface Props { + user: User; + databases: Database[]; + dashboards: Dashboard[]; + showPinMessage?: boolean; + onHidePinMessage?: () => void; +} const StartSection = ({ user, @@ -35,7 +35,7 @@ const StartSection = ({ dashboards, showPinMessage, onHidePinMessage, -}) => { +}: Props) => { const showDatabaseBanner = user.is_superuser && !databases.some(d => !d.is_sample); const showDashboardBanner = @@ -66,13 +66,11 @@ const StartSection = ({ ); }; -StartSection.propTypes = propTypes; - -const cardProps = { - dashboard: PropTypes.object, -}; +interface DashboardCardProps { + dashboard: Dashboard; +} -const DashboardCard = ({ dashboard }) => { +const DashboardCard = ({ dashboard }: DashboardCardProps) => { const dashboardUrl = Urls.dashboard(dashboard); return ( @@ -83,8 +81,6 @@ const DashboardCard = ({ dashboard }) => { ); }; -DashboardCard.propTypes = cardProps; - const DatabaseBanner = () => { const userUrl = Urls.newUser(); const databaseUrl = Urls.newDatabase(); @@ -117,12 +113,12 @@ const DatabaseBanner = () => { ); }; -const dashboardBannerProps = { - user: PropTypes.object.isRequired, - onHidePinMessage: PropTypes.func, -}; +interface DashboardBannerProps { + user: User; + onHidePinMessage?: () => void; +} -const DashboardBanner = ({ user, onHidePinMessage }) => { +const DashboardBanner = ({ user, onHidePinMessage }: DashboardBannerProps) => { const collectionUrl = Urls.collection(ROOT_COLLECTION); return ( @@ -145,6 +141,4 @@ const DashboardBanner = ({ user, onHidePinMessage }) => { ); }; -DashboardBanner.propTypes = dashboardBannerProps; - export default StartSection; diff --git a/frontend/src/metabase/home/homepage/components/StartSection/StartSection.unit.spec.js b/frontend/src/metabase/home/homepage/components/StartSection/StartSection.unit.spec.tsx similarity index 84% rename from frontend/src/metabase/home/homepage/components/StartSection/StartSection.unit.spec.js rename to frontend/src/metabase/home/homepage/components/StartSection/StartSection.unit.spec.tsx index 091041e41ee..25c0a492e8c 100644 --- a/frontend/src/metabase/home/homepage/components/StartSection/StartSection.unit.spec.js +++ b/frontend/src/metabase/home/homepage/components/StartSection/StartSection.unit.spec.tsx @@ -1,6 +1,7 @@ import React from "react"; import { render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; +import { Dashboard, Database, User } from "../../types"; import StartSection from "./StartSection"; describe("StartSection", () => { @@ -46,13 +47,12 @@ describe("StartSection", () => { it("should not show a banner for regular users when there are no user databases", () => { const user = getUser(); - const databases = []; const dashboards = [getDashboard({ name: "Our dashboard" })]; render( <StartSection user={user} - databases={databases} + databases={[]} dashboards={dashboards} showPinMessage={true} />, @@ -66,14 +66,12 @@ describe("StartSection", () => { it("should show a banner for admins when there are no pinned dashboards", () => { const user = getUser({ is_superuser: true }); - const databases = []; - const dashboards = []; render( <StartSection user={user} - databases={databases} - dashboards={dashboards} + databases={[]} + dashboards={[]} showPinMessage={true} />, ); @@ -85,14 +83,12 @@ describe("StartSection", () => { it("should show a banner for regular users when there are no pinned dashboards", () => { const user = getUser(); - const databases = []; - const dashboards = []; render( <StartSection user={user} - databases={databases} - dashboards={dashboards} + databases={[]} + dashboards={[]} showPinMessage={true} />, ); @@ -104,14 +100,12 @@ describe("StartSection", () => { it("should not hide the section for admins when there is no content", () => { const user = getUser({ is_superuser: true }); - const databases = []; - const dashboards = []; render( <StartSection user={user} - databases={databases} - dashboards={dashboards} + databases={[]} + dashboards={[]} showPinMessage={false} />, ); @@ -122,14 +116,12 @@ describe("StartSection", () => { it("should hide the section for regular users when there is no content", () => { const user = getUser(); - const databases = []; - const dashboards = []; render( <StartSection user={user} - databases={databases} - dashboards={dashboards} + databases={[]} + dashboards={[]} showPinMessage={false} />, ); @@ -140,14 +132,13 @@ describe("StartSection", () => { it("should allow admins to hide the dashboard banner", () => { const user = getUser({ is_superuser: true }); const databases = [getDatabase()]; - const dashboards = []; const onHidePinMessage = jest.fn(); render( <StartSection user={user} databases={databases} - dashboards={dashboards} + dashboards={[]} showPinMessage={true} onHidePinMessage={onHidePinMessage} />, @@ -160,14 +151,13 @@ describe("StartSection", () => { it("should not allow regular users to hide the dashboard banner", () => { const user = getUser(); const databases = [getDatabase()]; - const dashboards = []; const onHidePinMessage = jest.fn(); render( <StartSection user={user} databases={databases} - dashboards={dashboards} + dashboards={[]} showPinMessage={true} onHidePinMessage={onHidePinMessage} />, @@ -177,8 +167,22 @@ describe("StartSection", () => { }); }); -const getUser = ({ is_superuser = false } = {}) => ({ is_superuser }); +const getUser = (opts?: Partial<User>): User => ({ + first_name: "John", + is_superuser: false, + personal_collection_id: "personal", + ...opts, +}); -const getDatabase = ({ id = 1, is_sample = false } = {}) => ({ id, is_sample }); +const getDatabase = (opts?: Partial<Database>): Database => ({ + id: 1, + name: "Our database", + is_sample: false, + ...opts, +}); -const getDashboard = ({ id = 1, name } = {}) => ({ id, name }); +const getDashboard = (opts?: Partial<Dashboard>): Dashboard => ({ + id: 1, + name: "Our dashboard", + ...opts, +}); diff --git a/frontend/src/metabase/home/homepage/components/StartSection/index.js b/frontend/src/metabase/home/homepage/components/StartSection/index.ts similarity index 100% rename from frontend/src/metabase/home/homepage/components/StartSection/index.js rename to frontend/src/metabase/home/homepage/components/StartSection/index.ts diff --git a/frontend/src/metabase/home/homepage/components/XraySection/XraySection.styled.jsx b/frontend/src/metabase/home/homepage/components/XraySection/XraySection.styled.tsx similarity index 100% rename from frontend/src/metabase/home/homepage/components/XraySection/XraySection.styled.jsx rename to frontend/src/metabase/home/homepage/components/XraySection/XraySection.styled.tsx index 05d81942245..e1a4bab24fb 100644 --- a/frontend/src/metabase/home/homepage/components/XraySection/XraySection.styled.jsx +++ b/frontend/src/metabase/home/homepage/components/XraySection/XraySection.styled.tsx @@ -1,7 +1,7 @@ -import styled from "styled-components"; import { Link } from "react-router"; -import { color } from "metabase/lib/colors"; +import styled from "styled-components"; import Icon from "metabase/components/Icon"; +import { color } from "metabase/lib/colors"; import { breakpointMinMedium, breakpointMinSmall, diff --git a/frontend/src/metabase/home/homepage/components/XraySection/XraySection.jsx b/frontend/src/metabase/home/homepage/components/XraySection/XraySection.tsx similarity index 72% rename from frontend/src/metabase/home/homepage/components/XraySection/XraySection.jsx rename to frontend/src/metabase/home/homepage/components/XraySection/XraySection.tsx index e22fc0cd66e..7069f5b3815 100644 --- a/frontend/src/metabase/home/homepage/components/XraySection/XraySection.jsx +++ b/frontend/src/metabase/home/homepage/components/XraySection/XraySection.tsx @@ -1,12 +1,17 @@ -import React from "react"; -import PropTypes from "prop-types"; +import React, { ReactNode } from "react"; import { t } from "ttag"; import Button from "metabase/components/Button"; -import Tooltip from "metabase/components/Tooltip"; import ModalWithTrigger from "metabase/components/ModalWithTrigger"; +import Tooltip from "metabase/components/Tooltip"; +import { + Dashboard, + DatabaseCandidate, + TableCandidate, + User, +} from "../../types"; import Section, { - SectionHeader, SectionCloseIcon, + SectionHeader, SectionTitle, } from "../Section"; import { @@ -17,13 +22,13 @@ import { ListRoot, } from "./XraySection.styled"; -const propTypes = { - user: PropTypes.object.isRequired, - dashboards: PropTypes.array, - databaseCandidates: PropTypes.array, - showXrays: PropTypes.bool, - onHideXrays: PropTypes.func, -}; +interface Props { + user: User; + dashboards: Dashboard[]; + databaseCandidates?: DatabaseCandidate[]; + showXrays?: boolean; + onHideXrays?: () => void; +} const XraySection = ({ user, @@ -31,7 +36,7 @@ const XraySection = ({ databaseCandidates = [], showXrays, onHideXrays, -}) => { +}: Props) => { const options = databaseCandidates.flatMap(database => database.tables); if (!showXrays || dashboards.length || !options.length) { @@ -43,11 +48,11 @@ const XraySection = ({ <SectionHeader> <SectionTitle>{t`Try these x-rays based on your data`}</SectionTitle> {user.is_superuser && ( - <SectionRemoveModal onSubmit={onHideXrays}> + <HideSectionModal onSubmit={onHideXrays}> <Tooltip tooltip={t`Remove these suggestions`}> <SectionCloseIcon name="close" /> </Tooltip> - </SectionRemoveModal> + </HideSectionModal> )} </SectionHeader> <ListRoot> @@ -59,13 +64,11 @@ const XraySection = ({ ); }; -XraySection.propTypes = propTypes; +interface XrayCardProps { + option: TableCandidate; +} -const cardPropTypes = { - option: PropTypes.object.isRequired, -}; - -const XrayCard = ({ option }) => { +const XrayCard = ({ option }: XrayCardProps) => { return ( <CardRoot to={option.url}> <CardIconContainer> @@ -78,14 +81,12 @@ const XrayCard = ({ option }) => { ); }; -XrayCard.propTypes = cardPropTypes; +interface HideSectionModalProps { + children?: ReactNode; + onSubmit?: () => void; +} -const modalPropTypes = { - children: PropTypes.node, - onSubmit: PropTypes.func, -}; - -const SectionRemoveModal = ({ children, onSubmit }) => { +const HideSectionModal = ({ children, onSubmit }: HideSectionModalProps) => { return ( <ModalWithTrigger title={t`Remove these suggestions?`} @@ -99,6 +100,4 @@ const SectionRemoveModal = ({ children, onSubmit }) => { ); }; -SectionRemoveModal.propTypes = modalPropTypes; - export default XraySection; diff --git a/frontend/src/metabase/home/homepage/components/XraySection/XraySection.unit.spec.js b/frontend/src/metabase/home/homepage/components/XraySection/XraySection.unit.spec.tsx similarity index 82% rename from frontend/src/metabase/home/homepage/components/XraySection/XraySection.unit.spec.js rename to frontend/src/metabase/home/homepage/components/XraySection/XraySection.unit.spec.tsx index b1819d0625e..00d376f0e41 100644 --- a/frontend/src/metabase/home/homepage/components/XraySection/XraySection.unit.spec.js +++ b/frontend/src/metabase/home/homepage/components/XraySection/XraySection.unit.spec.tsx @@ -1,6 +1,12 @@ import React from "react"; import { render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; +import { + Dashboard, + DatabaseCandidate, + TableCandidate, + User, +} from "../../types"; import XraySection from "./XraySection"; describe("XraySection", () => { @@ -99,13 +105,12 @@ describe("XraySection", () => { it("should not be visible when there are no table candidates", () => { const user = getUser(); - const dashboards = []; const databaseCandidates = [getDatabaseCandidate()]; render( <XraySection user={user} - dashboards={dashboards} + dashboards={[]} databaseCandidates={databaseCandidates} showXrays />, @@ -115,10 +120,28 @@ describe("XraySection", () => { }); }); -const getUser = ({ is_superuser = false } = {}) => ({ is_superuser }); +const getUser = (opts?: Partial<User>): User => ({ + first_name: "John", + is_superuser: false, + personal_collection_id: "personal", + ...opts, +}); -const getDashboard = ({ id = 1 } = {}) => ({ id }); +const getDashboard = (opts?: Partial<Dashboard>): Dashboard => ({ + id: 1, + name: "Our dashboard", + ...opts, +}); -const getTableCandidate = ({ title, url = "/" } = {}) => ({ title, url }); +const getTableCandidate = (opts?: Partial<TableCandidate>): TableCandidate => ({ + title: "Our table", + url: "/auto", + ...opts, +}); -const getDatabaseCandidate = ({ tables = [] } = {}) => ({ tables }); +const getDatabaseCandidate = ( + opts?: Partial<DatabaseCandidate>, +): DatabaseCandidate => ({ + tables: [], + ...opts, +}); diff --git a/frontend/src/metabase/home/homepage/components/XraySection/index.js b/frontend/src/metabase/home/homepage/components/XraySection/index.ts similarity index 100% rename from frontend/src/metabase/home/homepage/components/XraySection/index.js rename to frontend/src/metabase/home/homepage/components/XraySection/index.ts diff --git a/frontend/src/metabase/home/homepage/containers/HomepageApp/HomepageApp.jsx b/frontend/src/metabase/home/homepage/containers/HomepageApp/HomepageApp.tsx similarity index 92% rename from frontend/src/metabase/home/homepage/containers/HomepageApp/HomepageApp.jsx rename to frontend/src/metabase/home/homepage/containers/HomepageApp/HomepageApp.tsx index b53226a0b32..d664f3eaf11 100644 --- a/frontend/src/metabase/home/homepage/containers/HomepageApp/HomepageApp.jsx +++ b/frontend/src/metabase/home/homepage/containers/HomepageApp/HomepageApp.tsx @@ -8,6 +8,7 @@ import { getUser } from "metabase/selectors/user"; import Homepage from "../../components/Homepage"; import { hideData, hidePinMessage, hideXrays } from "../../actions"; import { getShowData, getShowPinMessage, getShowXrays } from "../../selectors"; +import { Database } from "../../types"; const databasesProps = { loadingAndErrorWrapper: false, @@ -38,7 +39,7 @@ const dashboardsProps = { }; const databaseCandidatesProps = { - query: (state, { databases = [] }) => { + query: (state: any, { databases = [] }: { databases: Database[] }) => { const [sampleDatabases, userDatabases] = _.partition( databases, d => d.is_sample, @@ -53,7 +54,7 @@ const databaseCandidatesProps = { loadingAndErrorWrapper: false, }; -const mapStateToProps = state => ({ +const mapStateToProps = (state: any) => ({ user: getUser(state), showData: getShowData(state), showXrays: getShowXrays(state), diff --git a/frontend/src/metabase/home/homepage/containers/HomepageApp/index.js b/frontend/src/metabase/home/homepage/containers/HomepageApp/index.ts similarity index 100% rename from frontend/src/metabase/home/homepage/containers/HomepageApp/index.js rename to frontend/src/metabase/home/homepage/containers/HomepageApp/index.ts diff --git a/frontend/src/metabase/home/homepage/selectors.js b/frontend/src/metabase/home/homepage/selectors.ts similarity index 88% rename from frontend/src/metabase/home/homepage/selectors.js rename to frontend/src/metabase/home/homepage/selectors.ts index 5e1b95b72e4..f59450cbd03 100644 --- a/frontend/src/metabase/home/homepage/selectors.js +++ b/frontend/src/metabase/home/homepage/selectors.ts @@ -1,6 +1,6 @@ import { createSelector } from "reselect"; -export const getSettings = createSelector( +export const getSettings = createSelector<any, any, any>( state => state.settings, settings => settings.values, ); diff --git a/frontend/src/metabase/home/homepage/types.ts b/frontend/src/metabase/home/homepage/types.ts new file mode 100644 index 00000000000..0a850e07571 --- /dev/null +++ b/frontend/src/metabase/home/homepage/types.ts @@ -0,0 +1,29 @@ +export interface User { + first_name: string; + is_superuser: boolean; + personal_collection_id: string; +} + +export interface Database { + id: number; + name: string; + is_sample: boolean; +} + +export interface Collection { + id: string; +} + +export interface Dashboard { + id: number; + name: string; +} + +export interface DatabaseCandidate { + tables: TableCandidate[]; +} + +export interface TableCandidate { + title: string; + url: string; +} diff --git a/jest.unit.conf.json b/jest.unit.conf.json index c01a1dcc283..0a457db8a35 100644 --- a/jest.unit.conf.json +++ b/jest.unit.conf.json @@ -4,8 +4,8 @@ "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/frontend/test/__mocks__/fileMock.js", "^promise-loader\\?global\\!metabase\\/lib\\/ga-metadata$": "<rootDir>/frontend/src/metabase/lib/ga-metadata.js" }, - "testPathIgnorePatterns": ["<rootDir>/frontend/test/.*/.*.tz.unit.spec.{js,ts}"], - "testMatch": ["<rootDir>/**/*.unit.spec.js", "<rootDir>/**/*.unit.spec.{js,ts}"], + "testPathIgnorePatterns": ["<rootDir>/frontend/test/.*/.*.tz.unit.spec.{js,jsx,ts,tsx}"], + "testMatch": ["<rootDir>/**/*.unit.spec.js", "<rootDir>/**/*.unit.spec.{js,jsx,ts,tsx}"], "modulePaths": [ "<rootDir>/frontend/test", "<rootDir>/frontend/src", -- GitLab