From 87d50f17a26d19c76cdede4109ba2e5f6d17cc13 Mon Sep 17 00:00:00 2001 From: Bruno Quaresma Date: Tue, 19 Sep 2023 13:37:17 -0300 Subject: [PATCH] chore(site): refactor groups to use react-query (#9701) --- site/src/api/api.ts | 18 + site/src/api/queries/groups.ts | 101 +++++ site/src/pages/GroupsPage/CreateGroupPage.tsx | 30 +- site/src/pages/GroupsPage/GroupPage.tsx | 393 ++++++++++-------- site/src/pages/GroupsPage/GroupsPage.tsx | 26 +- .../pages/GroupsPage/SettingsGroupPage.tsx | 47 ++- .../xServices/groups/createGroupXService.ts | 59 --- .../src/xServices/groups/editGroupXService.ts | 100 ----- site/src/xServices/groups/groupXService.ts | 275 ------------ site/src/xServices/groups/groupsXService.ts | 60 --- 10 files changed, 386 insertions(+), 723 deletions(-) create mode 100644 site/src/api/queries/groups.ts delete mode 100644 site/src/xServices/groups/createGroupXService.ts delete mode 100644 site/src/xServices/groups/editGroupXService.ts delete mode 100644 site/src/xServices/groups/groupXService.ts delete mode 100644 site/src/xServices/groups/groupsXService.ts diff --git a/site/src/api/api.ts b/site/src/api/api.ts index d52a683785..ef50daad95 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -969,6 +969,24 @@ export const patchGroup = async ( return response.data; }; +export const addMember = async (groupId: string, userId: string) => { + return patchGroup(groupId, { + name: "", + display_name: "", + add_users: [userId], + remove_users: [], + }); +}; + +export const removeMember = async (groupId: string, userId: string) => { + return patchGroup(groupId, { + name: "", + display_name: "", + add_users: [], + remove_users: [userId], + }); +}; + export const deleteGroup = async (groupId: string): Promise => { await axios.delete(`/api/v2/groups/${groupId}`); }; diff --git a/site/src/api/queries/groups.ts b/site/src/api/queries/groups.ts new file mode 100644 index 0000000000..62b59515df --- /dev/null +++ b/site/src/api/queries/groups.ts @@ -0,0 +1,101 @@ +import { QueryClient } from "@tanstack/react-query"; +import * as API from "api/api"; +import { checkAuthorization } from "api/api"; +import { + CreateGroupRequest, + Group, + PatchGroupRequest, +} from "api/typesGenerated"; + +const GROUPS_QUERY_KEY = ["groups"]; + +const getGroupQueryKey = (groupId: string) => ["group", groupId]; + +export const groups = (organizationId: string) => { + return { + queryKey: GROUPS_QUERY_KEY, + queryFn: () => API.getGroups(organizationId), + }; +}; + +export const group = (groupId: string) => { + return { + queryKey: getGroupQueryKey(groupId), + queryFn: () => API.getGroup(groupId), + }; +}; + +export const groupPermissions = (groupId: string) => { + return { + queryKey: [...getGroupQueryKey(groupId), "permissions"], + queryFn: () => + checkAuthorization({ + checks: { + canUpdateGroup: { + object: { + resource_type: "group", + resource_id: groupId, + }, + action: "update", + }, + }, + }), + }; +}; + +export const createGroup = (queryClient: QueryClient) => { + return { + mutationFn: ({ + organizationId, + ...request + }: CreateGroupRequest & { organizationId: string }) => + API.createGroup(organizationId, request), + onSuccess: async () => { + await queryClient.invalidateQueries(GROUPS_QUERY_KEY); + }, + }; +}; + +export const patchGroup = (queryClient: QueryClient) => { + return { + mutationFn: ({ + groupId, + ...request + }: PatchGroupRequest & { groupId: string }) => + API.patchGroup(groupId, request), + onSuccess: async (updatedGroup: Group) => + invalidateGroup(queryClient, updatedGroup.id), + }; +}; + +export const deleteGroup = (queryClient: QueryClient) => { + return { + mutationFn: API.deleteGroup, + onSuccess: async (_: void, groupId: string) => + invalidateGroup(queryClient, groupId), + }; +}; + +export const addMember = (queryClient: QueryClient) => { + return { + mutationFn: ({ groupId, userId }: { groupId: string; userId: string }) => + API.addMember(groupId, userId), + onSuccess: async (updatedGroup: Group) => + invalidateGroup(queryClient, updatedGroup.id), + }; +}; + +export const removeMember = (queryClient: QueryClient) => { + return { + mutationFn: ({ groupId, userId }: { groupId: string; userId: string }) => + API.removeMember(groupId, userId), + onSuccess: async (updatedGroup: Group) => + invalidateGroup(queryClient, updatedGroup.id), + }; +}; + +export const invalidateGroup = (queryClient: QueryClient, groupId: string) => + Promise.all([ + queryClient.invalidateQueries(GROUPS_QUERY_KEY), + queryClient.invalidateQueries(getGroupQueryKey(groupId)), + ]); diff --git a/site/src/pages/GroupsPage/CreateGroupPage.tsx b/site/src/pages/GroupsPage/CreateGroupPage.tsx index 953f9cebb0..b09c4f9176 100644 --- a/site/src/pages/GroupsPage/CreateGroupPage.tsx +++ b/site/src/pages/GroupsPage/CreateGroupPage.tsx @@ -1,26 +1,17 @@ -import { useMachine } from "@xstate/react"; import { useOrganizationId } from "hooks/useOrganizationId"; import { FC } from "react"; import { Helmet } from "react-helmet-async"; import { useNavigate } from "react-router-dom"; import { pageTitle } from "utils/page"; -import { createGroupMachine } from "xServices/groups/createGroupXService"; import CreateGroupPageView from "./CreateGroupPageView"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { createGroup } from "api/queries/groups"; export const CreateGroupPage: FC = () => { + const queryClient = useQueryClient(); const navigate = useNavigate(); const organizationId = useOrganizationId(); - const [createState, sendCreateEvent] = useMachine(createGroupMachine, { - context: { - organizationId, - }, - actions: { - onCreate: (_, { data }) => { - navigate(`/groups/${data.id}`); - }, - }, - }); - const { error } = createState.context; + const createGroupMutation = useMutation(createGroup(queryClient)); return ( <> @@ -28,14 +19,15 @@ export const CreateGroupPage: FC = () => { {pageTitle("Create Group")} { - sendCreateEvent({ - type: "CREATE", - data, + onSubmit={async (data) => { + const newGroup = await createGroupMutation.mutateAsync({ + organizationId, + ...data, }); + navigate(`/groups/${newGroup.id}`); }} - formErrors={error} - isLoading={createState.matches("creatingGroup")} + formErrors={createGroupMutation.error} + isLoading={createGroupMutation.isLoading} /> ); diff --git a/site/src/pages/GroupsPage/GroupPage.tsx b/site/src/pages/GroupsPage/GroupPage.tsx index f0dc74ce80..162a273984 100644 --- a/site/src/pages/GroupsPage/GroupPage.tsx +++ b/site/src/pages/GroupsPage/GroupPage.tsx @@ -9,8 +9,7 @@ import TableRow from "@mui/material/TableRow"; import DeleteOutline from "@mui/icons-material/DeleteOutline"; import PersonAdd from "@mui/icons-material/PersonAdd"; import SettingsOutlined from "@mui/icons-material/SettingsOutlined"; -import { useMachine } from "@xstate/react"; -import { User } from "api/typesGenerated"; +import { Group, User } from "api/typesGenerated"; import { AvatarData } from "components/AvatarData/AvatarData"; import { ChooseOne, Cond } from "components/Conditionals/ChooseOne"; import { DeleteDialog } from "components/Dialogs/DeleteDialog/DeleteDialog"; @@ -30,7 +29,6 @@ import { useState } from "react"; import { Helmet } from "react-helmet-async"; import { Link as RouterLink, useNavigate, useParams } from "react-router-dom"; import { pageTitle } from "utils/page"; -import { groupMachine } from "xServices/groups/groupXService"; import { Maybe } from "components/Conditionals/Maybe"; import { makeStyles } from "@mui/styles"; import { @@ -39,6 +37,175 @@ import { } from "components/TableToolbar/TableToolbar"; import { UserAvatar } from "components/UserAvatar/UserAvatar"; import { isEveryoneGroup } from "utils/groups"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { + addMember, + deleteGroup, + group, + groupPermissions, + removeMember, +} from "api/queries/groups"; +import { displayError, displaySuccess } from "components/GlobalSnackbar/utils"; +import { getErrorMessage } from "api/errors"; + +export const GroupPage: React.FC = () => { + const { groupId } = useParams() as { groupId: string }; + const queryClient = useQueryClient(); + const navigate = useNavigate(); + const groupQuery = useQuery(group(groupId)); + const groupData = groupQuery.data; + const { data: permissions } = useQuery(groupPermissions(groupId)); + const addMemberMutation = useMutation(addMember(queryClient)); + const deleteGroupMutation = useMutation(deleteGroup(queryClient)); + const [isDeletingGroup, setIsDeletingGroup] = useState(false); + const isLoading = !groupData || !permissions; + const canUpdateGroup = permissions ? permissions.canUpdateGroup : false; + + return ( + <> + + + {pageTitle( + (groupData?.display_name || groupData?.name) ?? "Loading...", + )} + + + + + + + + + + + + + + + + } + > + + {groupData?.display_name || groupData?.name} + + + {/* Show the name if it differs from the display name. */} + {groupData?.display_name && + groupData?.display_name !== groupData?.name + ? groupData?.name + : ""}{" "} + + + + + + { + try { + await addMemberMutation.mutateAsync({ + groupId, + userId: user.id, + }); + reset(); + } catch (error) { + displayError( + getErrorMessage(error, "Failed to add member."), + ); + } + }} + /> + + + + + + + + + + User + + + + + + + + + + + + + + + + {groupData?.members.map((member) => ( + + ))} + + + +
+
+
+
+
+
+ + {group && ( + { + try { + await deleteGroupMutation.mutateAsync(groupId); + navigate("/groups"); + } catch (error) { + displayError(getErrorMessage(error, "Failed to delete group.")); + } + }} + onCancel={() => { + setIsDeletingGroup(false); + }} + /> + )} + + ); +}; const AddGroupMember: React.FC<{ isLoading: boolean; @@ -83,182 +250,56 @@ const AddGroupMember: React.FC<{ ); }; -export const GroupPage: React.FC = () => { - const { groupId } = useParams(); - if (!groupId) { - throw new Error("groupId is not defined."); - } - - const navigate = useNavigate(); - const [state, send] = useMachine(groupMachine, { - context: { - groupId, - }, - actions: { - redirectToGroups: () => { - navigate("/groups"); - }, - }, - }); - const { group, permissions } = state.context; - const isLoading = group === undefined || permissions === undefined; - const canUpdateGroup = permissions ? permissions.canUpdateGroup : false; +const GroupMemberRow = (props: { + member: User; + group: Group; + canUpdate: boolean; +}) => { + const { member, group, canUpdate } = props; + const queryClient = useQueryClient(); + const removeMemberMutation = useMutation(removeMember(queryClient)); return ( - <> - - - {pageTitle((group?.display_name || group?.name) ?? "Loading...")} - - - - - - - - - - - - - - - - } - > - - {group?.display_name || group?.name} - - - {/* Show the name if it differs from the display name. */} - {group?.display_name && group?.display_name !== group?.name - ? group?.name - : ""}{" "} - - - - - - { - send({ - type: "ADD_MEMBER", - userId: user.id, - callback: reset, - }); - }} - /> - - - - - - - - - - User - - - - - - - - - - - - - - - - {group?.members.map((member) => ( - - - - } - title={member.username} - subtitle={member.email} - /> - - - - { - send({ - type: "REMOVE_MEMBER", - userId: member.id, - }); - }, - disabled: - group.id === group.organization_id, - }, - ]} - /> - - - - ))} - - - -
-
-
-
-
-
- - {group && ( - { - send("CONFIRM_DELETE"); - }} - onCancel={() => { - send("CANCEL_DELETE"); - }} + + + + } + title={member.username} + subtitle={member.email} /> - )} - + + + + { + try { + await removeMemberMutation.mutateAsync({ + groupId: group.id, + userId: member.id, + }); + displaySuccess("Member removed successfully."); + } catch (error) { + displayError( + getErrorMessage(error, "Failed to remove member."), + ); + } + }, + disabled: group.id === group.organization_id, + }, + ]} + /> + + + ); }; diff --git a/site/src/pages/GroupsPage/GroupsPage.tsx b/site/src/pages/GroupsPage/GroupsPage.tsx index d63e4dfb69..1af0759b1b 100644 --- a/site/src/pages/GroupsPage/GroupsPage.tsx +++ b/site/src/pages/GroupsPage/GroupsPage.tsx @@ -1,24 +1,28 @@ -import { useMachine } from "@xstate/react"; import { useFeatureVisibility } from "hooks/useFeatureVisibility"; import { useOrganizationId } from "hooks/useOrganizationId"; import { usePermissions } from "hooks/usePermissions"; -import { FC } from "react"; +import { FC, useEffect } from "react"; import { Helmet } from "react-helmet-async"; import { pageTitle } from "utils/page"; -import { groupsMachine } from "xServices/groups/groupsXService"; import GroupsPageView from "./GroupsPageView"; +import { useQuery } from "@tanstack/react-query"; +import { groups } from "api/queries/groups"; +import { displayError } from "components/GlobalSnackbar/utils"; +import { getErrorMessage } from "api/errors"; export const GroupsPage: FC = () => { const organizationId = useOrganizationId(); const { createGroup: canCreateGroup } = usePermissions(); const { template_rbac: isTemplateRBACEnabled } = useFeatureVisibility(); - const [state] = useMachine(groupsMachine, { - context: { - organizationId, - shouldFetchGroups: isTemplateRBACEnabled, - }, - }); - const { groups } = state.context; + const groupsQuery = useQuery(groups(organizationId)); + + useEffect(() => { + if (groupsQuery.error) { + displayError( + getErrorMessage(groupsQuery.error, "Error on loading groups."), + ); + } + }, [groupsQuery.error]); return ( <> @@ -27,7 +31,7 @@ export const GroupsPage: FC = () => { diff --git a/site/src/pages/GroupsPage/SettingsGroupPage.tsx b/site/src/pages/GroupsPage/SettingsGroupPage.tsx index d61bc5beac..28ecb49080 100644 --- a/site/src/pages/GroupsPage/SettingsGroupPage.tsx +++ b/site/src/pages/GroupsPage/SettingsGroupPage.tsx @@ -1,33 +1,24 @@ -import { useMachine } from "@xstate/react"; import { FC } from "react"; import { Helmet } from "react-helmet-async"; import { useNavigate, useParams } from "react-router-dom"; import { pageTitle } from "utils/page"; -import { editGroupMachine } from "xServices/groups/editGroupXService"; import SettingsGroupPageView from "./SettingsGroupPageView"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { group, patchGroup } from "api/queries/groups"; +import { displayError } from "components/GlobalSnackbar/utils"; +import { getErrorMessage } from "api/errors"; export const SettingsGroupPage: FC = () => { - const { groupId } = useParams(); - if (!groupId) { - throw new Error("Group ID not defined."); - } - + const { groupId } = useParams() as { groupId: string }; + const queryClient = useQueryClient(); + const groupQuery = useQuery(group(groupId)); + const patchGroupMutation = useMutation(patchGroup(queryClient)); const navigate = useNavigate(); const navigateToGroup = () => { navigate(`/groups/${groupId}`); }; - const [editState, sendEditEvent] = useMachine(editGroupMachine, { - context: { - groupId, - }, - actions: { - onUpdate: navigateToGroup, - }, - }); - const { error, group } = editState.context; - return ( <> @@ -36,13 +27,23 @@ export const SettingsGroupPage: FC = () => { { - sendEditEvent({ type: "UPDATE", data }); + onSubmit={async (data) => { + try { + await patchGroupMutation.mutateAsync({ + groupId, + ...data, + add_users: [], + remove_users: [], + }); + navigateToGroup(); + } catch (error) { + displayError(getErrorMessage(error, "Failed to update group")); + } }} - group={group} - formErrors={error} - isLoading={editState.matches("loading")} - isUpdating={editState.matches("updating")} + group={groupQuery.data} + formErrors={groupQuery.error} + isLoading={groupQuery.isLoading} + isUpdating={patchGroupMutation.isLoading} /> ); diff --git a/site/src/xServices/groups/createGroupXService.ts b/site/src/xServices/groups/createGroupXService.ts deleted file mode 100644 index 69dc6b3f7e..0000000000 --- a/site/src/xServices/groups/createGroupXService.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { createGroup } from "api/api"; -import { CreateGroupRequest, Group } from "api/typesGenerated"; -import { createMachine, assign } from "xstate"; - -export const createGroupMachine = createMachine( - { - id: "createGroupMachine", - schema: { - context: {} as { - organizationId: string; - error?: unknown; - }, - services: {} as { - createGroup: { - data: Group; - }; - }, - events: {} as { - type: "CREATE"; - data: CreateGroupRequest; - }, - }, - tsTypes: {} as import("./createGroupXService.typegen").Typegen0, - initial: "idle", - states: { - idle: { - on: { - CREATE: { - target: "creatingGroup", - }, - }, - }, - creatingGroup: { - invoke: { - src: "createGroup", - onDone: { - target: "idle", - actions: ["onCreate"], - }, - onError: { - target: "idle", - actions: ["assignError"], - }, - }, - }, - }, - }, - { - services: { - createGroup: ({ organizationId }, { data }) => - createGroup(organizationId, data), - }, - actions: { - assignError: assign({ - error: (_, event) => event.data, - }), - }, - }, -); diff --git a/site/src/xServices/groups/editGroupXService.ts b/site/src/xServices/groups/editGroupXService.ts deleted file mode 100644 index 755a4863e5..0000000000 --- a/site/src/xServices/groups/editGroupXService.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { getGroup, patchGroup } from "api/api"; -import { getErrorMessage } from "api/errors"; -import { Group } from "api/typesGenerated"; -import { displayError } from "components/GlobalSnackbar/utils"; -import { assign, createMachine } from "xstate"; - -export const editGroupMachine = createMachine( - { - id: "editGroup", - schema: { - context: {} as { - groupId: string; - group?: Group; - error?: unknown; - }, - services: {} as { - loadGroup: { - data: Group; - }; - updateGroup: { - data: Group; - }; - }, - events: {} as { - type: "UPDATE"; - data: { - display_name: string; - name: string; - avatar_url: string; - quota_allowance: number; - }; - }, - }, - tsTypes: {} as import("./editGroupXService.typegen").Typegen0, - initial: "loading", - states: { - loading: { - invoke: { - src: "loadGroup", - onDone: { - actions: ["assignGroup"], - target: "idle", - }, - onError: { - actions: ["displayLoadGroupError"], - target: "idle", - }, - }, - }, - idle: { - on: { - UPDATE: { - target: "updating", - }, - }, - }, - updating: { - invoke: { - src: "updateGroup", - onDone: { - actions: ["onUpdate"], - }, - onError: { - target: "idle", - actions: ["assignError"], - }, - }, - }, - }, - }, - { - services: { - loadGroup: ({ groupId }) => getGroup(groupId), - - updateGroup: ({ group }, { data }) => { - if (!group) { - throw new Error("Group not defined."); - } - - return patchGroup(group.id, { - ...data, - add_users: [], - remove_users: [], - }); - }, - }, - actions: { - assignGroup: assign({ - group: (_, { data }) => data, - }), - displayLoadGroupError: (_, { data }) => { - const message = getErrorMessage(data, "Failed to the group."); - displayError(message); - }, - assignError: assign({ - error: (_, event) => event.data, - }), - }, - }, -); diff --git a/site/src/xServices/groups/groupXService.ts b/site/src/xServices/groups/groupXService.ts deleted file mode 100644 index 8d33d06700..0000000000 --- a/site/src/xServices/groups/groupXService.ts +++ /dev/null @@ -1,275 +0,0 @@ -import { deleteGroup, getGroup, patchGroup, checkAuthorization } from "api/api"; -import { getErrorMessage } from "api/errors"; -import { AuthorizationResponse, Group } from "api/typesGenerated"; -import { displayError, displaySuccess } from "components/GlobalSnackbar/utils"; -import { assign, createMachine } from "xstate"; - -export const groupMachine = createMachine( - { - id: "group", - schema: { - context: {} as { - groupId: string; - group?: Group; - permissions?: AuthorizationResponse; - addMemberCallback?: () => void; - removingMember?: string; - }, - services: {} as { - loadGroup: { - data: Group; - }; - loadPermissions: { - data: AuthorizationResponse; - }; - addMember: { - data: Group; - }; - removeMember: { - data: Group; - }; - deleteGroup: { - data: unknown; - }; - }, - events: {} as - | { - type: "ADD_MEMBER"; - userId: string; - callback: () => void; - } - | { - type: "REMOVE_MEMBER"; - userId: string; - } - | { - type: "DELETE"; - } - | { - type: "CONFIRM_DELETE"; - } - | { - type: "CANCEL_DELETE"; - }, - }, - tsTypes: {} as import("./groupXService.typegen").Typegen0, - initial: "loading", - states: { - loading: { - type: "parallel", - states: { - data: { - initial: "loading", - states: { - loading: { - invoke: { - src: "loadGroup", - onDone: { - actions: ["assignGroup"], - target: "success", - }, - onError: { - actions: ["displayLoadGroupError"], - }, - }, - }, - success: { - type: "final", - }, - }, - }, - permissions: { - initial: "loading", - states: { - loading: { - invoke: { - src: "loadPermissions", - onDone: { - actions: ["assignPermissions"], - target: "success", - }, - onError: { - actions: ["displayLoadPermissionsError"], - }, - }, - }, - success: { - type: "final", - }, - }, - }, - }, - onDone: "idle", - }, - idle: { - on: { - ADD_MEMBER: { - target: "addingMember", - actions: ["assignAddMemberCallback"], - }, - REMOVE_MEMBER: { - target: "removingMember", - actions: ["removeUserFromMembers"], - }, - DELETE: { - target: "confirmingDelete", - }, - }, - }, - addingMember: { - invoke: { - src: "addMember", - onDone: { - actions: ["assignGroup", "callAddMemberCallback"], - target: "idle", - }, - onError: { - target: "idle", - actions: ["displayAddMemberError"], - }, - }, - }, - removingMember: { - invoke: { - src: "removeMember", - onDone: { - actions: ["assignGroup", "displayRemoveMemberSuccess"], - target: "idle", - }, - onError: { - target: "idle", - actions: ["displayRemoveMemberError"], - }, - }, - }, - confirmingDelete: { - on: { - CONFIRM_DELETE: "deleting", - CANCEL_DELETE: "idle", - }, - }, - deleting: { - invoke: { - src: "deleteGroup", - onDone: { - actions: ["redirectToGroups"], - }, - onError: { - actions: ["displayDeleteGroupError"], - }, - }, - }, - }, - }, - { - services: { - loadGroup: ({ groupId }) => getGroup(groupId), - loadPermissions: ({ groupId }) => - checkAuthorization({ - checks: { - canUpdateGroup: { - object: { - resource_type: "group", - resource_id: groupId, - }, - action: "update", - }, - }, - }), - addMember: ({ group }, { userId }) => { - if (!group) { - throw new Error("Group not defined."); - } - - return patchGroup(group.id, { - name: "", - display_name: "", - add_users: [userId], - remove_users: [], - }); - }, - removeMember: ({ group }, { userId }) => { - if (!group) { - throw new Error("Group not defined."); - } - - return patchGroup(group.id, { - name: "", - display_name: "", - add_users: [], - remove_users: [userId], - }); - }, - deleteGroup: ({ group }) => { - if (!group) { - throw new Error("Group not defined."); - } - - return deleteGroup(group.id); - }, - }, - actions: { - assignGroup: assign({ - group: (_, { data }) => data, - }), - assignAddMemberCallback: assign({ - addMemberCallback: (_, { callback }) => callback, - }), - displayLoadGroupError: (_, { data }) => { - const message = getErrorMessage(data, "Failed to load the group."); - displayError(message); - }, - displayAddMemberError: (_, { data }) => { - const message = getErrorMessage( - data, - "Failed to add member to the group.", - ); - displayError(message); - }, - callAddMemberCallback: ({ addMemberCallback }) => { - if (addMemberCallback) { - addMemberCallback(); - } - }, - // Optimistically remove the user from members - removeUserFromMembers: assign({ - group: ({ group }, { userId }) => { - if (!group) { - throw new Error("Group is not defined."); - } - - return { - ...group, - members: group.members.filter( - (currentMember) => currentMember.id !== userId, - ), - }; - }, - }), - displayRemoveMemberError: (_, { data }) => { - const message = getErrorMessage( - data, - "Failed to remove member from the group.", - ); - displayError(message); - }, - displayRemoveMemberSuccess: () => { - displaySuccess("Member removed successfully."); - }, - displayDeleteGroupError: (_, { data }) => { - const message = getErrorMessage(data, "Failed to delete group."); - displayError(message); - }, - assignPermissions: assign({ - permissions: (_, { data }) => data, - }), - displayLoadPermissionsError: (_, { data }) => { - const message = getErrorMessage( - data, - "Failed to load the permissions.", - ); - displayError(message); - }, - }, - }, -); diff --git a/site/src/xServices/groups/groupsXService.ts b/site/src/xServices/groups/groupsXService.ts deleted file mode 100644 index 3552838724..0000000000 --- a/site/src/xServices/groups/groupsXService.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { getGroups } from "api/api"; -import { getErrorMessage } from "api/errors"; -import { Group } from "api/typesGenerated"; -import { displayError } from "components/GlobalSnackbar/utils"; -import { assign, createMachine } from "xstate"; - -export const groupsMachine = createMachine( - { - id: "groupsMachine", - predictableActionArguments: true, - schema: { - context: {} as { - organizationId: string; - shouldFetchGroups: boolean; - groups?: Group[]; - }, - services: {} as { - loadGroups: { - data: Group[]; - }; - }, - }, - tsTypes: {} as import("./groupsXService.typegen").Typegen0, - initial: "loading", - states: { - loading: { - always: [{ target: "idle", cond: "cantFetchGroups" }], - invoke: { - src: "loadGroups", - onDone: { - actions: ["assignGroups"], - target: "idle", - }, - onError: { - target: "idle", - actions: ["displayLoadingGroupsError"], - }, - }, - }, - idle: {}, - }, - }, - { - guards: { - cantFetchGroups: ({ shouldFetchGroups }) => !shouldFetchGroups, - }, - services: { - loadGroups: ({ organizationId }) => getGroups(organizationId), - }, - actions: { - assignGroups: assign({ - groups: (_, { data }) => data, - }), - displayLoadingGroupsError: (_, { data }) => { - const message = getErrorMessage(data, "Error on loading groups."); - displayError(message); - }, - }, - }, -);