-
Notifications
You must be signed in to change notification settings - Fork 582
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor(site): generalize UserCell component (#484)
Summary: This is a first step in porting over v1 AuditLog in a refactored/cleaned up fashion. The existing `UserCell` component was generalized for re-use across various tables (AuditLog, Users, Orgs). Details: - Move UserCell to `components/Table/Cells` - Add tests and stories for UserCell Impact: This unblocks future work in list views like the audit log, user management panel and organizations management panel. Relations: - This commit relates to #472, but does not finish it. - This commit should not merge until after #465 and #483 because it's based on them.
- Loading branch information
G r e y
committed
Mar 23, 2022
1 parent
038dd54
commit 6560f2e
Showing
10 changed files
with
222 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { ComponentMeta, Story } from "@storybook/react" | ||
import React from "react" | ||
import { MockUser, MockUserAgent } from "../../../test_helpers" | ||
import { UserCell, UserCellProps } from "./UserCell" | ||
|
||
export default { | ||
title: "Table/Cells/UserCell", | ||
component: UserCell, | ||
} as ComponentMeta<typeof UserCell> | ||
|
||
const Template: Story<UserCellProps> = (args) => <UserCell {...args} /> | ||
|
||
export const AuditLogExample = Template.bind({}) | ||
AuditLogExample.args = { | ||
Avatar: { | ||
username: MockUser.username, | ||
}, | ||
caption: MockUserAgent.ip_address, | ||
primaryText: MockUser.email, | ||
onPrimaryTextSelect: () => { | ||
return | ||
}, | ||
} | ||
|
||
export const AuditLogEmptyUserExample = Template.bind({}) | ||
AuditLogEmptyUserExample.args = { | ||
Avatar: { | ||
username: MockUser.username, | ||
}, | ||
caption: MockUserAgent.ip_address, | ||
primaryText: "Deleted User", | ||
onPrimaryTextSelect: undefined, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
import { MockUser, MockUserAgent, WrapperComponent } from "../../../test_helpers" | ||
import { UserCell, UserCellProps } from "./UserCell" | ||
import React from "react" | ||
import { fireEvent, render, screen } from "@testing-library/react" | ||
|
||
namespace Helpers { | ||
export const Props: UserCellProps = { | ||
Avatar: { | ||
username: MockUser.username, | ||
}, | ||
caption: MockUserAgent.ip_address, | ||
primaryText: MockUser.username, | ||
onPrimaryTextSelect: jest.fn(), | ||
} | ||
|
||
export const Component: React.FC<UserCellProps> = (props) => ( | ||
<WrapperComponent> | ||
<UserCell {...props} /> | ||
</WrapperComponent> | ||
) | ||
} | ||
|
||
describe("UserCell", () => { | ||
// callbacks | ||
it("calls onPrimaryTextSelect when primaryText is clicked", () => { | ||
// Given | ||
const onPrimaryTextSelectMock = jest.fn() | ||
const props: UserCellProps = { | ||
...Helpers.Props, | ||
onPrimaryTextSelect: onPrimaryTextSelectMock, | ||
} | ||
|
||
// When - click the user's email address | ||
render(<Helpers.Component {...props} />) | ||
fireEvent.click(screen.getByText(props.primaryText)) | ||
|
||
// Then - callback was fired once | ||
expect(onPrimaryTextSelectMock).toHaveBeenCalledTimes(1) | ||
}) | ||
|
||
// primaryText | ||
it("renders primaryText as a link when onPrimaryTextSelect is defined", () => { | ||
// Given | ||
const props: UserCellProps = Helpers.Props | ||
|
||
// When | ||
render(<Helpers.Component {...props} />) | ||
const primaryTextNode = screen.getByText(props.primaryText) | ||
|
||
// Then | ||
expect(primaryTextNode.tagName).toBe("A") | ||
}) | ||
it("renders primaryText without a link when onPrimaryTextSelect is undefined", () => { | ||
// Given | ||
const props: UserCellProps = { | ||
...Helpers.Props, | ||
onPrimaryTextSelect: undefined, | ||
} | ||
|
||
// When | ||
render(<Helpers.Component {...props} />) | ||
const primaryTextNode = screen.getByText(props.primaryText) | ||
|
||
// Then | ||
expect(primaryTextNode.tagName).toBe("P") | ||
}) | ||
|
||
// caption | ||
it("renders caption", () => { | ||
// Given | ||
const caption = "definitely a caption" | ||
const props: UserCellProps = { | ||
...Helpers.Props, | ||
caption, | ||
} | ||
|
||
// When | ||
render(<Helpers.Component {...props} />) | ||
|
||
// Then | ||
expect(screen.getByText(caption)).toBeDefined() | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import Box from "@material-ui/core/Box" | ||
import Link from "@material-ui/core/Link" | ||
import { makeStyles } from "@material-ui/core/styles" | ||
import Typography from "@material-ui/core/Typography" | ||
import React from "react" | ||
import { UserAvatar, UserAvatarProps } from "../../User" | ||
|
||
export interface UserCellProps { | ||
Avatar: UserAvatarProps | ||
/** | ||
* primaryText is rendered beside the avatar | ||
*/ | ||
primaryText: string /* | React.ReactNode <-- if needed */ | ||
/** | ||
* caption is rendered beneath the avatar and primaryText | ||
*/ | ||
caption?: string /* | React.ReactNode <-- if needed */ | ||
/** | ||
* onPrimaryTextSelect, if defined, is called when the primaryText is clicked | ||
*/ | ||
onPrimaryTextSelect?: () => void | ||
} | ||
|
||
const useStyles = makeStyles((theme) => ({ | ||
primaryText: { | ||
color: theme.palette.text.primary, | ||
fontFamily: theme.typography.fontFamily, | ||
fontSize: "16px", | ||
lineHeight: "15px", | ||
marginBottom: "5px", | ||
}, | ||
})) | ||
|
||
/** | ||
* UserCell is a single cell in an audit log table row that contains user-level | ||
* information | ||
*/ | ||
export const UserCell: React.FC<UserCellProps> = ({ Avatar, caption, primaryText, onPrimaryTextSelect }) => { | ||
const styles = useStyles() | ||
|
||
return ( | ||
<Box alignItems="center" display="flex" flexDirection="row"> | ||
<Box display="flex" margin="auto 14px auto 0"> | ||
<UserAvatar {...Avatar} /> | ||
</Box> | ||
|
||
<Box display="flex" flexDirection="column"> | ||
{onPrimaryTextSelect ? ( | ||
<Link className={styles.primaryText} onClick={onPrimaryTextSelect}> | ||
{primaryText} | ||
</Link> | ||
) : ( | ||
<Typography className={styles.primaryText}>{primaryText}</Typography> | ||
)} | ||
|
||
{caption && ( | ||
<Typography color="textSecondary" variant="caption"> | ||
{caption} | ||
</Typography> | ||
)} | ||
</Box> | ||
</Box> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,25 +1,12 @@ | ||
import Avatar from "@material-ui/core/Avatar" | ||
import React from "react" | ||
import { UserResponse } from "../../api/types" | ||
import { firstLetter } from "../../util/first-letter" | ||
|
||
export interface UserAvatarProps { | ||
user: UserResponse | ||
className?: string | ||
username: string | ||
} | ||
|
||
export const UserAvatar: React.FC<UserAvatarProps> = ({ user, className }) => { | ||
return <Avatar className={className}>{firstLetter(user.username)}</Avatar> | ||
} | ||
|
||
/** | ||
* `firstLetter` extracts the first character and returns it, uppercased | ||
* | ||
* If the string is empty or null, returns an empty string | ||
*/ | ||
export const firstLetter = (str: string): string => { | ||
if (str && str.length > 0) { | ||
return str[0].toLocaleUpperCase() | ||
} | ||
|
||
return "" | ||
export const UserAvatar: React.FC<UserAvatarProps> = ({ username, className }) => { | ||
return <Avatar className={className}>{firstLetter(username)}</Avatar> | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { firstLetter } from "./first-letter" | ||
|
||
describe("first-letter", () => { | ||
it.each<[string, string]>([ | ||
["", ""], | ||
["User", "U"], | ||
["test", "T"], | ||
])(`firstLetter(%p) returns %p`, (input, expected) => { | ||
expect(firstLetter(input)).toBe(expected) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
/** | ||
* firstLetter extracts the first character and returns it, uppercased. | ||
*/ | ||
export const firstLetter = (str: string): string => { | ||
if (str.length > 0) { | ||
return str[0].toLocaleUpperCase() | ||
} | ||
|
||
return "" | ||
} |