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 e689063a9f5107fb1558908caeb5c98ce481c5a1..c7003717d788c2288dfcc995b55ebd38c9107c75 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 0c96efa2ee9acfa9419ea146d5a368acc607abb0..7b9c490799c71ee1edfa0f591eadb21d3d02ab41 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 06e6a71e69fcdab21366f146a6c4f8a67563653d..bb7bf30c915ee459300f1f87459b29bc0b46613c 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 1e878b7e8b197c362293f827670333018e37ff36..f9df43b7b25dc9256de113679576ff9cae6a47d2 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 e5d6cbc3f34656ed6eae0777e7417b997fe33289..09fc3ca7026928111c886819e122fef0048d90aa 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 b9ee831ba9f6e17235dc2c322d1348d582184288..149192b953f389493c7680799f4af811277d8b01 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 d8cdb9e5ce305e3d1a50c0dfa1f7e2da1278497f..5dbe44558a5ed8c4a79a6f874ce92aea8f1b5a44 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 a27bfa21ef6eb3c245852a37fafe901798b6c28d..1ad178b441dcf2b01978b2c00b0f028fb53dff97 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 60990cfc86f91ad7bee5c113a2dec0dca0e2dacc..1e630be4352d80779ac83599d6a701e47dd5a69d 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 7ac38598adf68030bbc05582cfff796bc63b8ae8..b0ee92351f33831ed6fad3e205feb5ddc543b5b1 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 3784f71d82af3a15351725f8bec79cc719252316..c59410ebc778aa191f106ce89c00b4f859ce194f 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 0f2965e580072fb3801dbbad75f7454d2f9b04f7..e84f53a891d786d8b5af466973985096bf64aa8b 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 f6ff201b2b8b0b8bfde6ee71e616bfcc23b767d7..2d8deb9f80e3f493c2cb13a09ae59f3a5b13104f 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 091041e41ee5523a75e9af030452001a930cc526..25c0a492e8c70f63a23e9b9f5240cd89d768cc10 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 05d81942245177dd6b58f75055a412b3956a78a6..e1a4bab24fbf885fff3c906a6c5e8489c28b0038 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 e22fc0cd66eb140fcafb113293b4b17c91808cd0..7069f5b3815312f6596998ef3d3e603ce6e73a22 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 b1819d0625e2781261dd38f508e1dec447d8b3cc..00d376f0e419c437d4a52b3fedff5c011b5ae910 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 b53226a0b32db6f4b42fdd4461c09cd4466b2b5a..d664f3eaf112310893435cce87dcc60212e333e3 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 5e1b95b72e4f289ad7d2fc067fd5845708424a71..f59450cbd0392cffbe6a0c5e5fcf5ba694f11a33 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 0000000000000000000000000000000000000000..0a850e075711533bbd9e09787707537ab5f430a0 --- /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 c01a1dcc2837275e92555fd3a680b875bb15ba82..0a457db8a3584feda52b408f12800f397967b807 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",