From e5f675fc74fce8206558e834ba43da7583d940b6 Mon Sep 17 00:00:00 2001 From: Chris Chudzicki Date: Mon, 10 Feb 2025 16:01:09 -0500 Subject: [PATCH 1/5] fix test data --- .../test-utils/factories/learningResources.ts | 46 +++++++++++++++++++ .../LearningResourceDrawer.test.tsx | 18 +++----- 2 files changed, 53 insertions(+), 11 deletions(-) diff --git a/frontends/api/src/test-utils/factories/learningResources.ts b/frontends/api/src/test-utils/factories/learningResources.ts index c3d1f7bf0b..d991048ca7 100644 --- a/frontends/api/src/test-utils/factories/learningResources.ts +++ b/frontends/api/src/test-utils/factories/learningResources.ts @@ -27,6 +27,7 @@ import type { ProgramResource, VideoPlaylistResource, VideoResource, + LearningResourceRelationship, } from "api" import { AvailabilityEnum, @@ -390,6 +391,49 @@ const microLearningPathRelationship: Factory = ( } } +const learningResourceRelationship: Factory = ( + overrides = {}, +) => { + const micro = microLearningPathRelationship() + const resource = learningResource({ + id: micro.child, + }) + return { + ...micro, + position: faker.number.int(), + resource, + ...overrides, + } +} +const learningResourceRelationships = ({ + count, + parent, + pageSize, + next = null, + previous = null, +}: { + count: number + parent: number + pageSize?: number + next?: string | null + previous?: string | null +}) => { + const results: LearningPathRelationship[] = Array(pageSize ?? count) + .fill(null) + .map((_val, index) => { + return learningResourceRelationship({ + position: index + 1, + parent, + }) + }) + return { + count, + next, + previous, + results, + } satisfies PaginatedLearningPathRelationshipList +} + const learningPathRelationship: Factory = ( overrides = {}, ) => { @@ -525,6 +569,8 @@ export { microLearningPathRelationship, learningPathRelationship, learningPathRelationships, + learningResourceRelationship, + learningResourceRelationships, program, programs, course, diff --git a/frontends/main/src/page-components/LearningResourceDrawer/LearningResourceDrawer.test.tsx b/frontends/main/src/page-components/LearningResourceDrawer/LearningResourceDrawer.test.tsx index e1d8f0b3c5..312b3ff0cd 100644 --- a/frontends/main/src/page-components/LearningResourceDrawer/LearningResourceDrawer.test.tsx +++ b/frontends/main/src/page-components/LearningResourceDrawer/LearningResourceDrawer.test.tsx @@ -63,22 +63,18 @@ describe("LearningResourceDrawer", () => { resource.resource_type === ResourceTypeEnum.VideoPlaylist || resource.resource_type === ResourceTypeEnum.Podcast ) { - const items = factories.learningResources.resources({ - count: 10, - }) - items.results.forEach((item) => { - setMockResponse.get( - urls.learningResources.details({ id: item.id }), - item, - ) - }) + const relationships = + factories.learningResources.learningResourceRelationships({ + count: 3, + parent: resource.id, + }) setMockResponse.get( `${urls.learningResources.items({ id: resource.id })}?limit=12`, - items.results, + relationships, ) - return { resource, user, items } + return { resource, user } } return { resource, user } From 762956d12a32eb8ee99078f9dcf7434f776e9f36 Mon Sep 17 00:00:00 2001 From: Chris Chudzicki Date: Mon, 10 Feb 2025 16:03:05 -0500 Subject: [PATCH 2/5] remove keyfactory for learningresources --- .../api/src/hooks/learningPaths/index.test.ts | 4 +- .../api/src/hooks/learningPaths/keyFactory.ts | 2 +- .../api/src/hooks/learningResources/index.ts | 39 ++- .../src/hooks/learningResources/keyFactory.ts | 166 ----------- .../src/hooks/learningResources/queries.ts | 265 ++++++++++++++++++ .../api/src/hooks/userLists/keyFactory.ts | 2 +- .../api/src/ssr/usePrefetchWarnings.test.ts | 12 +- .../src/app/c/[channelType]/[name]/page.tsx | 13 +- frontends/main/src/app/departments/page.tsx | 4 +- frontends/main/src/app/page.tsx | 21 +- frontends/main/src/app/search/page.tsx | 9 +- frontends/main/src/app/topics/page.tsx | 4 +- .../ResourceCard/ResourceCard.test.tsx | 4 +- .../ResourceCarousel/ResourceCarousel.tsx | 19 +- 14 files changed, 342 insertions(+), 222 deletions(-) delete mode 100644 frontends/api/src/hooks/learningResources/keyFactory.ts create mode 100644 frontends/api/src/hooks/learningResources/queries.ts diff --git a/frontends/api/src/hooks/learningPaths/index.test.ts b/frontends/api/src/hooks/learningPaths/index.test.ts index d16672b12b..f1f3333bdc 100644 --- a/frontends/api/src/hooks/learningPaths/index.test.ts +++ b/frontends/api/src/hooks/learningPaths/index.test.ts @@ -5,7 +5,7 @@ import { LearningResource } from "../../generated/v1" import * as factories from "../../test-utils/factories" import { setupReactQueryTest } from "../test-utils" import { setMockResponse, urls, makeRequest } from "../../test-utils" -import keyFactory from "../learningResources/keyFactory" +import { learningResourceKeys } from "../learningResources/queries" import { useLearningPathsDetail, useLearningPathsList, @@ -115,7 +115,7 @@ describe("LearningPath CRUD", () => { const path = factory.learningPath() const relationship = factory.learningPathRelationship({ parent: path.id }) const keys = { - learningResources: keyFactory._def, + learningResources: learningResourceKeys.root, relationshipListing: learningPathKeyFactory.detail(path.id)._ctx .infiniteItems._def, } diff --git a/frontends/api/src/hooks/learningPaths/keyFactory.ts b/frontends/api/src/hooks/learningPaths/keyFactory.ts index fec3ba8d7d..8afed55333 100644 --- a/frontends/api/src/hooks/learningPaths/keyFactory.ts +++ b/frontends/api/src/hooks/learningPaths/keyFactory.ts @@ -6,7 +6,7 @@ import type { LearningpathsApiLearningpathsListRequest as ListRequest, PaginatedLearningPathRelationshipList, } from "../../generated/v1" -import { clearListMemberships } from "../learningResources/keyFactory" +import { clearListMemberships } from "../learningResources/queries" const learningPaths = createQueryKeys("learningPaths", { detail: (id: number) => ({ diff --git a/frontends/api/src/hooks/learningResources/index.ts b/frontends/api/src/hooks/learningResources/index.ts index e9e5a3b0eb..b14c811d89 100644 --- a/frontends/api/src/hooks/learningResources/index.ts +++ b/frontends/api/src/hooks/learningResources/index.ts @@ -16,7 +16,14 @@ import type { LearningResourcesApiLearningResourcesLearningPathsPartialUpdateRequest, LearningResource, } from "../../generated/v1" -import learningResources from "./keyFactory" +// import learningResources from "./keyFactory" +import { + learningResourceQueries, + offerorQueries, + topicQueries, + schoolQueries, + platformsQueries, +} from "./queries" import userLists from "../userLists/keyFactory" import learningPaths from "../learningPaths/keyFactory" import { useCallback } from "react" @@ -26,7 +33,7 @@ const useLearningResourcesList = ( opts: Pick = {}, ) => { return useQuery({ - ...learningResources.list(params), + ...learningResourceQueries.list(params), ...opts, }) } @@ -38,7 +45,7 @@ const useLearningResourceDetailSetCache = ( const onClick = useCallback(() => { if (resource) { queryClient.setQueryData( - learningResources.detail(resource.id).queryKey, + learningResourceQueries.detail(resource.id).queryKey, resource, ) } @@ -47,11 +54,11 @@ const useLearningResourceDetailSetCache = ( } const useLearningResourcesDetail = (id: number) => { - return useQuery(learningResources.detail(id)) + return useQuery(learningResourceQueries.detail(id)) } const useFeaturedLearningResourcesList = (params: FeaturedListParams = {}) => { - return useQuery(learningResources.featured(params)) + return useQuery(learningResourceQueries.featured(params)) } const useLearningResourceTopic = ( @@ -59,7 +66,7 @@ const useLearningResourceTopic = ( opts: Pick = {}, ) => { return useQuery({ - ...learningResources.topic(id), + ...topicQueries.detail(id), ...opts, }) } @@ -69,7 +76,7 @@ const useLearningResourceTopics = ( opts: Pick = {}, ) => { return useQuery({ - ...learningResources.topics(params), + ...topicQueries.list(params), ...opts, }) } @@ -79,7 +86,7 @@ const useLearningResourcesSearch = ( opts?: Pick, ) => { return useQuery({ - ...learningResources.search(params), + ...learningResourceQueries.search(params), ...opts, }) } @@ -114,7 +121,7 @@ const useOfferorsList = ( opts: Pick = {}, ) => { return useQuery({ - ...learningResources.offerors(params), + ...offerorQueries.list(params), ...opts, }) } @@ -124,13 +131,13 @@ const usePlatformsList = ( opts: Pick = {}, ) => { return useQuery({ - ...learningResources.platforms(params), + ...platformsQueries.list(params), ...opts, }) } const useSchoolsList = () => { - return useQuery(learningResources.schools()) + return useQuery(schoolQueries.list()) } const useSimilarLearningResources = ( @@ -138,7 +145,7 @@ const useSimilarLearningResources = ( opts: Pick = {}, ) => { return useQuery({ - ...learningResources.similar(id), + ...learningResourceQueries.similar(id), ...opts, }) } @@ -148,7 +155,7 @@ const useVectorSimilarLearningResources = ( opts: Pick = {}, ) => { return useQuery({ - ...learningResources.vectorSimilar(id), + ...learningResourceQueries.vectorSimilar(id), ...opts, }) } @@ -168,5 +175,9 @@ export { useSchoolsList, useSimilarLearningResources, useVectorSimilarLearningResources, - learningResources, + learningResourceQueries, + offerorQueries, + schoolQueries, + platformsQueries, + topicQueries, } diff --git a/frontends/api/src/hooks/learningResources/keyFactory.ts b/frontends/api/src/hooks/learningResources/keyFactory.ts deleted file mode 100644 index f3f0f4b504..0000000000 --- a/frontends/api/src/hooks/learningResources/keyFactory.ts +++ /dev/null @@ -1,166 +0,0 @@ -import { createQueryKeys } from "@lukemorales/query-key-factory" -import { - learningResourcesApi, - learningResourcesSearchApi, - topicsApi, - offerorsApi, - platformsApi, - schoolsApi, - featuredApi, -} from "../../clients" -import axiosInstance from "../../axios" -import type { - LearningResource, - LearningResourcesApiLearningResourcesListRequest as LearningResourcesListRequest, - TopicsApiTopicsListRequest as TopicsListRequest, - LearningResourcesSearchApiLearningResourcesSearchRetrieveRequest as LearningResourcesSearchRetrieveRequest, - OfferorsApiOfferorsListRequest, - PlatformsApiPlatformsListRequest, - FeaturedApiFeaturedListRequest as FeaturedListParams, - LearningResourcesApiLearningResourcesItemsListRequest as ItemsListRequest, - PaginatedLearningResourceRelationshipList, -} from "../../generated/v1" - -const shuffle = ([...arr]) => { - let m = arr.length - while (m) { - const i = Math.floor(Math.random() * m--) - ;[arr[m], arr[i]] = [arr[i], arr[m]] - } - return arr -} - -const randomizeResults = ([...results]) => { - const resultsByPosition: { - [position: string]: (LearningResource & { position?: string })[] | undefined - } = {} - const randomizedResults: LearningResource[] = [] - results.forEach((result) => { - if (!resultsByPosition[result?.position]) { - resultsByPosition[result?.position] = [] - } - resultsByPosition[result?.position ?? ""]?.push(result) - }) - Object.keys(resultsByPosition) - .sort() - .forEach((position) => { - const shuffled = shuffle(resultsByPosition[position] ?? []) - randomizedResults.push(...shuffled) - }) - return randomizedResults -} - -/* List memberships were previously determined in the learningResourcesApi - * from user_list_parents and learning_path_parents on each resource. - * Resource endpoints are now treated as public so that they can be - * server rendered and cached on public CDN. The membership endpoints on - * learningPathsApi and userListsApi are now used to determine membership. - * Removing here to ensure they are not depended on anywhere, though they can - * be removed from the GET APIs TODO. - */ -export const clearListMemberships = (resource: LearningResource) => ({ - ...resource, - user_list_parents: [], - learning_path_parents: [], -}) - -const learningResources = createQueryKeys("learningResources", { - detail: (id: number) => ({ - queryKey: ["detail", id], - queryFn: async () => { - const { data } = await learningResourcesApi.learningResourcesRetrieve({ - id, - }) - return clearListMemberships(data) - }, - contextQueries: { - items: (itemsP: ItemsListRequest) => ({ - queryKey: [itemsP], - queryFn: async ({ pageParam }: { pageParam?: string } = {}) => { - // Use generated API for first request, then use next parameter - const request = pageParam - ? axiosInstance.request({ - method: "get", - url: pageParam, - }) - : learningResourcesApi.learningResourcesItemsList(itemsP) - const { data } = await request - return { - ...data, - results: data.results?.map((relation) => ({ - ...clearListMemberships(relation.resource), - })), - } - }, - }), - }, - }), - list: (params: LearningResourcesListRequest) => ({ - queryKey: [params], - queryFn: async () => { - const { data } = await learningResourcesApi.learningResourcesList(params) - return { - ...data, - results: data.results.map(clearListMemberships), - } - }, - }), - featured: (params: FeaturedListParams = {}) => ({ - queryKey: [params], - queryFn: async () => { - const { data } = await featuredApi.featuredList(params) - return { - ...data, - results: randomizeResults(data.results.map(clearListMemberships)), - } - }, - }), - topic: (id: number | undefined) => ({ - queryKey: [id], - queryFn: () => - id ? topicsApi.topicsRetrieve({ id }).then((res) => res.data) : null, - }), - topics: (params: TopicsListRequest) => ({ - queryKey: [params], - queryFn: () => topicsApi.topicsList(params).then((res) => res.data), - }), - search: (params: LearningResourcesSearchRetrieveRequest) => ({ - queryKey: [params], - queryFn: async () => { - const { data } = - await learningResourcesSearchApi.learningResourcesSearchRetrieve(params) - return { - ...data, - results: data.results.map(clearListMemberships), - } - }, - }), - offerors: (params: OfferorsApiOfferorsListRequest) => ({ - queryKey: [params], - queryFn: () => offerorsApi.offerorsList(params).then((res) => res.data), - }), - platforms: (params: PlatformsApiPlatformsListRequest) => ({ - queryKey: [params], - queryFn: () => platformsApi.platformsList(params).then((res) => res.data), - }), - schools: () => ({ - queryKey: ["schools"], - queryFn: () => schoolsApi.schoolsList().then((res) => res.data), - }), - similar: (id: number) => ({ - queryKey: [`similar_resources-${id}`], - queryFn: () => - learningResourcesApi - .learningResourcesSimilarList({ id }) - .then((res) => res.data), - }), - vectorSimilar: (id: number) => ({ - queryKey: [`vector_similar_resources-${id}`], - queryFn: () => - learningResourcesApi - .learningResourcesVectorSimilarList({ id }) - .then((res) => res.data), - }), -}) - -export default learningResources diff --git a/frontends/api/src/hooks/learningResources/queries.ts b/frontends/api/src/hooks/learningResources/queries.ts new file mode 100644 index 0000000000..a2e0c4a2b5 --- /dev/null +++ b/frontends/api/src/hooks/learningResources/queries.ts @@ -0,0 +1,265 @@ +import { + learningResourcesApi, + learningResourcesSearchApi, + topicsApi, + offerorsApi, + platformsApi, + schoolsApi, + featuredApi, +} from "../../clients" +import axiosInstance from "../../axios" +import type { + LearningResource, + LearningResourcesApiLearningResourcesListRequest as LearningResourcesListRequest, + TopicsApiTopicsListRequest as TopicsListRequest, + LearningResourcesSearchApiLearningResourcesSearchRetrieveRequest as LearningResourcesSearchRetrieveRequest, + OfferorsApiOfferorsListRequest, + PlatformsApiPlatformsListRequest, + FeaturedApiFeaturedListRequest as FeaturedListParams, + LearningResourcesApiLearningResourcesItemsListRequest as ItemsListRequest, + PaginatedLearningResourceRelationshipList, +} from "../../generated/v1" +import type { QueryOptions } from "@tanstack/react-query" + +/* List memberships were previously determined in the learningResourcesApi + * from user_list_parents and learning_path_parents on each resource. + * Resource endpoints are now treated as public so that they can be + * server rendered and cached on public CDN. The membership endpoints on + * learningPathsApi and userListsApi are now used to determine membership. + * Removing here to ensure they are not depended on anywhere, though they can + * be removed from the GET APIs TODO. + */ +export const clearListMemberships = (resource: LearningResource) => ({ + ...resource, + user_list_parents: [], + learning_path_parents: [], +}) + +const shuffle = ([...arr]) => { + let m = arr.length + while (m) { + const i = Math.floor(Math.random() * m--) + ;[arr[m], arr[i]] = [arr[i], arr[m]] + } + return arr +} + +const randomizeResults = ([...results]) => { + const resultsByPosition: { + [position: string]: (LearningResource & { position?: string })[] | undefined + } = {} + const randomizedResults: LearningResource[] = [] + results.forEach((result) => { + if (!resultsByPosition[result?.position]) { + resultsByPosition[result?.position] = [] + } + resultsByPosition[result?.position ?? ""]?.push(result) + }) + Object.keys(resultsByPosition) + .sort() + .forEach((position) => { + const shuffled = shuffle(resultsByPosition[position] ?? []) + randomizedResults.push(...shuffled) + }) + return randomizedResults +} + +const learningResourceKeys = { + root: ["learning_resources"], + // list + listsRoot: () => [...learningResourceKeys.root, "listsAll"], + list: (params: LearningResourcesListRequest) => [ + ...learningResourceKeys.listsRoot(), + params, + ], + // detail + detailsRoot: () => [...learningResourceKeys.root, "detail"], + detail: (id: number) => [...learningResourceKeys.detailsRoot(), id], + similar: (id: number) => [...learningResourceKeys.detail(id), "similar"], + vectorSimilar: (id: number) => [ + ...learningResourceKeys.detail(id), + "vector_similar", + ], + itemsRoot: (id: number) => [...learningResourceKeys.detail(id), "items"], + items: (id: number, params: ItemsListRequest) => [ + ...learningResourceKeys.itemsRoot(id), + params, + ], + // featured + featuredRoot: () => [...learningResourceKeys.root, "featureds"], + featured: (params: FeaturedListParams) => [ + ...learningResourceKeys.featuredRoot(), + params, + ], + // searching + searchRoot: () => [...learningResourceKeys.root, "search"], + search: (params: LearningResourcesSearchRetrieveRequest) => [ + ...learningResourceKeys.searchRoot(), + params, + ], +} + +const topicsKeys = { + root: ["topics"], + listRoot: () => [...topicsKeys.root, "list"], + list: (params: TopicsListRequest) => [...topicsKeys.listRoot(), params], + detailRoot: () => [...topicsKeys.root, "detail"], + detail: (id: number) => [...topicsKeys.detailRoot(), id], +} + +const platformsKeys = { + root: ["platforms"], + listRoot: () => [...platformsKeys.root, "list"], + list: (params: PlatformsApiPlatformsListRequest) => [ + ...platformsKeys.listRoot(), + params, + ], +} + +const schoolKeys = { + root: ["schools"], + listRoot: () => [...schoolKeys.root, "list"], + list: () => [...schoolKeys.listRoot()], +} + +const offerorsKeys = { + root: ["offerors"], + listRoot: () => [...offerorsKeys.root, "list"], + list: (params: OfferorsApiOfferorsListRequest) => [ + ...offerorsKeys.listRoot(), + params, + ], +} + +const learningResourceQueries = { + detail: (id: number) => + ({ + queryKey: learningResourceKeys.detail(id), + queryFn: () => + learningResourcesApi + .learningResourcesRetrieve({ id }) + .then((res) => clearListMemberships(res.data)), + }) satisfies QueryOptions, + items: (id: number, params: ItemsListRequest) => { + return { + queryKey: learningResourceKeys.items(id, params), + queryFn: ({ pageParam }: { pageParam?: string } = {}) => { + // Use generated API for first request, then use next parameter + const request = pageParam + ? axiosInstance.request({ + method: "get", + url: pageParam, + }) + : learningResourcesApi.learningResourcesItemsList(params) + return request.then((res) => + res.data.results.map((rel) => clearListMemberships(rel.resource)), + ) + }, + } satisfies QueryOptions + }, + similar: (id: number) => { + return { + queryKey: learningResourceKeys.similar(id), + queryFn: () => + learningResourcesApi + .learningResourcesSimilarList({ id }) + .then((res) => res.data.map(clearListMemberships)), + } satisfies QueryOptions + }, + vectorSimilar: (id: number) => { + return { + queryKey: learningResourceKeys.vectorSimilar(id), + queryFn: () => + learningResourcesApi + .learningResourcesVectorSimilarList({ id }) + .then((res) => res.data.map(clearListMemberships)), + } satisfies QueryOptions + }, + list: (params: LearningResourcesListRequest) => { + return { + queryKey: learningResourceKeys.list(params), + queryFn: () => + learningResourcesApi.learningResourcesList(params).then((res) => ({ + ...res.data, + results: res.data.results.map(clearListMemberships), + })), + } satisfies QueryOptions + }, + featured: (params: FeaturedListParams = {}) => { + return { + queryKey: learningResourceKeys.featured(params), + queryFn: () => + featuredApi.featuredList(params).then((res) => { + return { + ...res.data, + results: randomizeResults( + res.data.results.map(clearListMemberships), + ), + } + }), + } satisfies QueryOptions + }, + search: (params: LearningResourcesSearchRetrieveRequest) => { + return { + queryKey: learningResourceKeys.search(params), + queryFn: () => + learningResourcesSearchApi + .learningResourcesSearchRetrieve(params) + .then((res) => ({ + ...res.data, + results: res.data.results.map(clearListMemberships), + })), + } satisfies QueryOptions + }, +} + +const topicQueries = { + list: (params: TopicsListRequest) => { + return { + queryKey: topicsKeys.list(params), + queryFn: () => topicsApi.topicsList(params).then((res) => res.data), + } satisfies QueryOptions + }, + detail: (id: number) => { + return { + queryKey: topicsKeys.detail(id), + queryFn: () => topicsApi.topicsRetrieve({ id }).then((res) => res.data), + } satisfies QueryOptions + }, +} + +const platformsQueries = { + list: (params: PlatformsApiPlatformsListRequest) => { + return { + queryKey: platformsKeys.list(params), + queryFn: () => platformsApi.platformsList(params).then((res) => res.data), + } satisfies QueryOptions + }, +} + +const schoolQueries = { + list: () => { + return { + queryKey: schoolKeys.list(), + queryFn: () => schoolsApi.schoolsList().then((res) => res.data), + } satisfies QueryOptions + }, +} + +const offerorQueries = { + list: (params: OfferorsApiOfferorsListRequest) => { + return { + queryKey: offerorsKeys.list(params), + queryFn: () => offerorsApi.offerorsList(params).then((res) => res.data), + } satisfies QueryOptions + }, +} + +export { + learningResourceKeys, + learningResourceQueries, + topicQueries, + platformsQueries, + schoolQueries, + offerorQueries, +} diff --git a/frontends/api/src/hooks/userLists/keyFactory.ts b/frontends/api/src/hooks/userLists/keyFactory.ts index ee3d61268c..e0df854ed1 100644 --- a/frontends/api/src/hooks/userLists/keyFactory.ts +++ b/frontends/api/src/hooks/userLists/keyFactory.ts @@ -6,7 +6,7 @@ import type { PaginatedUserListRelationshipList, } from "../../generated/v1" import { userListsApi } from "../../clients" -import { clearListMemberships } from "../learningResources/keyFactory" +import { clearListMemberships } from "../learningResources/queries" const userLists = createQueryKeys("userLists", { detail: (id: number) => ({ diff --git a/frontends/api/src/ssr/usePrefetchWarnings.test.ts b/frontends/api/src/ssr/usePrefetchWarnings.test.ts index 1b937459e1..0eae224862 100644 --- a/frontends/api/src/ssr/usePrefetchWarnings.test.ts +++ b/frontends/api/src/ssr/usePrefetchWarnings.test.ts @@ -4,7 +4,7 @@ import { usePrefetchWarnings } from "./usePrefetchWarnings" import { setupReactQueryTest } from "../hooks/test-utils" import { urls, factories, setMockResponse } from "../test-utils" import { - learningResources, + learningResourceQueries, useLearningResourcesDetail, } from "../hooks/learningResources" @@ -45,7 +45,7 @@ describe("SSR prefetch warnings", () => { expect.objectContaining({ disabled: false, initialStatus: "loading", - key: learningResources.detail(1).queryKey, + key: learningResourceQueries.detail(1).queryKey, observerCount: 1, }), ], @@ -65,7 +65,7 @@ describe("SSR prefetch warnings", () => { wrapper, initialProps: { queryClient, - exemptions: [learningResources.detail(1).queryKey], + exemptions: [learningResourceQueries.detail(1).queryKey], }, }) @@ -83,7 +83,7 @@ describe("SSR prefetch warnings", () => { const { unmount } = renderHook( () => useQuery({ - ...learningResources.detail(1), + ...learningResourceQueries.detail(1), initialData: data, }), { wrapper }, @@ -105,9 +105,9 @@ describe("SSR prefetch warnings", () => { [ { disabled: false, - hash: JSON.stringify(learningResources.detail(1).queryKey), + hash: JSON.stringify(learningResourceQueries.detail(1).queryKey), initialStatus: "success", - key: learningResources.detail(1).queryKey, + key: learningResourceQueries.detail(1).queryKey, observerCount: 0, status: "success", }, diff --git a/frontends/main/src/app/c/[channelType]/[name]/page.tsx b/frontends/main/src/app/c/[channelType]/[name]/page.tsx index 04a309948f..838b4874f9 100644 --- a/frontends/main/src/app/c/[channelType]/[name]/page.tsx +++ b/frontends/main/src/app/c/[channelType]/[name]/page.tsx @@ -11,7 +11,10 @@ import { import { getMetadataAsync } from "@/common/metadata" import { Hydrate } from "@tanstack/react-query" import { prefetch } from "api/ssr/prefetch" -import { learningResources } from "api/hooks/learningResources" +import { + learningResourceQueries, + offerorQueries, +} from "api/hooks/learningResources" import { channels } from "api/hooks/channels" import { testimonials } from "api/hooks/testimonials" import handleNotFound from "@/common/handleNotFound" @@ -54,9 +57,9 @@ const Page: React.FC = async ({ const search = await searchParams const { queryClient } = await prefetch([ - learningResources.offerors({}), + offerorQueries.list({}), channelType === ChannelTypeEnum.Unit && - learningResources.featured({ + learningResourceQueries.featured({ limit: 12, offered_by: [name], }), @@ -70,7 +73,7 @@ const Page: React.FC = async ({ ) const offerors = queryClient .getQueryData( - learningResources.offerors({}).queryKey, + offerorQueries.list({}).queryKey, )! .results.reduce( (memo, offeror) => ({ @@ -97,7 +100,7 @@ const Page: React.FC = async ({ }) const { dehydratedState } = await prefetch( - [learningResources.search(searchRequest as LRSearchRequest)], + [learningResourceQueries.search(searchRequest as LRSearchRequest)], queryClient, ) return ( diff --git a/frontends/main/src/app/departments/page.tsx b/frontends/main/src/app/departments/page.tsx index bfc54b2811..3e04df4db4 100644 --- a/frontends/main/src/app/departments/page.tsx +++ b/frontends/main/src/app/departments/page.tsx @@ -2,7 +2,7 @@ import React from "react" import { Metadata } from "next" import { Hydrate } from "@tanstack/react-query" import { standardizeMetadata } from "@/common/metadata" -import { learningResources } from "api/hooks/learningResources" +import { schoolQueries } from "api/hooks/learningResources" import { channels } from "api/hooks/channels" import { prefetch } from "api/ssr/prefetch" import DepartmentListingPage from "@/app-pages/DepartmentListingPage/DepartmentListingPage" @@ -14,7 +14,7 @@ export const metadata: Metadata = standardizeMetadata({ const Page: React.FC = async () => { const { dehydratedState } = await prefetch([ channels.countsByType("department"), - learningResources.schools(), + schoolQueries.list(), ]) return ( diff --git a/frontends/main/src/app/page.tsx b/frontends/main/src/app/page.tsx index 4a206e0e74..9cc0208709 100644 --- a/frontends/main/src/app/page.tsx +++ b/frontends/main/src/app/page.tsx @@ -3,7 +3,10 @@ import type { Metadata } from "next" import HomePage from "@/app-pages/HomePage/HomePage" import { getMetadataAsync } from "@/common/metadata" import { Hydrate } from "@tanstack/react-query" -import { learningResources } from "api/hooks/learningResources" +import { + learningResourceQueries, + topicQueries, +} from "api/hooks/learningResources" import { testimonials } from "api/hooks/testimonials" import { NewsEventsListFeedTypeEnum, newsEvents } from "api/hooks/newsEvents" import { prefetch } from "api/ssr/prefetch" @@ -26,45 +29,45 @@ export async function generateMetadata({ const Page: React.FC = async () => { const { dehydratedState } = await prefetch([ // Featured Courses carousel "All" - learningResources.featured({ + learningResourceQueries.featured({ limit: 12, }), // Featured Courses carousel "Free" - learningResources.featured({ + learningResourceQueries.featured({ limit: 12, free: true, }), // Featured Courses carousel "With Certificate" - learningResources.featured({ + learningResourceQueries.featured({ limit: 12, certification: true, professional: false, }), // Featured Courses carousel "Professional & Executive Learning" - learningResources.featured({ + learningResourceQueries.featured({ limit: 12, professional: true, }), // Media carousel "All" - learningResources.list({ + learningResourceQueries.list({ resource_type: ["video", "podcast_episode"], limit: 12, sortby: "new", }), // Media carousel "Videos" - learningResources.list({ + learningResourceQueries.list({ resource_type: ["video"], limit: 12, sortby: "new", }), // Media carousel "Podcasts" - learningResources.list({ + learningResourceQueries.list({ resource_type: ["podcast_episode"], limit: 12, sortby: "new", }), // Browse by Topic - learningResources.topics({ is_toplevel: true }), + topicQueries.list({ is_toplevel: true }), testimonials.list({ position: 1 }), newsEvents.list({ diff --git a/frontends/main/src/app/search/page.tsx b/frontends/main/src/app/search/page.tsx index 6befe4bc01..b3bcbc4db3 100644 --- a/frontends/main/src/app/search/page.tsx +++ b/frontends/main/src/app/search/page.tsx @@ -1,7 +1,10 @@ import React from "react" import { Hydrate } from "@tanstack/react-query" import { prefetch } from "api/ssr/prefetch" -import { learningResources } from "api/hooks/learningResources" +import { + learningResourceQueries, + offerorQueries, +} from "api/hooks/learningResources" import type { PageParams } from "@/app/types" import { getMetadataAsync } from "@/common/metadata" import SearchPage from "@/app-pages/SearchPage/SearchPage" @@ -43,8 +46,8 @@ const Page: React.FC = async ({ }) const { dehydratedState } = await prefetch([ - learningResources.offerors({}), - learningResources.search(params as LRSearchRequest), + offerorQueries.list({}), + learningResourceQueries.search(params as LRSearchRequest), ]) return ( diff --git a/frontends/main/src/app/topics/page.tsx b/frontends/main/src/app/topics/page.tsx index 0d742ce794..c6434198da 100644 --- a/frontends/main/src/app/topics/page.tsx +++ b/frontends/main/src/app/topics/page.tsx @@ -2,7 +2,7 @@ import React from "react" import { Metadata } from "next" import { Hydrate } from "@tanstack/react-query" import { prefetch } from "api/ssr/prefetch" -import { learningResources } from "api/hooks/learningResources" +import { topicQueries } from "api/hooks/learningResources" import { channels } from "api/hooks/channels" import TopicsListingPage from "@/app-pages/TopicsListingPage/TopicsListingPage" @@ -13,7 +13,7 @@ export const metadata: Metadata = standardizeMetadata({ const Page: React.FC = async () => { const { dehydratedState } = await prefetch([ - learningResources.topics({}), + topicQueries.list({}), channels.countsByType("topic"), ]) diff --git a/frontends/main/src/page-components/ResourceCard/ResourceCard.test.tsx b/frontends/main/src/page-components/ResourceCard/ResourceCard.test.tsx index 5afea18cf9..8d65a9dfee 100644 --- a/frontends/main/src/page-components/ResourceCard/ResourceCard.test.tsx +++ b/frontends/main/src/page-components/ResourceCard/ResourceCard.test.tsx @@ -8,7 +8,7 @@ import { waitFor, } from "@/test-utils" import type { User } from "api/hooks/user" -import { learningResources } from "api/hooks/learningResources" +import { learningResourceQueries } from "api/hooks/learningResources" import { ResourceCard } from "./ResourceCard" import { getReadableResourceType } from "ol-utilities" import { ResourceTypeEnum, MicroUserListRelationship } from "api" @@ -231,7 +231,7 @@ describe.each([ invariant(resource) const cached = queryClient.getQueryData( - learningResources.detail(resource.id).queryKey, + learningResourceQueries.detail(resource.id).queryKey, ) expect(cached).toEqual(resource) }) diff --git a/frontends/main/src/page-components/ResourceCarousel/ResourceCarousel.tsx b/frontends/main/src/page-components/ResourceCarousel/ResourceCarousel.tsx index 7e2e85f3cc..8a8f251e41 100644 --- a/frontends/main/src/page-components/ResourceCarousel/ResourceCarousel.tsx +++ b/frontends/main/src/page-components/ResourceCarousel/ResourceCarousel.tsx @@ -1,7 +1,7 @@ "use client" import React from "react" -import { learningResources } from "api/hooks/learningResources" +import { learningResourceQueries } from "api/hooks/learningResources" import { Carousel, TabButton, @@ -219,19 +219,20 @@ const ResourceCarousel: React.FC = ({ > => { switch (tab.data.type) { case "resources": - return learningResources.list(tab.data.params) + return learningResourceQueries.list(tab.data.params) case "resource_items": - return learningResources - .detail(tab.data.params.learning_resource_id) - ._ctx.items(tab.data.params) + return learningResourceQueries.items( + tab.data.params.learning_resource_id, + tab.data.params, + ) case "lr_search": - return learningResources.search(tab.data.params) + return learningResourceQueries.search(tab.data.params) case "lr_featured": - return learningResources.featured(tab.data.params) + return learningResourceQueries.featured(tab.data.params) case "lr_similar": - return learningResources.similar(tab.data.params.id) + return learningResourceQueries.similar(tab.data.params.id) case "lr_vector_similar": - return learningResources.vectorSimilar(tab.data.params.id) + return learningResourceQueries.vectorSimilar(tab.data.params.id) } }, ), From fbe5b60d1d8d12e38aa91b5d2e8f7ae3785eed59 Mon Sep 17 00:00:00 2001 From: Chris Chudzicki Date: Mon, 10 Feb 2025 16:37:25 -0500 Subject: [PATCH 3/5] remove keyfactory from article, channel, newsEvent, programletter --- .../api/src/hooks/articles/index.test.ts | 8 +-- frontends/api/src/hooks/articles/index.ts | 12 ++-- .../api/src/hooks/articles/keyFactory.ts | 19 ------- frontends/api/src/hooks/articles/queries.ts | 27 +++++++++ frontends/api/src/hooks/channels/index.ts | 12 ++-- .../api/src/hooks/channels/keyFactory.ts | 34 ------------ frontends/api/src/hooks/channels/queries.ts | 55 +++++++++++++++++++ frontends/api/src/hooks/newsEvents/index.ts | 8 +-- .../api/src/hooks/newsEvents/keyFactory.ts | 20 ------- frontends/api/src/hooks/newsEvents/queries.ts | 31 +++++++++++ .../api/src/hooks/programLetters/index.ts | 4 +- .../src/hooks/programLetters/keyFactory.ts | 16 ------ .../api/src/hooks/programLetters/queries.ts | 20 +++++++ .../src/app/c/[channelType]/[name]/page.tsx | 6 +- frontends/main/src/app/departments/page.tsx | 4 +- frontends/main/src/app/page.tsx | 9 ++- frontends/main/src/app/topics/page.tsx | 4 +- frontends/main/src/app/units/page.tsx | 6 +- 18 files changed, 171 insertions(+), 124 deletions(-) delete mode 100644 frontends/api/src/hooks/articles/keyFactory.ts create mode 100644 frontends/api/src/hooks/articles/queries.ts delete mode 100644 frontends/api/src/hooks/channels/keyFactory.ts create mode 100644 frontends/api/src/hooks/channels/queries.ts delete mode 100644 frontends/api/src/hooks/newsEvents/keyFactory.ts create mode 100644 frontends/api/src/hooks/newsEvents/queries.ts delete mode 100644 frontends/api/src/hooks/programLetters/keyFactory.ts create mode 100644 frontends/api/src/hooks/programLetters/queries.ts diff --git a/frontends/api/src/hooks/articles/index.test.ts b/frontends/api/src/hooks/articles/index.test.ts index 1d941716e0..a1068278a1 100644 --- a/frontends/api/src/hooks/articles/index.test.ts +++ b/frontends/api/src/hooks/articles/index.test.ts @@ -1,7 +1,7 @@ import { renderHook, waitFor } from "@testing-library/react" import { setupReactQueryTest } from "../test-utils" -import keyFactory from "./keyFactory" +import { articleKeys } from "./queries" import { setMockResponse, urls, makeRequest } from "../../test-utils" import { UseQueryResult } from "@tanstack/react-query" import { articles as factory } from "../../test-utils/factories" @@ -72,7 +72,7 @@ describe("Article CRUD", () => { await waitFor(() => expect(result.current.isSuccess).toBe(true)) expect(makeRequest).toHaveBeenCalledWith("post", url, requestData) expect(queryClient.invalidateQueries).toHaveBeenCalledWith( - keyFactory.list._def, + articleKeys.listRoot(), ) }) @@ -89,7 +89,7 @@ describe("Article CRUD", () => { await waitFor(() => expect(result.current.isSuccess).toBe(true)) const { id, ...patchData } = article expect(makeRequest).toHaveBeenCalledWith("patch", url, patchData) - expect(queryClient.invalidateQueries).toHaveBeenCalledWith(keyFactory._def) + expect(queryClient.invalidateQueries).toHaveBeenCalledWith(articleKeys.root) }) test("useArticleDestroy calls correct API", async () => { @@ -105,7 +105,7 @@ describe("Article CRUD", () => { await waitFor(() => expect(result.current.isSuccess).toBe(true)) expect(makeRequest).toHaveBeenCalledWith("delete", url, undefined) expect(queryClient.invalidateQueries).toHaveBeenCalledWith( - keyFactory.list._def, + articleKeys.listRoot(), ) }) }) diff --git a/frontends/api/src/hooks/articles/index.ts b/frontends/api/src/hooks/articles/index.ts index 1725019717..ce366ed0c1 100644 --- a/frontends/api/src/hooks/articles/index.ts +++ b/frontends/api/src/hooks/articles/index.ts @@ -10,14 +10,14 @@ import type { ArticlesApiArticlesListRequest as ArticleListRequest, Article, } from "../../generated/v1" -import articles from "./keyFactory" +import { articleQueries, articleKeys } from "./queries" const useArticleList = ( params: ArticleListRequest = {}, opts: Pick = {}, ) => { return useQuery({ - ...articles.list(params), + ...articleQueries.list(params), ...opts, }) } @@ -27,7 +27,7 @@ const useArticleList = ( */ const useArticleDetail = (id: number | undefined) => { return useQuery({ - ...articles.detail(id ?? -1), + ...articleQueries.detail(id ?? -1), enabled: id !== undefined, }) } @@ -40,7 +40,7 @@ const useArticleCreate = () => { .articlesCreate({ ArticleRequest: data }) .then((response) => response.data), onSuccess: () => { - client.invalidateQueries(articles.list._def) + client.invalidateQueries(articleKeys.listRoot()) }, }) } @@ -49,7 +49,7 @@ const useArticleDestroy = () => { return useMutation({ mutationFn: (id: number) => articlesApi.articlesDestroy({ id }), onSuccess: () => { - client.invalidateQueries(articles.list._def) + client.invalidateQueries(articleKeys.listRoot()) }, }) } @@ -64,7 +64,7 @@ const useArticlePartialUpdate = () => { }) .then((response) => response.data), onSuccess: (_data) => { - client.invalidateQueries(articles._def) + client.invalidateQueries(articleKeys.root) }, }) } diff --git a/frontends/api/src/hooks/articles/keyFactory.ts b/frontends/api/src/hooks/articles/keyFactory.ts deleted file mode 100644 index 15a0d42fbe..0000000000 --- a/frontends/api/src/hooks/articles/keyFactory.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { articlesApi } from "../../clients" -import type { ArticlesApiArticlesListRequest as ArticleListRequest } from "../../generated/v1" -import { createQueryKeys } from "@lukemorales/query-key-factory" - -const articles = createQueryKeys("articles", { - detail: (id: number) => ({ - queryKey: [id], - queryFn: () => { - if (id < 0) return Promise.reject("Invalid ID") - return articlesApi.articlesRetrieve({ id }).then((res) => res.data) - }, - }), - list: (params: ArticleListRequest) => ({ - queryKey: [params], - queryFn: () => articlesApi.articlesList(params).then((res) => res.data), - }), -}) - -export default articles diff --git a/frontends/api/src/hooks/articles/queries.ts b/frontends/api/src/hooks/articles/queries.ts new file mode 100644 index 0000000000..6d982c60cb --- /dev/null +++ b/frontends/api/src/hooks/articles/queries.ts @@ -0,0 +1,27 @@ +import { QueryOptions } from "@tanstack/react-query" +import { articlesApi } from "../../clients" +import type { ArticlesApiArticlesListRequest as ArticleListRequest } from "../../generated/v1" + +const articleKeys = { + root: ["articles"], + listRoot: () => [...articleKeys.root, "list"], + list: (params: ArticleListRequest) => [...articleKeys.listRoot(), params], + detailRoot: () => [...articleKeys.root, "detail"], + detail: (id: number) => [...articleKeys.detailRoot(), id], +} + +const articleQueries = { + list: (params: ArticleListRequest) => + ({ + queryKey: articleKeys.list(params), + queryFn: () => articlesApi.articlesList(params).then((res) => res.data), + }) satisfies QueryOptions, + detail: (id: number) => + ({ + queryKey: articleKeys.detail(id), + queryFn: () => + articlesApi.articlesRetrieve({ id }).then((res) => res.data), + }) satisfies QueryOptions, +} + +export { articleQueries, articleKeys } diff --git a/frontends/api/src/hooks/channels/index.ts b/frontends/api/src/hooks/channels/index.ts index cbcbf2c580..87a2293a13 100644 --- a/frontends/api/src/hooks/channels/index.ts +++ b/frontends/api/src/hooks/channels/index.ts @@ -10,27 +10,27 @@ import type { ChannelsApiChannelsListRequest, PatchedChannelWriteRequest, } from "../../generated/v0" -import channels from "./keyFactory" +import { channelKeys, channelQueries } from "./queries" const useChannelsList = ( params: ChannelsApiChannelsListRequest = {}, opts: Pick = {}, ) => { return useQuery({ - ...channels.list(params), + ...channelQueries.list(params), ...opts, }) } const useChannelDetail = (channelType: string, channelName: string) => { return useQuery({ - ...channels.detailByType(channelType, channelName), + ...channelQueries.detailByType(channelType, channelName), }) } const useChannelCounts = (channelType: string) => { return useQuery({ - ...channels.countsByType(channelType), + ...channelQueries.countsByType(channelType), }) } @@ -45,7 +45,7 @@ const useChannelPartialUpdate = () => { }) .then((response) => response.data), onSuccess: (_data) => { - client.invalidateQueries(channels._def) + client.invalidateQueries(channelKeys.root) }, }) } @@ -55,5 +55,5 @@ export { useChannelsList, useChannelPartialUpdate, useChannelCounts, - channels, + channelQueries, } diff --git a/frontends/api/src/hooks/channels/keyFactory.ts b/frontends/api/src/hooks/channels/keyFactory.ts deleted file mode 100644 index 69fdb16cd0..0000000000 --- a/frontends/api/src/hooks/channels/keyFactory.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { channelsApi } from "../../clients" -import type { ChannelsApiChannelsListRequest as FieldsApiListRequest } from "../../generated/v0" -import { createQueryKeys } from "@lukemorales/query-key-factory" - -const channels = createQueryKeys("channel", { - detailByType: (channelType: string, name: string) => ({ - queryKey: [channelType, name], - queryFn: () => { - return channelsApi - .channelsTypeRetrieve({ channel_type: channelType, name: name }) - .then((res) => res.data) - }, - }), - countsByType: (channelType: string) => ({ - queryKey: [channelType], - queryFn: () => { - return channelsApi - .channelsCountsList({ channel_type: channelType }) - .then((res) => res.data) - }, - }), - detail: (id: number) => ({ - queryKey: [id], - queryFn: () => { - return channelsApi.channelsRetrieve({ id: id }).then((res) => res.data) - }, - }), - list: (params: FieldsApiListRequest) => ({ - queryKey: [params], - queryFn: () => channelsApi.channelsList(params).then((res) => res.data), - }), -}) - -export default channels diff --git a/frontends/api/src/hooks/channels/queries.ts b/frontends/api/src/hooks/channels/queries.ts new file mode 100644 index 0000000000..3dd8835c59 --- /dev/null +++ b/frontends/api/src/hooks/channels/queries.ts @@ -0,0 +1,55 @@ +import { QueryOptions } from "@tanstack/react-query" +import { channelsApi } from "../../clients" +import type { ChannelsApiChannelsListRequest as FieldsApiListRequest } from "../../generated/v0" + +const channelKeys = { + root: ["channel"], + detailRoot: () => [...channelKeys.root, "detail"], + detail: (id: number) => [...channelKeys.detailRoot(), id], + detailByType: (channelType: string, name: string) => [ + ...channelKeys.detailRoot(), + channelType, + name, + ], + listRoot: () => [...channelKeys.root, "list"], + list: (params: FieldsApiListRequest) => [...channelKeys.listRoot(), params], + countsByType: (channelType: string) => [ + ...channelKeys.root, + "counts", + channelType, + ], +} + +const channelQueries = { + list: (params: FieldsApiListRequest) => + ({ + queryKey: channelKeys.list(params), + queryFn: () => channelsApi.channelsList(params).then((res) => res.data), + }) satisfies QueryOptions, + detail: (id: number) => + ({ + queryKey: channelKeys.detail(id), + queryFn: () => + channelsApi.channelsRetrieve({ id }).then((res) => res.data), + }) satisfies QueryOptions, + detailByType: (channelType: string, name: string) => + ({ + queryKey: channelKeys.detailByType(channelType, name), + queryFn: () => { + return channelsApi + .channelsTypeRetrieve({ channel_type: channelType, name: name }) + .then((res) => res.data) + }, + }) satisfies QueryOptions, + countsByType: (channelType: string) => + ({ + queryKey: channelKeys.countsByType(channelType), + queryFn: () => { + return channelsApi + .channelsCountsList({ channel_type: channelType }) + .then((res) => res.data) + }, + }) satisfies QueryOptions, +} + +export { channelQueries, channelKeys } diff --git a/frontends/api/src/hooks/newsEvents/index.ts b/frontends/api/src/hooks/newsEvents/index.ts index 9d20868f2d..32e575e874 100644 --- a/frontends/api/src/hooks/newsEvents/index.ts +++ b/frontends/api/src/hooks/newsEvents/index.ts @@ -1,5 +1,5 @@ import { useQuery } from "@tanstack/react-query" -import newsEvents from "./keyFactory" +import { newsEventsQueries } from "./queries" import { NewsEventsApiNewsEventsListRequest, NewsEventsListFeedTypeEnum, @@ -7,17 +7,17 @@ import { const useNewsEventsList = (params: NewsEventsApiNewsEventsListRequest) => { return useQuery({ - ...newsEvents.list(params), + ...newsEventsQueries.list(params), }) } const useNewsEventsDetail = (id: number) => { - return useQuery(newsEvents.detail(id)) + return useQuery(newsEventsQueries.detail(id)) } export { useNewsEventsList, useNewsEventsDetail, NewsEventsListFeedTypeEnum, - newsEvents, + newsEventsQueries, } diff --git a/frontends/api/src/hooks/newsEvents/keyFactory.ts b/frontends/api/src/hooks/newsEvents/keyFactory.ts deleted file mode 100644 index 191a6dd02d..0000000000 --- a/frontends/api/src/hooks/newsEvents/keyFactory.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { newsEventsApi } from "../../clients" -import type { NewsEventsApiNewsEventsListRequest } from "../../generated/v0" -import { createQueryKeys } from "@lukemorales/query-key-factory" - -const newsEvents = createQueryKeys("newsEvents", { - list: (params: NewsEventsApiNewsEventsListRequest) => ({ - queryKey: [params], - queryFn: () => newsEventsApi.newsEventsList(params).then((res) => res.data), - }), - detail: (id: number) => ({ - queryKey: [id], - queryFn: () => { - return newsEventsApi - .newsEventsRetrieve({ id: id }) - .then((res) => res.data) - }, - }), -}) - -export default newsEvents diff --git a/frontends/api/src/hooks/newsEvents/queries.ts b/frontends/api/src/hooks/newsEvents/queries.ts new file mode 100644 index 0000000000..fd6ddd108b --- /dev/null +++ b/frontends/api/src/hooks/newsEvents/queries.ts @@ -0,0 +1,31 @@ +import { QueryOptions } from "@tanstack/react-query" +import { newsEventsApi } from "../../clients" +import type { NewsEventsApiNewsEventsListRequest } from "../../generated/v0" + +const newsEventsKeys = { + root: ["newsEvents"], + listRoot: () => [...newsEventsKeys.root, "list"], + list: (params: NewsEventsApiNewsEventsListRequest) => [ + ...newsEventsKeys.listRoot(), + params, + ], + detailRoot: () => [...newsEventsKeys.root, "detail"], + detail: (id: number) => [...newsEventsKeys.detailRoot(), id], +} + +const newsEventsQueries = { + list: (params: NewsEventsApiNewsEventsListRequest) => + ({ + queryKey: newsEventsKeys.list(params), + queryFn: () => + newsEventsApi.newsEventsList(params).then((res) => res.data), + }) satisfies QueryOptions, + detail: (id: number) => + ({ + queryKey: newsEventsKeys.detail(id), + queryFn: () => + newsEventsApi.newsEventsRetrieve({ id }).then((res) => res.data), + }) satisfies QueryOptions, +} + +export { newsEventsQueries, newsEventsKeys } diff --git a/frontends/api/src/hooks/programLetters/index.ts b/frontends/api/src/hooks/programLetters/index.ts index 5f52a00208..174cd431b5 100644 --- a/frontends/api/src/hooks/programLetters/index.ts +++ b/frontends/api/src/hooks/programLetters/index.ts @@ -1,12 +1,12 @@ import { useQuery } from "@tanstack/react-query" -import programLetters from "./keyFactory" +import { programLetterQueries } from "./queries" /** * Query is disabled if id is undefined. */ const useProgramLettersDetail = (id: string | undefined) => { return useQuery({ - ...programLetters.detail(id ?? ""), + ...programLetterQueries.detail(id ?? ""), enabled: id !== undefined, }) } diff --git a/frontends/api/src/hooks/programLetters/keyFactory.ts b/frontends/api/src/hooks/programLetters/keyFactory.ts deleted file mode 100644 index 91830478c7..0000000000 --- a/frontends/api/src/hooks/programLetters/keyFactory.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { programLettersApi } from "../../clients" -import { createQueryKeys } from "@lukemorales/query-key-factory" - -const programLetters = createQueryKeys("programLetters", { - detail: (id: string) => ({ - queryKey: [id], - queryFn: () => { - if (id === "") return Promise.reject("Invalid ID") - return programLettersApi - .programLettersRetrieve({ id }) - .then((res) => res.data) - }, - }), -}) - -export default programLetters diff --git a/frontends/api/src/hooks/programLetters/queries.ts b/frontends/api/src/hooks/programLetters/queries.ts new file mode 100644 index 0000000000..0606045189 --- /dev/null +++ b/frontends/api/src/hooks/programLetters/queries.ts @@ -0,0 +1,20 @@ +import { QueryOptions } from "@tanstack/react-query" +import { programLettersApi } from "../../clients" + +const programLetterKeys = { + root: ["programLetters"], + detailRoot: () => [...programLetterKeys.root, "detail"], + detail: (id: string) => [...programLetterKeys.detailRoot(), id], +} +const programLetterQueries = { + detail: (id: string) => + ({ + queryKey: programLetterKeys.detail(id), + queryFn: () => + programLettersApi + .programLettersRetrieve({ id }) + .then((res) => res.data), + }) satisfies QueryOptions, +} + +export { programLetterQueries, programLetterKeys } diff --git a/frontends/main/src/app/c/[channelType]/[name]/page.tsx b/frontends/main/src/app/c/[channelType]/[name]/page.tsx index 838b4874f9..14c075c9c1 100644 --- a/frontends/main/src/app/c/[channelType]/[name]/page.tsx +++ b/frontends/main/src/app/c/[channelType]/[name]/page.tsx @@ -15,7 +15,7 @@ import { learningResourceQueries, offerorQueries, } from "api/hooks/learningResources" -import { channels } from "api/hooks/channels" +import { channelQueries } from "api/hooks/channels" import { testimonials } from "api/hooks/testimonials" import handleNotFound from "@/common/handleNotFound" import type { PageParams } from "@/app/types" @@ -65,11 +65,11 @@ const Page: React.FC = async ({ }), channelType === ChannelTypeEnum.Unit && testimonials.list({ offerors: [name] }), - channels.detailByType(channelType, name), + channelQueries.detailByType(channelType, name), ]) const channel = queryClient.getQueryData( - channels.detailByType(channelType, name).queryKey, + channelQueries.detailByType(channelType, name).queryKey, ) const offerors = queryClient .getQueryData( diff --git a/frontends/main/src/app/departments/page.tsx b/frontends/main/src/app/departments/page.tsx index 3e04df4db4..8cd84c0e92 100644 --- a/frontends/main/src/app/departments/page.tsx +++ b/frontends/main/src/app/departments/page.tsx @@ -3,7 +3,7 @@ import { Metadata } from "next" import { Hydrate } from "@tanstack/react-query" import { standardizeMetadata } from "@/common/metadata" import { schoolQueries } from "api/hooks/learningResources" -import { channels } from "api/hooks/channels" +import { channelQueries } from "api/hooks/channels" import { prefetch } from "api/ssr/prefetch" import DepartmentListingPage from "@/app-pages/DepartmentListingPage/DepartmentListingPage" @@ -13,7 +13,7 @@ export const metadata: Metadata = standardizeMetadata({ const Page: React.FC = async () => { const { dehydratedState } = await prefetch([ - channels.countsByType("department"), + channelQueries.countsByType("department"), schoolQueries.list(), ]) diff --git a/frontends/main/src/app/page.tsx b/frontends/main/src/app/page.tsx index 9cc0208709..739146544a 100644 --- a/frontends/main/src/app/page.tsx +++ b/frontends/main/src/app/page.tsx @@ -8,7 +8,10 @@ import { topicQueries, } from "api/hooks/learningResources" import { testimonials } from "api/hooks/testimonials" -import { NewsEventsListFeedTypeEnum, newsEvents } from "api/hooks/newsEvents" +import { + NewsEventsListFeedTypeEnum, + newsEventsQueries, +} from "api/hooks/newsEvents" import { prefetch } from "api/ssr/prefetch" type SearchParams = { @@ -70,12 +73,12 @@ const Page: React.FC = async () => { topicQueries.list({ is_toplevel: true }), testimonials.list({ position: 1 }), - newsEvents.list({ + newsEventsQueries.list({ feed_type: [NewsEventsListFeedTypeEnum.Events], limit: 5, sortby: "event_date", }), - newsEvents.list({ + newsEventsQueries.list({ feed_type: [NewsEventsListFeedTypeEnum.News], limit: 6, sortby: "-news_date", diff --git a/frontends/main/src/app/topics/page.tsx b/frontends/main/src/app/topics/page.tsx index c6434198da..dd2534b1f6 100644 --- a/frontends/main/src/app/topics/page.tsx +++ b/frontends/main/src/app/topics/page.tsx @@ -3,7 +3,7 @@ import { Metadata } from "next" import { Hydrate } from "@tanstack/react-query" import { prefetch } from "api/ssr/prefetch" import { topicQueries } from "api/hooks/learningResources" -import { channels } from "api/hooks/channels" +import { channelQueries } from "api/hooks/channels" import TopicsListingPage from "@/app-pages/TopicsListingPage/TopicsListingPage" import { standardizeMetadata } from "@/common/metadata" @@ -14,7 +14,7 @@ export const metadata: Metadata = standardizeMetadata({ const Page: React.FC = async () => { const { dehydratedState } = await prefetch([ topicQueries.list({}), - channels.countsByType("topic"), + channelQueries.countsByType("topic"), ]) return ( diff --git a/frontends/main/src/app/units/page.tsx b/frontends/main/src/app/units/page.tsx index 52cf658a24..e7b22e188c 100644 --- a/frontends/main/src/app/units/page.tsx +++ b/frontends/main/src/app/units/page.tsx @@ -3,7 +3,7 @@ import { Metadata } from "next" import { Hydrate } from "@tanstack/react-query" import { prefetch } from "api/ssr/prefetch" import { standardizeMetadata } from "@/common/metadata" -import { channels } from "api/hooks/channels" +import { channelQueries } from "api/hooks/channels" import UnitsListingPage from "@/app-pages/UnitsListingPage/UnitsListingPage" export const metadata: Metadata = standardizeMetadata({ @@ -12,8 +12,8 @@ export const metadata: Metadata = standardizeMetadata({ const Page: React.FC = async () => { const { dehydratedState } = await prefetch([ - channels.countsByType("unit"), - channels.list({ channel_type: "unit" }), + channelQueries.countsByType("unit"), + channelQueries.list({ channel_type: "unit" }), ]) return ( From cafe11e968fd8b77738af33893a8da997e52334e Mon Sep 17 00:00:00 2001 From: Chris Chudzicki Date: Mon, 10 Feb 2025 16:51:05 -0500 Subject: [PATCH 4/5] remove testimonials, subscriptions, widgets keyFactories --- .../api/src/hooks/searchSubscription/index.ts | 8 ++--- .../hooks/searchSubscription/keyFactory.ts | 33 ++++++++++++------- frontends/api/src/hooks/testimonials/index.ts | 8 ++--- .../api/src/hooks/testimonials/keyFactory.ts | 22 ------------- .../api/src/hooks/testimonials/queries.ts | 31 +++++++++++++++++ frontends/api/src/hooks/widget_lists/index.ts | 6 ++-- .../api/src/hooks/widget_lists/keyFactory.ts | 14 -------- .../api/src/hooks/widget_lists/queries.ts | 19 +++++++++++ .../src/app/c/[channelType]/[name]/page.tsx | 4 +-- frontends/main/src/app/page.tsx | 4 +-- 10 files changed, 86 insertions(+), 63 deletions(-) delete mode 100644 frontends/api/src/hooks/testimonials/keyFactory.ts create mode 100644 frontends/api/src/hooks/testimonials/queries.ts delete mode 100644 frontends/api/src/hooks/widget_lists/keyFactory.ts create mode 100644 frontends/api/src/hooks/widget_lists/queries.ts diff --git a/frontends/api/src/hooks/searchSubscription/index.ts b/frontends/api/src/hooks/searchSubscription/index.ts index 6119759b9d..41ab452940 100644 --- a/frontends/api/src/hooks/searchSubscription/index.ts +++ b/frontends/api/src/hooks/searchSubscription/index.ts @@ -4,7 +4,7 @@ import { useQueryClient, useQuery, } from "@tanstack/react-query" -import searchSubscriptions from "./keyFactory" +import { searchSubscriptionQueries, searchSubscriptionKeys } from "./keyFactory" import type { LearningResourcesUserSubscriptionApiLearningResourcesUserSubscriptionSubscribeCreateRequest as subscriptionCreateRequest } from "../../generated/v1" import { searchSubscriptionApi } from "../../clients" @@ -17,7 +17,7 @@ const useSearchSubscriptionCreate = () => { .learningResourcesUserSubscriptionSubscribeCreate(params) .then((res) => res.data), onSuccess: (_data) => { - queryClient.invalidateQueries(searchSubscriptions._def) + queryClient.invalidateQueries(searchSubscriptionKeys.root) }, }) } @@ -27,7 +27,7 @@ const useSearchSubscriptionList = ( opts: Pick = {}, ) => { return useQuery({ - ...searchSubscriptions.list(params), + ...searchSubscriptionQueries.list(params), ...opts, }) } @@ -42,7 +42,7 @@ const useSearchSubscriptionDelete = () => { .then((res) => res.data) }, onSuccess: (_data) => { - queryClient.invalidateQueries(searchSubscriptions._def) + queryClient.invalidateQueries(searchSubscriptionKeys.root) }, }) } diff --git a/frontends/api/src/hooks/searchSubscription/keyFactory.ts b/frontends/api/src/hooks/searchSubscription/keyFactory.ts index c99c23afd9..9ecc571a2a 100644 --- a/frontends/api/src/hooks/searchSubscription/keyFactory.ts +++ b/frontends/api/src/hooks/searchSubscription/keyFactory.ts @@ -1,16 +1,25 @@ import { searchSubscriptionApi } from "../../clients" -import { createQueryKeys } from "@lukemorales/query-key-factory" import type { LearningResourcesUserSubscriptionApiLearningResourcesUserSubscriptionCheckListRequest as subscriptionCheckListRequest } from "../../generated/v1" +import { QueryOptions } from "@tanstack/react-query" -const searchSubscriptions = createQueryKeys("searchSubscriptions", { - list: (params: subscriptionCheckListRequest) => ({ - queryKey: [params], - queryFn: () => { - return searchSubscriptionApi - .learningResourcesUserSubscriptionCheckList(params) - .then((res) => res.data) - }, - }), -}) +const searchSubscriptionKeys = { + root: ["searchSubscriptions"], + list: (params: subscriptionCheckListRequest) => [ + ...searchSubscriptionKeys.root, + "list", + params, + ], +} -export default searchSubscriptions +const searchSubscriptionQueries = { + list: (params: subscriptionCheckListRequest) => + ({ + queryKey: searchSubscriptionKeys.list(params), + queryFn: () => + searchSubscriptionApi + .learningResourcesUserSubscriptionCheckList(params) + .then((res) => res.data), + }) satisfies QueryOptions, +} + +export { searchSubscriptionQueries, searchSubscriptionKeys } diff --git a/frontends/api/src/hooks/testimonials/index.ts b/frontends/api/src/hooks/testimonials/index.ts index 9cb3ebc212..eb1759bbb0 100644 --- a/frontends/api/src/hooks/testimonials/index.ts +++ b/frontends/api/src/hooks/testimonials/index.ts @@ -1,14 +1,14 @@ import { UseQueryOptions, useQuery } from "@tanstack/react-query" import type { TestimonialsApiTestimonialsListRequest } from "../../generated/v0" -import testimonials from "./keyFactory" +import { testimonialsQueries } from "./queries" const useTestimonialList = ( params: TestimonialsApiTestimonialsListRequest = {}, opts: Pick = {}, ) => { return useQuery({ - ...testimonials.list(params), + ...testimonialsQueries.list(params), ...opts, }) } @@ -18,9 +18,9 @@ const useTestimonialList = ( */ const useTestimonialDetail = (id: number | undefined) => { return useQuery({ - ...testimonials.detail(id ?? -1), + ...testimonialsQueries.detail(id ?? -1), enabled: id !== undefined, }) } -export { useTestimonialDetail, useTestimonialList, testimonials } +export { useTestimonialDetail, useTestimonialList, testimonialsQueries } diff --git a/frontends/api/src/hooks/testimonials/keyFactory.ts b/frontends/api/src/hooks/testimonials/keyFactory.ts deleted file mode 100644 index 9adf96ce29..0000000000 --- a/frontends/api/src/hooks/testimonials/keyFactory.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { testimonialsApi } from "../../clients" -import type { TestimonialsApiTestimonialsListRequest as TestimonialsListRequest } from "../../generated/v0" -import { createQueryKeys } from "@lukemorales/query-key-factory" - -const testimonials = createQueryKeys("testimonials", { - detail: (id: number) => ({ - queryKey: [id], - queryFn: () => { - if (id < 0) return Promise.reject("Invalid ID") - return testimonialsApi - .testimonialsRetrieve({ id }) - .then((res) => res.data) - }, - }), - list: (params: TestimonialsListRequest) => ({ - queryKey: [params], - queryFn: () => - testimonialsApi.testimonialsList(params).then((res) => res.data), - }), -}) - -export default testimonials diff --git a/frontends/api/src/hooks/testimonials/queries.ts b/frontends/api/src/hooks/testimonials/queries.ts new file mode 100644 index 0000000000..4fa8fac21c --- /dev/null +++ b/frontends/api/src/hooks/testimonials/queries.ts @@ -0,0 +1,31 @@ +import { QueryOptions } from "@tanstack/react-query" +import { testimonialsApi } from "../../clients" +import type { TestimonialsApiTestimonialsListRequest as TestimonialsListRequest } from "../../generated/v0" + +const testimonialKeys = { + root: ["testimonials"], + listRoot: () => [...testimonialKeys.root, "list"], + list: (params: TestimonialsListRequest) => [ + ...testimonialKeys.listRoot(), + params, + ], + detailRoot: () => [...testimonialKeys.root, "detail"], + detail: (id: number) => [...testimonialKeys.detailRoot(), id], +} + +const testimonialsQueries = { + list: (params: TestimonialsListRequest) => + ({ + queryKey: testimonialKeys.list(params), + queryFn: () => + testimonialsApi.testimonialsList(params).then((res) => res.data), + }) satisfies QueryOptions, + detail: (id: number) => + ({ + queryKey: testimonialKeys.detail(id), + queryFn: () => + testimonialsApi.testimonialsRetrieve({ id }).then((res) => res.data), + }) satisfies QueryOptions, +} + +export { testimonialsQueries, testimonialKeys } diff --git a/frontends/api/src/hooks/widget_lists/index.ts b/frontends/api/src/hooks/widget_lists/index.ts index 1c8c2c42d2..fff8f1fe0f 100644 --- a/frontends/api/src/hooks/widget_lists/index.ts +++ b/frontends/api/src/hooks/widget_lists/index.ts @@ -1,7 +1,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" import { widgetListsApi } from "../../clients" -import widgetLists from "./keyFactory" +import { widgetListQueries, widgetListKeys } from "./queries" import { WidgetInstance } from "api/v0" /** @@ -9,7 +9,7 @@ import { WidgetInstance } from "api/v0" */ const useWidgetList = (id: number | undefined) => { return useQuery({ - ...widgetLists.detail(id ?? -1), + ...widgetListQueries.detail(id ?? -1), enabled: id !== undefined, }) } @@ -26,7 +26,7 @@ const useMutateWidgetsList = (id: number) => { .then((response) => response.data), onSuccess: (_data) => { - client.invalidateQueries(widgetLists._def) + client.invalidateQueries(widgetListKeys.root) }, }) } diff --git a/frontends/api/src/hooks/widget_lists/keyFactory.ts b/frontends/api/src/hooks/widget_lists/keyFactory.ts deleted file mode 100644 index e566d0c994..0000000000 --- a/frontends/api/src/hooks/widget_lists/keyFactory.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { widgetListsApi } from "../../clients" -import { createQueryKeys } from "@lukemorales/query-key-factory" - -const widgetLists = createQueryKeys("widgetLists", { - detail: (id: number) => ({ - queryKey: [id], - queryFn: () => { - if (id < 0) return Promise.reject("Invalid ID") - return widgetListsApi.widgetListsRetrieve({ id }).then((res) => res.data) - }, - }), -}) - -export default widgetLists diff --git a/frontends/api/src/hooks/widget_lists/queries.ts b/frontends/api/src/hooks/widget_lists/queries.ts new file mode 100644 index 0000000000..98f431b29c --- /dev/null +++ b/frontends/api/src/hooks/widget_lists/queries.ts @@ -0,0 +1,19 @@ +import { QueryOptions } from "@tanstack/react-query" +import { widgetListsApi } from "../../clients" + +const widgetListKeys = { + root: ["widgetList"], + detailRoot: () => [...widgetListKeys.root, "detail"], + detail: (id: number) => [...widgetListKeys.detailRoot(), id], +} + +const widgetListQueries = { + detail: (id: number) => + ({ + queryKey: widgetListKeys.detail(id), + queryFn: () => + widgetListsApi.widgetListsRetrieve({ id }).then((res) => res.data), + }) satisfies QueryOptions, +} + +export { widgetListQueries, widgetListKeys } diff --git a/frontends/main/src/app/c/[channelType]/[name]/page.tsx b/frontends/main/src/app/c/[channelType]/[name]/page.tsx index 14c075c9c1..e05a23af5a 100644 --- a/frontends/main/src/app/c/[channelType]/[name]/page.tsx +++ b/frontends/main/src/app/c/[channelType]/[name]/page.tsx @@ -16,7 +16,7 @@ import { offerorQueries, } from "api/hooks/learningResources" import { channelQueries } from "api/hooks/channels" -import { testimonials } from "api/hooks/testimonials" +import { testimonialsQueries } from "api/hooks/testimonials" import handleNotFound from "@/common/handleNotFound" import type { PageParams } from "@/app/types" import getSearchParams from "@/page-components/SearchDisplay/getSearchParams" @@ -64,7 +64,7 @@ const Page: React.FC = async ({ offered_by: [name], }), channelType === ChannelTypeEnum.Unit && - testimonials.list({ offerors: [name] }), + testimonialsQueries.list({ offerors: [name] }), channelQueries.detailByType(channelType, name), ]) diff --git a/frontends/main/src/app/page.tsx b/frontends/main/src/app/page.tsx index 739146544a..d681fe8083 100644 --- a/frontends/main/src/app/page.tsx +++ b/frontends/main/src/app/page.tsx @@ -7,7 +7,7 @@ import { learningResourceQueries, topicQueries, } from "api/hooks/learningResources" -import { testimonials } from "api/hooks/testimonials" +import { testimonialsQueries } from "api/hooks/testimonials" import { NewsEventsListFeedTypeEnum, newsEventsQueries, @@ -72,7 +72,7 @@ const Page: React.FC = async () => { // Browse by Topic topicQueries.list({ is_toplevel: true }), - testimonials.list({ position: 1 }), + testimonialsQueries.list({ position: 1 }), newsEventsQueries.list({ feed_type: [NewsEventsListFeedTypeEnum.Events], limit: 5, From aafeb791b6b06cac04a775170abc42dd263ccad9 Mon Sep 17 00:00:00 2001 From: Chris Chudzicki Date: Tue, 11 Feb 2025 08:49:19 -0500 Subject: [PATCH 5/5] remove last keyFactories --- frontends/api/package.json | 1 - .../api/src/hooks/learningPaths/index.test.ts | 5 +- .../api/src/hooks/learningPaths/index.ts | 28 ++++---- .../api/src/hooks/learningPaths/keyFactory.ts | 57 ---------------- .../api/src/hooks/learningPaths/queries.ts | 67 +++++++++++++++++++ .../api/src/hooks/learningResources/index.ts | 8 +-- frontends/api/src/hooks/userLists/index.ts | 26 ++++--- .../api/src/hooks/userLists/keyFactory.ts | 53 --------------- frontends/api/src/hooks/userLists/queries.ts | 65 ++++++++++++++++++ yarn.lock | 11 --- 10 files changed, 163 insertions(+), 158 deletions(-) delete mode 100644 frontends/api/src/hooks/learningPaths/keyFactory.ts create mode 100644 frontends/api/src/hooks/learningPaths/queries.ts delete mode 100644 frontends/api/src/hooks/userLists/keyFactory.ts create mode 100644 frontends/api/src/hooks/userLists/queries.ts diff --git a/frontends/api/package.json b/frontends/api/package.json index fc3b53e9bf..71f50bda02 100644 --- a/frontends/api/package.json +++ b/frontends/api/package.json @@ -27,7 +27,6 @@ "ol-test-utilities": "0.0.0" }, "dependencies": { - "@lukemorales/query-key-factory": "^1.3.2", "@tanstack/react-query": "^4.36.1", "axios": "^1.6.3" } diff --git a/frontends/api/src/hooks/learningPaths/index.test.ts b/frontends/api/src/hooks/learningPaths/index.test.ts index f1f3333bdc..782e9fd9b3 100644 --- a/frontends/api/src/hooks/learningPaths/index.test.ts +++ b/frontends/api/src/hooks/learningPaths/index.test.ts @@ -14,7 +14,7 @@ import { useLearningPathDestroy, useLearningPathUpdate, } from "./index" -import learningPathKeyFactory from "./keyFactory" +import { learningPathKeys } from "./queries" const factory = factories.learningResources @@ -116,8 +116,7 @@ describe("LearningPath CRUD", () => { const relationship = factory.learningPathRelationship({ parent: path.id }) const keys = { learningResources: learningResourceKeys.root, - relationshipListing: learningPathKeyFactory.detail(path.id)._ctx - .infiniteItems._def, + relationshipListing: learningPathKeys.infiniteItemsRoot(path.id), } const pathUrls = { list: urls.learningPaths.list(), diff --git a/frontends/api/src/hooks/learningPaths/index.ts b/frontends/api/src/hooks/learningPaths/index.ts index 48fb6a85d2..9772274d3b 100644 --- a/frontends/api/src/hooks/learningPaths/index.ts +++ b/frontends/api/src/hooks/learningPaths/index.ts @@ -13,7 +13,7 @@ import type { LearningPathResource, } from "../../generated/v1" import { learningPathsApi } from "../../clients" -import learningPaths from "./keyFactory" +import { learningPathQueries, learningPathKeys } from "./queries" import { useUserHasPermission, Permission } from "api/hooks/user" const useLearningPathsList = ( @@ -21,7 +21,7 @@ const useLearningPathsList = ( opts: Pick = {}, ) => { return useQuery({ - ...learningPaths.list(params), + ...learningPathQueries.list(params), ...opts, }) } @@ -31,9 +31,8 @@ const useInfiniteLearningPathItems = ( options: Pick = {}, ) => { return useInfiniteQuery({ - ...learningPaths - .detail(params.learning_resource_id) - ._ctx.infiniteItems(params), + ...learningPathQueries.infiniteItems(params.learning_resource_id, params), + // TODO: in v5, co-locate this with the query options via infiniteQueryOptions getNextPageParam: (lastPage) => { return lastPage.next ?? undefined }, @@ -42,7 +41,7 @@ const useInfiniteLearningPathItems = ( } const useLearningPathsDetail = (id: number) => { - return useQuery(learningPaths.detail(id)) + return useQuery(learningPathQueries.detail(id)) } type LearningPathCreateRequest = Omit< @@ -57,7 +56,7 @@ const useLearningPathCreate = () => { LearningPathResourceRequest: params, }), onSettled: () => { - queryClient.invalidateQueries(learningPaths.list._def) + queryClient.invalidateQueries(learningPathKeys.listRoot()) }, }) } @@ -73,8 +72,8 @@ const useLearningPathUpdate = () => { PatchedLearningPathResourceRequest: params, }), onSettled: (data, err, vars) => { - queryClient.invalidateQueries(learningPaths.list._def) - queryClient.invalidateQueries(learningPaths.detail(vars.id).queryKey) + queryClient.invalidateQueries(learningPathKeys.listRoot()) + queryClient.invalidateQueries(learningPathKeys.detail(vars.id)) }, }) } @@ -85,8 +84,8 @@ const useLearningPathDestroy = () => { mutationFn: (params: DestroyRequest) => learningPathsApi.learningpathsDestroy(params), onSettled: () => { - queryClient.invalidateQueries(learningPaths.list._def) - queryClient.invalidateQueries(learningPaths.membershipList._def) + queryClient.invalidateQueries(learningPathKeys.listRoot()) + queryClient.invalidateQueries(learningPathKeys.membershipList()) }, }) } @@ -109,7 +108,7 @@ const useLearningPathListItemMove = () => { }, onSettled: (_data, _err, vars) => { queryClient.invalidateQueries( - learningPaths.detail(vars.parent)._ctx.infiniteItems._def, + learningPathKeys.infiniteItemsRoot(vars.parent), ) }, }) @@ -117,7 +116,7 @@ const useLearningPathListItemMove = () => { const useIsLearningPathMember = (resourceId?: number) => { return useQuery({ - ...learningPaths.membershipList(), + ...learningPathQueries.membershipList(), select: (data) => { return !!data.find((relationship) => relationship.child === resourceId) }, @@ -128,8 +127,7 @@ const useIsLearningPathMember = (resourceId?: number) => { const useLearningPathMemberList = (resourceId?: number) => { return useQuery({ - ...learningPaths.membershipList(), - + ...learningPathQueries.membershipList(), select: (data) => { return data .filter((relationship) => relationship.child === resourceId) diff --git a/frontends/api/src/hooks/learningPaths/keyFactory.ts b/frontends/api/src/hooks/learningPaths/keyFactory.ts deleted file mode 100644 index 8afed55333..0000000000 --- a/frontends/api/src/hooks/learningPaths/keyFactory.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { createQueryKeys } from "@lukemorales/query-key-factory" -import { learningPathsApi } from "../../clients" -import axiosInstance from "../../axios" -import type { - LearningpathsApiLearningpathsItemsListRequest as ItemsListRequest, - LearningpathsApiLearningpathsListRequest as ListRequest, - PaginatedLearningPathRelationshipList, -} from "../../generated/v1" -import { clearListMemberships } from "../learningResources/queries" - -const learningPaths = createQueryKeys("learningPaths", { - detail: (id: number) => ({ - queryKey: [id], - queryFn: () => { - return learningPathsApi - .learningpathsRetrieve({ id }) - .then((res) => res.data) - }, - contextQueries: { - infiniteItems: (itemsP: ItemsListRequest) => ({ - queryKey: [itemsP], - queryFn: async ({ pageParam }: { pageParam?: string } = {}) => { - // Use generated API for first request, then use next parameter - const request = pageParam - ? axiosInstance.request({ - method: "get", - url: pageParam, - }) - : learningPathsApi.learningpathsItemsList(itemsP) - const { data } = await request - return { - ...data, - results: data.results.map((relation) => ({ - ...relation, - resource: clearListMemberships(relation.resource), - })), - } - }, - }), - }, - }), - list: (params: ListRequest) => ({ - queryKey: [params], - queryFn: () => { - return learningPathsApi.learningpathsList(params).then((res) => res.data) - }, - }), - membershipList: () => ({ - queryKey: ["membershipList"], - queryFn: async () => { - const { data } = await learningPathsApi.learningpathsMembershipList() - return data - }, - }), -}) - -export default learningPaths diff --git a/frontends/api/src/hooks/learningPaths/queries.ts b/frontends/api/src/hooks/learningPaths/queries.ts new file mode 100644 index 0000000000..84cf682a0d --- /dev/null +++ b/frontends/api/src/hooks/learningPaths/queries.ts @@ -0,0 +1,67 @@ +import { learningPathsApi } from "../../clients" +import axiosInstance from "../../axios" +import type { + LearningpathsApiLearningpathsItemsListRequest as ItemsListRequest, + LearningpathsApiLearningpathsListRequest as ListRequest, + PaginatedLearningPathRelationshipList, +} from "../../generated/v1" +import { clearListMemberships } from "../learningResources/queries" +import { QueryOptions, UseInfiniteQueryOptions } from "@tanstack/react-query" + +const learningPathKeys = { + root: ["learningPaths"], + listRoot: () => [...learningPathKeys.root, "list"], + list: (params: ListRequest) => [...learningPathKeys.listRoot(), params], + detailRoot: () => [...learningPathKeys.root, "detail"], + detail: (id: number) => [...learningPathKeys.detailRoot(), id], + infiniteItemsRoot: (id: number) => [...learningPathKeys.detail(id), "items"], + infiniteItems: (id: number, listingParams: ItemsListRequest) => [ + ...learningPathKeys.infiniteItemsRoot(id), + listingParams, + ], + membershipList: () => [...learningPathKeys.root, "membershipList"], +} + +const learningPathQueries = { + list: (params: ListRequest) => + ({ + queryKey: learningPathKeys.list(params), + queryFn: () => + learningPathsApi.learningpathsList(params).then((res) => res.data), + }) satisfies QueryOptions, + detail: (id: number) => + ({ + queryKey: learningPathKeys.detail(id), + queryFn: () => + learningPathsApi.learningpathsRetrieve({ id }).then((res) => res.data), + }) satisfies QueryOptions, + infiniteItems: (id: number, listingParams: ItemsListRequest) => + ({ + queryKey: learningPathKeys.infiniteItems(id, listingParams), + queryFn: async ({ pageParam }: { pageParam?: string } = {}) => { + // Use generated API for first request, then use next parameter + const request = pageParam + ? axiosInstance.request({ + method: "get", + url: pageParam, + }) + : learningPathsApi.learningpathsItemsList(listingParams) + const { data } = await request + return { + ...data, + results: data.results.map((relation) => ({ + ...relation, + resource: clearListMemberships(relation.resource), + })), + } + }, + }) satisfies UseInfiniteQueryOptions, + membershipList: () => + ({ + queryKey: learningPathKeys.membershipList(), + queryFn: () => + learningPathsApi.learningpathsMembershipList().then((res) => res.data), + }) satisfies QueryOptions, +} + +export { learningPathQueries, learningPathKeys } diff --git a/frontends/api/src/hooks/learningResources/index.ts b/frontends/api/src/hooks/learningResources/index.ts index b14c811d89..b2edc21653 100644 --- a/frontends/api/src/hooks/learningResources/index.ts +++ b/frontends/api/src/hooks/learningResources/index.ts @@ -24,8 +24,8 @@ import { schoolQueries, platformsQueries, } from "./queries" -import userLists from "../userLists/keyFactory" -import learningPaths from "../learningPaths/keyFactory" +import { userlistKeys } from "../userLists/queries" +import { learningPathKeys } from "../learningPaths/queries" import { useCallback } from "react" const useLearningResourcesList = ( @@ -98,7 +98,7 @@ const useLearningResourceSetUserListRelationships = () => { params: LearningResourcesApiLearningResourcesUserlistsPartialUpdateRequest, ) => learningResourcesApi.learningResourcesUserlistsPartialUpdate(params), onSettled: () => { - queryClient.invalidateQueries(userLists.membershipList().queryKey) + queryClient.invalidateQueries(userlistKeys.membershipList()) }, }) } @@ -111,7 +111,7 @@ const useLearningResourceSetLearningPathRelationships = () => { ) => learningResourcesApi.learningResourcesLearningPathsPartialUpdate(params), onSettled: () => { - queryClient.invalidateQueries(learningPaths.membershipList().queryKey) + queryClient.invalidateQueries(learningPathKeys.membershipList()) }, }) } diff --git a/frontends/api/src/hooks/userLists/index.ts b/frontends/api/src/hooks/userLists/index.ts index b241689234..96cdcc1b37 100644 --- a/frontends/api/src/hooks/userLists/index.ts +++ b/frontends/api/src/hooks/userLists/index.ts @@ -13,7 +13,7 @@ import type { UserlistsApiUserlistsItemsListRequest as ItemsListRequest, UserList, } from "../../generated/v1" -import userLists from "./keyFactory" +import { userlistKeys, userlistQueries } from "./queries" import { useUserIsAuthenticated } from "api/hooks/user" const useUserListList = ( @@ -21,13 +21,13 @@ const useUserListList = ( opts: Pick = {}, ) => { return useQuery({ - ...userLists.list(params), + ...userlistQueries.list(params), ...opts, }) } const useUserListsDetail = (id: number) => { - return useQuery(userLists.detail(id)) + return useQuery(userlistQueries.detail(id)) } const useUserListCreate = () => { @@ -38,7 +38,7 @@ const useUserListCreate = () => { UserListRequest: params, }), onSettled: () => { - queryClient.invalidateQueries(userLists.list._def) + queryClient.invalidateQueries(userlistKeys.listRoot()) }, }) } @@ -51,8 +51,8 @@ const useUserListUpdate = () => { PatchedUserListRequest: params, }), onSettled: (_data, _err, vars) => { - queryClient.invalidateQueries(userLists.list._def) - queryClient.invalidateQueries(userLists.detail(vars.id).queryKey) + queryClient.invalidateQueries(userlistKeys.listRoot()) + queryClient.invalidateQueries(userlistKeys.detail(vars.id)) }, }) } @@ -63,8 +63,8 @@ const useUserListDestroy = () => { mutationFn: (params: DestroyRequest) => userListsApi.userlistsDestroy(params), onSettled: () => { - queryClient.invalidateQueries(userLists.list._def) - queryClient.invalidateQueries(userLists.membershipList._def) + queryClient.invalidateQueries(userlistKeys.listRoot()) + queryClient.invalidateQueries(userlistKeys.membershipList()) }, }) } @@ -74,7 +74,7 @@ const useInfiniteUserListItems = ( options: Pick = {}, ) => { return useInfiniteQuery({ - ...userLists.detail(params.userlist_id)._ctx.infiniteItems(params), + ...userlistQueries.infiniteItems(params.userlist_id, params), getNextPageParam: (lastPage) => { return lastPage.next ?? undefined }, @@ -98,16 +98,14 @@ const useUserListListItemMove = () => { }) }, onSettled: (_data, _err, vars) => { - queryClient.invalidateQueries( - userLists.detail(vars.parent)._ctx.infiniteItems._def, - ) + queryClient.invalidateQueries(userlistKeys.infiniteItemsRoot(vars.parent)) }, }) } const useIsUserListMember = (resourceId?: number) => { return useQuery({ - ...userLists.membershipList(), + ...userlistQueries.membershipList(), select: (data) => { return !!data.find((relationship) => relationship.child === resourceId) }, @@ -117,7 +115,7 @@ const useIsUserListMember = (resourceId?: number) => { const useUserListMemberList = (resourceId?: number) => { return useQuery({ - ...userLists.membershipList(), + ...userlistQueries.membershipList(), select: (data) => { return data .filter((relationship) => relationship.child === resourceId) diff --git a/frontends/api/src/hooks/userLists/keyFactory.ts b/frontends/api/src/hooks/userLists/keyFactory.ts deleted file mode 100644 index e0df854ed1..0000000000 --- a/frontends/api/src/hooks/userLists/keyFactory.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { createQueryKeys } from "@lukemorales/query-key-factory" -import axiosInstance from "../../axios" -import type { - UserlistsApiUserlistsItemsListRequest as ItemsListRequest, - UserlistsApiUserlistsListRequest as ListRequest, - PaginatedUserListRelationshipList, -} from "../../generated/v1" -import { userListsApi } from "../../clients" -import { clearListMemberships } from "../learningResources/queries" - -const userLists = createQueryKeys("userLists", { - detail: (id: number) => ({ - queryKey: [id], - queryFn: async () => { - const { data } = await userListsApi.userlistsRetrieve({ id }) - return data - }, - contextQueries: { - infiniteItems: (itemsP: ItemsListRequest) => ({ - queryKey: [itemsP], - queryFn: async ({ pageParam }: { pageParam?: string } = {}) => { - const request = pageParam - ? axiosInstance.request({ - method: "get", - url: pageParam, - }) - : userListsApi.userlistsItemsList(itemsP) - const { data } = await request - return { - ...data, - results: data.results.map((relation) => ({ - ...relation, - resource: clearListMemberships(relation.resource), - })), - } - }, - }), - }, - }), - list: (params: ListRequest) => ({ - queryKey: [params], - queryFn: () => userListsApi.userlistsList(params).then((res) => res.data), - }), - membershipList: () => ({ - queryKey: ["membershipList"], - queryFn: async () => { - const { data } = await userListsApi.userlistsMembershipList() - return data - }, - }), -}) - -export default userLists diff --git a/frontends/api/src/hooks/userLists/queries.ts b/frontends/api/src/hooks/userLists/queries.ts new file mode 100644 index 0000000000..21c1662a34 --- /dev/null +++ b/frontends/api/src/hooks/userLists/queries.ts @@ -0,0 +1,65 @@ +import axiosInstance from "../../axios" +import type { + UserlistsApiUserlistsItemsListRequest as ItemsListRequest, + UserlistsApiUserlistsListRequest as ListRequest, + PaginatedUserListRelationshipList, +} from "../../generated/v1" +import { userListsApi } from "../../clients" +import { clearListMemberships } from "../learningResources/queries" +import { QueryOptions } from "@tanstack/react-query" + +const userlistKeys = { + root: ["userLists"], + listRoot: () => [...userlistKeys.root, "list"], + list: (params: ListRequest) => [...userlistKeys.listRoot(), params], + detailRoot: () => [...userlistKeys.root, "detail"], + detail: (id: number) => [...userlistKeys.detailRoot(), id], + infiniteItemsRoot: (id: number) => [...userlistKeys.detail(id), "items"], + infiniteItems: (id: number, listingParams: ItemsListRequest) => [ + ...userlistKeys.infiniteItemsRoot(id), + listingParams, + ], + membershipList: () => [...userlistKeys.root, "membershipList"], +} + +const userlistQueries = { + list: (params: ListRequest) => + ({ + queryKey: userlistKeys.list(params), + queryFn: () => userListsApi.userlistsList(params).then((res) => res.data), + }) satisfies QueryOptions, + detail: (id: number) => + ({ + queryKey: userlistKeys.detail(id), + queryFn: () => + userListsApi.userlistsRetrieve({ id }).then((res) => res.data), + }) satisfies QueryOptions, + infiniteItems: (id: number, listingParams: ItemsListRequest) => ({ + queryKey: userlistKeys.infiniteItems(id, listingParams), + queryFn: async ({ pageParam }: { pageParam?: string } = {}) => { + // Use generated API for first request, then use next parameter + const request = pageParam + ? axiosInstance.request({ + method: "get", + url: pageParam, + }) + : userListsApi.userlistsItemsList(listingParams) + const { data } = await request + return { + ...data, + results: data.results.map((relation) => ({ + ...relation, + resource: clearListMemberships(relation.resource), + })), + } + }, + }), + membershipList: () => + ({ + queryKey: userlistKeys.membershipList(), + queryFn: () => + userListsApi.userlistsMembershipList().then((res) => res.data), + }) satisfies QueryOptions, +} + +export { userlistQueries, userlistKeys } diff --git a/yarn.lock b/yarn.lock index 2c31f2cbd9..bdc620647c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2646,16 +2646,6 @@ __metadata: languageName: node linkType: hard -"@lukemorales/query-key-factory@npm:^1.3.2": - version: 1.3.4 - resolution: "@lukemorales/query-key-factory@npm:1.3.4" - peerDependencies: - "@tanstack/query-core": ">= 4.0.0" - "@tanstack/react-query": ">= 4.0.0" - checksum: 10/8954bbce9c5fc7103ccb8bc546eee746896af47a1453cf9a2413c0accae296727db60329a3b75a8f2ef15c516cfadb4eb17530242274f20bfd129bc85f799149 - languageName: node - linkType: hard - "@mdx-js/react@npm:^3.0.0": version: 3.0.1 resolution: "@mdx-js/react@npm:3.0.1" @@ -6518,7 +6508,6 @@ __metadata: resolution: "api@workspace:frontends/api" dependencies: "@faker-js/faker": "npm:^9.0.0" - "@lukemorales/query-key-factory": "npm:^1.3.2" "@tanstack/react-query": "npm:^4.36.1" "@testing-library/react": "npm:^16.1.0" axios: "npm:^1.6.3"