Skip to content

Commit

Permalink
add topics carousels to v2 drawer (#1912)
Browse files Browse the repository at this point in the history
* add topic carousels and exclude current drawer resource from any results

* add a test for excludeResourceId

* fix tests

* update topic carousel titles

* only display topic carousels for subtopics (topics that have a parent) and limit to 2
  • Loading branch information
gumaerc authored Dec 19, 2024
1 parent 4b543e4 commit 51bdf22
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import LearningResourceDrawerV2 from "./LearningResourceDrawerV2"
import { urls, factories, setMockResponse } from "api/test-utils"
import { LearningResourceExpandedV2 } from "ol-components"
import { RESOURCE_DRAWER_QUERY_PARAM } from "@/common/urls"
import { ResourceTypeEnum } from "api"
import { CourseResource, LearningResource, ResourceTypeEnum } from "api"
import { ControlledPromise } from "ol-test-utilities"

jest.mock("ol-components", () => {
const actual = jest.requireActual("ol-components")
Expand All @@ -20,6 +21,22 @@ jest.mock("ol-components", () => {
}
})

const makeSearchResponse = (results: CourseResource[] | LearningResource[]) => {
const responseData = {
metadata: {
suggestions: [],
aggregations: {},
},
count: results.length,
results: results,
next: null,
previous: null,
}
const promise = new ControlledPromise()
promise.resolve(responseData)
return responseData
}

const mockedPostHogCapture = jest.fn()

jest.mock("posthog-js/react", () => ({
Expand All @@ -32,6 +49,52 @@ jest.mock("posthog-js/react", () => ({
},
}))

const setupApis = (resource: LearningResource) => {
setMockResponse.get(
urls.learningResources.details({ id: resource.id }),
resource,
)
const count = 10
const similarResources = factories.learningResources.resources({
count,
}).results
setMockResponse.get(urls.userMe.get(), null, { code: 403 })
setMockResponse.get(
urls.learningResources.details({ id: resource.id }),
resource,
)
setMockResponse.get(
urls.learningResources.vectorSimilar({ id: resource.id }),
similarResources,
)
const topicsCourses: CourseResource[] = []
resource.topics?.forEach((topic) => {
const topicCourses = factories.learningResources.courses({ count: 10 })
topicCourses.results.map((course) => {
course.topics = [factories.learningResources.topic({ name: topic.name })]
})
topicsCourses.push(...topicCourses.results)
})
resource.topics?.forEach((topic) => {
setMockResponse.get(
expect.stringContaining(
urls.search.resources({
limit: 12,
resource_type: ["course"],
sortby: "-views",
topic: [topic.name],
}),
),
makeSearchResponse(
topicsCourses.filter(
(course) => course.topics?.[0].name === topic.name,
),
),
)
})
return { resource, similarResources }
}

describe("LearningResourceDrawerV2", () => {
it.each([
{ descriptor: "is enabled", enablePostHog: true },
Expand All @@ -46,18 +109,7 @@ describe("LearningResourceDrawerV2", () => {
? "12345abcdef" // pragma: allowlist secret
: ""
const resource = factories.learningResources.resource()
setMockResponse.get(
urls.learningResources.details({ id: resource.id }),
resource,
)
setMockResponse.get(
urls.learningResources.similar({ id: resource.id }),
[],
)
setMockResponse.get(
urls.learningResources.vectorSimilar({ id: resource.id }),
[],
)
setupApis(resource)

renderWithProviders(<LearningResourceDrawerV2 />, {
url: `?dog=woof&${RESOURCE_DRAWER_QUERY_PARAM}=${resource.id}`,
Expand Down Expand Up @@ -114,18 +166,7 @@ describe("LearningResourceDrawerV2", () => {
}),
],
})
setMockResponse.get(
urls.learningResources.details({ id: resource.id }),
resource,
)
setMockResponse.get(
urls.learningResources.similar({ id: resource.id }),
[],
)
setMockResponse.get(
urls.learningResources.vectorSimilar({ id: resource.id }),
[],
)
setupApis(resource)
const user = factories.user.user({
is_learning_path_editor: isLearningPathEditor,
})
Expand Down Expand Up @@ -169,19 +210,7 @@ describe("LearningResourceDrawerV2", () => {
}),
],
})
const count = 10
const similarResources = factories.learningResources.resources({
count,
}).results
setMockResponse.get(urls.userMe.get(), null, { code: 403 })
setMockResponse.get(
urls.learningResources.details({ id: resource.id }),
resource,
)
setMockResponse.get(
urls.learningResources.vectorSimilar({ id: resource.id }),
similarResources,
)
const { similarResources } = setupApis(resource)
renderWithProviders(<LearningResourceDrawerV2 />, {
url: `?resource=${resource.id}`,
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { usePostHog } from "posthog-js/react"
import ResourceCarousel from "../ResourceCarousel/ResourceCarousel"
import { useIsLearningPathMember } from "api/hooks/learningPaths"
import { useIsUserListMember } from "api/hooks/userLists"
import { TopicCarouselConfig } from "@/app-pages/DashboardPage/carousels"

const RESOURCE_DRAWER_PARAMS = [RESOURCE_DRAWER_QUERY_PARAM] as const

Expand Down Expand Up @@ -108,16 +109,31 @@ const DrawerContent: React.FC<{
},
},
]}
excludeResourceId={resourceId}
/>
)
const topics = resource.data?.topics
?.filter((topic) => topic.parent)
.slice(0, 2)
const topicCarousels = topics?.map((topic) => (
<ResourceCarousel
key={topic.id}
titleComponent="p"
titleVariant="subtitle1"
title={`Learning Resources in "${topic.name}"`}
config={TopicCarouselConfig(topic.name)}
data-testid={`topic-carousel-${topic}`}
excludeResourceId={resourceId}
/>
))

return (
<>
<LearningResourceExpandedV2
imgConfig={imgConfigs.large}
resourceId={resourceId}
resource={resource.data}
carousels={[similarResourcesCarousel]}
carousels={[similarResourcesCarousel, ...(topicCarousels || [])]}
user={user}
shareUrl={`${window.location.origin}/search?${RESOURCE_DRAWER_QUERY_PARAM}=${resourceId}`}
inLearningPath={inLearningPath}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,4 +220,28 @@ describe("ResourceCarousel", () => {
expect(title.tagName).toBe(expectedTag)
},
)

it("Excludes a resource if excludeResourceId is provided", async () => {
const config: ResourceCarouselProps["config"] = [
{
label: "Resources",
data: {
type: "resources",
params: { resource_type: ["course", "program"], professional: true },
},
},
]
const { resources } = setupApis()
renderWithProviders(
<ResourceCarousel
titleComponent="h1"
title="My Carousel"
config={config}
excludeResourceId={resources.list.results[1].id}
/>,
)
await screen.findByText(resources.list.results[0].title)
await screen.findByText(resources.list.results[2].title)
expect(screen.queryByText(resources.list.results[1].title)).toBeNull()
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ type ResourceCarouselProps = {
*/
titleComponent?: React.ElementType
titleVariant?: TypographyProps["variant"]
excludeResourceId?: number
}
/**
* A tabbed carousel that fetches resources based on the configuration provided.
Expand All @@ -197,6 +198,7 @@ const ResourceCarousel: React.FC<ResourceCarouselProps> = ({
"data-testid": dataTestId,
titleComponent = "h4",
titleVariant = "h4",
excludeResourceId,
}) => {
const [tab, setTab] = React.useState("0")
const [ref, setRef] = React.useState<HTMLDivElement | null>(null)
Expand Down Expand Up @@ -306,13 +308,15 @@ const ResourceCarousel: React.FC<ResourceCarouselProps> = ({
{...tabConfig.cardProps}
/>
))
: resources.map((resource) => (
<ResourceCard
key={resource.id}
resource={resource}
{...tabConfig.cardProps}
/>
))}
: resources.map((resource) =>
resource.id !== excludeResourceId ? (
<ResourceCard
key={resource.id}
resource={resource}
{...tabConfig.cardProps}
/>
) : null,
)}
</StyledCarousel>
)}
</PanelChildren>
Expand Down

0 comments on commit 51bdf22

Please sign in to comment.