Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
722d73f
WIP
gumaerc Aug 21, 2024
55aad9f
rework api endpoints and implement them
gumaerc Aug 23, 2024
6336383
remove the modal after we're done with it
gumaerc Aug 23, 2024
18de402
await setting new list parents before dismissing modal
gumaerc Aug 23, 2024
5197513
remove circularprogress for now
gumaerc Aug 26, 2024
95ff8e5
fix tests
gumaerc Aug 26, 2024
c688787
try and fix warning about api parameter
gumaerc Aug 26, 2024
edaac97
try and fix python tests again
gumaerc Aug 26, 2024
9eeb25b
remove id parameter because it broke everything
gumaerc Aug 26, 2024
02e15f8
remove extraneous API endpoints
gumaerc Aug 27, 2024
d51a33a
simplify api signature
gumaerc Aug 27, 2024
3a210af
remove extraneous debugging line
gumaerc Aug 27, 2024
5e5a50a
filter user list manipulation based on the current user and properly …
gumaerc Aug 27, 2024
25b66e5
restrict learning path editing to learning path editor
gumaerc Aug 27, 2024
879a2e1
add reordering logic to learning path setting too
gumaerc Aug 27, 2024
816877d
fix js tests
gumaerc Aug 27, 2024
fef3827
fix schema annotations
gumaerc Aug 27, 2024
fd7f945
fix annotations and response types
gumaerc Aug 27, 2024
ef893bc
override get_serializer_class to satisfy checks
gumaerc Aug 27, 2024
775d43d
regenerate spec
gumaerc Aug 27, 2024
7147102
fix invalidations
gumaerc Aug 27, 2024
9f9a655
revise invalidations based on feedback
gumaerc Aug 27, 2024
f5f798b
only specify parameters where necessary
gumaerc Aug 27, 2024
432aa2b
fix backwards classes
gumaerc Aug 27, 2024
c38a252
add learning path permission class instead of manual check
gumaerc Aug 27, 2024
c9af277
add some tests for the new API endpoints
gumaerc Aug 27, 2024
128b7c5
use get_serializer_class
gumaerc Aug 28, 2024
852129b
properly use get_serializer_class
gumaerc Aug 28, 2024
15274d9
throw an unauthorized error if you try to assign courses to userlists…
gumaerc Aug 28, 2024
037eba1
fix test by checking for raising of permission error
gumaerc Aug 28, 2024
4d70856
fix rebase issue yet again
gumaerc Aug 28, 2024
904549d
add a test to ensure accidental unassignment does not happen
gumaerc Aug 29, 2024
950dbc1
pass in course to userlist assignment function
gumaerc Aug 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
375 changes: 375 additions & 0 deletions frontends/api/src/generated/v1/api.ts

Large diffs are not rendered by default.

47 changes: 46 additions & 1 deletion frontends/api/src/hooks/learningResources/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import {
useQuery,
useQueryClient,
} from "@tanstack/react-query"
import { learningpathsApi, userListsApi } from "../../clients"
import {
learningpathsApi,
learningResourcesApi,
userListsApi,
} from "../../clients"
import type {
LearningResourcesApiLearningResourcesListRequest as LRListRequest,
TopicsApiTopicsListRequest as TopicsListRequest,
Expand All @@ -28,6 +32,8 @@ import type {
PlatformsApiPlatformsListRequest,
FeaturedApiFeaturedListRequest as FeaturedListParams,
PaginatedLearningResourceList,
LearningResourcesApiLearningResourcesUserlistsPartialUpdateRequest,
LearningResourcesApiLearningResourcesLearningPathsPartialUpdateRequest,
} from "../../generated/v1"
import learningResources, {
invalidateResourceQueries,
Expand Down Expand Up @@ -319,6 +325,43 @@ const useUserListRelationshipCreate = () => {
})
}

const useLearningResourceSetUserListRelationships = () => {
const queryClient = useQueryClient()
return useMutation({
mutationFn: (
params: LearningResourcesApiLearningResourcesUserlistsPartialUpdateRequest,
) => learningResourcesApi.learningResourcesUserlistsPartialUpdate(params),
onSettled: (_response, _err, vars) => {
invalidateResourceQueries(queryClient, vars.id, {
skipFeatured: false,
})
vars.userlist_id?.forEach((userlistId) => {
invalidateUserListQueries(queryClient, userlistId)
})
},
})
}

