Merge branch 'refactor-group-queries' into 'develop'
Refactor group queries See merge request soapbox-pub/soapbox!2579
This commit is contained in:
commit
05efc9f30e
8 changed files with 103 additions and 129 deletions
|
@ -0,0 +1,64 @@
|
|||
import { __stub } from 'soapbox/api';
|
||||
import { Entities } from 'soapbox/entity-store/entities';
|
||||
import { buildAccount, buildGroup } from 'soapbox/jest/factory';
|
||||
import { renderHook, waitFor } from 'soapbox/jest/test-helpers';
|
||||
import { normalizeInstance } from 'soapbox/normalizers';
|
||||
|
||||
import { usePendingGroups } from '../usePendingGroups';
|
||||
|
||||
const id = '1';
|
||||
const group = buildGroup({ id, display_name: 'soapbox' });
|
||||
const store = {
|
||||
instance: normalizeInstance({
|
||||
version: '3.4.1 (compatible; TruthSocial 1.0.0+unreleased)',
|
||||
}),
|
||||
me: '1',
|
||||
entities: {
|
||||
[Entities.ACCOUNTS]: {
|
||||
store: {
|
||||
[id]: buildAccount({
|
||||
id,
|
||||
acct: 'tiger',
|
||||
display_name: 'Tiger',
|
||||
avatar: 'test.jpg',
|
||||
verified: true,
|
||||
}),
|
||||
},
|
||||
lists: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe('usePendingGroups hook', () => {
|
||||
describe('with a successful request', () => {
|
||||
beforeEach(() => {
|
||||
__stub((mock) => {
|
||||
mock.onGet('/api/v1/groups').reply(200, [group]);
|
||||
});
|
||||
});
|
||||
|
||||
it('is successful', async () => {
|
||||
const { result } = renderHook(usePendingGroups, undefined, store);
|
||||
|
||||
await waitFor(() => expect(result.current.isFetching).toBe(false));
|
||||
|
||||
expect(result.current.groups).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with an unsuccessful query', () => {
|
||||
beforeEach(() => {
|
||||
__stub((mock) => {
|
||||
mock.onGet('/api/v1/groups').networkError();
|
||||
});
|
||||
});
|
||||
|
||||
it('is has error state', async() => {
|
||||
const { result } = renderHook(usePendingGroups, undefined, store);
|
||||
|
||||
await waitFor(() => expect(result.current.isFetching).toBe(false));
|
||||
|
||||
expect(result.current.groups).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
});
|
30
app/soapbox/api/hooks/groups/usePendingGroups.ts
Normal file
30
app/soapbox/api/hooks/groups/usePendingGroups.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
import { Entities } from 'soapbox/entity-store/entities';
|
||||
import { useEntities } from 'soapbox/entity-store/hooks';
|
||||
import { useApi, useFeatures, useOwnAccount } from 'soapbox/hooks';
|
||||
import { Group, groupSchema } from 'soapbox/schemas';
|
||||
|
||||
function usePendingGroups() {
|
||||
const api = useApi();
|
||||
const { account } = useOwnAccount();
|
||||
const features = useFeatures();
|
||||
|
||||
const { entities, ...result } = useEntities<Group>(
|
||||
[Entities.GROUPS, account?.id!, 'pending'],
|
||||
() => api.get('/api/v1/groups', {
|
||||
params: {
|
||||
pending: true,
|
||||
},
|
||||
}),
|
||||
{
|
||||
schema: groupSchema,
|
||||
enabled: !!account && features.groupsPending,
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
...result,
|
||||
groups: entities,
|
||||
};
|
||||
}
|
||||
|
||||
export { usePendingGroups };
|
|
@ -35,6 +35,7 @@ export { useGroupsFromTag } from './groups/useGroupsFromTag';
|
|||
export { useJoinGroup } from './groups/useJoinGroup';
|
||||
export { useMuteGroup } from './groups/useMuteGroup';
|
||||
export { useLeaveGroup } from './groups/useLeaveGroup';
|
||||
export { usePendingGroups } from './groups/usePendingGroups';
|
||||
export { usePopularGroups } from './groups/usePopularGroups';
|
||||
export { usePopularTags } from './groups/usePopularTags';
|
||||
export { usePromoteGroupMember } from './groups/usePromoteGroupMember';
|
||||
|
|
|
@ -3,13 +3,11 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
|||
|
||||
import { fetchGroupRelationshipsSuccess } from 'soapbox/actions/groups';
|
||||
import { openModal } from 'soapbox/actions/modals';
|
||||
import { useCancelMembershipRequest, useJoinGroup, useLeaveGroup } from 'soapbox/api/hooks';
|
||||
import { useCancelMembershipRequest, useJoinGroup, useLeaveGroup, usePendingGroups } from 'soapbox/api/hooks';
|
||||
import { Button } from 'soapbox/components/ui';
|
||||
import { importEntities } from 'soapbox/entity-store/actions';
|
||||
import { Entities } from 'soapbox/entity-store/entities';
|
||||
import { useAppDispatch, useOwnAccount } from 'soapbox/hooks';
|
||||
import { queryClient } from 'soapbox/queries/client';
|
||||
import { GroupKeys } from 'soapbox/queries/groups';
|
||||
import { useAppDispatch } from 'soapbox/hooks';
|
||||
import { GroupRoles } from 'soapbox/schemas/group-member';
|
||||
import toast from 'soapbox/toast';
|
||||
|
||||
|
@ -31,11 +29,11 @@ const messages = defineMessages({
|
|||
const GroupActionButton = ({ group }: IGroupActionButton) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const intl = useIntl();
|
||||
const { account } = useOwnAccount();
|
||||
|
||||
const joinGroup = useJoinGroup(group);
|
||||
const leaveGroup = useLeaveGroup(group);
|
||||
const cancelRequest = useCancelMembershipRequest(group);
|
||||
const { invalidate: invalidatePendingGroups } = usePendingGroups();
|
||||
|
||||
const isRequested = group.relationship?.requested;
|
||||
const isNonMember = !group.relationship?.member && !isRequested;
|
||||
|
@ -46,8 +44,8 @@ const GroupActionButton = ({ group }: IGroupActionButton) => {
|
|||
const onJoinGroup = () => joinGroup.mutate({}, {
|
||||
onSuccess(entity) {
|
||||
joinGroup.invalidate();
|
||||
invalidatePendingGroups();
|
||||
dispatch(fetchGroupRelationshipsSuccess([entity]));
|
||||
queryClient.invalidateQueries(GroupKeys.pendingGroups(account?.id as string));
|
||||
|
||||
toast.success(
|
||||
group.locked
|
||||
|
@ -84,7 +82,7 @@ const GroupActionButton = ({ group }: IGroupActionButton) => {
|
|||
requested: false,
|
||||
};
|
||||
dispatch(importEntities([entity], Entities.GROUP_RELATIONSHIPS));
|
||||
queryClient.invalidateQueries(GroupKeys.pendingGroups(account?.id as string));
|
||||
invalidatePendingGroups();
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { useGroupTags } from 'soapbox/api/hooks';
|
||||
import { useGroup, useGroupTags } from 'soapbox/api/hooks';
|
||||
import ScrollableList from 'soapbox/components/scrollable-list';
|
||||
import { Icon, Stack, Text } from 'soapbox/components/ui';
|
||||
import { useGroup } from 'soapbox/queries/groups';
|
||||
|
||||
import PlaceholderAccount from '../placeholder/components/placeholder-account';
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import React from 'react';
|
||||
|
||||
import { usePendingGroups } from 'soapbox/api/hooks';
|
||||
import { PendingItemsRow } from 'soapbox/components/pending-items-row';
|
||||
import { Divider } from 'soapbox/components/ui';
|
||||
import { useFeatures } from 'soapbox/hooks';
|
||||
import { usePendingGroups } from 'soapbox/queries/groups';
|
||||
|
||||
export default () => {
|
||||
const features = useFeatures();
|
||||
|
|
|
@ -2,10 +2,10 @@ import React from 'react';
|
|||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { usePendingGroups } from 'soapbox/api/hooks';
|
||||
import GroupCard from 'soapbox/components/group-card';
|
||||
import ScrollableList from 'soapbox/components/scrollable-list';
|
||||
import { Column, Stack, Text } from 'soapbox/components/ui';
|
||||
import { usePendingGroups } from 'soapbox/queries/groups';
|
||||
|
||||
import PlaceholderGroupCard from '../placeholder/components/placeholder-group-card';
|
||||
|
||||
|
|
|
@ -1,118 +0,0 @@
|
|||
import { useInfiniteQuery, useQuery } from '@tanstack/react-query';
|
||||
import { AxiosRequestConfig } from 'axios';
|
||||
|
||||
import { getNextLink } from 'soapbox/api';
|
||||
import { useApi, useFeatures, useOwnAccount } from 'soapbox/hooks';
|
||||
import { normalizeGroup, normalizeGroupRelationship } from 'soapbox/normalizers';
|
||||
import { Group, GroupRelationship } from 'soapbox/types/entities';
|
||||
import { flattenPages, PaginatedResult } from 'soapbox/utils/queries';
|
||||
|
||||
const GroupKeys = {
|
||||
group: (id: string) => ['groups', 'group', id] as const,
|
||||
pendingGroups: (userId: string) => ['groups', userId, 'pending'] as const,
|
||||
};
|
||||
|
||||
const useGroupsApi = () => {
|
||||
const api = useApi();
|
||||
|
||||
const getGroupRelationships = async (ids: string[]) => {
|
||||
const queryString = ids.map((id) => `id[]=${id}`).join('&');
|
||||
const { data } = await api.get<GroupRelationship[]>(`/api/v1/groups/relationships?${queryString}`);
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const fetchGroups = async (endpoint: string, params: AxiosRequestConfig['params'] = {}) => {
|
||||
const response = await api.get<Group[]>(endpoint, {
|
||||
params,
|
||||
});
|
||||
const groups = [response.data].flat();
|
||||
const relationships = await getGroupRelationships(groups.map((group) => group.id));
|
||||
const result = groups.map((group) => {
|
||||
const relationship = relationships.find((relationship) => relationship.id === group.id);
|
||||
|
||||
return normalizeGroup({
|
||||
...group,
|
||||
relationship: relationship ? normalizeGroupRelationship(relationship) : null,
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
response,
|
||||
groups: result,
|
||||
};
|
||||
};
|
||||
|
||||
return { fetchGroups };
|
||||
};
|
||||
|
||||
const usePendingGroups = () => {
|
||||
const features = useFeatures();
|
||||
const { account } = useOwnAccount();
|
||||
const { fetchGroups } = useGroupsApi();
|
||||
|
||||
const getGroups = async (pageParam?: any): Promise<PaginatedResult<Group>> => {
|
||||
const endpoint = '/api/v1/groups';
|
||||
const nextPageLink = pageParam?.link;
|
||||
const uri = nextPageLink || endpoint;
|
||||
const { response, groups } = await fetchGroups(uri, {
|
||||
pending: true,
|
||||
});
|
||||
|
||||
const link = getNextLink(response);
|
||||
const hasMore = !!link;
|
||||
|
||||
return {
|
||||
result: groups,
|
||||
hasMore,
|
||||
link,
|
||||
};
|
||||
};
|
||||
|
||||
const queryInfo = useInfiniteQuery(
|
||||
GroupKeys.pendingGroups(account?.id as string),
|
||||
({ pageParam }: any) => getGroups(pageParam),
|
||||
{
|
||||
enabled: !!account && features.groupsPending,
|
||||
keepPreviousData: true,
|
||||
getNextPageParam: (config) => {
|
||||
if (config?.hasMore) {
|
||||
return { nextLink: config?.link };
|
||||
}
|
||||
|
||||
return undefined;
|
||||
},
|
||||
});
|
||||
|
||||
const data = flattenPages(queryInfo.data);
|
||||
|
||||
return {
|
||||
...queryInfo,
|
||||
groups: data || [],
|
||||
};
|
||||
};
|
||||
|
||||
const useGroup = (id: string) => {
|
||||
const features = useFeatures();
|
||||
const { fetchGroups } = useGroupsApi();
|
||||
|
||||
const getGroup = async () => {
|
||||
const { groups } = await fetchGroups(`/api/v1/groups/${id}`);
|
||||
return groups[0];
|
||||
};
|
||||
|
||||
const queryInfo = useQuery<Group>(GroupKeys.group(id), getGroup, {
|
||||
enabled: features.groups && !!id,
|
||||
});
|
||||
|
||||
return {
|
||||
...queryInfo,
|
||||
group: queryInfo.data,
|
||||
};
|
||||
};
|
||||
|
||||
export {
|
||||
useGroup,
|
||||
usePendingGroups,
|
||||
GroupKeys,
|
||||
};
|
Loading…
Reference in a new issue