Skip to content

Commit

Permalink
Add userlists page and refactor LearningResourceCardTemplate (#650)
Browse files Browse the repository at this point in the history
* add appropriate userlist api clients, url routes and frontend components, refactor LearningResourceCardTemplate to inherit CardTemplate

* add User Lists to the UserMenu

* fix typecheck issue

* pass opts to useQuery in useUserListList

* fix some language in test naming

* remove stray console.log's

* remove default on sortable and invariant call on UserListCardTemplate and LearningResourceCardTemplate

* restrict the UserList listing route
  • Loading branch information
gumaerc authored Mar 26, 2024
1 parent d453990 commit 398ad13
Show file tree
Hide file tree
Showing 26 changed files with 618 additions and 197 deletions.
4 changes: 4 additions & 0 deletions frontends/api/src/clients.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
LearningResourcesApi,
LearningpathsApi,
UserlistsApi,
TopicsApi,
ArticlesApi,
ProgramLettersApi,
Expand Down Expand Up @@ -30,6 +31,8 @@ const learningpathsApi = new LearningpathsApi(
axiosInstance,
)

const userListsApi = new UserlistsApi(undefined, BASE_PATH, axiosInstance)

const topicsApi = new TopicsApi(undefined, BASE_PATH, axiosInstance)

const articlesApi = new ArticlesApi(undefined, BASE_PATH, axiosInstance)
Expand All @@ -46,6 +49,7 @@ const widgetListsApi = new WidgetListsApi(undefined, BASE_PATH, axiosInstance)
export {
learningResourcesApi,
learningpathsApi,
userListsApi,
topicsApi,
articlesApi,
programLettersApi,
Expand Down
29 changes: 29 additions & 0 deletions frontends/api/src/hooks/learningResources/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import type {
MicroLearningPathRelationship,
LearningResource,
LearningResourcesSearchApiLearningResourcesSearchRetrieveRequest as LRSearchRequest,
UserlistsApiUserlistsListRequest as ULListRequest,
UserlistsApiUserlistsItemsListRequest as ULItemsListRequest,
} from "../../generated/v1"
import learningResources, { invalidateResourceQueries } from "./keyFactory"

Expand Down Expand Up @@ -202,6 +204,31 @@ const useLearningResourcesSearch = (
})
}

const useUserListList = (
params: ULListRequest = {},
opts: Pick<UseQueryOptions, "enabled"> = {},
) => {
return useQuery({
...learningResources.userlists._ctx.list(params),
...opts,
})
}

const useInfiniteUserListItems = (
params: ULItemsListRequest,
options: Pick<UseQueryOptions, "enabled"> = {},
) => {
return useInfiniteQuery({
...learningResources.userlists._ctx
.detail(params.userlist_id)
._ctx.infiniteItems(params),
getNextPageParam: (lastPage) => {
return lastPage.next ?? undefined
},
...options,
})
}

export {
useLearningResourcesList,
useLearningResourcesDetail,
Expand All @@ -216,4 +243,6 @@ export {
useLearningpathRelationshipCreate,
useLearningpathRelationshipDestroy,
useLearningResourcesSearch,
useUserListList,
useInfiniteUserListItems,
}
60 changes: 58 additions & 2 deletions frontends/api/src/hooks/learningResources/keyFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
learningpathsApi,
learningResourcesSearchApi,
topicsApi,
userListsApi,
} from "../../clients"
import axiosInstance from "../../axios"
import type {
Expand All @@ -15,6 +16,10 @@ import type {
LearningResource,
PaginatedLearningPathRelationshipList,
LearningResourcesSearchApiLearningResourcesSearchRetrieveRequest as LRSearchRequest,
UserlistsApiUserlistsItemsListRequest as ULResourcesListRequest,
UserlistsApiUserlistsListRequest as ULListRequest,
PaginatedUserListRelationshipList,
UserList,
} from "../../generated/v1"
import { createQueryKeys } from "@lukemorales/query-key-factory"

Expand Down Expand Up @@ -79,9 +84,38 @@ const learningResources = createQueryKeys("learningResources", {
.then((res) => res.data),
}
},
userlists: {
queryKey: ["user_lists"],
contextQueries: {
detail: (id: number) => ({
queryKey: [id],
queryFn: () =>
userListsApi.userlistsRetrieve({ id }).then((res) => res.data),
contextQueries: {
infiniteItems: (itemsP: ULResourcesListRequest) => ({
queryKey: [itemsP],
queryFn: ({ pageParam }: { pageParam?: string } = {}) => {
const request = pageParam
? axiosInstance.request<PaginatedUserListRelationshipList>({
method: "get",
url: pageParam,
})
: userListsApi.userlistsItemsList(itemsP)
return request.then((res) => res.data)
},
}),
},
}),
list: (params: ULListRequest) => ({
queryKey: [params],
queryFn: () =>
userListsApi.userlistsList(params).then((res) => res.data),
}),
},
},
})