const useLearningResourceSetLearningPathRelationships = () => {
const queryClient = useQueryClient()
return useMutation({
mutationFn: (
params: LearningResourcesApiLearningResourcesLearningPathsPartialUpdateRequest,
) =>
learningResourcesApi.learningResourcesLearningPathsPartialUpdate(params),
onSettled: (_response, _err, vars) => {
invalidateResourceQueries(queryClient, vars.id, {
skipFeatured: false,
})
vars.learning_path_id?.forEach((learningPathId) => {
invalidateResourceQueries(queryClient, learningPathId, {
skipFeatured: false,
})
})
},
})
}

const useUserListRelationshipDestroy = () => {
const queryClient = useQueryClient()
return useMutation({
Expand Down Expand Up @@ -454,6 +497,8 @@ export {
useLearningpathRelationshipCreate,
useLearningpathRelationshipDestroy,
useLearningResourcesSearch,
useLearningResourceSetUserListRelationships,
useLearningResourceSetLearningPathRelationships,
useUserListList,
useUserListsDetail,
useUserListCreate,
Expand Down
18 changes: 17 additions & 1 deletion frontends/api/src/test-utils/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,15 @@ const { MITOL_API_BASE_URL: API_BASE_URL } = APP_SETTINGS
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const query = (params: any) => {
if (!params || Object.keys(params).length === 0) return ""
return `?${new URLSearchParams(params).toString()}`
const queryString = new URLSearchParams()
for (const [key, value] of Object.entries(params)) {
if (Array.isArray(value)) {
value.forEach((v) => queryString.append(key, String(v)))
} else {
queryString.append(key, String(value))
}
}
return `?${queryString.toString()}`
}

const queryify = (params: unknown) => {
Expand Down Expand Up @@ -67,6 +75,14 @@ const learningResources = {
`${API_BASE_URL}/api/v1/learning_resources/${params.id}/`,
featured: (params?: Params<FeaturedApi, "featuredList">) =>
`${API_BASE_URL}/api/v1/featured/${query(params)}`,
setLearningPathRelationships: (
params?: Params<LRApi, "learningResourcesLearningPathsPartialUpdate">,
) =>
`${API_BASE_URL}/api/v1/learning_resources/${params?.id}/learning_paths/${params?.learning_path_id ? query({ learning_path_id: params?.learning_path_id }) : ""}`,
setUserListRelationships: (
params?: Params<LRApi, "learningResourcesUserlistsPartialUpdate">,
) =>
`${API_BASE_URL}/api/v1/learning_resources/${params?.id}/userlists/${params?.userlist_id ? query({ userlist_id: params?.userlist_id }) : ""}`,
}

const offerors = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ const addToUserList = (
describe.each([ListType.LearningPath, ListType.UserList])(
"AddToListDialog",
(listType: string) => {
test("List is checked iff resource is in list", async () => {
test("List is checked if resource is in list", async () => {
const index = faker.number.int({ min: 0, max: 2 })
if (listType === ListType.LearningPath) {
setupLearningPaths({ inLists: [index] })
Expand All @@ -169,62 +169,57 @@ describe.each([ListType.LearningPath, ListType.UserList])(
expect(checkboxes[2].checked).toBe(index === 2)
})

test("Clicking an unchecked list adds item to that list", async () => {
test("Clicking an unchecked list and clicking save adds item to that list", async () => {
let title = ""
let id = -1
let addToListUrl = ""
let setRelationshipsUrl = ""
if (listType === ListType.LearningPath) {
const { resource, lists } = setupLearningPaths()
const list = faker.helpers.arrayElement(lists)

addToListUrl = urls.learningPaths.resources({
learning_resource_id: list.id,
})
setRelationshipsUrl =
urls.learningResources.setLearningPathRelationships({
id: resource.id,
learning_path_id: [list.id],
})
const newRelationship = addToLearningPath(resource, list)
setMockResponse.post(addToListUrl, newRelationship)
setMockResponse.patch(setRelationshipsUrl, newRelationship)
setMockResponse.get(
urls.learningResources.details({ id: resource.id }),
resource,
)
title = list.title
id = list.id
} else if (listType === ListType.UserList) {
const { resource, lists } = setupUserLists()
const list = faker.helpers.arrayElement(lists)

addToListUrl = urls.userLists.resources({
userlist_id: list.id,
setRelationshipsUrl = urls.learningResources.setUserListRelationships({
id: resource.id,
userlist_id: [list.id],
})
const newRelationship = addToUserList(resource, list)
setMockResponse.post(addToListUrl, newRelationship)
setMockResponse.patch(setRelationshipsUrl, newRelationship)
setMockResponse.get(
urls.learningResources.details({ id: resource.id }),
resource,
)
title = list.title
id = list.id
}

const listButton = await screen.findByRole("button", { name: title })
const checkbox =
within(listButton).getByRole<HTMLInputElement>("checkbox")
const checkbox = await screen.findByLabelText<HTMLInputElement>(title)

expect(checkbox.checked).toBe(false)
await user.click(listButton)
await user.click(checkbox)

expect(makeRequest).toHaveBeenCalledWith(
"post",
addToListUrl,
expect.objectContaining({
parent: id,
}),
)
const saveButton = await screen.findByRole("button", { name: "Save" })
await user.click(saveButton)

expect(makeRequest).toHaveBeenCalledWith("patch", setRelationshipsUrl, {})
})

test("Clicking a checked list removes item from that list", async () => {
test("Clicking a checked list and clicking save removes item from that list", async () => {
const index = faker.number.int({ min: 0, max: 2 })
let title = ""
let removalUrl = ""
let setRelationshipUrl = ""
if (listType === ListType.LearningPath) {
const { resource, lists, parents } = setupLearningPaths({
inLists: [index],
Expand All @@ -234,11 +229,12 @@ describe.each([ListType.LearningPath, ListType.UserList])(
invariant(relationship)

title = list.title
removalUrl = urls.learningPaths.resourceDetails({
id: relationship.id,
learning_resource_id: relationship.parent,
})
setMockResponse.delete(removalUrl)
setRelationshipUrl =
urls.learningResources.setLearningPathRelationships({
id: relationship.child,
})
setMockResponse.patch(setRelationshipUrl, relationship)

setMockResponse.get(
urls.learningResources.details({ id: resource.id }),
resource,
Expand All @@ -252,28 +248,28 @@ describe.each([ListType.LearningPath, ListType.UserList])(
invariant(relationship)

title = list.title
removalUrl = urls.userLists.resourceDetails({
id: relationship.id,
userlist_id: relationship.parent,
setRelationshipUrl = urls.learningResources.setUserListRelationships({
id: relationship.child,
})
setMockResponse.delete(removalUrl)
setMockResponse.patch(setRelationshipUrl, relationship)
setMockResponse.get(
urls.userLists.details({ id: resource.id }),
resource,
)
}

const listButton = await screen.findByRole("button", { name: title })
const checkbox =
within(listButton).getByRole<HTMLInputElement>("checkbox")
const checkbox = await screen.findByLabelText<HTMLInputElement>(title)

expect(checkbox.checked).toBe(true)
await user.click(listButton)
await user.click(checkbox)

const saveButton = await screen.findByRole("button", { name: "Save" })
await user.click(saveButton)

expect(makeRequest).toHaveBeenCalledWith("delete", removalUrl, undefined)
expect(makeRequest).toHaveBeenCalledWith("patch", setRelationshipUrl, {})
})

test("Clicking 'Create a new list' opens the create list dialog", async () => {
test("Clicking 'Create New list' opens the create list dialog", async () => {
let createList = null
if (listType === ListType.LearningPath) {
// Don't actually open the 'Create List' modal, or we'll need to mock API responses.
Expand All @@ -289,7 +285,7 @@ describe.each([ListType.LearningPath, ListType.UserList])(
setupUserLists()
}
const button = await screen.findByRole("button", {
name: "Create a new list",
name: "Create New List",
})
expect(createList).not.toHaveBeenCalled()
await user.click(button)
Expand Down
Loading