Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ON HOLD feat: update get one item's memberships #982

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions src/api/membership.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,21 @@ import {
import {
buildDeleteItemMembershipRoute,
buildEditItemMembershipRoute,
buildGetItemMembershipsForItemsRoute,
buildGetItemMembershipsForItemRoute,
buildPostItemMembershipRoute,
buildPostManyItemMembershipsRoute,
} from '../routes.js';
import { PartialQueryConfigForApi } from '../types.js';
import { verifyAuthentication } from './axios.js';

export const getMembershipsForItems = async (
ids: UUID[],
export const getMembershipsForItem = async (
id: UUID,
{ API_HOST, axios }: PartialQueryConfigForApi,
) =>
axios
.get<
ResultOf<ItemMembership[]>
>(`${API_HOST}/${buildGetItemMembershipsForItemsRoute(ids)}`)
ItemMembership[]
>(`${API_HOST}/${buildGetItemMembershipsForItemRoute(id)}`)
.then(({ data }) => data);

export const postManyItemMemberships = async (
Expand Down
126 changes: 9 additions & 117 deletions src/hooks/membership.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
import {
FolderItemFactory,
ItemMembership,
MAX_TARGETS_FOR_READ_REQUEST,
ResultOf,
} from '@graasp/sdk';
import { FolderItemFactory, ItemMembership } from '@graasp/sdk';

import { StatusCodes } from 'http-status-codes';
import nock from 'nock';
Expand All @@ -12,12 +7,10 @@ import { afterEach, describe, expect, it } from 'vitest';
import {
ITEM_MEMBERSHIPS_RESPONSE,
UNAUTHORIZED_RESPONSE,
buildResultOfData,
generateFolders,
} from '../../test/constants.js';
import { mockHook, setUpTest, splitEndpointByIds } from '../../test/utils.js';
import { buildManyItemMembershipsKey, itemKeys } from '../keys.js';
import { buildGetItemMembershipsForItemsRoute } from '../routes.js';
import { mockHook, setUpTest } from '../../test/utils.js';
import { itemKeys } from '../keys.js';
import { buildGetItemMembershipsForItemRoute } from '../routes.js';

const { hooks, wrapper, queryClient } = setUpTest();

Expand All @@ -30,23 +23,21 @@ describe('Membership Hooks', () => {
describe('useItemMemberships', () => {
const { id } = FolderItemFactory();
// this hook uses the many endpoint
const response = buildResultOfData([ITEM_MEMBERSHIPS_RESPONSE]);
const route = `/${buildGetItemMembershipsForItemsRoute([id])}`;
const response = ITEM_MEMBERSHIPS_RESPONSE;
const route = `/${buildGetItemMembershipsForItemRoute(id)}`;
const key = itemKeys.single(id).memberships;

it(`Receive one item's memberships`, async () => {
const hook = () => hooks.useItemMemberships(id);
const endpoints = [{ route, response }];
const { data } = await mockHook({ endpoints, hook, wrapper });

expect(data).toEqual(response.data[id]);
expect(data).toEqual(response);
// verify cache keys
expect(queryClient.getQueryData<ItemMembership>(key)).toEqual(
response.data[id],
);
expect(queryClient.getQueryData<ItemMembership[]>(key)).toEqual(response);
});

it(`Undefined ids does not fetch`, async () => {
it(`Undefined id does not fetch`, async () => {
const hook = () => hooks.useItemMemberships(undefined);
const endpoints = [{ route, response }];
const { data, isFetched } = await mockHook({
Expand Down Expand Up @@ -83,103 +74,4 @@ describe('Membership Hooks', () => {
expect(queryClient.getQueryData(key)).toBeFalsy();
});
});

describe('useManyItemMemberships', () => {
const ids = [
FolderItemFactory().id,
FolderItemFactory().id,
FolderItemFactory().id,
];
const response = buildResultOfData([
ITEM_MEMBERSHIPS_RESPONSE,
ITEM_MEMBERSHIPS_RESPONSE,
]);
const route = `/${buildGetItemMembershipsForItemsRoute(ids)}`;
const key = buildManyItemMembershipsKey(ids);

it(`Receive one item memberships`, async () => {
const id = [ids[0]];
const oneRoute = `/${buildGetItemMembershipsForItemsRoute(id)}`;
const oneResponse = buildResultOfData([ITEM_MEMBERSHIPS_RESPONSE]);
const oneKey = buildManyItemMembershipsKey(id);
const hook = () => hooks.useManyItemMemberships(id);
const endpoints = [{ route: oneRoute, response: oneResponse }];
const { data } = await mockHook({ endpoints, hook, wrapper });

expect(data).toEqual(oneResponse);
// verify cache keys
expect(queryClient.getQueryData<ItemMembership>(oneKey)).toEqual(
oneResponse,
);
});

it(`Receive two item memberships`, async () => {
const endpoints = [{ route, response }];
const hook = () => hooks.useManyItemMemberships(ids);
const { data } = await mockHook({ endpoints, hook, wrapper });

expect(data).toMatchObject(response);
// verify cache keys
expect(queryClient.getQueryData<ResultOf<ItemMembership>>(key)).toEqual(
response,
);
});

it(`Receive lots of item memberships`, async () => {
const manyIds = generateFolders(MAX_TARGETS_FOR_READ_REQUEST + 1).map(
({ id }) => id,
);
const memberships = manyIds.map(() => ITEM_MEMBERSHIPS_RESPONSE);
const manyResponse = buildResultOfData(memberships);
const manyKey = buildManyItemMembershipsKey(manyIds);
const hook = () => hooks.useManyItemMemberships(manyIds);
const endpoints = splitEndpointByIds(
manyIds,
MAX_TARGETS_FOR_READ_REQUEST,
(chunk) => `/${buildGetItemMembershipsForItemsRoute(chunk)}`,
memberships,
);
const { data } = await mockHook({ endpoints, hook, wrapper });

expect(data).toEqual(manyResponse);
// verify cache keys
expect(queryClient.getQueryData(manyKey)).toEqual(manyResponse);
});

it(`Undefined ids does not fetch`, async () => {
const hook = () => hooks.useManyItemMemberships(undefined);
const { data, isFetched } = await mockHook({
endpoints: [],
hook,
wrapper,
enabled: false,
});

expect(isFetched).toBeFalsy();
expect(data).toBeFalsy();
// verify cache keys
expect(queryClient.getQueryData(key)).toBeFalsy();
});

it(`Unauthorized`, async () => {
const hook = () => hooks.useManyItemMemberships(ids);
const endpoints = [
{
route,
response: UNAUTHORIZED_RESPONSE,
statusCode: StatusCodes.UNAUTHORIZED,
},
];
const { data, isError } = await mockHook({
hook,
endpoints,
wrapper,
});

expect(isError).toBeTruthy();
expect(data).toBeFalsy();
// verify cache keys
expect(queryClient.getQueryData(key)).toBeFalsy();
});
});
});
39 changes: 3 additions & 36 deletions src/hooks/membership.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
import {
MAX_TARGETS_FOR_READ_REQUEST,
UUID,
WebsocketClient,
} from '@graasp/sdk';
import { UUID, WebsocketClient } from '@graasp/sdk';

import { useQuery } from '@tanstack/react-query';

import { splitRequestByIdsAndReturn } from '../api/axios.js';
import * as Api from '../api/membership.js';
import { UndefinedArgument } from '../config/errors.js';
import { buildManyItemMembershipsKey, itemKeys } from '../keys.js';
import { itemKeys } from '../keys.js';
import { QueryClientConfig } from '../types.js';
import { configureWsMembershipHooks } from '../ws/index.js';

Expand Down Expand Up @@ -39,39 +34,11 @@ export default (
throw new UndefinedArgument();
}

return Api.getMembershipsForItems([id], queryConfig).then(
(data) => data.data[id],
);
return Api.getMembershipsForItem(id, queryConfig);
},
enabled: Boolean(id),
...defaultQueryOptions,
});
},

useManyItemMemberships: (
ids?: UUID[],
options?: { getUpdates?: boolean },
) => {
const getUpdates = options?.getUpdates ?? enableWebsocket;

membershipWsHooks?.useItemsMembershipsUpdates(getUpdates ? ids : null);

return useQuery({
queryKey: buildManyItemMembershipsKey(ids),
queryFn: () => {
if (!ids) {
throw new UndefinedArgument();
}

return splitRequestByIdsAndReturn(
ids,
MAX_TARGETS_FOR_READ_REQUEST,
(chunk) => Api.getMembershipsForItems(chunk, queryConfig),
);
},
enabled: Boolean(ids?.length) && ids?.every((id) => Boolean(id)),
...defaultQueryOptions,
});
},
};
};
7 changes: 0 additions & 7 deletions src/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,12 +285,6 @@ export const buildMentionKey = () => [MENTIONS_CONTEXT];
export const getKeyForParentId = (parentId?: UUID | null) =>
parentId ? itemKeys.single(parentId).allChildren : itemKeys.allAccessible();

export const buildManyItemMembershipsKey = (ids?: UUID[]) => [
ITEMS_CONTEXT,
'memberships',
ids,
];

export const categoryKeys = {
all: ['category'] as const,
single: (id?: UUID) => [...categoryKeys.all, id] as const,
Expand Down Expand Up @@ -372,7 +366,6 @@ export const DATA_KEYS = {
buildItemChatKey,
buildMentionKey,
getKeyForParentId,
buildManyItemMembershipsKey,
buildInvitationKey,
CARDS_KEY,
itemsWithGeolocationKeys,
Expand Down
7 changes: 1 addition & 6 deletions src/mutations/membership.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';

import * as InvitationApi from '../api/invitation.js';
import * as Api from '../api/membership.js';
import { buildManyItemMembershipsKey, itemKeys } from '../keys.js';
import { itemKeys } from '../keys.js';
import { membershipRequestsKeys } from '../membership/request/keys.js';
import {
deleteItemMembershipRoutine,
Expand Down Expand Up @@ -46,11 +46,6 @@ export default (queryConfig: QueryClientConfig) => {
},
onSettled: (_data, _error, { id }) => {
// invalidate memberships
// todo: invalidate all pack of memberships containing the given id
// this won't trigger too many errors as long as the stale time is low
queryClient.invalidateQueries({
queryKey: buildManyItemMembershipsKey([id]),
});
queryClient.invalidateQueries({
queryKey: itemKeys.single(id).memberships,
});
Expand Down
6 changes: 3 additions & 3 deletions src/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ export const buildPostItemMembershipRoute = (id: UUID) =>
export const buildPostManyItemMembershipsRoute = (id: UUID) =>
`item-memberships/${id}`;
export const buildInviteRoute = (id: UUID) => `invite/${id}`;
export const buildGetItemMembershipsForItemsRoute = (ids: UUID[]) =>
`item-memberships?${new URLSearchParams(ids.map((id) => ['itemId', id]))}`;
export const buildGetItemMembershipsForItemRoute = (id: UUID) =>
`item-memberships/${id}`;
export const buildGetItemInvitationsForItemRoute = (id: UUID) =>
`${ITEMS_ROUTE}/${id}/invitations`;
export const buildPostUserCSVUploadRoute = (id: UUID) =>
Expand Down Expand Up @@ -391,7 +391,7 @@ export const API_ROUTES = {
buildGetItemLikesRoute,
buildGetItemLoginSchemaRoute,
buildGetItemLoginSchemaTypeRoute,
buildGetItemMembershipsForItemsRoute,
buildGetItemMembershipsForItemRoute,
buildGetItemPublishedInformationRoute,
buildGetItemsInCategoryRoute,
buildGetLastItemValidationGroupRoute,
Expand Down