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

Cleanup new homepage (#21448)

parent bdc1e665
No related branches found
No related tags found
No related merge requests found
Showing
with 301 additions and 37 deletions
......@@ -4,12 +4,12 @@ export interface ModelObject {
name: string;
}
export interface RecentView {
export interface RecentItem {
model: ModelType;
model_object: ModelObject;
}
export interface PopularView {
export interface PopularItem {
model: ModelType;
model_object: ModelObject;
}
import { ModelObject, PopularView, RecentView } from "metabase-types/api";
import { ModelObject, PopularItem, RecentItem } from "metabase-types/api";
export const createMockModelObject = (
opts?: Partial<ModelObject>,
......@@ -7,17 +7,17 @@ export const createMockModelObject = (
...opts,
});
export const createMockRecentView = (
opts?: Partial<RecentView>,
): RecentView => ({
export const createMockRecentItem = (
opts?: Partial<RecentItem>,
): RecentItem => ({
model: "table",
model_object: createMockModelObject(),
...opts,
});
export const createMockPopularView = (
opts?: Partial<PopularView>,
): PopularView => ({
export const createMockPopularItem = (
opts?: Partial<PopularItem>,
): PopularItem => ({
model: "table",
model_object: createMockModelObject(),
...opts,
......
......@@ -15,6 +15,7 @@ export const createMockUser = (opts?: Partial<User>): User => ({
has_question_and_dashboard: false,
personal_collection_id: 1,
date_joined: new Date().toISOString(),
first_login: new Date().toISOString(),
last_login: new Date().toISOString(),
...opts,
});
......@@ -12,6 +12,7 @@ export interface User {
has_invited_second_user: boolean;
has_question_and_dashboard: boolean;
date_joined: string;
first_login: string;
last_login: string;
personal_collection_id: number;
}
......@@ -24,6 +24,6 @@ export { default as users } from "./users";
export { default as groups } from "./groups";
export { default as search } from "./search";
export { default as popularViews } from "./popular-views";
export { default as recentViews } from "./recent-views";
export { default as recentItems } from "./recent-items";
export { default as popularItems } from "./popular-items";
export { default as snippets } from "./snippets";
import { createEntity } from "metabase/lib/entities";
import { entityTypeForObject } from "metabase/lib/schema";
import { PopularViewsSchema } from "metabase/schema";
import { PopularItemSchema } from "metabase/schema";
export const getEntity = item => {
const entities = require("metabase/entities");
......@@ -16,11 +16,11 @@ export const getIcon = item => {
return entity.objectSelectors.getIcon(item.model_object);
};
const PopularViews = createEntity({
name: "popularViews",
nameOne: "popularView",
path: "/api/activity/popular_views",
schema: PopularViewsSchema,
const PopularItems = createEntity({
name: "popularItems",
nameOne: "popularItem",
path: "/api/activity/popular_items",
schema: PopularItemSchema,
wrapEntity(item, dispatch = null) {
const entity = getEntity(item);
......@@ -33,4 +33,4 @@ const PopularViews = createEntity({
},
});
export default PopularViews;
export default PopularItems;
import { createEntity } from "metabase/lib/entities";
import { entityTypeForObject } from "metabase/lib/schema";
import { RecentViewsSchema } from "metabase/schema";
import { RecentItemSchema } from "metabase/schema";
export const getEntity = item => {
const entities = require("metabase/entities");
......@@ -16,11 +16,11 @@ export const getIcon = item => {
return entity.objectSelectors.getIcon(item.model_object);
};
const RecentViews = createEntity({
name: "recentViews",
nameOne: "recentView",
const RecentItems = createEntity({
name: "recentItems",
nameOne: "recentItem",
path: "/api/activity/recent_views",
schema: RecentViewsSchema,
schema: RecentItemSchema,
wrapEntity(item, dispatch = null) {
const entity = getEntity(item);
......@@ -33,4 +33,4 @@ const RecentViews = createEntity({
},
});
export default RecentViews;
export default RecentItems;
import styled from "@emotion/styled";
import { color } from "metabase/lib/colors";
import { breakpointMinExtraLarge } from "metabase/styled-components/theme";
export interface CaptionProps {
primary?: boolean;
}
export const CaptionRoot = styled.div<CaptionProps>`
display: flex;
align-items: center;
color: ${props =>
props.primary ? color("text-dark") : color("text-medium")};
font-weight: bold;
margin-bottom: 1.5rem;
${breakpointMinExtraLarge} {
margin-bottom: 2rem;
}
`;
import React, { ReactNode } from "react";
import { CaptionRoot } from "./HomeCaption.styled";
export interface HomeCaptionProps {
primary?: boolean;
children?: ReactNode;
}
const HomeCaption = ({ primary, children }: HomeCaptionProps): JSX.Element => {
return <CaptionRoot primary={primary}>{children}</CaptionRoot>;
};
export default HomeCaption;
import React from "react";
import { render, screen } from "@testing-library/react";
import HomeCaption from "./HomeCaption";
describe("HomeCaption", () => {
it("should render correctly", () => {
render(<HomeCaption>Title</HomeCaption>);
expect(screen.getByText("Title")).toBeInTheDocument();
});
});
export { default } from "./HomeCaption";
import styled from "@emotion/styled";
import { alpha, color } from "metabase/lib/colors";
import Link from "metabase/core/components/Link";
import { breakpointMinSmall } from "metabase/styled-components/theme";
import {
breakpointMinLarge,
breakpointMinSmall,
} from "metabase/styled-components/theme";
export const CardRoot = styled(Link)`
display: flex;
......@@ -17,6 +20,10 @@ export const CardRoot = styled(Link)`
max-width: 50%;
}
${breakpointMinLarge} {
padding: 1.5rem;
}
&:hover {
box-shadow: 0 10px 22px ${alpha("shadow", 0.09)};
}
......
import React from "react";
import { render, screen } from "@testing-library/react";
import HomeCard from "./HomeCard";
describe("HomeCard", () => {
it("should render correctly", () => {
render(<HomeCard>A look at table</HomeCard>);
expect(screen.getByText("A look at table")).toBeInTheDocument();
});
});
......@@ -2,7 +2,7 @@ import React from "react";
import moment from "moment";
import { parseTimestamp } from "metabase/lib/time";
import { isSyncCompleted } from "metabase/lib/syncing";
import { Database, RecentView, User } from "metabase-types/api";
import { Database, RecentItem, User } from "metabase-types/api";
import HomePopularSection from "../../containers/HomePopularSection";
import HomeRecentSection from "../../containers/HomeRecentSection";
import HomeXraySection from "../../containers/HomeXraySection";
......@@ -10,7 +10,7 @@ import HomeXraySection from "../../containers/HomeXraySection";
export interface HomeContentProps {
user: User;
databases: Database[];
recentViews: RecentView[];
recentItems: RecentItem[];
}
const HomeContent = (props: HomeContentProps): JSX.Element | null => {
......@@ -29,16 +29,16 @@ const HomeContent = (props: HomeContentProps): JSX.Element | null => {
return null;
};
const isPopularSection = ({ user, recentViews }: HomeContentProps) => {
const isPopularSection = ({ user, recentItems }: HomeContentProps) => {
return (
!user.is_installer &&
user.has_question_and_dashboard &&
(isWithinWeek(user.date_joined) || !recentViews.length)
(isWithinWeek(user.first_login) || !recentItems.length)
);
};
const isRecentSection = ({ recentViews }: HomeContentProps) => {
return recentViews.length > 0;
const isRecentSection = ({ recentItems }: HomeContentProps) => {
return recentItems.length > 0;
};
const isXraySection = ({ databases }: HomeContentProps) => {
......@@ -47,10 +47,8 @@ const isXraySection = ({ databases }: HomeContentProps) => {
const isWithinWeek = (timestamp: string) => {
const date = parseTimestamp(timestamp);
const today = moment();
const weekAgo = today.clone().subtract(1, "week");
return date.isBetween(weekAgo, today);
const weekAgo = moment().subtract(1, "week");
return date.isAfter(weekAgo);
};
export default HomeContent;
import React from "react";
import { render, screen } from "@testing-library/react";
import {
createMockDatabase,
createMockRecentItem,
createMockUser,
} from "metabase-types/api/mocks";
import HomeContent, { HomeContentProps } from "./HomeContent";
const PopularSectionMock = () => <div>PopularSection</div>;
jest.mock("../../containers/HomePopularSection", () => PopularSectionMock);
const RecentSectionMock = () => <div>RecentSection</div>;
jest.mock("../../containers/HomeRecentSection", () => RecentSectionMock);
const XraySectionMock = () => <div>XraySection</div>;
jest.mock("../../containers/HomeXraySection", () => XraySectionMock);
describe("HomeContent", () => {
beforeEach(() => {
jest.useFakeTimers();
jest.setSystemTime(new Date(2020, 0, 10));
});
afterEach(() => {
jest.useRealTimers();
});
it("should render popular items for a new user", () => {
const props = getProps({
user: createMockUser({
is_installer: false,
has_question_and_dashboard: true,
first_login: "2020-01-05T00:00:00Z",
}),
databases: [createMockDatabase()],
recentItems: [createMockRecentItem()],
});
render(<HomeContent {...props} />);
expect(screen.getByText("PopularSection")).toBeInTheDocument();
});
it("should render popular items for a user without recent items", () => {
const props = getProps({
user: createMockUser({
is_installer: false,
has_question_and_dashboard: true,
first_login: "2020-01-05T00:00:00Z",
}),
databases: [createMockDatabase()],
recentItems: [],
});
render(<HomeContent {...props} />);
expect(screen.getByText("PopularSection")).toBeInTheDocument();
});
it("should render recent items for an existing user", () => {
const props = getProps({
user: createMockUser({
is_installer: false,
has_question_and_dashboard: true,
first_login: "2020-01-01T00:00:00Z",
}),
databases: [createMockDatabase()],
recentItems: [createMockRecentItem()],
});
render(<HomeContent {...props} />);
expect(screen.getByText("RecentSection")).toBeInTheDocument();
});
it("should render x-rays for an installer after the setup", () => {
const props = getProps({
user: createMockUser({
is_installer: true,
has_question_and_dashboard: false,
first_login: "2020-01-10T00:00:00Z",
}),
databases: [createMockDatabase()],
recentItems: [],
});
render(<HomeContent {...props} />);
expect(screen.getByText("XraySection")).toBeInTheDocument();
});
it("should render nothing if there are no databases", () => {
const props = getProps({
user: createMockUser({
is_installer: true,
has_question_and_dashboard: false,
first_login: "2020-01-10T00:00:00Z",
}),
databases: [],
recentItems: [],
});
render(<HomeContent {...props} />);
expect(screen.queryByText("XraySection")).not.toBeInTheDocument();
});
});
const getProps = (opts?: Partial<HomeContentProps>): HomeContentProps => ({
user: createMockUser(),
databases: [],
recentItems: [],
...opts,
});
import styled from "@emotion/styled";
import { color } from "metabase/lib/colors";
import MetabotLogo from "metabase/components/MetabotLogo";
import { breakpointMinExtraLarge } from "metabase/styled-components/theme";
export const GreetingRoot = styled.div`
display: flex;
......@@ -9,6 +10,10 @@ export const GreetingRoot = styled.div`
export const GreetingLogo = styled(MetabotLogo)`
height: 2rem;
${breakpointMinExtraLarge} {
height: 2.5rem;
}
`;
export interface GreetingMessageProps {
......@@ -21,4 +26,8 @@ export const GreetingMessage = styled.span<GreetingMessageProps>`
font-weight: bold;
line-height: 1.5rem;
margin-left: ${props => props.showLogo && "0.5rem"};
${breakpointMinExtraLarge} {
font-size: ${props => (props.showLogo ? "1.25rem" : "1.5rem")};
}
`;
import React from "react";
import { render, screen } from "@testing-library/react";
import { createMockUser } from "metabase-types/api/mocks";
import HomeGreeting, { HomeGreetingProps } from "./HomeGreeting";
describe("HomeGreeting", () => {
it("should render with logo", () => {
const props = getProps({
user: createMockUser({ first_name: "John" }),
showLogo: true,
});
render(<HomeGreeting {...props} />);
expect(screen.getByText(/John/)).toBeInTheDocument();
expect(screen.getByRole("img")).toBeInTheDocument();
});
it("should render without logo", () => {
const props = getProps({
user: createMockUser({ first_name: "John" }),
showLogo: false,
});
render(<HomeGreeting {...props} />);
expect(screen.getByText(/John/)).toBeInTheDocument();
expect(screen.queryByRole("img")).not.toBeInTheDocument();
});
});
const getProps = (opts?: Partial<HomeGreetingProps>): HomeGreetingProps => ({
user: createMockUser(),
showLogo: false,
...opts,
});
......@@ -2,6 +2,7 @@ import styled from "@emotion/styled";
import { color } from "metabase/lib/colors";
import Icon from "metabase/components/Icon";
import ExternalLink from "metabase/core/components/ExternalLink";
import { breakpointMinLarge } from "metabase/styled-components/theme";
export const CardRoot = styled(ExternalLink)`
display: flex;
......@@ -9,6 +10,10 @@ export const CardRoot = styled(ExternalLink)`
padding: 1rem;
border: 1px solid ${color("focus")};
border-radius: 0.5rem;
${breakpointMinLarge} {
padding: 1.5rem;
}
`;
export const CardIcon = styled(Icon)`
......
import React from "react";
import { render, screen } from "@testing-library/react";
import HomeHelpCard from "./HomeHelpCard";
describe("HomeHelpCard", () => {
it("should render correctly", () => {
render(<HomeHelpCard />);
expect(screen.getByText("Metabase tips")).toBeInTheDocument();
});
});
import styled from "@emotion/styled";
import { css } from "@emotion/react";
import { alpha, color } from "metabase/lib/colors";
import {
breakpointMinExtraLarge,
breakpointMinLarge,
breakpointMinMedium,
} from "metabase/styled-components/theme";
export interface LayoutProps {
showScene?: boolean;
......@@ -24,10 +29,30 @@ const gradientStyles = css`
export const LayoutRoot = styled.div<LayoutProps>`
min-height: 100%;
padding: 4rem 7rem;
padding: 1rem;
${props => (props.showScene ? sceneStyles : gradientStyles)};
${breakpointMinMedium} {
padding: 3rem 4rem;
}
${breakpointMinLarge} {
padding: 4rem 7rem 2rem;
}
${breakpointMinExtraLarge} {
padding: 10rem 15rem 4rem;
}
`;
export const LayoutBody = styled.div`
margin-top: 6rem;
margin-top: 2.5rem;
${breakpointMinMedium} {
margin-top: 4rem;
}
${breakpointMinLarge} {
margin-top: 6rem;
}
`;
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