mirror of
https://github.com/coder/coder.git
synced 2025-07-13 21:36:50 +00:00
chore(site): refactor groups to use react-query (#9701)
This commit is contained in:
@ -969,6 +969,24 @@ export const patchGroup = async (
|
|||||||
return response.data;
|
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<void> => {
|
export const deleteGroup = async (groupId: string): Promise<void> => {
|
||||||
await axios.delete(`/api/v2/groups/${groupId}`);
|
await axios.delete(`/api/v2/groups/${groupId}`);
|
||||||
};
|
};
|
||||||
|
101
site/src/api/queries/groups.ts
Normal file
101
site/src/api/queries/groups.ts
Normal file
@ -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)),
|
||||||
|
]);
|
@ -1,26 +1,17 @@
|
|||||||
import { useMachine } from "@xstate/react";
|
|
||||||
import { useOrganizationId } from "hooks/useOrganizationId";
|
import { useOrganizationId } from "hooks/useOrganizationId";
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import { Helmet } from "react-helmet-async";
|
import { Helmet } from "react-helmet-async";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { pageTitle } from "utils/page";
|
import { pageTitle } from "utils/page";
|
||||||
import { createGroupMachine } from "xServices/groups/createGroupXService";
|
|
||||||
import CreateGroupPageView from "./CreateGroupPageView";
|
import CreateGroupPageView from "./CreateGroupPageView";
|
||||||
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
|
import { createGroup } from "api/queries/groups";
|
||||||
|
|
||||||
export const CreateGroupPage: FC = () => {
|
export const CreateGroupPage: FC = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const organizationId = useOrganizationId();
|
const organizationId = useOrganizationId();
|
||||||
const [createState, sendCreateEvent] = useMachine(createGroupMachine, {
|
const createGroupMutation = useMutation(createGroup(queryClient));
|
||||||
context: {
|
|
||||||
organizationId,
|
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
onCreate: (_, { data }) => {
|
|
||||||
navigate(`/groups/${data.id}`);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const { error } = createState.context;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -28,14 +19,15 @@ export const CreateGroupPage: FC = () => {
|
|||||||
<title>{pageTitle("Create Group")}</title>
|
<title>{pageTitle("Create Group")}</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<CreateGroupPageView
|
<CreateGroupPageView
|
||||||
onSubmit={(data) => {
|
onSubmit={async (data) => {
|
||||||
sendCreateEvent({
|
const newGroup = await createGroupMutation.mutateAsync({
|
||||||
type: "CREATE",
|
organizationId,
|
||||||
data,
|
...data,
|
||||||
});
|
});
|
||||||
|
navigate(`/groups/${newGroup.id}`);
|
||||||
}}
|
}}
|
||||||
formErrors={error}
|
formErrors={createGroupMutation.error}
|
||||||
isLoading={createState.matches("creatingGroup")}
|
isLoading={createGroupMutation.isLoading}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -9,8 +9,7 @@ import TableRow from "@mui/material/TableRow";
|
|||||||
import DeleteOutline from "@mui/icons-material/DeleteOutline";
|
import DeleteOutline from "@mui/icons-material/DeleteOutline";
|
||||||
import PersonAdd from "@mui/icons-material/PersonAdd";
|
import PersonAdd from "@mui/icons-material/PersonAdd";
|
||||||
import SettingsOutlined from "@mui/icons-material/SettingsOutlined";
|
import SettingsOutlined from "@mui/icons-material/SettingsOutlined";
|
||||||
import { useMachine } from "@xstate/react";
|
import { Group, User } from "api/typesGenerated";
|
||||||
import { User } from "api/typesGenerated";
|
|
||||||
import { AvatarData } from "components/AvatarData/AvatarData";
|
import { AvatarData } from "components/AvatarData/AvatarData";
|
||||||
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne";
|
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne";
|
||||||
import { DeleteDialog } from "components/Dialogs/DeleteDialog/DeleteDialog";
|
import { DeleteDialog } from "components/Dialogs/DeleteDialog/DeleteDialog";
|
||||||
@ -30,7 +29,6 @@ import { useState } from "react";
|
|||||||
import { Helmet } from "react-helmet-async";
|
import { Helmet } from "react-helmet-async";
|
||||||
import { Link as RouterLink, useNavigate, useParams } from "react-router-dom";
|
import { Link as RouterLink, useNavigate, useParams } from "react-router-dom";
|
||||||
import { pageTitle } from "utils/page";
|
import { pageTitle } from "utils/page";
|
||||||
import { groupMachine } from "xServices/groups/groupXService";
|
|
||||||
import { Maybe } from "components/Conditionals/Maybe";
|
import { Maybe } from "components/Conditionals/Maybe";
|
||||||
import { makeStyles } from "@mui/styles";
|
import { makeStyles } from "@mui/styles";
|
||||||
import {
|
import {
|
||||||
@ -39,6 +37,175 @@ import {
|
|||||||
} from "components/TableToolbar/TableToolbar";
|
} from "components/TableToolbar/TableToolbar";
|
||||||
import { UserAvatar } from "components/UserAvatar/UserAvatar";
|
import { UserAvatar } from "components/UserAvatar/UserAvatar";
|
||||||
import { isEveryoneGroup } from "utils/groups";
|
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 (
|
||||||
|
<>
|
||||||
|
<Helmet>
|
||||||
|
<title>
|
||||||
|
{pageTitle(
|
||||||
|
(groupData?.display_name || groupData?.name) ?? "Loading...",
|
||||||
|
)}
|
||||||
|
</title>
|
||||||
|
</Helmet>
|
||||||
|
<ChooseOne>
|
||||||
|
<Cond condition={isLoading}>
|
||||||
|
<Loader />
|
||||||
|
</Cond>
|
||||||
|
|
||||||
|
<Cond>
|
||||||
|
<Margins>
|
||||||
|
<PageHeader
|
||||||
|
actions={
|
||||||
|
<Maybe condition={canUpdateGroup}>
|
||||||
|
<Link to="settings" component={RouterLink}>
|
||||||
|
<Button startIcon={<SettingsOutlined />}>Settings</Button>
|
||||||
|
</Link>
|
||||||
|
<Button
|
||||||
|
disabled={groupData?.id === groupData?.organization_id}
|
||||||
|
onClick={() => {
|
||||||
|
setIsDeletingGroup(true);
|
||||||
|
}}
|
||||||
|
startIcon={<DeleteOutline />}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</Maybe>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<PageHeaderTitle>
|
||||||
|
{groupData?.display_name || groupData?.name}
|
||||||
|
</PageHeaderTitle>
|
||||||
|
<PageHeaderSubtitle>
|
||||||
|
{/* Show the name if it differs from the display name. */}
|
||||||
|
{groupData?.display_name &&
|
||||||
|
groupData?.display_name !== groupData?.name
|
||||||
|
? groupData?.name
|
||||||
|
: ""}{" "}
|
||||||
|
</PageHeaderSubtitle>
|
||||||
|
</PageHeader>
|
||||||
|
|
||||||
|
<Stack spacing={1}>
|
||||||
|
<Maybe
|
||||||
|
condition={
|
||||||
|
canUpdateGroup &&
|
||||||
|
groupData !== undefined &&
|
||||||
|
!isEveryoneGroup(groupData)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<AddGroupMember
|
||||||
|
isLoading={addMemberMutation.isLoading}
|
||||||
|
onSubmit={async (user, reset) => {
|
||||||
|
try {
|
||||||
|
await addMemberMutation.mutateAsync({
|
||||||
|
groupId,
|
||||||
|
userId: user.id,
|
||||||
|
});
|
||||||
|
reset();
|
||||||
|
} catch (error) {
|
||||||
|
displayError(
|
||||||
|
getErrorMessage(error, "Failed to add member."),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Maybe>
|
||||||
|
<TableToolbar>
|
||||||
|
<PaginationStatus
|
||||||
|
isLoading={Boolean(isLoading)}
|
||||||
|
showing={groupData?.members.length ?? 0}
|
||||||
|
total={groupData?.members.length ?? 0}
|
||||||
|
label="members"
|
||||||
|
/>
|
||||||
|
</TableToolbar>
|
||||||
|
|
||||||
|
<TableContainer>
|
||||||
|
<Table>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell width="99%">User</TableCell>
|
||||||
|
<TableCell width="1%"></TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
|
||||||
|
<TableBody>
|
||||||
|
<ChooseOne>
|
||||||
|
<Cond
|
||||||
|
condition={Boolean(groupData?.members.length === 0)}
|
||||||
|
>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={999}>
|
||||||
|
<EmptyState
|
||||||
|
message="No members yet"
|
||||||
|
description="Add a member using the controls above"
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</Cond>
|
||||||
|
|
||||||
|
<Cond>
|
||||||
|
{groupData?.members.map((member) => (
|
||||||
|
<GroupMemberRow
|
||||||
|
member={member}
|
||||||
|
group={groupData}
|
||||||
|
key={member.id}
|
||||||
|
canUpdate={canUpdateGroup}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Cond>
|
||||||
|
</ChooseOne>
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
</Stack>
|
||||||
|
</Margins>
|
||||||
|
</Cond>
|
||||||
|
</ChooseOne>
|
||||||
|
|
||||||
|
{group && (
|
||||||
|
<DeleteDialog
|
||||||
|
isOpen={isDeletingGroup}
|
||||||
|
confirmLoading={deleteGroupMutation.isLoading}
|
||||||
|
name={group.name}
|
||||||
|
entity="group"
|
||||||
|
onConfirm={async () => {
|
||||||
|
try {
|
||||||
|
await deleteGroupMutation.mutateAsync(groupId);
|
||||||
|
navigate("/groups");
|
||||||
|
} catch (error) {
|
||||||
|
displayError(getErrorMessage(error, "Failed to delete group."));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onCancel={() => {
|
||||||
|
setIsDeletingGroup(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const AddGroupMember: React.FC<{
|
const AddGroupMember: React.FC<{
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
@ -83,122 +250,16 @@ const AddGroupMember: React.FC<{
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GroupPage: React.FC = () => {
|
const GroupMemberRow = (props: {
|
||||||
const { groupId } = useParams();
|
member: User;
|
||||||
if (!groupId) {
|
group: Group;
|
||||||
throw new Error("groupId is not defined.");
|
canUpdate: boolean;
|
||||||
}
|
}) => {
|
||||||
|
const { member, group, canUpdate } = props;
|
||||||
const navigate = useNavigate();
|
const queryClient = useQueryClient();
|
||||||
const [state, send] = useMachine(groupMachine, {
|
const removeMemberMutation = useMutation(removeMember(queryClient));
|
||||||
context: {
|
|
||||||
groupId,
|
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
redirectToGroups: () => {
|
|
||||||
navigate("/groups");
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const { group, permissions } = state.context;
|
|
||||||
const isLoading = group === undefined || permissions === undefined;
|
|
||||||
const canUpdateGroup = permissions ? permissions.canUpdateGroup : false;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
|
||||||
<Helmet>
|
|
||||||
<title>
|
|
||||||
{pageTitle((group?.display_name || group?.name) ?? "Loading...")}
|
|
||||||
</title>
|
|
||||||
</Helmet>
|
|
||||||
<ChooseOne>
|
|
||||||
<Cond condition={isLoading}>
|
|
||||||
<Loader />
|
|
||||||
</Cond>
|
|
||||||
|
|
||||||
<Cond>
|
|
||||||
<Margins>
|
|
||||||
<PageHeader
|
|
||||||
actions={
|
|
||||||
<Maybe condition={canUpdateGroup}>
|
|
||||||
<Link to="settings" component={RouterLink}>
|
|
||||||
<Button startIcon={<SettingsOutlined />}>Settings</Button>
|
|
||||||
</Link>
|
|
||||||
<Button
|
|
||||||
disabled={group?.id === group?.organization_id}
|
|
||||||
onClick={() => {
|
|
||||||
send("DELETE");
|
|
||||||
}}
|
|
||||||
startIcon={<DeleteOutline />}
|
|
||||||
>
|
|
||||||
Delete
|
|
||||||
</Button>
|
|
||||||
</Maybe>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<PageHeaderTitle>
|
|
||||||
{group?.display_name || group?.name}
|
|
||||||
</PageHeaderTitle>
|
|
||||||
<PageHeaderSubtitle>
|
|
||||||
{/* Show the name if it differs from the display name. */}
|
|
||||||
{group?.display_name && group?.display_name !== group?.name
|
|
||||||
? group?.name
|
|
||||||
: ""}{" "}
|
|
||||||
</PageHeaderSubtitle>
|
|
||||||
</PageHeader>
|
|
||||||
|
|
||||||
<Stack spacing={1}>
|
|
||||||
<Maybe
|
|
||||||
condition={
|
|
||||||
canUpdateGroup &&
|
|
||||||
group !== undefined &&
|
|
||||||
!isEveryoneGroup(group)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<AddGroupMember
|
|
||||||
isLoading={state.matches("addingMember")}
|
|
||||||
onSubmit={(user, reset) => {
|
|
||||||
send({
|
|
||||||
type: "ADD_MEMBER",
|
|
||||||
userId: user.id,
|
|
||||||
callback: reset,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Maybe>
|
|
||||||
<TableToolbar>
|
|
||||||
<PaginationStatus
|
|
||||||
isLoading={Boolean(isLoading)}
|
|
||||||
showing={group?.members.length ?? 0}
|
|
||||||
total={group?.members.length ?? 0}
|
|
||||||
label="members"
|
|
||||||
/>
|
|
||||||
</TableToolbar>
|
|
||||||
|
|
||||||
<TableContainer>
|
|
||||||
<Table>
|
|
||||||
<TableHead>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell width="99%">User</TableCell>
|
|
||||||
<TableCell width="1%"></TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableHead>
|
|
||||||
|
|
||||||
<TableBody>
|
|
||||||
<ChooseOne>
|
|
||||||
<Cond condition={Boolean(group?.members.length === 0)}>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell colSpan={999}>
|
|
||||||
<EmptyState
|
|
||||||
message="No members yet"
|
|
||||||
description="Add a member using the controls above"
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</Cond>
|
|
||||||
|
|
||||||
<Cond>
|
|
||||||
{group?.members.map((member) => (
|
|
||||||
<TableRow key={member.id}>
|
<TableRow key={member.id}>
|
||||||
<TableCell width="99%">
|
<TableCell width="99%">
|
||||||
<AvatarData
|
<AvatarData
|
||||||
@ -213,52 +274,32 @@ export const GroupPage: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell width="1%">
|
<TableCell width="1%">
|
||||||
<Maybe condition={canUpdateGroup}>
|
<Maybe condition={canUpdate}>
|
||||||
<TableRowMenu
|
<TableRowMenu
|
||||||
data={member}
|
data={member}
|
||||||
menuItems={[
|
menuItems={[
|
||||||
{
|
{
|
||||||
label: "Remove",
|
label: "Remove",
|
||||||
onClick: () => {
|
onClick: async () => {
|
||||||
send({
|
try {
|
||||||
type: "REMOVE_MEMBER",
|
await removeMemberMutation.mutateAsync({
|
||||||
|
groupId: group.id,
|
||||||
userId: member.id,
|
userId: member.id,
|
||||||
});
|
});
|
||||||
|
displaySuccess("Member removed successfully.");
|
||||||
|
} catch (error) {
|
||||||
|
displayError(
|
||||||
|
getErrorMessage(error, "Failed to remove member."),
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
disabled:
|
disabled: group.id === group.organization_id,
|
||||||
group.id === group.organization_id,
|
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</Maybe>
|
</Maybe>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
|
||||||
</Cond>
|
|
||||||
</ChooseOne>
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</TableContainer>
|
|
||||||
</Stack>
|
|
||||||
</Margins>
|
|
||||||
</Cond>
|
|
||||||
</ChooseOne>
|
|
||||||
|
|
||||||
{group && (
|
|
||||||
<DeleteDialog
|
|
||||||
isOpen={state.matches("confirmingDelete")}
|
|
||||||
confirmLoading={state.matches("deleting")}
|
|
||||||
name={group.name}
|
|
||||||
entity="group"
|
|
||||||
onConfirm={() => {
|
|
||||||
send("CONFIRM_DELETE");
|
|
||||||
}}
|
|
||||||
onCancel={() => {
|
|
||||||
send("CANCEL_DELETE");
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,24 +1,28 @@
|
|||||||
import { useMachine } from "@xstate/react";
|
|
||||||
import { useFeatureVisibility } from "hooks/useFeatureVisibility";
|
import { useFeatureVisibility } from "hooks/useFeatureVisibility";
|
||||||
import { useOrganizationId } from "hooks/useOrganizationId";
|
import { useOrganizationId } from "hooks/useOrganizationId";
|
||||||
import { usePermissions } from "hooks/usePermissions";
|
import { usePermissions } from "hooks/usePermissions";
|
||||||
import { FC } from "react";
|
import { FC, useEffect } from "react";
|
||||||
import { Helmet } from "react-helmet-async";
|
import { Helmet } from "react-helmet-async";
|
||||||
import { pageTitle } from "utils/page";
|
import { pageTitle } from "utils/page";
|
||||||
import { groupsMachine } from "xServices/groups/groupsXService";
|
|
||||||
import GroupsPageView from "./GroupsPageView";
|
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 = () => {
|
export const GroupsPage: FC = () => {
|
||||||
const organizationId = useOrganizationId();
|
const organizationId = useOrganizationId();
|
||||||
const { createGroup: canCreateGroup } = usePermissions();
|
const { createGroup: canCreateGroup } = usePermissions();
|
||||||
const { template_rbac: isTemplateRBACEnabled } = useFeatureVisibility();
|
const { template_rbac: isTemplateRBACEnabled } = useFeatureVisibility();
|
||||||
const [state] = useMachine(groupsMachine, {
|
const groupsQuery = useQuery(groups(organizationId));
|
||||||
context: {
|
|
||||||
organizationId,
|
useEffect(() => {
|
||||||
shouldFetchGroups: isTemplateRBACEnabled,
|
if (groupsQuery.error) {
|
||||||
},
|
displayError(
|
||||||
});
|
getErrorMessage(groupsQuery.error, "Error on loading groups."),
|
||||||
const { groups } = state.context;
|
);
|
||||||
|
}
|
||||||
|
}, [groupsQuery.error]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -27,7 +31,7 @@ export const GroupsPage: FC = () => {
|
|||||||
</Helmet>
|
</Helmet>
|
||||||
|
|
||||||
<GroupsPageView
|
<GroupsPageView
|
||||||
groups={groups}
|
groups={groupsQuery.data}
|
||||||
canCreateGroup={canCreateGroup}
|
canCreateGroup={canCreateGroup}
|
||||||
isTemplateRBACEnabled={isTemplateRBACEnabled}
|
isTemplateRBACEnabled={isTemplateRBACEnabled}
|
||||||
/>
|
/>
|
||||||
|
@ -1,33 +1,24 @@
|
|||||||
import { useMachine } from "@xstate/react";
|
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import { Helmet } from "react-helmet-async";
|
import { Helmet } from "react-helmet-async";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import { pageTitle } from "utils/page";
|
import { pageTitle } from "utils/page";
|
||||||
import { editGroupMachine } from "xServices/groups/editGroupXService";
|
|
||||||
import SettingsGroupPageView from "./SettingsGroupPageView";
|
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 = () => {
|
export const SettingsGroupPage: FC = () => {
|
||||||
const { groupId } = useParams();
|
const { groupId } = useParams() as { groupId: string };
|
||||||
if (!groupId) {
|
const queryClient = useQueryClient();
|
||||||
throw new Error("Group ID not defined.");
|
const groupQuery = useQuery(group(groupId));
|
||||||
}
|
const patchGroupMutation = useMutation(patchGroup(queryClient));
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const navigateToGroup = () => {
|
const navigateToGroup = () => {
|
||||||
navigate(`/groups/${groupId}`);
|
navigate(`/groups/${groupId}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const [editState, sendEditEvent] = useMachine(editGroupMachine, {
|
|
||||||
context: {
|
|
||||||
groupId,
|
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
onUpdate: navigateToGroup,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const { error, group } = editState.context;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
@ -36,13 +27,23 @@ export const SettingsGroupPage: FC = () => {
|
|||||||
|
|
||||||
<SettingsGroupPageView
|
<SettingsGroupPageView
|
||||||
onCancel={navigateToGroup}
|
onCancel={navigateToGroup}
|
||||||
onSubmit={(data) => {
|
onSubmit={async (data) => {
|
||||||
sendEditEvent({ type: "UPDATE", data });
|
try {
|
||||||
|
await patchGroupMutation.mutateAsync({
|
||||||
|
groupId,
|
||||||
|
...data,
|
||||||
|
add_users: [],
|
||||||
|
remove_users: [],
|
||||||
|
});
|
||||||
|
navigateToGroup();
|
||||||
|
} catch (error) {
|
||||||
|
displayError(getErrorMessage(error, "Failed to update group"));
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
group={group}
|
group={groupQuery.data}
|
||||||
formErrors={error}
|
formErrors={groupQuery.error}
|
||||||
isLoading={editState.matches("loading")}
|
isLoading={groupQuery.isLoading}
|
||||||
isUpdating={editState.matches("updating")}
|
isUpdating={patchGroupMutation.isLoading}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -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,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
@ -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,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
@ -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);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
@ -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);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
Reference in New Issue
Block a user