From 3051967c9474a1fdb3896c1778305fc91db34da6 Mon Sep 17 00:00:00 2001 From: Alexander Polyankin <alexander.polyankin@metabase.com> Date: Thu, 1 Jun 2023 20:34:09 +0300 Subject: [PATCH] Migrate home to redux toolkit and unify jest tests (#31213) --- frontend/src/metabase-types/api/user.ts | 4 +- .../use-entity-list-query.ts | 10 +- .../CustomHomePageModal.tsx | 14 +- .../components/CustomHomePageModal/index.ts | 1 + .../components/HomeCaption/HomeCaption.tsx | 10 +- .../HomeCaption/HomeCaption.unit.spec.tsx | 11 +- .../home/components/HomeCaption/index.ts | 3 +- .../home/components/HomeCard/HomeCard.tsx | 7 +- .../HomeCard/HomeCard.unit.spec.tsx | 11 +- .../home/components/HomeCard/index.ts | 3 +- .../components/HomeContent/HomeContent.tsx | 77 +++++----- .../HomeContent/HomeContent.unit.spec.tsx | 136 ++++++++---------- .../home/components/HomeContent/index.ts | 3 +- .../components/HomeGreeting/HomeGreeting.tsx | 25 +--- .../HomeGreeting/HomeGreeting.unit.spec.tsx | 2 +- .../home/components/HomeGreeting/index.ts | 3 +- .../components/HomeHelpCard/HomeHelpCard.tsx | 5 +- .../HomeHelpCard/HomeHelpCard.unit.spec.tsx | 11 +- .../home/components/HomeHelpCard/index.ts | 3 +- .../home/components/HomeLayout/HomeLayout.tsx | 34 ++--- .../HomeLayout/HomeLayout.unit.spec.tsx | 8 +- .../home/components/HomeLayout/index.ts | 3 +- .../HomeModelCard/HomeModelCard.tsx | 9 +- .../HomeModelCard/HomeModelCard.unit.spec.tsx | 26 ++-- .../home/components/HomeModelCard/index.ts | 3 +- .../home/components/HomePage/HomePage.tsx | 71 ++++++--- .../HomePage/HomePage.unit.spec.tsx | 89 +++++++----- .../home/components/HomePage/index.ts | 3 +- .../HomePopularSection/HomePopularSection.tsx | 32 ++--- .../HomePopularSection.unit.spec.tsx | 77 +++++----- .../components/HomePopularSection/index.ts | 3 +- .../HomeRecentSection/HomeRecentSection.tsx | 46 ++---- .../HomeRecentSection.unit.spec.tsx | 40 +++--- .../components/HomeRecentSection/index.ts | 3 +- .../components/HomeXrayCard/HomeXrayCard.tsx | 9 +- .../HomeXrayCard/HomeXrayCard.unit.spec.tsx | 26 ++-- .../home/components/HomeXrayCard/index.ts | 3 +- .../HomeXraySection/HomeXraySection.tsx | 53 +++++-- .../HomeXraySection.unit.spec.tsx | 52 ++++--- .../home/components/HomeXraySection/index.ts | 3 +- .../containers/HomeContent/HomeContent.tsx | 22 --- .../home/containers/HomeContent/index.ts | 2 - .../containers/HomePageApp/HomePageApp.tsx | 53 ------- .../HomePageApp/HomePageApp.unit.spec.tsx | 54 ------- .../home/containers/HomePageApp/index.ts | 2 - .../HomeXraySection/HomeXraySection.tsx | 34 ----- .../home/containers/HomeXraySection/index.ts | 2 - frontend/src/metabase/home/selectors.ts | 25 ++++ frontend/src/metabase/routes.jsx | 4 +- frontend/src/metabase/selectors/user.ts | 5 - 50 files changed, 505 insertions(+), 630 deletions(-) rename frontend/src/metabase/home/components/{Modals => }/CustomHomePageModal/CustomHomePageModal.tsx (87%) create mode 100644 frontend/src/metabase/home/components/CustomHomePageModal/index.ts delete mode 100644 frontend/src/metabase/home/containers/HomeContent/HomeContent.tsx delete mode 100644 frontend/src/metabase/home/containers/HomeContent/index.ts delete mode 100644 frontend/src/metabase/home/containers/HomePageApp/HomePageApp.tsx delete mode 100644 frontend/src/metabase/home/containers/HomePageApp/HomePageApp.unit.spec.tsx delete mode 100644 frontend/src/metabase/home/containers/HomePageApp/index.ts delete mode 100644 frontend/src/metabase/home/containers/HomeXraySection/HomeXraySection.tsx delete mode 100644 frontend/src/metabase/home/containers/HomeXraySection/index.ts create mode 100644 frontend/src/metabase/home/selectors.ts diff --git a/frontend/src/metabase-types/api/user.ts b/frontend/src/metabase-types/api/user.ts index 70cefe2ae84..a5e2973c1e6 100644 --- a/frontend/src/metabase-types/api/user.ts +++ b/frontend/src/metabase-types/api/user.ts @@ -1,3 +1,5 @@ +import { DashboardId } from "./dashboard"; + export type UserId = number; export type UserAttribute = string; @@ -27,7 +29,7 @@ export interface User extends BaseUser { has_question_and_dashboard: boolean; personal_collection_id: number; custom_homepage: { - dashboard_id: number; + dashboard_id: DashboardId; } | null; } diff --git a/frontend/src/metabase/common/hooks/use-entity-list-query/use-entity-list-query.ts b/frontend/src/metabase/common/hooks/use-entity-list-query/use-entity-list-query.ts index 6bd034014cf..f2a46d18c5e 100644 --- a/frontend/src/metabase/common/hooks/use-entity-list-query/use-entity-list-query.ts +++ b/frontend/src/metabase/common/hooks/use-entity-list-query/use-entity-list-query.ts @@ -1,4 +1,4 @@ -import { useDeepCompareEffect } from "react-use"; +import { useDeepCompareEffect, usePrevious } from "react-use"; import type { Action } from "@reduxjs/toolkit"; import { useDispatch, useSelector } from "metabase/lib/redux"; import { State } from "metabase-types/store"; @@ -50,17 +50,19 @@ export const useEntityListQuery = <TItem, TQuery = never>( ): UseEntityListQueryResult<TItem> => { const options = { entityQuery }; const data = useSelector(state => getList(state, options)); + const error = useSelector(state => getError(state, options)); const isLoading = useSelector(state => getLoading(state, options)); const isLoaded = useSelector(state => getLoaded(state, options)); - const error = useSelector(state => getError(state, options)); + const isLoadedPreviously = usePrevious(isLoaded); + const isInvalidated = !isLoaded && isLoadedPreviously; const dispatch = useDispatch(); useDeepCompareEffect(() => { - if (enabled && !isLoaded) { + if (enabled || (enabled && isInvalidated)) { const action = dispatch(fetchList(entityQuery, { reload })); Promise.resolve(action).catch(() => undefined); } - }, [dispatch, fetchList, entityQuery, reload, enabled, isLoaded]); + }, [dispatch, fetchList, entityQuery, reload, enabled, isInvalidated]); return { data, isLoading, error }; }; diff --git a/frontend/src/metabase/home/components/Modals/CustomHomePageModal/CustomHomePageModal.tsx b/frontend/src/metabase/home/components/CustomHomePageModal/CustomHomePageModal.tsx similarity index 87% rename from frontend/src/metabase/home/components/Modals/CustomHomePageModal/CustomHomePageModal.tsx rename to frontend/src/metabase/home/components/CustomHomePageModal/CustomHomePageModal.tsx index 4a6c415495b..a9481531de4 100644 --- a/frontend/src/metabase/home/components/Modals/CustomHomePageModal/CustomHomePageModal.tsx +++ b/frontend/src/metabase/home/components/CustomHomePageModal/CustomHomePageModal.tsx @@ -23,13 +23,13 @@ export const CustomHomePageModal = ({ isOpen, onClose, }: CustomHomePageModalProps) => { - const [dashboard, setDashboard] = useState<DashboardId>(); + const [dashboardId, setDashboardId] = useState<DashboardId>(); const dispatch = useDispatch(); const handleSave = async () => { await dispatch( updateSettings({ - [CUSTOM_HOMEPAGE_DASHBOARD_SETTING_KEY]: dashboard, + [CUSTOM_HOMEPAGE_DASHBOARD_SETTING_KEY]: dashboardId, [CUSTOM_HOMEPAGE_SETTING_KEY]: true, }), ); @@ -37,14 +37,14 @@ export const CustomHomePageModal = ({ }; const handleChange = useCallback( - (value: number | null | undefined | string) => { + (value: DashboardId | null | undefined) => { if (value) { - setDashboard(value); + setDashboardId(value); } else { - setDashboard(undefined); + setDashboardId(undefined); } }, - [setDashboard], + [setDashboardId], ); return ( @@ -63,7 +63,7 @@ export const CustomHomePageModal = ({ > <p>{t`Pick one of your dashboards to serve as homepage. Users without dashboard access will be directed to the default homepage. You can update or reset this anytime in Admin Settings > Settings > General`}</p> <DashboardSelector - value={dashboard} + value={dashboardId} onChange={handleChange} collectionFilter={(collection: Collection) => collection.personal_owner_id === null || collection.id === "root" diff --git a/frontend/src/metabase/home/components/CustomHomePageModal/index.ts b/frontend/src/metabase/home/components/CustomHomePageModal/index.ts new file mode 100644 index 00000000000..c79c8c4b31c --- /dev/null +++ b/frontend/src/metabase/home/components/CustomHomePageModal/index.ts @@ -0,0 +1 @@ +export * from "./CustomHomePageModal"; diff --git a/frontend/src/metabase/home/components/HomeCaption/HomeCaption.tsx b/frontend/src/metabase/home/components/HomeCaption/HomeCaption.tsx index f4397725832..169add72026 100644 --- a/frontend/src/metabase/home/components/HomeCaption/HomeCaption.tsx +++ b/frontend/src/metabase/home/components/HomeCaption/HomeCaption.tsx @@ -1,14 +1,14 @@ import { ReactNode } from "react"; import { CaptionRoot } from "./HomeCaption.styled"; -export interface HomeCaptionProps { +interface HomeCaptionProps { primary?: boolean; children?: ReactNode; } -const HomeCaption = ({ primary, children }: HomeCaptionProps): JSX.Element => { +export const HomeCaption = ({ + primary, + children, +}: HomeCaptionProps): JSX.Element => { return <CaptionRoot primary={primary}>{children}</CaptionRoot>; }; - -// eslint-disable-next-line import/no-default-export -- deprecated usage -export default HomeCaption; diff --git a/frontend/src/metabase/home/components/HomeCaption/HomeCaption.unit.spec.tsx b/frontend/src/metabase/home/components/HomeCaption/HomeCaption.unit.spec.tsx index 5641bb406f2..f4ca106f699 100644 --- a/frontend/src/metabase/home/components/HomeCaption/HomeCaption.unit.spec.tsx +++ b/frontend/src/metabase/home/components/HomeCaption/HomeCaption.unit.spec.tsx @@ -1,10 +1,13 @@ -import { render, screen } from "@testing-library/react"; -import HomeCaption from "./HomeCaption"; +import { render, screen } from "__support__/ui"; +import { HomeCaption } from "./HomeCaption"; + +const setup = () => { + render(<HomeCaption>Title</HomeCaption>); +}; describe("HomeCaption", () => { it("should render correctly", () => { - render(<HomeCaption>Title</HomeCaption>); - + setup(); expect(screen.getByText("Title")).toBeInTheDocument(); }); }); diff --git a/frontend/src/metabase/home/components/HomeCaption/index.ts b/frontend/src/metabase/home/components/HomeCaption/index.ts index eab17793d4f..efc7c457b24 100644 --- a/frontend/src/metabase/home/components/HomeCaption/index.ts +++ b/frontend/src/metabase/home/components/HomeCaption/index.ts @@ -1,2 +1 @@ -// eslint-disable-next-line import/no-default-export -- deprecated usage -export { default } from "./HomeCaption"; +export * from "./HomeCaption"; diff --git a/frontend/src/metabase/home/components/HomeCard/HomeCard.tsx b/frontend/src/metabase/home/components/HomeCard/HomeCard.tsx index b4e7f17f087..781828d5cf0 100644 --- a/frontend/src/metabase/home/components/HomeCard/HomeCard.tsx +++ b/frontend/src/metabase/home/components/HomeCard/HomeCard.tsx @@ -1,14 +1,14 @@ import { ReactNode } from "react"; import { CardRoot } from "./HomeCard.styled"; -export interface HomeCardProps { +interface HomeCardProps { className?: string; url?: string; external?: boolean; children?: ReactNode; } -const HomeCard = ({ +export const HomeCard = ({ className, url = "", children, @@ -19,6 +19,3 @@ const HomeCard = ({ </CardRoot> ); }; - -// eslint-disable-next-line import/no-default-export -- deprecated usage -export default HomeCard; diff --git a/frontend/src/metabase/home/components/HomeCard/HomeCard.unit.spec.tsx b/frontend/src/metabase/home/components/HomeCard/HomeCard.unit.spec.tsx index e12df0cee83..753b8a38739 100644 --- a/frontend/src/metabase/home/components/HomeCard/HomeCard.unit.spec.tsx +++ b/frontend/src/metabase/home/components/HomeCard/HomeCard.unit.spec.tsx @@ -1,10 +1,13 @@ -import { render, screen } from "@testing-library/react"; -import HomeCard from "./HomeCard"; +import { render, screen } from "__support__/ui"; +import { HomeCard } from "./HomeCard"; + +const setup = () => { + render(<HomeCard>A look at table</HomeCard>); +}; describe("HomeCard", () => { it("should render correctly", () => { - render(<HomeCard>A look at table</HomeCard>); - + setup(); expect(screen.getByText("A look at table")).toBeInTheDocument(); }); }); diff --git a/frontend/src/metabase/home/components/HomeCard/index.ts b/frontend/src/metabase/home/components/HomeCard/index.ts index 9a209044c5a..46b2bea9a12 100644 --- a/frontend/src/metabase/home/components/HomeCard/index.ts +++ b/frontend/src/metabase/home/components/HomeCard/index.ts @@ -1,2 +1 @@ -// eslint-disable-next-line import/no-default-export -- deprecated usage -export { default } from "./HomeCard"; +export * from "./HomeCard"; diff --git a/frontend/src/metabase/home/components/HomeContent/HomeContent.tsx b/frontend/src/metabase/home/components/HomeContent/HomeContent.tsx index 139265e72cc..6ba8790e239 100644 --- a/frontend/src/metabase/home/components/HomeContent/HomeContent.tsx +++ b/frontend/src/metabase/home/components/HomeContent/HomeContent.tsx @@ -1,46 +1,52 @@ +import { useSelector } from "metabase/lib/redux"; import { isSyncCompleted } from "metabase/lib/syncing"; +import { getUser } from "metabase/selectors/user"; import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper"; +import { + useDatabaseListQuery, + usePopularItemListQuery, + useRecentItemListQuery, +} from "metabase/common/hooks"; import { PopularItem, RecentItem, User } from "metabase-types/api"; import Database from "metabase-lib/metadata/Database"; -import HomePopularSection from "../HomePopularSection"; -import HomeRecentSection from "../HomeRecentSection"; -import HomeXraySection from "../../containers/HomeXraySection"; +import { HomePopularSection } from "../HomePopularSection"; +import { HomeRecentSection } from "../HomeRecentSection"; +import { HomeXraySection } from "../HomeXraySection"; +import { getIsXrayEnabled } from "../../selectors"; import { isWithinWeeks } from "../../utils"; -export interface HomeContentProps { - user: User; - databases?: Database[]; - recentItems?: RecentItem[]; - popularItems?: PopularItem[]; - isXrayEnabled: boolean; -} +export const HomeContent = (): JSX.Element | null => { + const user = useSelector(getUser); + const isXrayEnabled = useSelector(getIsXrayEnabled); + const { data: databases } = useDatabaseListQuery(); + const { data: recentItems } = useRecentItemListQuery({ reload: true }); + const { data: popularItems } = usePopularItemListQuery({ reload: true }); -const HomeContent = (props: HomeContentProps): JSX.Element | null => { - if (isLoading(props)) { + if (!user || isLoading(user, databases, recentItems, popularItems)) { return <LoadingAndErrorWrapper loading />; } - if (isPopularSection(props)) { + if (isPopularSection(user, recentItems, popularItems)) { return <HomePopularSection />; } - if (isRecentSection(props)) { + if (isRecentSection(user, recentItems)) { return <HomeRecentSection />; } - if (isXraySection(props)) { + if (isXraySection(databases, isXrayEnabled)) { return <HomeXraySection />; } return null; }; -const isLoading = ({ - user, - databases, - recentItems, - popularItems, -}: HomeContentProps): boolean => { +const isLoading = ( + user: User, + databases: Database[] | undefined, + recentItems: RecentItem[] | undefined, + popularItems: PopularItem[] | undefined, +): boolean => { if (!user.has_question_and_dashboard) { return databases == null; } else if (user.is_installer || !isWithinWeeks(user.first_login, 1)) { @@ -50,11 +56,11 @@ const isLoading = ({ } }; -const isPopularSection = ({ - user, - recentItems = [], - popularItems = [], -}: HomeContentProps): boolean => { +const isPopularSection = ( + user: User, + recentItems: RecentItem[] = [], + popularItems: PopularItem[] = [], +): boolean => { return ( !user.is_installer && user.has_question_and_dashboard && @@ -63,19 +69,16 @@ const isPopularSection = ({ ); }; -const isRecentSection = ({ - user, - recentItems = [], -}: HomeContentProps): boolean => { +const isRecentSection = ( + user: User, + recentItems: RecentItem[] = [], +): boolean => { return user.has_question_and_dashboard && recentItems.length > 0; }; -const isXraySection = ({ - databases = [], - isXrayEnabled, -}: HomeContentProps): boolean => { +const isXraySection = ( + databases: Database[] = [], + isXrayEnabled: boolean, +): boolean => { return databases.some(isSyncCompleted) && isXrayEnabled; }; - -// eslint-disable-next-line import/no-default-export -- deprecated usage -export default HomeContent; diff --git a/frontend/src/metabase/home/components/HomeContent/HomeContent.unit.spec.tsx b/frontend/src/metabase/home/components/HomeContent/HomeContent.unit.spec.tsx index 981d046bc0c..6af29871397 100644 --- a/frontend/src/metabase/home/components/HomeContent/HomeContent.unit.spec.tsx +++ b/frontend/src/metabase/home/components/HomeContent/HomeContent.unit.spec.tsx @@ -1,5 +1,3 @@ -import { checkNotNull } from "metabase/core/utils/types"; -import { getMetadata } from "metabase/selectors/metadata"; import { Database, PopularItem, RecentItem, User } from "metabase-types/api"; import { createMockDatabase, @@ -7,19 +5,22 @@ import { createMockRecentItem, createMockUser, } from "metabase-types/api/mocks"; -import { createMockState } from "metabase-types/store/mocks"; -import { createMockEntitiesState } from "__support__/store"; -import { renderWithProviders, screen } from "__support__/ui"; -import HomeContent from "./HomeContent"; - -const PopularSectionMock = () => <div>PopularSection</div>; -jest.mock("../HomePopularSection", () => PopularSectionMock); - -const RecentSectionMock = () => <div>RecentSection</div>; -jest.mock("../HomeRecentSection", () => RecentSectionMock); - -const XraySectionMock = () => <div>XraySection</div>; -jest.mock("../../containers/HomeXraySection", () => XraySectionMock); +import { + createMockSettingsState, + createMockState, +} from "metabase-types/store/mocks"; +import { + renderWithProviders, + screen, + waitForElementToBeRemoved, +} from "__support__/ui"; +import { + setupDatabaseCandidatesEndpoint, + setupDatabasesEndpoints, + setupPopularItemsEndpoints, + setupRecentViewsEndpoints, +} from "__support__/server-mocks"; +import { HomeContent } from "./HomeContent"; interface SetupOpts { user: User; @@ -29,35 +30,33 @@ interface SetupOpts { isXrayEnabled?: boolean; } -const setup = ({ +const setup = async ({ user, - databases, - recentItems, - popularItems, + databases = [], + recentItems = [], + popularItems = [], isXrayEnabled = true, }: SetupOpts) => { const state = createMockState({ - entities: createMockEntitiesState({ databases }), + currentUser: user, + settings: createMockSettingsState({ + "enable-xrays": isXrayEnabled, + }), }); - const metadata = getMetadata(state); - - renderWithProviders( - <HomeContent - user={user} - databases={databases?.map(({ id }) => - checkNotNull(metadata.database(id)), - )} - recentItems={recentItems} - popularItems={popularItems} - isXrayEnabled={isXrayEnabled} - />, - { storeInitialState: state }, - ); + + setupDatabasesEndpoints(databases); + setupRecentViewsEndpoints(recentItems); + setupPopularItemsEndpoints(popularItems); + databases.forEach(({ id }) => setupDatabaseCandidatesEndpoint(id, [])); + + renderWithProviders(<HomeContent />, { storeInitialState: state }); + + await waitForElementToBeRemoved(() => screen.queryByText(/Loading/i)); }; describe("HomeContent", () => { beforeEach(() => { - jest.useFakeTimers(); + jest.useFakeTimers({ advanceTimers: true }); jest.setSystemTime(new Date(2020, 0, 10)); }); @@ -65,8 +64,8 @@ describe("HomeContent", () => { jest.useRealTimers(); }); - it("should render popular items for a new user", () => { - setup({ + it("should render popular items for a new user", async () => { + await setup({ user: createMockUser({ is_installer: false, has_question_and_dashboard: true, @@ -77,26 +76,29 @@ describe("HomeContent", () => { popularItems: [createMockPopularItem()], }); - expect(screen.getByText("PopularSection")).toBeInTheDocument(); + expect( + screen.getByText("Here are some popular tables"), + ).toBeInTheDocument(); }); - it("should render popular items for a user without recent items", () => { - setup({ + it("should render popular items for a user without recent items", async () => { + await setup({ user: createMockUser({ is_installer: false, has_question_and_dashboard: true, first_login: "2020-01-05T00:00:00Z", }), databases: [createMockDatabase()], - recentItems: [], popularItems: [createMockPopularItem()], }); - expect(screen.getByText("PopularSection")).toBeInTheDocument(); + expect( + screen.getByText("Here are some popular tables"), + ).toBeInTheDocument(); }); - it("should render recent items for an existing user", () => { - setup({ + it("should render recent items for an existing user", async () => { + await setup({ user: createMockUser({ is_installer: false, has_question_and_dashboard: true, @@ -106,25 +108,24 @@ describe("HomeContent", () => { recentItems: [createMockRecentItem()], }); - expect(screen.getByText("RecentSection")).toBeInTheDocument(); + expect(screen.getByText("Pick up where you left off")).toBeInTheDocument(); }); - it("should render x-rays for an installer after the setup", () => { - setup({ + it("should render x-rays for an installer after the setup", async () => { + await setup({ user: createMockUser({ is_installer: true, has_question_and_dashboard: false, first_login: "2020-01-10T00:00:00Z", }), databases: [createMockDatabase()], - recentItems: [], }); - expect(screen.getByText("XraySection")).toBeInTheDocument(); + expect(screen.getByText(/Here are some explorations/)).toBeInTheDocument(); }); - it("should render x-rays for the installer when there is no question and dashboard", () => { - setup({ + it("should render x-rays for the installer when there is no question and dashboard", async () => { + await setup({ user: createMockUser({ is_installer: true, has_question_and_dashboard: false, @@ -134,11 +135,11 @@ describe("HomeContent", () => { recentItems: [createMockRecentItem()], }); - expect(screen.getByText("XraySection")).toBeInTheDocument(); + expect(screen.getByText(/Here are some explorations/)).toBeInTheDocument(); }); - it("should not render x-rays for the installer when there is no question and dashboard if the x-rays feature is disabled", () => { - setup({ + it("should not render x-rays for the installer when there is no question and dashboard if the x-rays feature is disabled", async () => { + await setup({ user: createMockUser({ is_installer: true, has_question_and_dashboard: false, @@ -149,33 +150,22 @@ describe("HomeContent", () => { isXrayEnabled: false, }); - expect(screen.queryByText("XraySection")).not.toBeInTheDocument(); - }); - - it("should render nothing if there are no databases", () => { - setup({ - user: createMockUser({ - is_installer: true, - has_question_and_dashboard: false, - first_login: "2020-01-10T00:00:00Z", - }), - databases: [], - recentItems: [], - }); - - expect(screen.queryByText("XraySection")).not.toBeInTheDocument(); + expect( + screen.queryByText(/Here are some explorations/), + ).not.toBeInTheDocument(); }); - it("should render loading state if there is not enough data to choose a section", () => { - setup({ + it("should render nothing if there are no databases", async () => { + await setup({ user: createMockUser({ is_installer: true, has_question_and_dashboard: false, first_login: "2020-01-10T00:00:00Z", }), - databases: undefined, }); - expect(screen.getByText("Loading...")).toBeInTheDocument(); + expect( + screen.queryByText(/Here are some explorations/), + ).not.toBeInTheDocument(); }); }); diff --git a/frontend/src/metabase/home/components/HomeContent/index.ts b/frontend/src/metabase/home/components/HomeContent/index.ts index acc76cd9ddd..37350dbc698 100644 --- a/frontend/src/metabase/home/components/HomeContent/index.ts +++ b/frontend/src/metabase/home/components/HomeContent/index.ts @@ -1,2 +1 @@ -// eslint-disable-next-line import/no-default-export -- deprecated usage -export { default } from "./HomeContent"; +export * from "./HomeContent"; diff --git a/frontend/src/metabase/home/components/HomeGreeting/HomeGreeting.tsx b/frontend/src/metabase/home/components/HomeGreeting/HomeGreeting.tsx index c381008484c..a341905228b 100644 --- a/frontend/src/metabase/home/components/HomeGreeting/HomeGreeting.tsx +++ b/frontend/src/metabase/home/components/HomeGreeting/HomeGreeting.tsx @@ -1,31 +1,19 @@ import { useMemo } from "react"; -import { connect } from "react-redux"; import { t } from "ttag"; import _ from "underscore"; +import { useSelector } from "metabase/lib/redux"; import Tooltip from "metabase/core/components/Tooltip"; import { getUser } from "metabase/selectors/user"; -import { getSetting } from "metabase/selectors/settings"; -import { User } from "metabase-types/api"; -import { State } from "metabase-types/store"; +import { getHasMetabotLogo } from "../../selectors"; import { GreetingLogo, GreetingMessage, GreetingRoot, } from "./HomeGreeting.styled"; -interface StateProps { - user: User | null; - showLogo?: boolean; -} - -type HomeGreetingProps = StateProps; - -const mapStateToProps = (state: State): StateProps => ({ - user: getUser(state), - showLogo: getSetting(state, "show-metabot"), -}); - -const HomeGreeting = ({ user, showLogo }: HomeGreetingProps): JSX.Element => { +export const HomeGreeting = (): JSX.Element => { + const user = useSelector(getUser); + const showLogo = useSelector(getHasMetabotLogo); const name = user?.first_name; const message = useMemo(() => getMessage(name), [name]); @@ -56,6 +44,3 @@ const getMessage = (name: string | null | undefined): string => { return _.sample(options) ?? ""; }; - -// eslint-disable-next-line import/no-default-export -- deprecated usage -export default connect(mapStateToProps)(HomeGreeting); diff --git a/frontend/src/metabase/home/components/HomeGreeting/HomeGreeting.unit.spec.tsx b/frontend/src/metabase/home/components/HomeGreeting/HomeGreeting.unit.spec.tsx index 1295fd31a9e..0aae6c7ec7c 100644 --- a/frontend/src/metabase/home/components/HomeGreeting/HomeGreeting.unit.spec.tsx +++ b/frontend/src/metabase/home/components/HomeGreeting/HomeGreeting.unit.spec.tsx @@ -5,7 +5,7 @@ import { createMockSettingsState, createMockState, } from "metabase-types/store/mocks"; -import HomeGreeting from "./HomeGreeting"; +import { HomeGreeting } from "./HomeGreeting"; interface SetupOpts { currentUser?: User; diff --git a/frontend/src/metabase/home/components/HomeGreeting/index.ts b/frontend/src/metabase/home/components/HomeGreeting/index.ts index f58e73bb891..f9e9c333a00 100644 --- a/frontend/src/metabase/home/components/HomeGreeting/index.ts +++ b/frontend/src/metabase/home/components/HomeGreeting/index.ts @@ -1,2 +1 @@ -// eslint-disable-next-line import/no-default-export -- deprecated usage -export { default } from "./HomeGreeting"; +export * from "./HomeGreeting"; diff --git a/frontend/src/metabase/home/components/HomeHelpCard/HomeHelpCard.tsx b/frontend/src/metabase/home/components/HomeHelpCard/HomeHelpCard.tsx index 673730ed6d5..eda1ddb0fdb 100644 --- a/frontend/src/metabase/home/components/HomeHelpCard/HomeHelpCard.tsx +++ b/frontend/src/metabase/home/components/HomeHelpCard/HomeHelpCard.tsx @@ -2,7 +2,7 @@ import { t } from "ttag"; import MetabaseSettings from "metabase/lib/settings"; import { CardIcon, CardRoot, CardTitle } from "./HomeHelpCard.styled"; -const HomeHelpCard = (): JSX.Element => { +export const HomeHelpCard = (): JSX.Element => { return ( <CardRoot href={MetabaseSettings.learnUrl()}> <CardIcon name="reference" /> @@ -10,6 +10,3 @@ const HomeHelpCard = (): JSX.Element => { </CardRoot> ); }; - -// eslint-disable-next-line import/no-default-export -- deprecated usage -export default HomeHelpCard; diff --git a/frontend/src/metabase/home/components/HomeHelpCard/HomeHelpCard.unit.spec.tsx b/frontend/src/metabase/home/components/HomeHelpCard/HomeHelpCard.unit.spec.tsx index 9e6ca6ca150..e5a7aa71793 100644 --- a/frontend/src/metabase/home/components/HomeHelpCard/HomeHelpCard.unit.spec.tsx +++ b/frontend/src/metabase/home/components/HomeHelpCard/HomeHelpCard.unit.spec.tsx @@ -1,10 +1,13 @@ -import { render, screen } from "@testing-library/react"; -import HomeHelpCard from "./HomeHelpCard"; +import { render, screen } from "__support__/ui"; +import { HomeHelpCard } from "./HomeHelpCard"; + +const setup = () => { + render(<HomeHelpCard />); +}; describe("HomeHelpCard", () => { it("should render correctly", () => { - render(<HomeHelpCard />); - + setup(); expect(screen.getByText("Metabase tips")).toBeInTheDocument(); }); }); diff --git a/frontend/src/metabase/home/components/HomeHelpCard/index.ts b/frontend/src/metabase/home/components/HomeHelpCard/index.ts index d2e4fcca41f..4ba1e46a4c4 100644 --- a/frontend/src/metabase/home/components/HomeHelpCard/index.ts +++ b/frontend/src/metabase/home/components/HomeHelpCard/index.ts @@ -1,2 +1 @@ -// eslint-disable-next-line import/no-default-export -- deprecated usage -export { default } from "./HomeHelpCard"; +export * from "./HomeHelpCard"; diff --git a/frontend/src/metabase/home/components/HomeLayout/HomeLayout.tsx b/frontend/src/metabase/home/components/HomeLayout/HomeLayout.tsx index 31314e8a75e..be618a6530e 100644 --- a/frontend/src/metabase/home/components/HomeLayout/HomeLayout.tsx +++ b/frontend/src/metabase/home/components/HomeLayout/HomeLayout.tsx @@ -1,12 +1,11 @@ import { ReactNode, useState } from "react"; -import { connect } from "react-redux"; -import { getSetting } from "metabase/selectors/settings"; +import { useSelector } from "metabase/lib/redux"; import { getUserIsAdmin } from "metabase/selectors/user"; import MetabotWidget from "metabase/metabot/components/MetabotWidget"; -import { State } from "metabase-types/store"; import Tooltip from "metabase/core/components/Tooltip/Tooltip"; -import HomeGreeting from "../HomeGreeting"; -import { CustomHomePageModal } from "../Modals/CustomHomePageModal/CustomHomePageModal"; +import { HomeGreeting } from "../HomeGreeting"; +import { getHasIllustration } from "../../selectors"; +import { CustomHomePageModal } from "../CustomHomePageModal"; import { LayoutBody, LayoutEditButton, @@ -14,30 +13,18 @@ import { LayoutRoot, } from "./HomeLayout.styled"; -interface OwnProps { - hasMetabot?: boolean; +interface HomeLayoutProps { + hasMetabot: boolean; children?: ReactNode; } -interface StateProps { - hasIllustration?: boolean; - isAdmin?: boolean; -} - -type HomeLayoutProps = OwnProps & StateProps; - -const mapStateToProps = (state: State) => ({ - hasIllustration: getSetting(state, "show-lighthouse-illustration"), - isAdmin: getUserIsAdmin(state), -}); - -const HomeLayout = ({ +export const HomeLayout = ({ hasMetabot, - hasIllustration, children, - isAdmin, }: HomeLayoutProps): JSX.Element => { const [showModal, setShowModal] = useState(false); + const isAdmin = useSelector(getUserIsAdmin); + const hasIllustration = useSelector(getHasIllustration); return ( <LayoutRoot> @@ -62,6 +49,3 @@ const HomeLayout = ({ </LayoutRoot> ); }; - -// eslint-disable-next-line import/no-default-export -- deprecated usage -export default connect(mapStateToProps)(HomeLayout); diff --git a/frontend/src/metabase/home/components/HomeLayout/HomeLayout.unit.spec.tsx b/frontend/src/metabase/home/components/HomeLayout/HomeLayout.unit.spec.tsx index a1801a96715..bf69ab287f9 100644 --- a/frontend/src/metabase/home/components/HomeLayout/HomeLayout.unit.spec.tsx +++ b/frontend/src/metabase/home/components/HomeLayout/HomeLayout.unit.spec.tsx @@ -1,8 +1,8 @@ -import { renderWithProviders, screen } from "__support__/ui"; import { User } from "metabase-types/api"; +import { renderWithProviders, screen } from "__support__/ui"; import { createMockUser } from "metabase-types/api/mocks"; import { createMockState } from "metabase-types/store/mocks"; -import HomeLayout from "./HomeLayout"; +import { HomeLayout } from "./HomeLayout"; interface SetupOpts { currentUser?: User; @@ -13,7 +13,9 @@ const setup = ({ currentUser }: SetupOpts = {}) => { currentUser, }); - renderWithProviders(<HomeLayout />, { storeInitialState: state }); + renderWithProviders(<HomeLayout hasMetabot={false} />, { + storeInitialState: state, + }); }; describe("HomeLayout", () => { diff --git a/frontend/src/metabase/home/components/HomeLayout/index.ts b/frontend/src/metabase/home/components/HomeLayout/index.ts index 5aa9e4a4bdd..f498bac3c0c 100644 --- a/frontend/src/metabase/home/components/HomeLayout/index.ts +++ b/frontend/src/metabase/home/components/HomeLayout/index.ts @@ -1,2 +1 @@ -// eslint-disable-next-line import/no-default-export -- deprecated usage -export { default } from "./HomeLayout"; +export * from "./HomeLayout"; diff --git a/frontend/src/metabase/home/components/HomeModelCard/HomeModelCard.tsx b/frontend/src/metabase/home/components/HomeModelCard/HomeModelCard.tsx index a002aa1c0aa..890d062d4a4 100644 --- a/frontend/src/metabase/home/components/HomeModelCard/HomeModelCard.tsx +++ b/frontend/src/metabase/home/components/HomeModelCard/HomeModelCard.tsx @@ -1,7 +1,7 @@ -import HomeCard from "../HomeCard"; +import { HomeCard } from "../HomeCard"; import { CardIcon, CardTitle } from "./HomeModelCard.styled"; -export interface HomeModelCardProps { +interface HomeModelCardProps { title: string; icon: HomeModelIconProps; url: string; @@ -11,7 +11,7 @@ export interface HomeModelIconProps { name: string; } -const HomeModelCard = ({ +export const HomeModelCard = ({ title, icon, url, @@ -23,6 +23,3 @@ const HomeModelCard = ({ </HomeCard> ); }; - -// eslint-disable-next-line import/no-default-export -- deprecated usage -export default HomeModelCard; diff --git a/frontend/src/metabase/home/components/HomeModelCard/HomeModelCard.unit.spec.tsx b/frontend/src/metabase/home/components/HomeModelCard/HomeModelCard.unit.spec.tsx index 90b29220f34..ef90340b8c8 100644 --- a/frontend/src/metabase/home/components/HomeModelCard/HomeModelCard.unit.spec.tsx +++ b/frontend/src/metabase/home/components/HomeModelCard/HomeModelCard.unit.spec.tsx @@ -1,23 +1,25 @@ -import { render, screen } from "@testing-library/react"; -import HomeModelCard, { HomeModelCardProps } from "./HomeModelCard"; +import { render, screen } from "__support__/ui"; +import { HomeModelCard, HomeModelIconProps } from "./HomeModelCard"; + +interface SetupOpts { + title: string; + icon: HomeModelIconProps; + url: string; +} + +const setup = ({ title, icon, url }: SetupOpts) => { + render(<HomeModelCard title={title} icon={icon} url={url} />); +}; describe("HomeModelCard", () => { it("should render correctly", () => { - const props = getProps({ + setup({ title: "Orders", icon: { name: "table" }, + url: "/question/1", }); - render(<HomeModelCard {...props} />); - expect(screen.getByText("Orders")).toBeInTheDocument(); expect(screen.getByLabelText("table icon")).toBeInTheDocument(); }); }); - -const getProps = (opts?: Partial<HomeModelCardProps>): HomeModelCardProps => ({ - title: "Orders", - icon: { name: "card" }, - url: "/question/1", - ...opts, -}); diff --git a/frontend/src/metabase/home/components/HomeModelCard/index.ts b/frontend/src/metabase/home/components/HomeModelCard/index.ts index ba1a983149e..8b703e2ec4f 100644 --- a/frontend/src/metabase/home/components/HomeModelCard/index.ts +++ b/frontend/src/metabase/home/components/HomeModelCard/index.ts @@ -1,2 +1 @@ -// eslint-disable-next-line import/no-default-export -- deprecated usage -export { default } from "./HomeModelCard"; +export * from "./HomeModelCard"; diff --git a/frontend/src/metabase/home/components/HomePage/HomePage.tsx b/frontend/src/metabase/home/components/HomePage/HomePage.tsx index 557c07cd449..1de14f067a8 100644 --- a/frontend/src/metabase/home/components/HomePage/HomePage.tsx +++ b/frontend/src/metabase/home/components/HomePage/HomePage.tsx @@ -1,31 +1,55 @@ import { useEffect } from "react"; import { replace } from "react-router-redux"; import { isSmallScreen } from "metabase/lib/dom"; -import { useDispatch } from "metabase/lib/redux"; +import { openNavbar } from "metabase/redux/app"; +import { useDispatch, useSelector } from "metabase/lib/redux"; +import { + useDatabaseListQuery, + useSearchListQuery, +} from "metabase/common/hooks"; +import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper"; +import { canUseMetabotOnDatabase } from "metabase/metabot/utils"; +import { CollectionItem } from "metabase-types/api"; +import Database from "metabase-lib/metadata/Database"; +import { + getCustomHomePageDashboardId, + getIsMetabotEnabled, +} from "../../selectors"; +import { HomeLayout } from "../HomeLayout"; +import { HomeContent } from "../HomeContent"; -import HomeLayout from "../HomeLayout"; -import HomeContent from "../../containers/HomeContent"; +const SEARCH_QUERY = { models: "dataset", limit: 1 } as const; -export interface HomePageProps { - hasMetabot: boolean; - homepageDashboard?: number; - onOpenNavbar: () => void; -} - -const HomePage = ({ - hasMetabot, - onOpenNavbar, - homepageDashboard, -}: HomePageProps): JSX.Element => { +export const HomePage = (): JSX.Element => { + const databaseListState = useDatabaseListQuery(); + const modelListState = useSearchListQuery({ + query: SEARCH_QUERY, + }); + const isLoading = databaseListState.isLoading || modelListState.isLoading; + const error = databaseListState.error ?? modelListState.error; + const dashboardId = useSelector(getCustomHomePageDashboardId); + const isMetabotEnabled = useSelector(getIsMetabotEnabled); + const hasMetabot = getHasMetabot( + databaseListState.data, + modelListState.data, + isMetabotEnabled, + ); const dispatch = useDispatch(); + useEffect(() => { if (!isSmallScreen()) { - onOpenNavbar(); + dispatch(openNavbar()); } - }, [onOpenNavbar]); + }, [dispatch]); - if (homepageDashboard) { - dispatch(replace(`/dashboard/${homepageDashboard}`)); + useEffect(() => { + if (dashboardId) { + dispatch(replace(`/dashboard/${dashboardId}`)); + } + }, [dashboardId, dispatch]); + + if (isLoading || error) { + return <LoadingAndErrorWrapper loading={isLoading} error={error} />; } return ( @@ -35,5 +59,12 @@ const HomePage = ({ ); }; -// eslint-disable-next-line import/no-default-export -- deprecated usage -export default HomePage; +const getHasMetabot = ( + databases: Database[] = [], + models: CollectionItem[] = [], + isMetabotEnabled = false, +) => { + const hasModels = models.length > 0; + const hasSupportedDatabases = databases.some(canUseMetabotOnDatabase); + return hasModels && hasSupportedDatabases && isMetabotEnabled; +}; diff --git a/frontend/src/metabase/home/components/HomePage/HomePage.unit.spec.tsx b/frontend/src/metabase/home/components/HomePage/HomePage.unit.spec.tsx index 702d5d21d40..d26fd97a4d8 100644 --- a/frontend/src/metabase/home/components/HomePage/HomePage.unit.spec.tsx +++ b/frontend/src/metabase/home/components/HomePage/HomePage.unit.spec.tsx @@ -1,45 +1,60 @@ -import { renderWithProviders } from "__support__/ui"; -import * as dom from "metabase/lib/dom"; -import HomePage from "./HomePage"; - -jest.mock("metabase/lib/dom"); - -const LayoutMock = () => <div />; -jest.mock("../HomeLayout", () => LayoutMock); - -const ContentMock = () => <div />; -jest.mock("../../containers/HomeContent", () => ContentMock); - -describe("HomePage", () => { - let isSmallScreenSpy: jest.SpyInstance; - - beforeEach(() => { - isSmallScreenSpy = jest.spyOn(dom, "isSmallScreen"); +import { Route } from "react-router"; +import { DashboardId } from "metabase-types/api"; +import { createMockState } from "metabase-types/store/mocks"; +import { createMockUser } from "metabase-types/api/mocks"; +import { renderWithProviders, screen, waitFor } from "__support__/ui"; +import { + setupDatabasesEndpoints, + setupPopularItemsEndpoints, + setupRecentViewsEndpoints, + setupSearchEndpoints, +} from "__support__/server-mocks"; +import { HomePage } from "./HomePage"; + +const TEST_DASHBOARD_NAME = "Dashboard"; + +const TestDashboard = () => <div>{TEST_DASHBOARD_NAME}</div>; + +interface SetupOpts { + dashboardId?: DashboardId; +} + +const setup = async ({ dashboardId }: SetupOpts = {}) => { + const state = createMockState({ + currentUser: createMockUser({ + custom_homepage: dashboardId ? { dashboard_id: dashboardId } : null, + }), }); - afterEach(() => { - isSmallScreenSpy.mockRestore(); + setupDatabasesEndpoints([]); + setupSearchEndpoints([]); + setupRecentViewsEndpoints([]); + setupPopularItemsEndpoints([]); + + renderWithProviders( + <> + <Route path="/" component={HomePage} /> + <Route path="/dashboard/:slug" component={TestDashboard} /> + </>, + { + withRouter: true, + storeInitialState: state, + }, + ); + + await waitFor(() => { + expect(screen.queryByText(/Loading/i)).not.toBeInTheDocument(); }); +}; - it("should open the navbar on a regular screen", () => { - const onOpenNavbar = jest.fn(); - isSmallScreenSpy.mockReturnValue(false); - - renderWithProviders( - <HomePage hasMetabot={false} onOpenNavbar={onOpenNavbar} />, - ); - - expect(onOpenNavbar).toHaveBeenCalled(); +describe("HomePage", () => { + it("should redirect you to a dashboard when one has been defined to be used as a homepage", async () => { + await setup({ dashboardId: 1 }); + expect(screen.getByText(TEST_DASHBOARD_NAME)).toBeInTheDocument(); }); - it("should not open the navbar on a small screen", () => { - const onOpenNavbar = jest.fn(); - isSmallScreenSpy.mockReturnValue(true); - - renderWithProviders( - <HomePage hasMetabot={false} onOpenNavbar={onOpenNavbar} />, - ); - - expect(onOpenNavbar).not.toHaveBeenCalled(); + it("should render the homepage when a custom dashboard is not set", async () => { + await setup(); + expect(screen.queryByText(TEST_DASHBOARD_NAME)).not.toBeInTheDocument(); }); }); diff --git a/frontend/src/metabase/home/components/HomePage/index.ts b/frontend/src/metabase/home/components/HomePage/index.ts index b9bd5af1f99..f5d126e7b2d 100644 --- a/frontend/src/metabase/home/components/HomePage/index.ts +++ b/frontend/src/metabase/home/components/HomePage/index.ts @@ -1,2 +1 @@ -// eslint-disable-next-line import/no-default-export -- deprecated usage -export { default } from "./HomePage"; +export * from "./HomePage"; diff --git a/frontend/src/metabase/home/components/HomePopularSection/HomePopularSection.tsx b/frontend/src/metabase/home/components/HomePopularSection/HomePopularSection.tsx index bbb766cc687..dc1be8fbf22 100644 --- a/frontend/src/metabase/home/components/HomePopularSection/HomePopularSection.tsx +++ b/frontend/src/metabase/home/components/HomePopularSection/HomePopularSection.tsx @@ -1,23 +1,26 @@ import { t } from "ttag"; import _ from "underscore"; import * as Urls from "metabase/lib/urls"; -import PopularItems, { - getIcon, - getName, -} from "metabase/entities/popular-items"; +import { getIcon, getName } from "metabase/entities/popular-items"; +import { usePopularItemListQuery } from "metabase/common/hooks"; +import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper"; import { PopularItem } from "metabase-types/api"; -import HomeCaption from "../HomeCaption"; -import HomeHelpCard from "../HomeHelpCard"; -import HomeModelCard from "../HomeModelCard"; +import { HomeCaption } from "../HomeCaption"; +import { HomeHelpCard } from "../HomeHelpCard"; +import { HomeModelCard } from "../HomeModelCard"; import { SectionBody } from "./HomePopularSection.styled"; -export interface HomePopularSectionProps { - popularItems: PopularItem[]; -} +export const HomePopularSection = (): JSX.Element => { + const { + data: popularItems = [], + isLoading, + error, + } = usePopularItemListQuery(); + + if (isLoading || error) { + return <LoadingAndErrorWrapper loading={isLoading} error={error} />; + } -const HomePopularSection = ({ - popularItems, -}: HomePopularSectionProps): JSX.Element => { return ( <div> <HomeCaption>{getTitle(popularItems)}</HomeCaption> @@ -56,6 +59,3 @@ const getTitle = (popularItems: PopularItem[]) => { return t`Here are some popular items`; } }; - -// eslint-disable-next-line import/no-default-export -- deprecated usage -export default PopularItems.loadList()(HomePopularSection); diff --git a/frontend/src/metabase/home/components/HomePopularSection/HomePopularSection.unit.spec.tsx b/frontend/src/metabase/home/components/HomePopularSection/HomePopularSection.unit.spec.tsx index 2c508254eb6..4d52f8876f1 100644 --- a/frontend/src/metabase/home/components/HomePopularSection/HomePopularSection.unit.spec.tsx +++ b/frontend/src/metabase/home/components/HomePopularSection/HomePopularSection.unit.spec.tsx @@ -1,51 +1,60 @@ import { screen } from "@testing-library/react"; import { createMockPopularItem } from "metabase-types/api/mocks"; -import { renderWithProviders } from "__support__/ui"; +import { renderWithProviders, waitForElementToBeRemoved } from "__support__/ui"; import { setupPopularItemsEndpoints } from "__support__/server-mocks"; import { PopularItem } from "metabase-types/api"; -import HomePopularSection from "./HomePopularSection"; +import { HomePopularSection } from "./HomePopularSection"; -const setup = (items: PopularItem[]) => { - setupPopularItemsEndpoints(items); +interface SetupOpts { + popularItems: PopularItem[]; +} + +const setup = async ({ popularItems }: SetupOpts) => { + setupPopularItemsEndpoints(popularItems); renderWithProviders(<HomePopularSection />); + await waitForElementToBeRemoved(() => screen.queryByText(/Loading/i)); }; describe("HomePopularSection", () => { it("should render a list of items of the same type", async () => { - setup([ - createMockPopularItem({ - model: "dashboard", - model_object: { - name: "Metrics", - }, - }), - createMockPopularItem({ - model: "dashboard", - model_object: { - name: "Revenue", - }, - }), - ]); + await setup({ + popularItems: [ + createMockPopularItem({ + model: "dashboard", + model_object: { + name: "Metrics", + }, + }), + createMockPopularItem({ + model: "dashboard", + model_object: { + name: "Revenue", + }, + }), + ], + }); - expect(await screen.findByText(/popular dashboards/)).toBeInTheDocument(); + expect(screen.getByText(/popular dashboards/)).toBeInTheDocument(); }); it("should render a list of items of different types", async () => { - setup([ - createMockPopularItem({ - model: "dashboard", - model_object: { - name: "Metrics", - }, - }), - createMockPopularItem({ - model: "card", - model_object: { - name: "Revenue", - }, - }), - ]); + await setup({ + popularItems: [ + createMockPopularItem({ + model: "dashboard", + model_object: { + name: "Metrics", + }, + }), + createMockPopularItem({ + model: "card", + model_object: { + name: "Revenue", + }, + }), + ], + }); - expect(await screen.findByText(/popular items/)).toBeInTheDocument(); + expect(screen.getByText(/popular items/)).toBeInTheDocument(); }); }); diff --git a/frontend/src/metabase/home/components/HomePopularSection/index.ts b/frontend/src/metabase/home/components/HomePopularSection/index.ts index 7a84c0f0ceb..d7e5751760c 100644 --- a/frontend/src/metabase/home/components/HomePopularSection/index.ts +++ b/frontend/src/metabase/home/components/HomePopularSection/index.ts @@ -1,2 +1 @@ -// eslint-disable-next-line import/no-default-export -- deprecated usage -export { default } from "./HomePopularSection"; +export * from "./HomePopularSection"; diff --git a/frontend/src/metabase/home/components/HomeRecentSection/HomeRecentSection.tsx b/frontend/src/metabase/home/components/HomeRecentSection/HomeRecentSection.tsx index d2b6f5d7c99..b8266f9a0df 100644 --- a/frontend/src/metabase/home/components/HomeRecentSection/HomeRecentSection.tsx +++ b/frontend/src/metabase/home/components/HomeRecentSection/HomeRecentSection.tsx @@ -1,38 +1,26 @@ -import { connect } from "react-redux"; import { t } from "ttag"; -import _ from "underscore"; +import { useSelector } from "metabase/lib/redux"; import * as Urls from "metabase/lib/urls"; -import RecentItems, { getIcon, getName } from "metabase/entities/recent-items"; +import { getIcon, getName } from "metabase/entities/recent-items"; import { getUser } from "metabase/selectors/user"; -import { RecentItem, User } from "metabase-types/api"; -import { State } from "metabase-types/store"; -import HomeCaption from "../HomeCaption"; -import HomeHelpCard from "../HomeHelpCard"; -import HomeModelCard from "../HomeModelCard"; +import { useRecentItemListQuery } from "metabase/common/hooks"; +import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper"; +import { HomeCaption } from "../HomeCaption"; +import { HomeHelpCard } from "../HomeHelpCard"; +import { HomeModelCard } from "../HomeModelCard"; import { isWithinWeeks } from "../../utils"; import { SectionBody } from "./HomeRecentSection.styled"; -interface EntityLoaderProps { - recentItems: RecentItem[]; -} - -interface StateProps { - user: User | null; -} - -export type HomeRecentSectionProps = EntityLoaderProps & StateProps; - -const mapStateToProps = (state: State): StateProps => ({ - user: getUser(state), -}); - -const HomeRecentSection = ({ - user, - recentItems, -}: HomeRecentSectionProps): JSX.Element => { +export const HomeRecentSection = () => { + const { data: recentItems = [], isLoading, error } = useRecentItemListQuery(); + const user = useSelector(getUser); const hasHelpCard = user != null && user.is_installer && isWithinWeeks(user.first_login, 2); + if (isLoading || error) { + return <LoadingAndErrorWrapper loading={isLoading} error={error} />; + } + return ( <div> <HomeCaption>{t`Pick up where you left off`}</HomeCaption> @@ -50,9 +38,3 @@ const HomeRecentSection = ({ </div> ); }; - -// eslint-disable-next-line import/no-default-export -- deprecated usage -export default _.compose( - RecentItems.loadList(), - connect(mapStateToProps), -)(HomeRecentSection); diff --git a/frontend/src/metabase/home/components/HomeRecentSection/HomeRecentSection.unit.spec.tsx b/frontend/src/metabase/home/components/HomeRecentSection/HomeRecentSection.unit.spec.tsx index cf6039b4615..2c0580d434f 100644 --- a/frontend/src/metabase/home/components/HomeRecentSection/HomeRecentSection.unit.spec.tsx +++ b/frontend/src/metabase/home/components/HomeRecentSection/HomeRecentSection.unit.spec.tsx @@ -3,14 +3,13 @@ import { createMockRecentItem, createMockUser } from "metabase-types/api/mocks"; import { renderWithProviders } from "__support__/ui"; import { setupRecentViewsEndpoints } from "__support__/server-mocks"; import { User } from "metabase-types/api"; -import * as utils from "../../utils"; -import HomeRecentSection from "./HomeRecentSection"; +import { HomeRecentSection } from "./HomeRecentSection"; -jest.mock("../../utils", () => ({ - isWithinWeeks: jest.fn(), -})); +interface SetupOpts { + user?: User; +} -const setup = async (user?: User) => { +const setup = async ({ user = createMockUser() }: SetupOpts = {}) => { setupRecentViewsEndpoints([ createMockRecentItem({ model: "table", @@ -30,28 +29,35 @@ const setup = async (user?: User) => { }; describe("HomeRecentSection", () => { + beforeEach(() => { + jest.useFakeTimers({ advanceTimers: true }); + jest.setSystemTime(new Date(2020, 0, 4)); + }); + afterEach(() => { + jest.useRealTimers(); jest.restoreAllMocks(); }); describe("new installers", () => { it("should show a help link for new installers", async () => { - jest.spyOn(utils, "isWithinWeeks").mockImplementationOnce(() => true); - - await setup( - createMockUser({ + await setup({ + user: createMockUser({ is_installer: true, first_login: "2020-01-05T00:00:00Z", }), - ); + }); - expect(await screen.findByText("Metabase tips")).toBeInTheDocument(); + expect(screen.getByText("Metabase tips")).toBeInTheDocument(); }); it("should not show a help link for regular users", async () => { - jest.spyOn(utils, "isWithinWeeks").mockImplementationOnce(() => false); - - await setup(); + await setup({ + user: createMockUser({ + is_installer: false, + first_login: "2019-11-05T00:00:00Z", + }), + }); expect(screen.queryByText("Metabase tips")).not.toBeInTheDocument(); }); @@ -60,9 +66,7 @@ describe("HomeRecentSection", () => { it("should render a list of recent items", async () => { await setup(); - expect( - await screen.findByText("Pick up where you left off"), - ).toBeInTheDocument(); + expect(screen.getByText("Pick up where you left off")).toBeInTheDocument(); expect(screen.getByText("Orders")).toBeInTheDocument(); }); }); diff --git a/frontend/src/metabase/home/components/HomeRecentSection/index.ts b/frontend/src/metabase/home/components/HomeRecentSection/index.ts index 0efb9a3d7c1..8bf83463d64 100644 --- a/frontend/src/metabase/home/components/HomeRecentSection/index.ts +++ b/frontend/src/metabase/home/components/HomeRecentSection/index.ts @@ -1,2 +1 @@ -// eslint-disable-next-line import/no-default-export -- deprecated usage -export { default } from "./HomeRecentSection"; +export * from "./HomeRecentSection"; diff --git a/frontend/src/metabase/home/components/HomeXrayCard/HomeXrayCard.tsx b/frontend/src/metabase/home/components/HomeXrayCard/HomeXrayCard.tsx index 29da2174560..742c6272611 100644 --- a/frontend/src/metabase/home/components/HomeXrayCard/HomeXrayCard.tsx +++ b/frontend/src/metabase/home/components/HomeXrayCard/HomeXrayCard.tsx @@ -1,4 +1,4 @@ -import HomeCard from "../HomeCard"; +import { HomeCard } from "../HomeCard"; import { CardIcon, CardIconContainer, @@ -7,13 +7,13 @@ import { CardTitleSecondary, } from "./HomeXrayCard.styled"; -export interface HomeXrayCardProps { +interface HomeXrayCardProps { title: string; url: string; message: string; } -const HomeXrayCard = ({ +export const HomeXrayCard = ({ title, url, message, @@ -30,6 +30,3 @@ const HomeXrayCard = ({ </HomeCard> ); }; - -// eslint-disable-next-line import/no-default-export -- deprecated usage -export default HomeXrayCard; diff --git a/frontend/src/metabase/home/components/HomeXrayCard/HomeXrayCard.unit.spec.tsx b/frontend/src/metabase/home/components/HomeXrayCard/HomeXrayCard.unit.spec.tsx index e248df95702..51b5c4748e0 100644 --- a/frontend/src/metabase/home/components/HomeXrayCard/HomeXrayCard.unit.spec.tsx +++ b/frontend/src/metabase/home/components/HomeXrayCard/HomeXrayCard.unit.spec.tsx @@ -1,23 +1,25 @@ -import { render, screen } from "@testing-library/react"; -import HomeXrayCard, { HomeXrayCardProps } from "./HomeXrayCard"; +import { render, screen } from "__support__/ui"; +import { HomeXrayCard } from "./HomeXrayCard"; + +interface SetupOpts { + title: string; + message: string; + url: string; +} + +const setup = ({ title, message, url }: SetupOpts) => { + render(<HomeXrayCard title={title} message={message} url={url} />); +}; describe("HomeXrayCard", () => { it("should render correctly", () => { - const props = getProps({ + setup({ title: "Orders", message: "A look at", + url: "/question/1", }); - render(<HomeXrayCard {...props} />); - expect(screen.getByText("Orders")).toBeInTheDocument(); expect(screen.getByText("A look at")).toBeInTheDocument(); }); }); - -const getProps = (opts?: Partial<HomeXrayCardProps>): HomeXrayCardProps => ({ - title: "Orders", - message: "A look at", - url: "/question/1", - ...opts, -}); diff --git a/frontend/src/metabase/home/components/HomeXrayCard/index.ts b/frontend/src/metabase/home/components/HomeXrayCard/index.ts index 1d14ac702e4..f3d248ac0a0 100644 --- a/frontend/src/metabase/home/components/HomeXrayCard/index.ts +++ b/frontend/src/metabase/home/components/HomeXrayCard/index.ts @@ -1,2 +1 @@ -// eslint-disable-next-line import/no-default-export -- deprecated usage -export { default } from "./HomeXrayCard"; +export * from "./HomeXrayCard"; diff --git a/frontend/src/metabase/home/components/HomeXraySection/HomeXraySection.tsx b/frontend/src/metabase/home/components/HomeXraySection/HomeXraySection.tsx index 67a60d178c6..7ec7fb81eac 100644 --- a/frontend/src/metabase/home/components/HomeXraySection/HomeXraySection.tsx +++ b/frontend/src/metabase/home/components/HomeXraySection/HomeXraySection.tsx @@ -2,12 +2,18 @@ import { ChangeEvent, useCallback, useMemo, useState } from "react"; import _ from "underscore"; import { t } from "ttag"; import * as Urls from "metabase/lib/urls"; +import { isSyncCompleted } from "metabase/lib/syncing"; import Select from "metabase/core/components/Select"; +import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper"; +import { + useDatabaseCandidateListQuery, + useDatabaseListQuery, +} from "metabase/common/hooks"; import { DatabaseCandidate } from "metabase-types/api"; import Database from "metabase-lib/metadata/Database"; -import HomeCaption from "../HomeCaption"; -import HomeHelpCard from "../HomeHelpCard"; -import HomeXrayCard from "../HomeXrayCard"; +import { HomeCaption } from "../HomeCaption"; +import { HomeHelpCard } from "../HomeHelpCard"; +import { HomeXrayCard } from "../HomeXrayCard"; import { DatabaseLinkIcon, DatabaseLink, @@ -18,15 +24,35 @@ import { SchemaTriggerIcon, } from "./HomeXraySection.styled"; -export interface HomeXraySectionProps { +export const HomeXraySection = () => { + const databaseListState = useDatabaseListQuery(); + const database = getXrayDatabase(databaseListState.data); + const candidateListState = useDatabaseCandidateListQuery({ + query: database ? { id: database.id } : undefined, + enabled: database != null, + }); + const isLoading = databaseListState.isLoading || candidateListState.isLoading; + const error = databaseListState.error ?? candidateListState.error; + + if (isLoading || error) { + return <LoadingAndErrorWrapper loading={isLoading} error={error} />; + } + + if (!database) { + return null; + } + + return ( + <HomeXrayView database={database} candidates={candidateListState.data} /> + ); +}; + +interface HomeXrayViewProps { database: Database; - candidates: DatabaseCandidate[]; + candidates?: DatabaseCandidate[]; } -const HomeXraySection = ({ - database, - candidates, -}: HomeXraySectionProps): JSX.Element => { +const HomeXrayView = ({ database, candidates = [] }: HomeXrayViewProps) => { const isSample = database.is_sample; const schemas = candidates.map(d => d.schema); const [schema, setSchema] = useState(schemas[0]); @@ -119,6 +145,12 @@ const DatabaseInfo = ({ database }: DatabaseInfoProps) => { ); }; +const getXrayDatabase = (databases: Database[] = []) => { + const sampleDatabase = databases.find(d => d.is_sample && isSyncCompleted(d)); + const userDatabase = databases.find(d => !d.is_sample && isSyncCompleted(d)); + return userDatabase ?? sampleDatabase; +}; + const getMessages = (count: number) => { const options = [ t`A look at`, @@ -137,6 +169,3 @@ const getMessages = (count: number) => { const getSchemaOption = (schema: string) => { return schema; }; - -// eslint-disable-next-line import/no-default-export -- deprecated usage -export default HomeXraySection; diff --git a/frontend/src/metabase/home/components/HomeXraySection/HomeXraySection.unit.spec.tsx b/frontend/src/metabase/home/components/HomeXraySection/HomeXraySection.unit.spec.tsx index 9748a45888e..9d38d94b0d2 100644 --- a/frontend/src/metabase/home/components/HomeXraySection/HomeXraySection.unit.spec.tsx +++ b/frontend/src/metabase/home/components/HomeXraySection/HomeXraySection.unit.spec.tsx @@ -1,49 +1,43 @@ -import { checkNotNull } from "metabase/core/utils/types"; -import { getMetadata } from "metabase/selectors/metadata"; +import { Database, DatabaseCandidate } from "metabase-types/api"; import { createMockDatabase, createMockDatabaseCandidate, createMockTableCandidate, } from "metabase-types/api/mocks"; -import { Database, DatabaseCandidate } from "metabase-types/api"; -import { createMockState } from "metabase-types/store/mocks"; -import { createMockEntitiesState } from "__support__/store"; -import { renderWithProviders, screen } from "__support__/ui"; -import HomeXraySection from "./HomeXraySection"; +import { + setupDatabaseCandidatesEndpoint, + setupDatabasesEndpoints, +} from "__support__/server-mocks"; +import { + renderWithProviders, + screen, + waitForElementToBeRemoved, +} from "__support__/ui"; +import { HomeXraySection } from "./HomeXraySection"; interface SetupOpts { database: Database; candidates: DatabaseCandidate[]; } -const setup = ({ database, candidates }: SetupOpts) => { - const state = createMockState({ - entities: createMockEntitiesState({ databases: [database] }), - }); - const metadata = getMetadata(state); - - renderWithProviders( - <HomeXraySection - database={checkNotNull(metadata.database(database.id))} - candidates={candidates} - />, - { - storeInitialState: state, - }, - ); +const setup = async ({ database, candidates }: SetupOpts) => { + setupDatabasesEndpoints([database]); + setupDatabaseCandidatesEndpoint(database.id, candidates); + renderWithProviders(<HomeXraySection />); + await waitForElementToBeRemoved(() => screen.queryByText(/Loading/i)); }; describe("HomeXraySection", () => { - it("should show x-rays for a sample database", () => { - setup({ + it("should show x-rays for a sample database", async () => { + await setup({ database: createMockDatabase({ is_sample: true, }), candidates: [ createMockDatabaseCandidate({ tables: [ - createMockTableCandidate({ title: "Orders" }), - createMockTableCandidate({ title: "People" }), + createMockTableCandidate({ title: "Orders", url: "/auto/1" }), + createMockTableCandidate({ title: "People", url: "/auto/2" }), ], }), ], @@ -54,18 +48,20 @@ describe("HomeXraySection", () => { expect(screen.getByText("People")).toBeInTheDocument(); }); - it("should show x-rays for a user database", () => { - setup({ + it("should show x-rays for a user database", async () => { + await setup({ database: createMockDatabase({ name: "H2", is_sample: false, }), candidates: [ createMockDatabaseCandidate({ + id: "1/public", schema: "public", tables: [createMockTableCandidate({ title: "Orders" })], }), createMockDatabaseCandidate({ + id: "1/internal", schema: "internal", tables: [createMockTableCandidate({ title: "People" })], }), diff --git a/frontend/src/metabase/home/components/HomeXraySection/index.ts b/frontend/src/metabase/home/components/HomeXraySection/index.ts index e0f5cc71d03..23cf5ffaa97 100644 --- a/frontend/src/metabase/home/components/HomeXraySection/index.ts +++ b/frontend/src/metabase/home/components/HomeXraySection/index.ts @@ -1,2 +1 @@ -// eslint-disable-next-line import/no-default-export -- deprecated usage -export { default } from "./HomeXraySection"; +export * from "./HomeXraySection"; diff --git a/frontend/src/metabase/home/containers/HomeContent/HomeContent.tsx b/frontend/src/metabase/home/containers/HomeContent/HomeContent.tsx deleted file mode 100644 index aa38fb9cc7a..00000000000 --- a/frontend/src/metabase/home/containers/HomeContent/HomeContent.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { connect } from "react-redux"; -import _ from "underscore"; -import Databases from "metabase/entities/databases"; -import RecentItems from "metabase/entities/recent-items"; -import PopularItems from "metabase/entities/popular-items"; -import { getUser } from "metabase/selectors/user"; -import { State } from "metabase-types/store"; -import { getSetting } from "metabase/selectors/settings"; -import HomeContent from "../../components/HomeContent"; - -const mapStateToProps = (state: State) => ({ - user: getUser(state), - isXrayEnabled: getSetting(state, "enable-xrays"), -}); - -// eslint-disable-next-line import/no-default-export -- deprecated usage -export default _.compose( - Databases.loadList({ loadingAndErrorWrapper: false }), - RecentItems.loadList({ reload: true, loadingAndErrorWrapper: false }), - PopularItems.loadList({ reload: true, loadingAndErrorWrapper: false }), - connect(mapStateToProps), -)(HomeContent); diff --git a/frontend/src/metabase/home/containers/HomeContent/index.ts b/frontend/src/metabase/home/containers/HomeContent/index.ts deleted file mode 100644 index acc76cd9ddd..00000000000 --- a/frontend/src/metabase/home/containers/HomeContent/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line import/no-default-export -- deprecated usage -export { default } from "./HomeContent"; diff --git a/frontend/src/metabase/home/containers/HomePageApp/HomePageApp.tsx b/frontend/src/metabase/home/containers/HomePageApp/HomePageApp.tsx deleted file mode 100644 index addf98af2a5..00000000000 --- a/frontend/src/metabase/home/containers/HomePageApp/HomePageApp.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { connect } from "react-redux"; -import _ from "underscore"; -import Databases from "metabase/entities/databases"; -import Search from "metabase/entities/search"; -import { openNavbar } from "metabase/redux/app"; -import { getSetting } from "metabase/selectors/settings"; -import { getUserRedirectHomepageToDashboard } from "metabase/selectors/user"; -import { CollectionItem } from "metabase-types/api"; -import { State } from "metabase-types/store"; -import { canUseMetabotOnDatabase } from "metabase/metabot/utils"; -import Database from "metabase-lib/metadata/Database"; -import HomePage from "../../components/HomePage"; - -interface EntityLoaderProps { - databases: Database[]; - models: CollectionItem[]; -} - -interface StateProps { - hasMetabot: boolean; - homepageDashboard: number | null; -} - -const mapStateToProps = ( - state: State, - { databases, models }: EntityLoaderProps, -): StateProps => { - const hasModels = models.length > 0; - const hasSupportedDatabases = databases.some(canUseMetabotOnDatabase); - const isMetabotEnabled = getSetting(state, "is-metabot-enabled"); - - return { - hasMetabot: hasModels && hasSupportedDatabases && isMetabotEnabled, - homepageDashboard: getUserRedirectHomepageToDashboard(state), - }; -}; - -const mapDispatchToProps = { - onOpenNavbar: openNavbar, -}; - -// eslint-disable-next-line import/no-default-export -- deprecated usage -export default _.compose( - Databases.loadList(), - Search.loadList({ - query: { - models: "dataset", - limit: 1, - }, - listName: "models", - }), - connect(mapStateToProps, mapDispatchToProps), -)(HomePage); diff --git a/frontend/src/metabase/home/containers/HomePageApp/HomePageApp.unit.spec.tsx b/frontend/src/metabase/home/containers/HomePageApp/HomePageApp.unit.spec.tsx deleted file mode 100644 index f0f94012b0a..00000000000 --- a/frontend/src/metabase/home/containers/HomePageApp/HomePageApp.unit.spec.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { Route, Router } from "react-router"; -import fetchMock from "fetch-mock"; -import DashboardApp from "metabase/dashboard/containers/DashboardApp"; - -import { createMockDashboard, createMockUser } from "metabase-types/api/mocks"; -import { createMockState } from "metabase-types/store/mocks"; - -import { renderWithProviders, screen } from "__support__/ui"; -import HomePageApp from "./HomePageApp"; - -const LayoutMock = () => <div>Content</div>; -jest.mock("../../components/HomeLayout", () => LayoutMock); - -const DashboardMock = () => <div>Dashboard</div>; -jest.mock("metabase/dashboard/containers/DashboardApp", () => DashboardMock); - -const setupForRedirect = ({ customDashboardSet = true } = {}) => { - const dashboard = createMockDashboard(); - - fetchMock.get("path:/api/dashboard/1", dashboard); - fetchMock.get("path:/api/database", []); - fetchMock.get("path:/api/search", []); - - renderWithProviders( - <Router> - <Route path="/" component={HomePageApp} /> - <Route path="/dashboard/:slug" component={DashboardApp} /> - </Router>, - { - withRouter: true, - storeInitialState: createMockState({ - currentUser: createMockUser({ - custom_homepage: customDashboardSet - ? { - dashboard_id: 1, - } - : null, - }), - }), - }, - ); -}; - -it("should redirect you to a dashboard when one has been defined to be used as a homepage", async () => { - setupForRedirect(); - expect(await screen.findByText("Dashboard")).toBeInTheDocument(); -}); - -it("should render homepage when custom-homepage is false", async () => { - setupForRedirect({ - customDashboardSet: false, - }); - expect(await screen.findByText("Content")).toBeInTheDocument(); -}); diff --git a/frontend/src/metabase/home/containers/HomePageApp/index.ts b/frontend/src/metabase/home/containers/HomePageApp/index.ts deleted file mode 100644 index a4c0203dd9a..00000000000 --- a/frontend/src/metabase/home/containers/HomePageApp/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line import/no-default-export -- deprecated usage -export { default } from "./HomePageApp"; diff --git a/frontend/src/metabase/home/containers/HomeXraySection/HomeXraySection.tsx b/frontend/src/metabase/home/containers/HomeXraySection/HomeXraySection.tsx deleted file mode 100644 index a4d002840bf..00000000000 --- a/frontend/src/metabase/home/containers/HomeXraySection/HomeXraySection.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { connect } from "react-redux"; -import _ from "underscore"; -import Databases from "metabase/entities/databases"; -import DatabaseCandidates from "metabase/entities/database-candidates"; -import { isSyncCompleted } from "metabase/lib/syncing"; -import { State } from "metabase-types/store"; -import Database from "metabase-lib/metadata/Database"; -import HomeXraySection from "../../components/HomeXraySection"; - -interface XraySectionProps { - database?: Database; - databases: Database[]; -} - -const getXrayDatabase = ({ databases }: XraySectionProps) => { - const sampleDatabase = databases.find(d => d.is_sample && isSyncCompleted(d)); - const userDatabase = databases.find(d => !d.is_sample && isSyncCompleted(d)); - return userDatabase ?? sampleDatabase; -}; - -const getXrayQuery = (state: State, { database }: XraySectionProps) => ({ - id: database?.id, -}); - -const mapStateToProps = (state: State, props: XraySectionProps) => ({ - database: getXrayDatabase(props), -}); - -// eslint-disable-next-line import/no-default-export -- deprecated usage -export default _.compose( - Databases.loadList(), - connect(mapStateToProps), - DatabaseCandidates.loadList({ query: getXrayQuery, listName: "candidates" }), -)(HomeXraySection); diff --git a/frontend/src/metabase/home/containers/HomeXraySection/index.ts b/frontend/src/metabase/home/containers/HomeXraySection/index.ts deleted file mode 100644 index e0f5cc71d03..00000000000 --- a/frontend/src/metabase/home/containers/HomeXraySection/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line import/no-default-export -- deprecated usage -export { default } from "./HomeXraySection"; diff --git a/frontend/src/metabase/home/selectors.ts b/frontend/src/metabase/home/selectors.ts new file mode 100644 index 00000000000..5e499dced0a --- /dev/null +++ b/frontend/src/metabase/home/selectors.ts @@ -0,0 +1,25 @@ +import { createSelector } from "@reduxjs/toolkit"; +import { getSetting } from "metabase/selectors/settings"; +import { State } from "metabase-types/store"; +import { getUser } from "metabase/selectors/user"; + +export const getIsXrayEnabled = (state: State) => { + return getSetting(state, "enable-xrays"); +}; + +export const getIsMetabotEnabled = (state: State) => { + return getSetting(state, "is-metabot-enabled"); +}; + +export const getHasMetabotLogo = (state: State) => { + return getSetting(state, "show-metabot"); +}; + +export const getHasIllustration = (state: State) => { + return getSetting(state, "show-lighthouse-illustration"); +}; + +export const getCustomHomePageDashboardId = createSelector( + [getUser], + user => user?.custom_homepage?.dashboard_id || null, +); diff --git a/frontend/src/metabase/routes.jsx b/frontend/src/metabase/routes.jsx index 0e62f2aee1b..dbfcd02420c 100644 --- a/frontend/src/metabase/routes.jsx +++ b/frontend/src/metabase/routes.jsx @@ -55,7 +55,7 @@ import DashboardMoveModal from "metabase/dashboard/components/DashboardMoveModal import DashboardCopyModal from "metabase/dashboard/components/DashboardCopyModal"; import { ModalRoute } from "metabase/hoc/ModalRoute"; -import HomePageApp from "metabase/home/containers/HomePageApp"; +import { HomePage } from "metabase/home/components/HomePage"; import CollectionLanding from "metabase/collections/components/CollectionLanding"; import ArchiveApp from "metabase/archive/containers/ArchiveApp"; @@ -120,7 +120,7 @@ export const getRoutes = store => ( {/* The global all hands routes, things in here are for all the folks */} <Route path="/" - component={HomePageApp} + component={HomePage} onEnter={(nextState, replace) => { const page = PLUGIN_LANDING_PAGE[0] && PLUGIN_LANDING_PAGE[0](); if (page && page !== "/") { diff --git a/frontend/src/metabase/selectors/user.ts b/frontend/src/metabase/selectors/user.ts index 59a1585c7d4..0f82edad0ed 100644 --- a/frontend/src/metabase/selectors/user.ts +++ b/frontend/src/metabase/selectors/user.ts @@ -30,8 +30,3 @@ export const getUserPersonalCollectionId = createSelector( [getUser], user => user?.personal_collection_id, ); - -export const getUserRedirectHomepageToDashboard = createSelector( - [getUser], - user => user?.custom_homepage?.dashboard_id || null, -); -- GitLab