const listHasResource =
const learningPathHasResource =
(resourceId: number) =>
(query: Query): boolean => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand All @@ -93,6 +127,20 @@ const listHasResource =
: data.results
return resources.some((res) => res.id === resourceId)
}

const userListHasResource =
(resourceId: number) =>
(query: Query): boolean => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const data = query.state.data as any
const resources: UserList[] = data.pages
? data.pages.flatMap(
(page: PaginatedLearningResourceList) => page.results,
)
: data.results
return resources.some((res) => res.id === resourceId)
}

const invalidateResourceQueries = (
queryClient: QueryClient,
resourceId: LearningResource["id"],
Expand All @@ -105,18 +153,26 @@ const invalidateResourceQueries = (
queryClient.invalidateQueries(
learningResources.learningpaths._ctx.detail(resourceId).queryKey,
)
queryClient.invalidateQueries(
learningResources.userlists._ctx.detail(resourceId).queryKey,
)
/**
* Invalidate lists that the resource belongs to.
* Check for actual membership.
*/
const lists = [
learningResources.list._def,
learningResources.learningpaths._ctx.list._def,
learningResources.userlists._ctx.list._def,
]
lists.forEach((queryKey) => {
queryClient.invalidateQueries({
queryKey,
predicate: listHasResource(resourceId),
predicate: learningPathHasResource(resourceId),
})
queryClient.invalidateQueries({
queryKey,
predicate: userListHasResource(resourceId),
})
})
}
Expand Down
2 changes: 1 addition & 1 deletion frontends/api/src/test-utils/factories/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export * as learningResources from "./learningResources"

export * as userLists from "./userLists"
export * as articles from "./articles"
export * as letters from "./programLetters"
export * as fields from "./fields"
18 changes: 18 additions & 0 deletions frontends/api/src/test-utils/factories/userLists.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Factory, makePaginatedFactory } from "ol-test-utilities"
import { UserList } from "api"
import { faker } from "@faker-js/faker/locale/en"

const userList: Factory<UserList> = (overrides = {}) => {
const list: UserList = {
id: faker.helpers.unique(faker.datatype.number),
title: faker.helpers.unique(faker.lorem.words),
item_count: 4,
image: {},
author: faker.helpers.unique(faker.datatype.number),
...overrides,
}
return list
}
const userLists = makePaginatedFactory(userList)

export { userList, userLists }
19 changes: 19 additions & 0 deletions frontends/api/src/test-utils/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {
TopicsApi,
LearningpathsApi,
ArticlesApi,
UserlistsApi,
} from "../generated/v1"
import type { BaseAPI } from "../generated/v1/base"

Expand Down Expand Up @@ -57,6 +58,23 @@ const learningPaths = {
`/api/v1/learningpaths/${params.id}/`,
}

const userLists = {
list: (params?: Params<UserlistsApi, "userlistsList">) =>
`/api/v1/userlists/${query(params)}`,
resources: ({
userlist_id: parentId,
...others
}: Params<UserlistsApi, "userlistsItemsList">) =>
`/api/v1/userlists/${parentId}/items/${query(others)}`,
resourceDetails: ({
userlist_id: parentId,
id,
}: Params<UserlistsApi, "userlistsItemsPartialUpdate">) =>
`/api/v1/userlists/${parentId}/items/${id}/`,
details: (params: Params<UserlistsApi, "userlistsRetrieve">) =>
`/api/v1/userlists/${params.id}/`,
}

const articles = {
list: (params?: Params<ArticlesApi, "articlesList">) =>
`/api/v1/articles/${query(params)}`,
Expand Down Expand Up @@ -85,6 +103,7 @@ export {
learningPaths,
articles,
search,
userLists,
programLetters,
fields,
widgetLists,
Expand Down
1 change: 1 addition & 0 deletions frontends/mit-open/src/common/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export const HOME = "/"

export const LEARNINGPATH_LISTING = "/learningpaths/"
export const LEARNINGPATH_VIEW = "/learningpaths/:id"
export const USERLIST_LISTING = "/userlists/"
export const learningPathsView = (id: number) =>
generatePath(LEARNINGPATH_VIEW, { id: String(id) })
export const PROGRAMLETTER_VIEW = "/program_letter/:id/view/"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from "react"
import { render, screen } from "@testing-library/react"
import CardTemplate from "./CardTemplate"

describe("CardTemplate", () => {
it("renders title and cover image", () => {
const title = "Test Title"
render(<CardTemplate variant="column" title={title} />)
const heading = screen.getByRole("heading", { name: title })
expect(heading).toHaveAccessibleName(title)
})
})
Loading

0 comments on commit 398ad13

Please sign in to comment.