mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add user roles to menu (#1862)
* view user roles in menu resolves #1524 * fix stories * PR feedback
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import Box from "@material-ui/core/Box"
|
||||
import { Story } from "@storybook/react"
|
||||
import React from "react"
|
||||
import { MockUser } from "../../testHelpers/entities"
|
||||
import { UserDropdown, UserDropdownProps } from "./UsersDropdown"
|
||||
|
||||
export default {
|
||||
@@ -17,9 +18,9 @@ const Template: Story<UserDropdownProps> = (args: UserDropdownProps) => (
|
||||
</Box>
|
||||
)
|
||||
|
||||
export const Example = Template.bind({})
|
||||
Example.args = {
|
||||
user: { id: "1", username: "CathyCoder", email: "cathy@coder.com", created_at: "dawn" },
|
||||
export const ExampleNoRoles = Template.bind({})
|
||||
ExampleNoRoles.args = {
|
||||
user: MockUser,
|
||||
onSignOut: () => {
|
||||
return Promise.resolve()
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { screen } from "@testing-library/react"
|
||||
import React from "react"
|
||||
import { MockUser } from "../../testHelpers/entities"
|
||||
import { MockAdminRole, MockMemberRole, MockUser } from "../../testHelpers/entities"
|
||||
import { render } from "../../testHelpers/renderHelpers"
|
||||
import { Language, UserDropdown, UserDropdownProps } from "./UsersDropdown"
|
||||
|
||||
@@ -33,6 +33,36 @@ describe("UserDropdown", () => {
|
||||
})
|
||||
|
||||
describe("when the menu is open", () => {
|
||||
it("displays the user's roles", async () => {
|
||||
await renderAndClick()
|
||||
|
||||
expect(screen.getByText(MockAdminRole.display_name)).toBeDefined()
|
||||
expect(screen.getByText(MockMemberRole.display_name)).toBeDefined()
|
||||
})
|
||||
|
||||
it("has the correct link for the documentation item", async () => {
|
||||
process.env.CODER_VERSION = "v0.5.4"
|
||||
await renderAndClick()
|
||||
|
||||
const link = screen.getByText(Language.docsLabel).closest("a")
|
||||
if (!link) {
|
||||
throw new Error("Anchor tag not found for the documentation menu item")
|
||||
}
|
||||
|
||||
expect(link.getAttribute("href")).toBe(`https://github.com/coder/coder/tree/${process.env.CODER_VERSION}/docs`)
|
||||
})
|
||||
|
||||
it("has the correct link for the account item", async () => {
|
||||
await renderAndClick()
|
||||
|
||||
const link = screen.getByText(Language.accountLabel).closest("a")
|
||||
if (!link) {
|
||||
throw new Error("Anchor tag not found for the account menu item")
|
||||
}
|
||||
|
||||
expect(link.getAttribute("href")).toBe("/settings/account")
|
||||
})
|
||||
|
||||
describe("and sign out is clicked", () => {
|
||||
it("calls the onSignOut function", async () => {
|
||||
const onSignOut = jest.fn()
|
||||
@@ -42,27 +72,4 @@ describe("UserDropdown", () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it("has the correct link for the documentation item", async () => {
|
||||
process.env.CODER_VERSION = "v0.5.4"
|
||||
await renderAndClick()
|
||||
|
||||
const link = screen.getByText(Language.docsLabel).closest("a")
|
||||
if (!link) {
|
||||
throw new Error("Anchor tag not found for the documentation menu item")
|
||||
}
|
||||
|
||||
expect(link.getAttribute("href")).toBe(`https://github.com/coder/coder/tree/${process.env.CODER_VERSION}/docs`)
|
||||
})
|
||||
|
||||
it("has the correct link for the account item", async () => {
|
||||
await renderAndClick()
|
||||
|
||||
const link = screen.getByText(Language.accountLabel).closest("a")
|
||||
if (!link) {
|
||||
throw new Error("Anchor tag not found for the account menu item")
|
||||
}
|
||||
|
||||
expect(link.getAttribute("href")).toBe("/settings/account")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { Story } from "@storybook/react"
|
||||
import React from "react"
|
||||
import { MockUser } from "../../testHelpers/entities"
|
||||
import { UserProfileCard, UserProfileCardProps } from "./UserProfileCard"
|
||||
|
||||
export default {
|
||||
title: "components/UserDropdown",
|
||||
component: UserProfileCard,
|
||||
argTypes: {
|
||||
onSignOut: { action: "Sign Out" },
|
||||
},
|
||||
}
|
||||
|
||||
const Template: Story<UserProfileCardProps> = (args: UserProfileCardProps) => <UserProfileCard {...args} />
|
||||
|
||||
export const ExampleNoRoles = Template.bind({})
|
||||
ExampleNoRoles.args = {
|
||||
user: {
|
||||
...MockUser,
|
||||
roles: [],
|
||||
},
|
||||
}
|
||||
|
||||
export const ExampleOneRole = Template.bind({})
|
||||
ExampleOneRole.args = {
|
||||
user: {
|
||||
...MockUser,
|
||||
roles: [{ name: "member", display_name: "Member" }],
|
||||
},
|
||||
}
|
||||
|
||||
export const ExampleThreeRoles = Template.bind({})
|
||||
ExampleThreeRoles.args = {
|
||||
user: {
|
||||
...MockUser,
|
||||
roles: [
|
||||
{ name: "admin", display_name: "Admin" },
|
||||
{ name: "member", display_name: "Member" },
|
||||
{ name: "auditor", display_name: "Auditor" },
|
||||
],
|
||||
},
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
import Chip from "@material-ui/core/Chip"
|
||||
import { makeStyles } from "@material-ui/core/styles"
|
||||
import Typography from "@material-ui/core/Typography"
|
||||
import React from "react"
|
||||
import * as TypesGen from "../../api/typesGenerated"
|
||||
import { Role } from "../../api/typesGenerated"
|
||||
import { UserAvatar } from "../UserAvatar/UserAvatar"
|
||||
|
||||
interface UserProfileCardProps {
|
||||
export interface UserProfileCardProps {
|
||||
user: TypesGen.User
|
||||
}
|
||||
|
||||
@@ -18,6 +20,13 @@ export const UserProfileCard: React.FC<UserProfileCardProps> = ({ user }) => {
|
||||
</div>
|
||||
<Typography className={styles.userName}>{user.username}</Typography>
|
||||
<Typography className={styles.userEmail}>{user.email}</Typography>
|
||||
<ul className={styles.chipContainer}>
|
||||
{user.roles.map((role: Role) => (
|
||||
<li key={role.name} className={styles.chipStyles}>
|
||||
<Chip classes={{ root: styles.chipRoot }} label={role.display_name} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -52,6 +61,19 @@ const useStyles = makeStyles((theme) => ({
|
||||
fontSize: 14,
|
||||
letterSpacing: 0.2,
|
||||
color: theme.palette.text.secondary,
|
||||
marginBottom: theme.spacing(1.5),
|
||||
},
|
||||
chipContainer: {
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
flexWrap: "wrap",
|
||||
listStyle: "none",
|
||||
margin: "0",
|
||||
padding: `${theme.spacing(1.5)}px ${theme.spacing(2.75)}px`,
|
||||
},
|
||||
chipStyles: {
|
||||
margin: theme.spacing(0.5),
|
||||
},
|
||||
chipRoot: {
|
||||
backgroundColor: "#7057FF",
|
||||
},
|
||||
}))
|
||||
|
||||
Reference in New Issue
Block a user