feat: Add update user roles action (#1361)

This commit is contained in:
Bruno Quaresma 2022-05-10 14:13:07 -05:00 committed by GitHub
parent c96d439f3d
commit 2df92e6fd3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 469 additions and 46 deletions

View File

@ -158,3 +158,16 @@ export const suspendUser = async (userId: TypesGen.User["id"]): Promise<TypesGen
export const updateUserPassword = async (password: string, userId: TypesGen.User["id"]): Promise<undefined> =>
axios.put(`/api/v2/users/${userId}/password`, { password })
export const getSiteRoles = async (): Promise<Array<TypesGen.Role>> => {
const response = await axios.get<Array<TypesGen.Role>>(`/api/v2/users/roles`)
return response.data
}
export const updateUserRoles = async (
roles: TypesGen.Role["name"][],
userId: TypesGen.User["id"],
): Promise<TypesGen.User> => {
const response = await axios.put<TypesGen.User>(`/api/v2/users/${userId}/roles`, { roles })
return response.data
}

View File

@ -0,0 +1,24 @@
import { ComponentMeta, Story } from "@storybook/react"
import React from "react"
import { MockAdminRole, MockMemberRole, MockSiteRoles } from "../../testHelpers"
import { RoleSelect, RoleSelectProps } from "./RoleSelect"
export default {
title: "components/RoleSelect",
component: RoleSelect,
} as ComponentMeta<typeof RoleSelect>
const Template: Story<RoleSelectProps> = (args) => <RoleSelect {...args} />
export const Close = Template.bind({})
Close.args = {
roles: MockSiteRoles,
selectedRoles: [MockAdminRole, MockMemberRole],
}
export const Open = Template.bind({})
Open.args = {
open: true,
roles: MockSiteRoles,
selectedRoles: [MockAdminRole, MockMemberRole],
}

View File

@ -0,0 +1,59 @@
import Checkbox from "@material-ui/core/Checkbox"
import MenuItem from "@material-ui/core/MenuItem"
import Select from "@material-ui/core/Select"
import { makeStyles, Theme } from "@material-ui/core/styles"
import React from "react"
import { Role } from "../../api/typesGenerated"
export const Language = {
label: "Roles",
}
export interface RoleSelectProps {
roles: Role[]
selectedRoles: Role[]
onChange: (roles: Role["name"][]) => void
loading?: boolean
open?: boolean
}
export const RoleSelect: React.FC<RoleSelectProps> = ({ roles, selectedRoles, loading, onChange, open }) => {
const styles = useStyles()
const value = selectedRoles.map((r) => r.name)
const renderValue = () => selectedRoles.map((r) => r.display_name).join(", ")
const sortedRoles = roles.sort((a, b) => a.display_name.localeCompare(b.display_name))
return (
<Select
aria-label={Language.label}
open={open}
multiple
value={value}
renderValue={renderValue}
variant="outlined"
className={styles.select}
onChange={(e) => {
const { value } = e.target
onChange(value as string[])
}}
>
{sortedRoles.map((r) => {
const isChecked = selectedRoles.some((selectedRole) => selectedRole.name === r.name)
return (
<MenuItem key={r.name} value={r.name} disabled={loading}>
<Checkbox color="primary" checked={isChecked} /> {r.display_name}
</MenuItem>
)
})}
</Select>
)
}
const useStyles = makeStyles((theme: Theme) => ({
select: {
margin: 0,
// Set a fixed width for the select. It avoids selects having different sizes
// depending on how many roles they have selected.
width: theme.spacing(25),
},
}))

View File

@ -8,10 +8,14 @@ export interface TableHeadersProps {
hasMenu?: boolean
}
export const TableHeaders: React.FC<TableHeadersProps> = ({ columns, hasMenu }) => {
export const TableHeaderRow: React.FC = ({ children }) => {
const styles = useStyles()
return <TableRow className={styles.root}>{children}</TableRow>
}
export const TableHeaders: React.FC<TableHeadersProps> = ({ columns, hasMenu }) => {
return (
<TableRow className={styles.root}>
<TableHeaderRow>
{columns.map((c, idx) => (
<TableCell key={idx} size="small">
{c}
@ -19,7 +23,7 @@ export const TableHeaders: React.FC<TableHeadersProps> = ({ columns, hasMenu })
))}
{/* 1% is a trick to make the table cell width fit the content */}
{hasMenu && <TableCell width="1%" />}
</TableRow>
</TableHeaderRow>
)
}

View File

@ -1,6 +1,6 @@
import { ComponentMeta, Story } from "@storybook/react"
import React from "react"
import { MockUser, MockUser2 } from "../../testHelpers"
import { MockSiteRoles, MockUser, MockUser2 } from "../../testHelpers"
import { UsersTable, UsersTableProps } from "./UsersTable"
export default {
@ -13,9 +13,11 @@ const Template: Story<UsersTableProps> = (args) => <UsersTable {...args} />
export const Example = Template.bind({})
Example.args = {
users: [MockUser, MockUser2],
roles: MockSiteRoles,
}
export const Empty = Template.bind({})
Empty.args = {
users: [],
roles: MockSiteRoles,
}

View File

@ -1,8 +1,17 @@
import Box from "@material-ui/core/Box"
import Table from "@material-ui/core/Table"
import TableBody from "@material-ui/core/TableBody"
import TableCell from "@material-ui/core/TableCell"
import TableHead from "@material-ui/core/TableHead"
import TableRow from "@material-ui/core/TableRow"
import React from "react"
import { UserResponse } from "../../api/types"
import * as TypesGen from "../../api/typesGenerated"
import { EmptyState } from "../EmptyState/EmptyState"
import { Column, Table } from "../Table/Table"
import { RoleSelect } from "../RoleSelect/RoleSelect"
import { TableHeaderRow } from "../TableHeaders/TableHeaders"
import { TableRowMenu } from "../TableRowMenu/TableRowMenu"
import { TableTitle } from "../TableTitle/TableTitle"
import { UserCell } from "../UserCell/UserCell"
export const Language = {
@ -12,48 +21,79 @@ export const Language = {
usernameLabel: "User",
suspendMenuItem: "Suspend",
resetPasswordMenuItem: "Reset password",
rolesLabel: "Roles",
}
const emptyState = <EmptyState message={Language.emptyMessage} />
const columns: Column<UserResponse>[] = [
{
key: "username",
name: Language.usernameLabel,
renderer: (field, data) => {
return <UserCell Avatar={{ username: data.username }} primaryText={data.username} caption={data.email} />
},
},
]
export interface UsersTableProps {
users: UserResponse[]
onSuspendUser: (user: UserResponse) => void
onResetUserPassword: (user: UserResponse) => void
onUpdateUserRoles: (user: UserResponse, roles: TypesGen.Role["name"][]) => void
roles: TypesGen.Role[]
isUpdatingUserRoles?: boolean
}
export const UsersTable: React.FC<UsersTableProps> = ({ users, onSuspendUser, onResetUserPassword }) => {
export const UsersTable: React.FC<UsersTableProps> = ({
users,
roles,
onSuspendUser,
onResetUserPassword,
onUpdateUserRoles,
isUpdatingUserRoles,
}) => {
return (
<Table
columns={columns}
data={users}
title={Language.usersTitle}
emptyState={emptyState}
rowMenu={(user) => (
<TableRowMenu
data={user}
menuItems={[
{
label: Language.suspendMenuItem,
onClick: onSuspendUser,
},
{
label: Language.resetPasswordMenuItem,
onClick: onResetUserPassword,
},
]}
/>
)}
/>
<Table>
<TableHead>
<TableTitle title={Language.usersTitle} />
<TableHeaderRow>
<TableCell size="small">{Language.usernameLabel}</TableCell>
<TableCell size="small">{Language.rolesLabel}</TableCell>
{/* 1% is a trick to make the table cell width fit the content */}
<TableCell size="small" width="1%" />
</TableHeaderRow>
</TableHead>
<TableBody>
{users.map((u) => (
<TableRow key={u.id}>
<TableCell>
<UserCell Avatar={{ username: u.username }} primaryText={u.username} caption={u.email} />{" "}
</TableCell>
<TableCell>
<RoleSelect
roles={roles}
selectedRoles={u.roles}
loading={isUpdatingUserRoles}
onChange={(roles) => onUpdateUserRoles(u, roles)}
/>
</TableCell>
<TableCell>
<TableRowMenu
data={u}
menuItems={[
{
label: Language.suspendMenuItem,
onClick: onSuspendUser,
},
{
label: Language.resetPasswordMenuItem,
onClick: onResetUserPassword,
},
]}
/>
</TableCell>
</TableRow>
))}
{users.length === 0 && (
<TableRow>
<TableCell colSpan={999}>
<Box p={4}>
<EmptyState message={Language.emptyMessage} />
</Box>
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
)
}

View File

@ -1,10 +1,12 @@
import { fireEvent, screen, waitFor, within } from "@testing-library/react"
import React from "react"
import * as API from "../../api"
import { Role } from "../../api/typesGenerated"
import { GlobalSnackbar } from "../../components/GlobalSnackbar/GlobalSnackbar"
import { Language as ResetPasswordDialogLanguage } from "../../components/ResetPasswordDialog/ResetPasswordDialog"
import { Language as RoleSelectLanguage } from "../../components/RoleSelect/RoleSelect"
import { Language as UsersTableLanguage } from "../../components/UsersTable/UsersTable"
import { MockUser, MockUser2, render } from "../../testHelpers"
import { MockAuditorRole, MockUser, MockUser2, render } from "../../testHelpers"
import { Language as usersXServiceLanguage } from "../../xServices/users/usersXService"
import { Language as UsersPageLanguage, UsersPage } from "./UsersPage"
@ -62,6 +64,34 @@ const resetUserPassword = async (setupActionSpies: () => void) => {
fireEvent.click(confirmButton)
}
const updateUserRole = async (setupActionSpies: () => void, role: Role) => {
// Get the first user in the table
const users = await screen.findAllByText(/.*@coder.com/)
const firstUserRow = users[0].closest("tr")
if (!firstUserRow) {
throw new Error("Error on get the first user row")
}
// Click on the "roles" menu to display the role options
const rolesLabel = within(firstUserRow).getByLabelText(RoleSelectLanguage.label)
const rolesMenuTrigger = within(rolesLabel).getByRole("button")
// For MUI v4, the Select was changed to open on mouseDown instead of click
// https://github.com/mui-org/material-ui/pull/17978
fireEvent.mouseDown(rolesMenuTrigger)
// Setup spies to check the actions after
setupActionSpies()
// Click on the role option
const listBox = screen.getByRole("listbox")
const auditorOption = within(listBox).getByRole("option", { name: role.display_name })
fireEvent.click(auditorOption)
return {
rolesMenuTrigger,
}
}
describe("Users Page", () => {
it("shows users", async () => {
render(<UsersPage />)
@ -164,4 +194,55 @@ describe("Users Page", () => {
})
})
})
describe("Update user role", () => {
describe("when it is success", () => {
it("updates the roles", async () => {
render(
<>
<UsersPage />
<GlobalSnackbar />
</>,
)
const { rolesMenuTrigger } = await updateUserRole(() => {
jest.spyOn(API, "updateUserRoles").mockResolvedValueOnce({
...MockUser,
roles: [...MockUser.roles, MockAuditorRole],
})
}, MockAuditorRole)
// Check if the select text was updated with the Auditor role
await waitFor(() => expect(rolesMenuTrigger).toHaveTextContent("Admin, Member, Auditor"))
// Check if the API was called correctly
const currentRoles = MockUser.roles.map((r) => r.name)
expect(API.updateUserRoles).toBeCalledTimes(1)
expect(API.updateUserRoles).toBeCalledWith([...currentRoles, MockAuditorRole.name], MockUser.id)
})
})
describe("when it fails", () => {
it("shows an error message", async () => {
render(
<>
<UsersPage />
<GlobalSnackbar />
</>,
)
await updateUserRole(() => {
jest.spyOn(API, "updateUserRoles").mockRejectedValueOnce({})
}, MockAuditorRole)
// Check if the error message is displayed
await screen.findByText(usersXServiceLanguage.updateUserRolesError)
// Check if the API was called correctly
const currentRoles = MockUser.roles.map((r) => r.name)
expect(API.updateUserRoles).toBeCalledTimes(1)
expect(API.updateUserRoles).toBeCalledWith([...currentRoles, MockAuditorRole.name], MockUser.id)
})
})
})
})

View File

@ -13,6 +13,23 @@ export const Language = {
suspendDialogMessagePrefix: "Do you want to suspend the user",
}
const useRoles = () => {
const xServices = useContext(XServiceContext)
const [rolesState, rolesSend] = useActor(xServices.siteRolesXService)
const { roles } = rolesState.context
/**
* Fetch roles on component mount
*/
useEffect(() => {
rolesSend({
type: "GET_ROLES",
})
}, [rolesSend])
return roles
}
export const UsersPage: React.FC = () => {
const xServices = useContext(XServiceContext)
const [usersState, usersSend] = useActor(xServices.usersXService)
@ -20,6 +37,7 @@ export const UsersPage: React.FC = () => {
const navigate = useNavigate()
const userToBeSuspended = users?.find((u) => u.id === userIdToSuspend)
const userToResetPassword = users?.find((u) => u.id === userIdToResetPassword)
const roles = useRoles()
/**
* Fetch users on component mount
@ -28,12 +46,13 @@ export const UsersPage: React.FC = () => {
usersSend("GET_USERS")
}, [usersSend])
if (!users) {
if (!users || !roles) {
return <FullScreenLoader />
} else {
return (
<>
<UsersPageView
roles={roles}
users={users}
openUserCreationDialog={() => {
navigate("/users/create")
@ -44,7 +63,15 @@ export const UsersPage: React.FC = () => {
onResetUserPassword={(user) => {
usersSend({ type: "RESET_USER_PASSWORD", userId: user.id })
}}
onUpdateUserRoles={(user, roles) => {
usersSend({
type: "UPDATE_USER_ROLES",
userId: user.id,
roles,
})
}}
error={getUsersError}
isUpdatingUserRoles={usersState.matches("updatingUserRoles")}
/>
<ConfirmDialog

View File

@ -1,6 +1,6 @@
import { ComponentMeta, Story } from "@storybook/react"
import React from "react"
import { MockUser, MockUser2 } from "../../testHelpers"
import { MockSiteRoles, MockUser, MockUser2 } from "../../testHelpers"
import { UsersPageView, UsersPageViewProps } from "./UsersPageView"
export default {
@ -13,8 +13,10 @@ const Template: Story<UsersPageViewProps> = (args) => <UsersPageView {...args} /
export const Ready = Template.bind({})
Ready.args = {
users: [MockUser, MockUser2],
roles: MockSiteRoles,
}
export const Empty = Template.bind({})
Empty.args = {
users: [],
roles: MockSiteRoles,
}

View File

@ -1,5 +1,6 @@
import React from "react"
import { UserResponse } from "../../api/types"
import * as TypesGen from "../../api/typesGenerated"
import { ErrorSummary } from "../../components/ErrorSummary/ErrorSummary"
import { Header } from "../../components/Header/Header"
import { Margins } from "../../components/Margins/Margins"
@ -16,15 +17,21 @@ export interface UsersPageViewProps {
openUserCreationDialog: () => void
onSuspendUser: (user: UserResponse) => void
onResetUserPassword: (user: UserResponse) => void
onUpdateUserRoles: (user: UserResponse, roles: TypesGen.Role["name"][]) => void
roles: TypesGen.Role[]
error?: unknown
isUpdatingUserRoles?: boolean
}
export const UsersPageView: React.FC<UsersPageViewProps> = ({
users,
roles,
openUserCreationDialog,
onSuspendUser,
onResetUserPassword,
onUpdateUserRoles,
error,
isUpdatingUserRoles,
}) => {
return (
<Stack spacing={4}>
@ -33,7 +40,14 @@ export const UsersPageView: React.FC<UsersPageViewProps> = ({
{error ? (
<ErrorSummary error={error} />
) : (
<UsersTable users={users} onSuspendUser={onSuspendUser} onResetUserPassword={onResetUserPassword} />
<UsersTable
users={users}
onSuspendUser={onSuspendUser}
onResetUserPassword={onResetUserPassword}
onUpdateUserRoles={onUpdateUserRoles}
roles={roles}
isUpdatingUserRoles={isUpdatingUserRoles}
/>
)}
</Margins>
</Stack>

View File

@ -10,7 +10,7 @@ import {
WorkspaceAutostartRequest,
WorkspaceResource,
} from "../api/types"
import { AuthMethods } from "../api/typesGenerated"
import { AuthMethods, Role } from "../api/typesGenerated"
export const MockSessionToken = { session_token: "my-session-token" }
@ -21,6 +21,23 @@ export const MockBuildInfo: BuildInfoResponse = {
version: "v99.999.9999+c9cdf14",
}
export const MockAdminRole: Role = {
name: "admin",
display_name: "Admin",
}
export const MockMemberRole: Role = {
name: "member",
display_name: "Member",
}
export const MockAuditorRole: Role = {
name: "auditor",
display_name: "Auditor",
}
export const MockSiteRoles = [MockAdminRole, MockAuditorRole, MockMemberRole]
export const MockUser: UserResponse = {
id: "test-user",
username: "TestUser",
@ -28,7 +45,7 @@ export const MockUser: UserResponse = {
created_at: "",
status: "active",
organization_ids: ["fc0774ce-cc9e-48d4-80ae-88f7a4d4a8b0"],
roles: [],
roles: [MockAdminRole, MockMemberRole],
}
export const MockUser2: UserResponse = {
@ -38,7 +55,7 @@ export const MockUser2: UserResponse = {
created_at: "",
status: "active",
organization_ids: ["fc0774ce-cc9e-48d4-80ae-88f7a4d4a8b0"],
roles: [],
roles: [MockMemberRole],
}
export const MockOrganization: Organization = {

View File

@ -51,6 +51,9 @@ export const handlers = [
rest.get("/api/v2/users/authmethods", async (req, res, ctx) => {
return res(ctx.status(200), ctx.json(M.MockAuthMethods))
}),
rest.get("/api/v2/users/roles", async (req, res, ctx) => {
return res(ctx.status(200), ctx.json(M.MockSiteRoles))
}),
// workspaces
rest.get("/api/v2/organizations/:organizationId/workspaces/:userName/:workspaceName", (req, res, ctx) => {

View File

@ -4,6 +4,7 @@ import { useNavigate } from "react-router"
import { ActorRefFrom } from "xstate"
import { authMachine } from "./auth/authXService"
import { buildInfoMachine } from "./buildInfo/buildInfoXService"
import { siteRolesMachine } from "./roles/siteRolesXService"
import { usersMachine } from "./users/usersXService"
import { workspaceMachine } from "./workspace/workspaceXService"
@ -12,6 +13,7 @@ interface XServiceContextType {
buildInfoXService: ActorRefFrom<typeof buildInfoMachine>
usersXService: ActorRefFrom<typeof usersMachine>
workspaceXService: ActorRefFrom<typeof workspaceMachine>
siteRolesXService: ActorRefFrom<typeof siteRolesMachine>
}
/**
@ -37,6 +39,7 @@ export const XServiceProvider: React.FC = ({ children }) => {
buildInfoXService: useInterpret(buildInfoMachine),
usersXService: useInterpret(() => usersMachine.withConfig({ actions: { redirectToUsersPage } })),
workspaceXService: useInterpret(workspaceMachine),
siteRolesXService: useInterpret(siteRolesMachine),
}}
>
{children}

View File

@ -0,0 +1,75 @@
import { assign, createMachine } from "xstate"
import * as API from "../../api"
import * as TypesGen from "../../api/typesGenerated"
import { displayError } from "../../components/GlobalSnackbar/utils"
export const Language = {
getRolesError: "Error on get the roles.",
}
type SiteRolesContext = {
roles?: TypesGen.Role[]
getRolesError: Error | unknown
}
type SiteRolesEvent = {
type: "GET_ROLES"
}
export const siteRolesMachine = createMachine(
{
id: "siteRolesState",
initial: "idle",
schema: {
context: {} as SiteRolesContext,
events: {} as SiteRolesEvent,
services: {
getRoles: {
data: {} as TypesGen.Role[],
},
},
},
tsTypes: {} as import("./siteRolesXService.typegen").Typegen0,
states: {
idle: {
on: {
GET_ROLES: "gettingRoles",
},
},
gettingRoles: {
entry: "clearGetRolesError",
invoke: {
id: "getRoles",
src: "getRoles",
onDone: {
target: "idle",
actions: ["assignRoles"],
},
onError: {
target: "idle",
actions: ["assignGetRolesError", "displayGetRolesError"],
},
},
},
},
},
{
actions: {
assignRoles: assign({
roles: (_, event) => event.data,
}),
assignGetRolesError: assign({
getRolesError: (_, event) => event.data,
}),
displayGetRolesError: () => {
displayError(Language.getRolesError)
},
clearGetRolesError: assign({
getRolesError: (_) => undefined,
}),
},
services: {
getRoles: () => API.getSiteRoles(),
},
},
)

View File

@ -12,6 +12,8 @@ export const Language = {
suspendUserError: "Error on suspend the user.",
resetUserPasswordSuccess: "Successfully updated the user password.",
resetUserPasswordError: "Error on reset the user password.",
updateUserRolesSuccess: "Successfully updated the user roles.",
updateUserRolesError: "Error on update the user roles.",
}
export interface UsersContext {
@ -27,6 +29,9 @@ export interface UsersContext {
userIdToResetPassword?: TypesGen.User["id"]
resetUserPasswordError?: Error | unknown
newUserPassword?: string
// Update user roles
userIdToUpdateRoles?: TypesGen.User["id"]
updateUserRolesError?: Error | unknown
}
export type UsersEvent =
@ -40,6 +45,8 @@ export type UsersEvent =
| { type: "RESET_USER_PASSWORD"; userId: TypesGen.User["id"] }
| { type: "CONFIRM_USER_PASSWORD_RESET" }
| { type: "CANCEL_USER_PASSWORD_RESET" }
// Update roles events
| { type: "UPDATE_USER_ROLES"; userId: TypesGen.User["id"]; roles: TypesGen.Role["name"][] }
export const usersMachine = createMachine(
{
@ -60,6 +67,9 @@ export const usersMachine = createMachine(
updateUserPassword: {
data: undefined
}
updateUserRoles: {
data: TypesGen.User
}
},
},
id: "usersState",
@ -80,6 +90,10 @@ export const usersMachine = createMachine(
target: "confirmUserPasswordReset",
actions: ["assignUserIdToResetPassword", "generateRandomPassword"],
},
UPDATE_USER_ROLES: {
target: "updatingUserRoles",
actions: ["assignUserIdToUpdateRoles"],
},
},
},
gettingUsers: {
@ -166,6 +180,21 @@ export const usersMachine = createMachine(
},
},
},
updatingUserRoles: {
entry: "clearUpdateUserRolesError",
invoke: {
src: "updateUserRoles",
id: "updateUserRoles",
onDone: {
target: "idle",
actions: ["updateUserRolesInTheList"],
},
onError: {
target: "idle",
actions: ["assignUpdateRolesError", "displayUpdateRolesErrorMessage"],
},
},
},
error: {
on: {
GET_USERS: "gettingUsers",
@ -198,6 +227,13 @@ export const usersMachine = createMachine(
return API.updateUserPassword(context.newUserPassword, context.userIdToResetPassword)
},
updateUserRoles: (context, event) => {
if (!context.userIdToUpdateRoles) {
throw new Error("userIdToUpdateRoles is undefined")
}
return API.updateUserRoles(event.roles, context.userIdToUpdateRoles)
},
},
guards: {
isFormError: (_, event) => isApiError(event.data),
@ -215,6 +251,9 @@ export const usersMachine = createMachine(
assignUserIdToResetPassword: assign({
userIdToResetPassword: (_, event) => event.userId,
}),
assignUserIdToUpdateRoles: assign({
userIdToUpdateRoles: (_, event) => event.userId,
}),
clearGetUsersError: assign((context: UsersContext) => ({
...context,
getUsersError: undefined,
@ -232,6 +271,9 @@ export const usersMachine = createMachine(
assignResetUserPasswordError: assign({
resetUserPasswordError: (_, event) => event.data,
}),
assignUpdateRolesError: assign({
updateUserRolesError: (_, event) => event.data,
}),
clearCreateUserError: assign((context: UsersContext) => ({
...context,
createUserError: undefined,
@ -242,6 +284,9 @@ export const usersMachine = createMachine(
clearResetUserPasswordError: assign({
resetUserPasswordError: (_) => undefined,
}),
clearUpdateUserRolesError: assign({
updateUserRolesError: (_) => undefined,
}),
displayCreateUserSuccess: () => {
displaySuccess(Language.createUserSuccess)
},
@ -257,9 +302,23 @@ export const usersMachine = createMachine(
displayResetPasswordErrorMessage: () => {
displayError(Language.resetUserPasswordError)
},
displayUpdateRolesErrorMessage: () => {
displayError(Language.updateUserRolesError)
},
generateRandomPassword: assign({
newUserPassword: (_) => generateRandomString(12),
}),
updateUserRolesInTheList: assign({
users: ({ users }, event) => {
if (!users) {
return users
}
return users.map((u) => {
return u.id === event.data.id ? event.data : u
})
},
}),
},
},
)