Skip to content
Snippets Groups Projects
Unverified Commit b912e4f9 authored by Alexander Polyankin's avatar Alexander Polyankin Committed by GitHub
Browse files

Convert homepage to TypeScript (#19204)

parent 3201c976
No related branches found
No related tags found
No related merge requests found
Showing
with 128 additions and 120 deletions
......@@ -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));
};
......
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";
......
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;
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,
});
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,
......
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;
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,
});
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;
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,
});
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;
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;
......
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;
......
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;
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment