mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix: Role assign ui fixes (#3521)
Co-authored-by: Kira Pilot <kira@coder.com>
This commit is contained in:
co-authored by
Kira Pilot
parent
4b6a82f92a
commit
4be61d9250
+2
-2
@@ -311,8 +311,8 @@ export const updateUserPassword = async (
|
||||
updatePassword: TypesGen.UpdateUserPasswordRequest,
|
||||
): Promise<undefined> => axios.put(`/api/v2/users/${userId}/password`, updatePassword)
|
||||
|
||||
export const getSiteRoles = async (): Promise<Array<TypesGen.Role>> => {
|
||||
const response = await axios.get<Array<TypesGen.Role>>(`/api/v2/users/roles`)
|
||||
export const getSiteRoles = async (): Promise<Array<TypesGen.AssignableRoles>> => {
|
||||
const response = await axios.get<Array<TypesGen.AssignableRoles>>(`/api/v2/users/roles`)
|
||||
return response.data
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,11 @@ export interface AgentGitSSHKey {
|
||||
readonly private_key: string
|
||||
}
|
||||
|
||||
// From codersdk/roles.go
|
||||
export interface AssignableRoles extends Role {
|
||||
readonly assignable: boolean
|
||||
}
|
||||
|
||||
// From codersdk/users.go
|
||||
export interface AuthMethods {
|
||||
readonly password: boolean
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import { ComponentMeta, Story } from "@storybook/react"
|
||||
import { MockAdminRole, MockMemberRole, MockSiteRoles } from "../../testHelpers/renderHelpers"
|
||||
import {
|
||||
assignableRole,
|
||||
MockAuditorRole,
|
||||
MockMemberRole,
|
||||
MockOwnerRole,
|
||||
MockTemplateAdminRole,
|
||||
MockUserAdminRole,
|
||||
} from "../../testHelpers/renderHelpers"
|
||||
import { RoleSelect, RoleSelectProps } from "./RoleSelect"
|
||||
|
||||
export default {
|
||||
@@ -9,15 +16,30 @@ export default {
|
||||
|
||||
const Template: Story<RoleSelectProps> = (args) => <RoleSelect {...args} />
|
||||
|
||||
// Include 4 roles:
|
||||
// - owner (disabled, not checked)
|
||||
// - template admin (disabled, checked)
|
||||
// - auditor (enabled, not checked)
|
||||
// - user admin (enabled, checked)
|
||||
export const Close = Template.bind({})
|
||||
Close.args = {
|
||||
roles: MockSiteRoles,
|
||||
selectedRoles: [MockAdminRole, MockMemberRole],
|
||||
roles: [
|
||||
assignableRole(MockOwnerRole, false),
|
||||
assignableRole(MockTemplateAdminRole, false),
|
||||
assignableRole(MockAuditorRole, true),
|
||||
assignableRole(MockUserAdminRole, true),
|
||||
],
|
||||
selectedRoles: [MockUserAdminRole, MockTemplateAdminRole, MockMemberRole],
|
||||
}
|
||||
|
||||
export const Open = Template.bind({})
|
||||
Open.args = {
|
||||
open: true,
|
||||
roles: MockSiteRoles,
|
||||
selectedRoles: [MockAdminRole, MockMemberRole],
|
||||
roles: [
|
||||
assignableRole(MockOwnerRole, false),
|
||||
assignableRole(MockTemplateAdminRole, false),
|
||||
assignableRole(MockAuditorRole, true),
|
||||
assignableRole(MockUserAdminRole, true),
|
||||
],
|
||||
selectedRoles: [MockUserAdminRole, MockTemplateAdminRole, MockMemberRole],
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { screen } from "@testing-library/react"
|
||||
import {
|
||||
assignableRole,
|
||||
MockAuditorRole,
|
||||
MockMemberRole,
|
||||
MockOwnerRole,
|
||||
MockTemplateAdminRole,
|
||||
MockUserAdminRole,
|
||||
render,
|
||||
} from "testHelpers/renderHelpers"
|
||||
import { RoleSelect } from "./RoleSelect"
|
||||
|
||||
describe("UserRoleSelect", () => {
|
||||
it("renders content", async () => {
|
||||
// When
|
||||
render(
|
||||
<RoleSelect
|
||||
roles={[
|
||||
assignableRole(MockOwnerRole, false),
|
||||
assignableRole(MockTemplateAdminRole, false),
|
||||
assignableRole(MockAuditorRole, true),
|
||||
assignableRole(MockUserAdminRole, true),
|
||||
]}
|
||||
selectedRoles={[MockUserAdminRole, MockTemplateAdminRole, MockMemberRole]}
|
||||
loading={false}
|
||||
onChange={jest.fn()}
|
||||
open
|
||||
/>,
|
||||
)
|
||||
|
||||
// Then
|
||||
const owner = await screen.findByText(MockOwnerRole.display_name)
|
||||
const templateAdmin = await screen.findByText(MockTemplateAdminRole.display_name)
|
||||
const auditor = await screen.findByText(MockAuditorRole.display_name)
|
||||
const userAdmin = await screen.findByText(MockUserAdminRole.display_name)
|
||||
|
||||
// The attributes are "strings", not boolean types.
|
||||
expect(owner.getAttribute("aria-disabled")).toBe("true")
|
||||
expect(templateAdmin.getAttribute("aria-disabled")).toBe("true")
|
||||
|
||||
expect(userAdmin.getAttribute("aria-disabled")).toBe("false")
|
||||
expect(auditor.getAttribute("aria-disabled")).toBe("false")
|
||||
})
|
||||
})
|
||||
@@ -3,13 +3,13 @@ import MenuItem from "@material-ui/core/MenuItem"
|
||||
import Select from "@material-ui/core/Select"
|
||||
import { makeStyles, Theme } from "@material-ui/core/styles"
|
||||
import { FC } from "react"
|
||||
import { Role } from "../../api/typesGenerated"
|
||||
import { AssignableRoles, Role } from "../../api/typesGenerated"
|
||||
|
||||
export const Language = {
|
||||
label: "Roles",
|
||||
}
|
||||
export interface RoleSelectProps {
|
||||
roles: Role[]
|
||||
roles: AssignableRoles[]
|
||||
selectedRoles: Role[]
|
||||
onChange: (roles: Role["name"][]) => void
|
||||
loading?: boolean
|
||||
@@ -46,7 +46,7 @@ export const RoleSelect: FC<RoleSelectProps> = ({
|
||||
const isChecked = selectedRoles.some((selectedRole) => selectedRole.name === r.name)
|
||||
|
||||
return (
|
||||
<MenuItem key={r.name} value={r.name} disabled={loading}>
|
||||
<MenuItem key={r.name} value={r.name} disabled={loading || !r.assignable}>
|
||||
<Checkbox size="small" color="primary" checked={isChecked} /> {r.display_name}
|
||||
</MenuItem>
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { screen } from "@testing-library/react"
|
||||
import { MockAdminRole, MockUser } from "../../testHelpers/entities"
|
||||
import { MockOwnerRole, MockUser } from "../../testHelpers/entities"
|
||||
import { render } from "../../testHelpers/renderHelpers"
|
||||
import { Language, UserDropdownContent } from "./UserDropdownContent"
|
||||
|
||||
@@ -26,7 +26,7 @@ describe("UserDropdownContent", () => {
|
||||
it("displays the user's roles", () => {
|
||||
render(<UserDropdownContent user={MockUser} onSignOut={jest.fn()} onPopoverClose={jest.fn()} />)
|
||||
|
||||
expect(screen.getByText(MockAdminRole.display_name)).toBeDefined()
|
||||
expect(screen.getByText(MockOwnerRole.display_name)).toBeDefined()
|
||||
})
|
||||
|
||||
it("has the correct link for the account item", () => {
|
||||
|
||||
@@ -18,7 +18,7 @@ export const Language = {
|
||||
|
||||
export interface UsersTableProps {
|
||||
users?: TypesGen.User[]
|
||||
roles?: TypesGen.Role[]
|
||||
roles?: TypesGen.AssignableRoles[]
|
||||
isUpdatingUserRoles?: boolean
|
||||
canEditUsers?: boolean
|
||||
isLoading?: boolean
|
||||
|
||||
@@ -20,7 +20,7 @@ export const Language = {
|
||||
|
||||
interface UsersTableBodyProps {
|
||||
users?: TypesGen.User[]
|
||||
roles?: TypesGen.Role[]
|
||||
roles?: TypesGen.AssignableRoles[]
|
||||
isUpdatingUserRoles?: boolean
|
||||
canEditUsers?: boolean
|
||||
isLoading?: boolean
|
||||
|
||||
@@ -311,7 +311,7 @@ describe("Users Page", () => {
|
||||
}, MockAuditorRole)
|
||||
|
||||
// Check if the select text was updated with the Auditor role
|
||||
await waitFor(() => expect(rolesMenuTrigger).toHaveTextContent("Admin, Auditor"))
|
||||
await waitFor(() => expect(rolesMenuTrigger).toHaveTextContent("Owner, Auditor"))
|
||||
|
||||
// Check if the API was called correctly
|
||||
const currentRoles = MockUser.roles.map((r) => r.name)
|
||||
|
||||
@@ -17,7 +17,7 @@ export const Language = {
|
||||
|
||||
export interface UsersPageViewProps {
|
||||
users?: TypesGen.User[]
|
||||
roles?: TypesGen.Role[]
|
||||
roles?: TypesGen.AssignableRoles[]
|
||||
filter?: string
|
||||
error?: unknown
|
||||
isUpdatingUserRoles?: boolean
|
||||
|
||||
@@ -15,9 +15,19 @@ export const MockBuildInfo: TypesGen.BuildInfoResponse = {
|
||||
version: "v99.999.9999+c9cdf14",
|
||||
}
|
||||
|
||||
export const MockAdminRole: TypesGen.Role = {
|
||||
name: "admin",
|
||||
display_name: "Admin",
|
||||
export const MockOwnerRole: TypesGen.Role = {
|
||||
name: "owner",
|
||||
display_name: "Owner",
|
||||
}
|
||||
|
||||
export const MockUserAdminRole: TypesGen.Role = {
|
||||
name: "user_admin",
|
||||
display_name: "User Admin",
|
||||
}
|
||||
|
||||
export const MockTemplateAdminRole: TypesGen.Role = {
|
||||
name: "template_admin",
|
||||
display_name: "Template Admin",
|
||||
}
|
||||
|
||||
export const MockMemberRole: TypesGen.Role = {
|
||||
@@ -30,7 +40,16 @@ export const MockAuditorRole: TypesGen.Role = {
|
||||
display_name: "Auditor",
|
||||
}
|
||||
|
||||
export const MockSiteRoles = [MockAdminRole, MockAuditorRole]
|
||||
export const MockSiteRoles = [MockUserAdminRole, MockAuditorRole]
|
||||
|
||||
// assignableRole takes a role and a boolean. The boolean implies if the
|
||||
// actor can assign (add/remove) the role from other users.
|
||||
export function assignableRole(role: TypesGen.Role, assignable: boolean): TypesGen.AssignableRoles {
|
||||
return {
|
||||
...role,
|
||||
assignable: assignable,
|
||||
}
|
||||
}
|
||||
|
||||
export const MockUser: TypesGen.User = {
|
||||
id: "test-user",
|
||||
@@ -39,7 +58,7 @@ export const MockUser: TypesGen.User = {
|
||||
created_at: "",
|
||||
status: "active",
|
||||
organization_ids: ["fc0774ce-cc9e-48d4-80ae-88f7a4d4a8b0"],
|
||||
roles: [MockAdminRole],
|
||||
roles: [MockOwnerRole],
|
||||
}
|
||||
|
||||
export const MockUser2: TypesGen.User = {
|
||||
|
||||
@@ -8,7 +8,7 @@ export const Language = {
|
||||
}
|
||||
|
||||
type SiteRolesContext = {
|
||||
roles?: TypesGen.Role[]
|
||||
roles?: TypesGen.AssignableRoles[]
|
||||
getRolesError: Error | unknown
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ export const siteRolesMachine = createMachine(
|
||||
events: {} as SiteRolesEvent,
|
||||
services: {
|
||||
getRoles: {
|
||||
data: {} as TypesGen.Role[],
|
||||
data: {} as TypesGen.AssignableRoles[],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user