mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add count endpoint for users, enabling better pagination (#4848)
* Start on backend * Hook up frontend * Add to frontend test * Add go test, wip * Fix some test bugs * Fix test * Format * Add to authorize.go * copy user array into local variable * Authorize route * Log count error * Authorize better * Tweaks to authorization * More authorization tweaks * Make gen * Fix test Co-authored-by: Garrett <garrett@coder.com>
This commit is contained in:
@@ -139,6 +139,14 @@ export const getUsers = async (
|
||||
return response.data
|
||||
}
|
||||
|
||||
export const getUserCount = async (
|
||||
options: TypesGen.UserCountRequest,
|
||||
): Promise<TypesGen.UserCountResponse> => {
|
||||
const url = getURLWithSearchParams("/api/v2/users/count", options)
|
||||
const response = await axios.get(url.toString())
|
||||
return response.data
|
||||
}
|
||||
|
||||
export const getOrganization = async (
|
||||
organizationId: string,
|
||||
): Promise<TypesGen.Organization> => {
|
||||
|
||||
@@ -746,6 +746,16 @@ export interface User {
|
||||
readonly avatar_url: string
|
||||
}
|
||||
|
||||
// From codersdk/users.go
|
||||
export interface UserCountRequest {
|
||||
readonly q?: string
|
||||
}
|
||||
|
||||
// From codersdk/users.go
|
||||
export interface UserCountResponse {
|
||||
readonly count: number
|
||||
}
|
||||
|
||||
// From codersdk/users.go
|
||||
export interface UserRoles {
|
||||
readonly roles: string[]
|
||||
|
||||
@@ -240,7 +240,7 @@ describe("UsersPage", () => {
|
||||
|
||||
describe("pagination", () => {
|
||||
it("goes to next and previous page", async () => {
|
||||
renderPage()
|
||||
const { container } = renderPage()
|
||||
const user = userEvent.setup()
|
||||
|
||||
const mock = jest
|
||||
@@ -248,6 +248,9 @@ describe("UsersPage", () => {
|
||||
.mockResolvedValueOnce([MockUser, MockUser2])
|
||||
|
||||
const nextButton = await screen.findByLabelText("Next page")
|
||||
expect(nextButton).toBeEnabled()
|
||||
const previousButton = await screen.findByLabelText("Previous page")
|
||||
expect(previousButton).toBeDisabled()
|
||||
await user.click(nextButton)
|
||||
|
||||
await waitFor(() =>
|
||||
@@ -255,12 +258,17 @@ describe("UsersPage", () => {
|
||||
)
|
||||
|
||||
mock.mockClear()
|
||||
const previousButton = await screen.findByLabelText("Previous page")
|
||||
await user.click(previousButton)
|
||||
|
||||
await waitFor(() =>
|
||||
expect(API.getUsers).toBeCalledWith({ offset: 0, limit: 25, q: "" }),
|
||||
)
|
||||
|
||||
const pageButtons = await container.querySelectorAll(
|
||||
`button[name="Page button"]`,
|
||||
)
|
||||
// count handler says there are 2 pages of results
|
||||
expect(pageButtons.length).toBe(2)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useActor, useMachine } from "@xstate/react"
|
||||
import { getErrorDetail } from "api/errors"
|
||||
import { User } from "api/typesGenerated"
|
||||
import { DeleteDialog } from "components/Dialogs/DeleteDialog/DeleteDialog"
|
||||
import { getPaginationContext } from "components/PaginationWidget/utils"
|
||||
@@ -44,12 +45,14 @@ export const UsersPage: FC<{ children?: ReactNode }> = () => {
|
||||
const {
|
||||
users,
|
||||
getUsersError,
|
||||
getCountError,
|
||||
usernameToDelete,
|
||||
usernameToSuspend,
|
||||
usernameToActivate,
|
||||
userIdToResetPassword,
|
||||
newUserPassword,
|
||||
paginationRef,
|
||||
count,
|
||||
} = usersState.context
|
||||
|
||||
const { updateUsers: canEditUsers } = usePermissions()
|
||||
@@ -60,7 +63,7 @@ export const UsersPage: FC<{ children?: ReactNode }> = () => {
|
||||
// - users are loading or
|
||||
// - the user can edit the users but the roles are loading
|
||||
const isLoading =
|
||||
usersState.matches("gettingUsers") ||
|
||||
usersState.matches("users.gettingUsers") ||
|
||||
(canEditUsers && rolesState.matches("gettingRoles"))
|
||||
|
||||
// Fetch roles on component mount
|
||||
@@ -73,6 +76,10 @@ export const UsersPage: FC<{ children?: ReactNode }> = () => {
|
||||
}
|
||||
}, [canEditUsers, rolesSend])
|
||||
|
||||
if (getCountError) {
|
||||
console.error(getErrorDetail(getCountError))
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
@@ -81,6 +88,7 @@ export const UsersPage: FC<{ children?: ReactNode }> = () => {
|
||||
<UsersPageView
|
||||
roles={roles}
|
||||
users={users}
|
||||
count={count}
|
||||
onListWorkspaces={(user) => {
|
||||
navigate(
|
||||
"/workspaces?filter=" +
|
||||
@@ -119,7 +127,7 @@ export const UsersPage: FC<{ children?: ReactNode }> = () => {
|
||||
})
|
||||
}}
|
||||
error={getUsersError}
|
||||
isUpdatingUserRoles={usersState.matches("updatingUserRoles")}
|
||||
isUpdatingUserRoles={usersState.matches("users.updatingUserRoles")}
|
||||
isLoading={isLoading}
|
||||
canEditUsers={canEditUsers}
|
||||
filter={usersState.context.filter}
|
||||
@@ -131,10 +139,10 @@ export const UsersPage: FC<{ children?: ReactNode }> = () => {
|
||||
|
||||
<DeleteDialog
|
||||
isOpen={
|
||||
usersState.matches("confirmUserDeletion") ||
|
||||
usersState.matches("deletingUser")
|
||||
usersState.matches("users.confirmUserDeletion") ||
|
||||
usersState.matches("users.deletingUser")
|
||||
}
|
||||
confirmLoading={usersState.matches("deletingUser")}
|
||||
confirmLoading={usersState.matches("users.deletingUser")}
|
||||
name={usernameToDelete ?? ""}
|
||||
entity="user"
|
||||
onConfirm={() => {
|
||||
@@ -149,10 +157,10 @@ export const UsersPage: FC<{ children?: ReactNode }> = () => {
|
||||
type="delete"
|
||||
hideCancel={false}
|
||||
open={
|
||||
usersState.matches("confirmUserSuspension") ||
|
||||
usersState.matches("suspendingUser")
|
||||
usersState.matches("users.confirmUserSuspension") ||
|
||||
usersState.matches("users.suspendingUser")
|
||||
}
|
||||
confirmLoading={usersState.matches("suspendingUser")}
|
||||
confirmLoading={usersState.matches("users.suspendingUser")}
|
||||
title={Language.suspendDialogTitle}
|
||||
confirmText={Language.suspendDialogAction}
|
||||
onConfirm={() => {
|
||||
@@ -174,10 +182,10 @@ export const UsersPage: FC<{ children?: ReactNode }> = () => {
|
||||
type="success"
|
||||
hideCancel={false}
|
||||
open={
|
||||
usersState.matches("confirmUserActivation") ||
|
||||
usersState.matches("activatingUser")
|
||||
usersState.matches("users.confirmUserActivation") ||
|
||||
usersState.matches("users.activatingUser")
|
||||
}
|
||||
confirmLoading={usersState.matches("activatingUser")}
|
||||
confirmLoading={usersState.matches("users.activatingUser")}
|
||||
title={Language.activateDialogTitle}
|
||||
confirmText={Language.activateDialogAction}
|
||||
onConfirm={() => {
|
||||
@@ -198,10 +206,10 @@ export const UsersPage: FC<{ children?: ReactNode }> = () => {
|
||||
{userIdToResetPassword && (
|
||||
<ResetPasswordDialog
|
||||
open={
|
||||
usersState.matches("confirmUserPasswordReset") ||
|
||||
usersState.matches("resettingUserPassword")
|
||||
usersState.matches("users.confirmUserPasswordReset") ||
|
||||
usersState.matches("users.resettingUserPassword")
|
||||
}
|
||||
loading={usersState.matches("resettingUserPassword")}
|
||||
loading={usersState.matches("users.resettingUserPassword")}
|
||||
user={getSelectedUser(userIdToResetPassword, users)}
|
||||
newPassword={newUserPassword}
|
||||
onClose={() => {
|
||||
|
||||
@@ -12,6 +12,7 @@ export const Language = {
|
||||
}
|
||||
export interface UsersPageViewProps {
|
||||
users?: TypesGen.User[]
|
||||
count?: number
|
||||
roles?: TypesGen.AssignableRoles[]
|
||||
filter?: string
|
||||
error?: unknown
|
||||
@@ -33,6 +34,7 @@ export interface UsersPageViewProps {
|
||||
|
||||
export const UsersPageView: FC<React.PropsWithChildren<UsersPageViewProps>> = ({
|
||||
users,
|
||||
count,
|
||||
roles,
|
||||
onSuspendUser,
|
||||
onDeleteUser,
|
||||
@@ -76,7 +78,7 @@ export const UsersPageView: FC<React.PropsWithChildren<UsersPageViewProps>> = ({
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
|
||||
<PaginationWidget paginationRef={paginationRef} />
|
||||
<PaginationWidget numRecords={count} paginationRef={paginationRef} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -82,6 +82,10 @@ export const MockUser: TypesGen.User = {
|
||||
last_seen_at: "",
|
||||
}
|
||||
|
||||
export const MockUserCountResponse: TypesGen.UserCountResponse = {
|
||||
count: 26,
|
||||
}
|
||||
|
||||
export const MockUserAdmin: TypesGen.User = {
|
||||
id: "test-user",
|
||||
username: "TestUser",
|
||||
|
||||
@@ -74,8 +74,8 @@ export const handlers = [
|
||||
ctx.json([M.MockUser, M.MockUser2, M.SuspendedMockUser]),
|
||||
)
|
||||
}),
|
||||
rest.post("/api/v2/users", async (req, res, ctx) => {
|
||||
return res(ctx.status(200), ctx.json(M.MockUser))
|
||||
rest.get("/api/v2/users/count", async (req, res, ctx) => {
|
||||
return res(ctx.status(200), ctx.json(M.MockUserCountResponse))
|
||||
}),
|
||||
rest.get("/api/v2/users/me/organizations", (req, res, ctx) => {
|
||||
return res(ctx.status(200), ctx.json([M.MockOrganization]))
|
||||
|
||||
@@ -57,6 +57,8 @@ export interface UsersContext {
|
||||
updateUserRolesError?: Error | unknown
|
||||
paginationContext: PaginationContext
|
||||
paginationRef: PaginationMachineRef
|
||||
count: number
|
||||
getCountError: Error | unknown
|
||||
}
|
||||
|
||||
export type UsersEvent =
|
||||
@@ -101,7 +103,7 @@ export type UsersEvent =
|
||||
| { type: "UPDATE_PAGE"; page: string }
|
||||
|
||||
export const usersMachine =
|
||||
/** @xstate-layout N4IgpgJg5mDOIC5QFdZgE6wMoBcCGOYAdLPujgJYB2UACnlNQRQPZUDEA2gAwC6ioAA4tYFSmwEgAHogCMADiLcA7AFYVAFgCcW5bNkaAbACZDAGhABPOVsNEAzN1XGDhw92PduW+wF9fFqgY2PiERDA4lDQAqmiY7BBsxNQAbiwA1sQRscE8-EggwqLiVJIyCPKyRHryyk4m9lrGzfYW1ggaGqpERvaq9soasqrKfgEgQZi4BFlgkdRQOfEY6CzoRIIANgQAZmsAtuFzS7B5kkVirKUF5QC0ssrGRLV1rs2VyrptiJ3K1cbyHQaUyqSoDVT+QJxEIzIgUCCbMDsLDRLC0ACiADkACIAfVR6IASmcChcSmVEMZOkRDPpgYZ5J4NNx5PZWlY5A8qvZjH15Nx7IZVFp5KoNJCJtDpmF4Yj2Nj0QAZdEAFXR+KwRJJQhElwkN0phi0RAMqncjIZo0Z3wqtSIOgZGg+3HNsglkxhMoRSIAggBhFUASQAaj61RqtXxzrryQaEKZ7Pb7KLBQCTLJ3Kobe5E2adINjE1DE7jO6paFkt72IT0ZqVRHCbjaD6sFgAOoAeUJ2O1hRjVwp8dkxq6A2UFuTyhMWY5CFcdlkhbB8n5vNBZeC0srcuitGxYfVBMbhI7yqwvbJA7jtwBfwF3lG-WHPhtjO4NIZ7lk9Q0fI3UwrOEq13fdw2bABxdEL37fVQDuHk7BUTQnEcAEdGzLoaQtdQ+h8Ywp3-T1tyRECD1xAAxQNFTVYko1JGDrjgxB7m6LQjABfCtBUYduFkV9h2qAZv3w5wuIhcYPS3IgAGM2B2Ch0H2JYsFQQQwCoUQ2HYP0O0xSjCQAWQbXEUTRLEsEDXToOKK8mNtRMCyFXpPjcLQbX0FcTU+EtnFwhlCKk2SqHkxTlNU9TNI4P0fUxP0lWM0yMUxCyrLonUbNg6REFBbpmU+LiHipbhOnc-D3xXUURVpWlioCwCgpCpS4mxMBERKbTdP0oyj1xBVlTVay9UYrKKh8e1vHkbRgRUBobS0fQelFRc9F-Nj5EMOrYQahSmowFq2qubSYrixVjL61UoLSvsMuG8paSeYZ7AzNlDHHN65rcGliv5AVPjNExNrCbbQriH1pMoFJmC0nS9MDQzjP9INQyDVL8nSobBxeD8BnsTops6NzZ0MXHFrZKk2NUfQNok8strknaljBiGoai474p6xGQzDSzMUG2M7OFVjuMXQVeNUSmbTqRNlt-NwnuUR5xRpzdANgcKqAgBYlgSJI4SoNJMhIdWICWPnbJG4ZFxNcX+XHbxhzUOa6i8o1vBFdRaTdZWANhNXYDUjWtbidgVjWDZthwPZFKN-31JNuIzcy8oPPfEYXEm3R8NGPjZ1kRwNCUYU8-w2Xv3EqEVdhCBWrmIOMB1qhkn1jJiGrtqwFNq7LyTuRKeNAxmS0UENEeXjJZGapmRGEfVrTwHW5rqJFmD0P1i2XYDiINu5g7hOu4Ywd9CH6oWVFV4uItdzeONMU6TZWpFxH+eiDwcGKEhpftcSRu9YN4hX+ZoQTuaNroYzjJbRMPgeQujUOob85hZxmlTrSEYLpTAKDNM-AB79mAxBXugVYa8I5R0ONgj+u8MCJ1upyWwk9vwsmTMVAw48C7aAZFyMUU59DP2BrtdA9BYCwAAO5rAgISOAcwOqw3hj1ZsrZOzdlxDWOsVDMZimqPAgwxhKbixFO5HCX1ibaABJTV6ygeH0xBhgARwjRHiLQDgI6sV2aakbHI9sXY8TKNVKouMad7T9FsMTI0Csnr6LtAYUWahFxMnMd7IiRB0ASPmHg6xeBBEiPQBABuTc-6JOSUsGxmSIC+LsnndRecmjqD0KMfC7lxwLlMLofot5RTUwrj7MISSHGfziEU0RIcCFh3XpHTe3Tjh9PSbYrJpSLYPGNCoRwoJ3iihXO5IUTwRS6BqMWAYwJn7IEEBAXBy8MCEhYIiWAOTf4tyIIc45QC4jnMubMu4VoTTFSFG+SpGgPp-CnFA4SzhtDl0lJXMI9yTlLGeXAQZhDw4b2jpCx5ZyLlwFecxL8Dghi6FegoPogp+LGlGHoDwahCxOH8OMKgLBq7wAKJJVWZAl70EYFQFm0YbqDluM4E+nxPgsmLCKXkr5uiFktDob8zJXpKw6QkiIvTgicrAXZW47geiSqGHigEi4ZztBBEQfobIjWeBiVoZ+sowDKv5hbHkzwmiDB5M0NwYpfmzjUAXZwZ9OjfjYk9CxwUGZxBUrHDS5tu7UIQKCRQXEhgjzluUnO7Qj4F0qBgqBeF2lgs6cQXhSx9q10yhGwcJgnhGlpI8IeT5xZzQWk6dQ6h+QPHFmMOVgVLF8KZjgm1xa4zVUnkaZkuMvBDD1YgIxpMeRTUphmZ+fsA6a1Sega15tk4ZkUEXV2TgxTCnkO5YYfwnoKHFVSSccS22AW3oq5d9EuXgIUFUHQvFTCvAMHo2cK4C4eCEn0dwXQHhYLfh-OuN70Y2rXWNbRDribzSNUm8dngTTfkBLyFB8aA2NUKVM4p9i5grp7lG+ahq1AijzsCcl8G5wGPcEYpoS0zHP3GSk05-DsOiPw5GjyC5nBeFZOaDwY65xsI-GoWoDb1pqAOUcqFTy0X0rA6u5inR3xrlsCyOoAohhzSMPaGpr03C8TZPPDj3KGkfKMMswzbEbS3AcgKRcSEnArkGPIKlvggA */
|
||||
/** @xstate-layout N4IgpgJg5mDOIC5QFdZgE6wMoBcCGOYAdAMYD2yAdjkTDjgJaVQDCF1AxBGZcUwG5kA1sToBVNOjZUcAbQAMAXUSgADmVgNGPFSAAeiAIwBWQ-KIBmABwAWCwHYrAJhcA2YwE4ANCACeiZ0Miew8LBxsrCw8Pe1MAXzifVAxsfEJSdho6RmZpTgx0MnQiVQAbAgAzIoBbWjAcCQw8uSVddU1tSl0DBEMneSDXC0MrQ2G3KysffwRo4yJjYxtlh1c1m2NXBKTJVIJichkOMQAFABEAQQAVAFEAfQAxAEkAGVuAJQVlJBB2rQYdD8en1DB4iE5NvYbCMokNvH5EK4NkRbI5XIYbE5DGNDPZtiBkphcPsiITYERYPh0DkoCc8FAmAQAZQOF82hp-oDQD0ALRQ8zyEIWYz2dwhJw2VzTRGOIiGdyjJwWSXyZV4xIE3bE9Jkur0JhQRqYLg8PiUQQiPVG2Bsn5-TrdRA84wuYLyVxWexRDy2Vz2ezShCRGwLRZ+0VOWzGT34sna4i67IG60cApFErlHBVdC1cS7W1qDkOoFOxxOSxK7HySKReSmQNWd0LEJ9L1OWLGeQeWNatIJ3ZEBgQUpgDhYMRYE43AByZzuE5un1adqLzMdCB5SNcKOhNdcddiHsDGJCKOjHjGStsZicPZS8dJA6HI44ZxuLxut3nWEXBd+q65fQnSReZrEcKwfSsJZNgsY9hkGNUIklRZBQsO8iT7R8UkHYdRwuFgrieAA1a57gXJdvkLDo1xLDcQKId1llcGIoUiJx4RmbEbDBP1QXFGIIVFdC9h1J9cI4d4bh-K5v0XO4TguLAsAAdQAeXeM4-3tGjuWAmx7Dlex2MlCEPCGGxjyGbcbEFDxBRskx7FxYSH11Z9R1OS4v3Iu53lUj8sC0gCulonkfW3eUsRiDEfWMaxjxGAy4rDD1wwxLYNTjTC3PEzzSPki4AHEbiC6jAN5DxJTlDEIOhD15TsQMPE7Bi1n9Vs7MbdUdnvbKB3ISgKgYHMjSwVBVDAShNB4DgWFU6dnneABZWT3jucdJxnLAnnm0rORC3SNzMCxgghCw1ja7ioUMY9uKsBiJU2aElVFDEXL67CBqGkbJDG2AJqm5lZouacWHfVb1onKdp223blyo-b1x5Gz7rsMZ3XkeQbLA49NnmDwJXdF1qz9DKeowkldS+4bqiNM4wBHTpZvmxaVp8t8P1uPbi0OnkuIWJEoQ2DY4vsQU4OFOUlXkFw4osLtIneyn+p4b7ackenGaBlgQbBl4IY5z8Svh-8yoOoCNwcAyYjssJG0lFiLIRXobPLaIImDOFFiV0TPtVmmjQuEhGH4JkZrmhanmWiH8MIkjCLhyjTcR0KQSIAmhlFRsBkjetnexIYiFcdsBn9OEZbJzVeuVv3BoDyQg5DsOWR10HwZ82PiOuHbp25nSLZ5ax7sbSYsRcUZroDfOlW3CVIzM1UK5dH3+2w2BxsmiBk0kE1eEHc1hGIdf-s3o0+-KxAy4WAnVUjEY62hW7C-YjHOyWUYYhXrDMApDfKC35gRpUzoEKMUMolQai-xPv-M+JttIXwQKYAyZYGqhCGC4G6+dzrzFMJEHE1ZpaVyyjXH+EAGb1G3hgXeZoLTEDIYzMAsCk7wPNj0RwJ1oyqkSo4aITg4LYiLohX0KFYhf11PQihgCd5pjAZmbMtQJGECYeyM2644puxGJMJYJkHAcSMFiEM1ZMaRCHpiSUYiBx4GDgwUONIgHcD3gIQ+RArFNyUZIc+rDL6ynOpVAYSIIJlmPF2csY8RieGatCMYFjsKuJsUyKRVCZEZggTmFx1jbGMI8XA4KailQLHlJ2L0LYInBPlBWVU3ExjCkqTEn+1MfoYDpLAWAAB3IoEB3hwHqMzSO0cfIKSUmpDSvkpKfk8UjSqRB9K1X8ZKdwMRLJehRFGOsfirDMTqeSBp6sml4Bae09AnTuk4GBm3fWAzFIqXUnOSS0kJmhUxAZQIWjJhmBdHo3osQTomNMJjGIEE1hbKIOgE5djJDNLaR06h+9aEgpOUaSFhyIAPMOi6E6VYx7u2LljY8IQQwXiMshYu78bDAtBWgfUiT0BIuhck8BWZIEUvqIi-ZUKjmootm-HcUYqx1kjBsSyfQUSxDFtozO1hgXIFUBABJhpJDvDICOWAMKnGWmlbK9xGBFXKs5byPo8si4j0KcsMVx4JSDGvPpSCfQzJSplXKo0Oq4DANASkxlaSNX7CdUquAeqnRjFCNMuyr02JO04r44IJ5+jy09C6dUGpKBkDIfAH4xD0iHGoDhEcKiU6HRMNESwywuwDEqh6eKztmpBFxFBFY8tzpDC-pmrI9QaTNFzTzC22J8lz2hO-Wy2JAw8LlOiT0VazK+ibZkDt-dgSCnmL2kwEQB2YJmGGHcaJkqRj9GhTKvYSHkkpHgakBo6QMkoM3GdCC+aYyCHWasFgXD9BcCEQMSIQzywhMXZw+lVREP3b7H+SZqWpoRp23kxdQkQQhJ6dsEYp4zDWPdAmEFGwE2hLw4F7kr1eLohecEazoxREFGLWC+csYhickhFD3p3TAp2aNP+01zYsKRudcs2dGINrhGEXGqoi1OU9PLfcOd6P+0aegTWFDAKsdCuxhifEnJ+icCBVdRgwjzANTLM68tYpibrhJxu8TO2yd5uda2HpYp+gcjLcNRh5QnWLmLDYlUoSxDJXu6ugHD1-wAfKjAOH1wjDBOh8z0Z0r+mMLdJyCwNhOS7EMdR3ZPMU280QRRlD0CBdosF8E98ohl0bH0KY08YhF2lpU5YUFJjAribYzL2X82nigg+5dthmrFwSpLNYMtlTth6xefTatWUHI6V0yljWB7YKLi6Eu8ozIuhK5xHdKzljcRFA4JUxhyVgsy7So5k22EhnYt+3ORlFgYjxcXQynCRZmGOvazVmXnWgeTuBgNjE5Sobiu6R6dnejCjvS2GW0QLxvRSyJVemBDsBuWEEbigpR2Pv+4sUCMtNj8o2VjZL5NIcw6OhEMECPQ3I8DPMyw6PPQLKhPubbCQ4hAA */
|
||||
createMachine(
|
||||
{
|
||||
tsTypes: {} as import("./usersXService.typegen").Typegen0,
|
||||
@@ -130,222 +132,261 @@ export const usersMachine =
|
||||
updateUserRoles: {
|
||||
data: TypesGen.User
|
||||
}
|
||||
getUserCount: {
|
||||
data: TypesGen.UserCountResponse
|
||||
}
|
||||
},
|
||||
},
|
||||
predictableActionArguments: true,
|
||||
id: "usersState",
|
||||
initial: "startingPagination",
|
||||
type: "parallel",
|
||||
states: {
|
||||
startingPagination: {
|
||||
entry: "assignPaginationRef",
|
||||
always: {
|
||||
target: "gettingUsers",
|
||||
},
|
||||
},
|
||||
gettingUsers: {
|
||||
entry: "clearGetUsersError",
|
||||
invoke: {
|
||||
src: "getUsers",
|
||||
id: "getUsers",
|
||||
onDone: [
|
||||
{
|
||||
target: "idle",
|
||||
actions: "assignUsers",
|
||||
},
|
||||
],
|
||||
onError: [
|
||||
{
|
||||
target: "idle",
|
||||
actions: [
|
||||
"clearUsers",
|
||||
"assignGetUsersError",
|
||||
"displayGetUsersErrorMessage",
|
||||
count: {
|
||||
initial: "gettingCount",
|
||||
states: {
|
||||
idle: {},
|
||||
gettingCount: {
|
||||
entry: "clearGetCountError",
|
||||
invoke: {
|
||||
src: "getUserCount",
|
||||
id: "getUserCount",
|
||||
onDone: [
|
||||
{
|
||||
target: "idle",
|
||||
actions: "assignCount",
|
||||
},
|
||||
],
|
||||
onError: [
|
||||
{
|
||||
target: "idle",
|
||||
actions: "assignGetCountError",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
tags: "loading",
|
||||
},
|
||||
idle: {
|
||||
entry: "clearSelectedUser",
|
||||
on: {
|
||||
SUSPEND_USER: {
|
||||
target: "confirmUserSuspension",
|
||||
actions: "assignUserToSuspend",
|
||||
},
|
||||
DELETE_USER: {
|
||||
target: "confirmUserDeletion",
|
||||
actions: "assignUserToDelete",
|
||||
},
|
||||
ACTIVATE_USER: {
|
||||
target: "confirmUserActivation",
|
||||
actions: "assignUserToActivate",
|
||||
},
|
||||
RESET_USER_PASSWORD: {
|
||||
target: "confirmUserPasswordReset",
|
||||
actions: [
|
||||
"assignUserIdToResetPassword",
|
||||
"generateRandomPassword",
|
||||
],
|
||||
},
|
||||
UPDATE_USER_ROLES: {
|
||||
target: "updatingUserRoles",
|
||||
actions: "assignUserIdToUpdateRoles",
|
||||
},
|
||||
UPDATE_PAGE: {
|
||||
target: "gettingUsers",
|
||||
actions: "updateURL",
|
||||
},
|
||||
UPDATE_FILTER: {
|
||||
target: ".gettingCount",
|
||||
actions: ["assignFilter", "sendResetPage"],
|
||||
},
|
||||
},
|
||||
},
|
||||
confirmUserSuspension: {
|
||||
on: {
|
||||
CONFIRM_USER_SUSPENSION: {
|
||||
target: "suspendingUser",
|
||||
},
|
||||
CANCEL_USER_SUSPENSION: {
|
||||
target: "idle",
|
||||
},
|
||||
},
|
||||
},
|
||||
confirmUserDeletion: {
|
||||
on: {
|
||||
CONFIRM_USER_DELETE: {
|
||||
target: "deletingUser",
|
||||
},
|
||||
CANCEL_USER_DELETE: {
|
||||
target: "idle",
|
||||
},
|
||||
},
|
||||
},
|
||||
confirmUserActivation: {
|
||||
on: {
|
||||
CONFIRM_USER_ACTIVATION: {
|
||||
target: "activatingUser",
|
||||
},
|
||||
CANCEL_USER_ACTIVATION: {
|
||||
target: "idle",
|
||||
},
|
||||
},
|
||||
},
|
||||
suspendingUser: {
|
||||
entry: "clearSuspendUserError",
|
||||
invoke: {
|
||||
src: "suspendUser",
|
||||
id: "suspendUser",
|
||||
onDone: [
|
||||
{
|
||||
users: {
|
||||
initial: "startingPagination",
|
||||
states: {
|
||||
startingPagination: {
|
||||
entry: "assignPaginationRef",
|
||||
always: {
|
||||
target: "gettingUsers",
|
||||
actions: "displaySuspendSuccess",
|
||||
},
|
||||
],
|
||||
onError: [
|
||||
{
|
||||
target: "idle",
|
||||
actions: [
|
||||
"assignSuspendUserError",
|
||||
"displaySuspendedErrorMessage",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
deletingUser: {
|
||||
entry: "clearDeleteUserError",
|
||||
invoke: {
|
||||
src: "deleteUser",
|
||||
id: "deleteUser",
|
||||
onDone: [
|
||||
{
|
||||
target: "gettingUsers",
|
||||
actions: "displayDeleteSuccess",
|
||||
},
|
||||
],
|
||||
onError: [
|
||||
{
|
||||
target: "idle",
|
||||
actions: ["assignDeleteUserError", "displayDeleteErrorMessage"],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
activatingUser: {
|
||||
entry: "clearActivateUserError",
|
||||
invoke: {
|
||||
src: "activateUser",
|
||||
id: "activateUser",
|
||||
onDone: [
|
||||
{
|
||||
target: "gettingUsers",
|
||||
actions: "displayActivateSuccess",
|
||||
},
|
||||
],
|
||||
onError: [
|
||||
{
|
||||
target: "idle",
|
||||
actions: [
|
||||
"assignActivateUserError",
|
||||
"displayActivatedErrorMessage",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
confirmUserPasswordReset: {
|
||||
on: {
|
||||
CONFIRM_USER_PASSWORD_RESET: {
|
||||
target: "resettingUserPassword",
|
||||
},
|
||||
CANCEL_USER_PASSWORD_RESET: {
|
||||
target: "idle",
|
||||
gettingUsers: {
|
||||
entry: "clearGetUsersError",
|
||||
invoke: {
|
||||
src: "getUsers",
|
||||
id: "getUsers",
|
||||
onDone: [
|
||||
{
|
||||
target: "idle",
|
||||
actions: "assignUsers",
|
||||
},
|
||||
],
|
||||
onError: [
|
||||
{
|
||||
target: "idle",
|
||||
actions: [
|
||||
"clearUsers",
|
||||
"assignGetUsersError",
|
||||
"displayGetUsersErrorMessage",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
tags: "loading",
|
||||
},
|
||||
},
|
||||
},
|
||||
resettingUserPassword: {
|
||||
entry: "clearResetUserPasswordError",
|
||||
invoke: {
|
||||
src: "resetUserPassword",
|
||||
id: "resetUserPassword",
|
||||
onDone: [
|
||||
{
|
||||
target: "idle",
|
||||
actions: "displayResetPasswordSuccess",
|
||||
idle: {
|
||||
entry: "clearSelectedUser",
|
||||
on: {
|
||||
SUSPEND_USER: {
|
||||
target: "confirmUserSuspension",
|
||||
actions: "assignUserToSuspend",
|
||||
},
|
||||
DELETE_USER: {
|
||||
target: "confirmUserDeletion",
|
||||
actions: "assignUserToDelete",
|
||||
},
|
||||
ACTIVATE_USER: {
|
||||
target: "confirmUserActivation",
|
||||
actions: "assignUserToActivate",
|
||||
},
|
||||
RESET_USER_PASSWORD: {
|
||||
target: "confirmUserPasswordReset",
|
||||
actions: [
|
||||
"assignUserIdToResetPassword",
|
||||
"generateRandomPassword",
|
||||
],
|
||||
},
|
||||
UPDATE_USER_ROLES: {
|
||||
target: "updatingUserRoles",
|
||||
actions: "assignUserIdToUpdateRoles",
|
||||
},
|
||||
UPDATE_PAGE: {
|
||||
target: "gettingUsers",
|
||||
actions: "updateURL",
|
||||
},
|
||||
},
|
||||
],
|
||||
onError: [
|
||||
{
|
||||
target: "idle",
|
||||
actions: [
|
||||
"assignResetUserPasswordError",
|
||||
"displayResetPasswordErrorMessage",
|
||||
},
|
||||
confirmUserSuspension: {
|
||||
on: {
|
||||
CONFIRM_USER_SUSPENSION: {
|
||||
target: "suspendingUser",
|
||||
},
|
||||
CANCEL_USER_SUSPENSION: {
|
||||
target: "idle",
|
||||
},
|
||||
},
|
||||
},
|
||||
confirmUserDeletion: {
|
||||
on: {
|
||||
CONFIRM_USER_DELETE: {
|
||||
target: "deletingUser",
|
||||
},
|
||||
CANCEL_USER_DELETE: {
|
||||
target: "idle",
|
||||
},
|
||||
},
|
||||
},
|
||||
confirmUserActivation: {
|
||||
on: {
|
||||
CONFIRM_USER_ACTIVATION: {
|
||||
target: "activatingUser",
|
||||
},
|
||||
CANCEL_USER_ACTIVATION: {
|
||||
target: "idle",
|
||||
},
|
||||
},
|
||||
},
|
||||
suspendingUser: {
|
||||
entry: "clearSuspendUserError",
|
||||
invoke: {
|
||||
src: "suspendUser",
|
||||
id: "suspendUser",
|
||||
onDone: [
|
||||
{
|
||||
target: "gettingUsers",
|
||||
actions: "displaySuspendSuccess",
|
||||
},
|
||||
],
|
||||
onError: [
|
||||
{
|
||||
target: "idle",
|
||||
actions: [
|
||||
"assignSuspendUserError",
|
||||
"displaySuspendedErrorMessage",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
updatingUserRoles: {
|
||||
entry: "clearUpdateUserRolesError",
|
||||
invoke: {
|
||||
src: "updateUserRoles",
|
||||
id: "updateUserRoles",
|
||||
onDone: [
|
||||
{
|
||||
target: "idle",
|
||||
actions: "updateUserRolesInTheList",
|
||||
},
|
||||
],
|
||||
onError: [
|
||||
{
|
||||
target: "idle",
|
||||
actions: [
|
||||
"assignUpdateRolesError",
|
||||
"displayUpdateRolesErrorMessage",
|
||||
},
|
||||
deletingUser: {
|
||||
entry: "clearDeleteUserError",
|
||||
invoke: {
|
||||
src: "deleteUser",
|
||||
id: "deleteUser",
|
||||
onDone: [
|
||||
{
|
||||
target: "gettingUsers",
|
||||
actions: "displayDeleteSuccess",
|
||||
},
|
||||
],
|
||||
onError: [
|
||||
{
|
||||
target: "idle",
|
||||
actions: [
|
||||
"assignDeleteUserError",
|
||||
"displayDeleteErrorMessage",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
activatingUser: {
|
||||
entry: "clearActivateUserError",
|
||||
invoke: {
|
||||
src: "activateUser",
|
||||
id: "activateUser",
|
||||
onDone: [
|
||||
{
|
||||
target: "gettingUsers",
|
||||
actions: "displayActivateSuccess",
|
||||
},
|
||||
],
|
||||
onError: [
|
||||
{
|
||||
target: "idle",
|
||||
actions: [
|
||||
"assignActivateUserError",
|
||||
"displayActivatedErrorMessage",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
confirmUserPasswordReset: {
|
||||
on: {
|
||||
CONFIRM_USER_PASSWORD_RESET: {
|
||||
target: "resettingUserPassword",
|
||||
},
|
||||
CANCEL_USER_PASSWORD_RESET: {
|
||||
target: "idle",
|
||||
},
|
||||
},
|
||||
},
|
||||
resettingUserPassword: {
|
||||
entry: "clearResetUserPasswordError",
|
||||
invoke: {
|
||||
src: "resetUserPassword",
|
||||
id: "resetUserPassword",
|
||||
onDone: [
|
||||
{
|
||||
target: "idle",
|
||||
actions: "displayResetPasswordSuccess",
|
||||
},
|
||||
],
|
||||
onError: [
|
||||
{
|
||||
target: "idle",
|
||||
actions: [
|
||||
"assignResetUserPasswordError",
|
||||
"displayResetPasswordErrorMessage",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
updatingUserRoles: {
|
||||
entry: "clearUpdateUserRolesError",
|
||||
invoke: {
|
||||
src: "updateUserRoles",
|
||||
id: "updateUserRoles",
|
||||
onDone: [
|
||||
{
|
||||
target: "idle",
|
||||
actions: "updateUserRolesInTheList",
|
||||
},
|
||||
],
|
||||
onError: [
|
||||
{
|
||||
target: "idle",
|
||||
actions: [
|
||||
"assignUpdateRolesError",
|
||||
"displayUpdateRolesErrorMessage",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -363,6 +404,9 @@ export const usersMachine =
|
||||
limit,
|
||||
})
|
||||
},
|
||||
getUserCount: (context) => {
|
||||
return API.getUserCount(queryToFilter(context.filter))
|
||||
},
|
||||
suspendUser: (context) => {
|
||||
if (!context.userIdToSuspend) {
|
||||
throw new Error("userIdToSuspend is undefined")
|
||||
@@ -420,6 +464,15 @@ export const usersMachine =
|
||||
assignUsers: assign({
|
||||
users: (_, event) => event.data,
|
||||
}),
|
||||
assignCount: assign({
|
||||
count: (_, event) => event.data.count,
|
||||
}),
|
||||
assignGetCountError: assign({
|
||||
getCountError: (_, event) => event.data,
|
||||
}),
|
||||
clearGetCountError: assign({
|
||||
getCountError: (_) => undefined,
|
||||
}),
|
||||
assignFilter: assign({
|
||||
filter: (_, event) => event.query,
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user