From 51bdf22a745be73cd15031c0609810397eb8dcd6 Mon Sep 17 00:00:00 2001 From: Carey P Gumaer Date: Thu, 19 Dec 2024 09:32:54 -0500 Subject: [PATCH] add topics carousels to v2 drawer (#1912) * 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 --- .../LearningResourceDrawerV2.test.tsx | 105 +++++++++++------- .../LearningResourceDrawerV2.tsx | 18 ++- .../ResourceCarousel.test.tsx | 24 ++++ .../ResourceCarousel/ResourceCarousel.tsx | 18 +-- 4 files changed, 119 insertions(+), 46 deletions(-) diff --git a/frontends/main/src/page-components/LearningResourceDrawer/LearningResourceDrawerV2.test.tsx b/frontends/main/src/page-components/LearningResourceDrawer/LearningResourceDrawerV2.test.tsx index fc6fb4ce60..731b1939fd 100644 --- a/frontends/main/src/page-components/LearningResourceDrawer/LearningResourceDrawerV2.test.tsx +++ b/frontends/main/src/page-components/LearningResourceDrawer/LearningResourceDrawerV2.test.tsx @@ -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") @@ -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", () => ({ @@ -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 }, @@ -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(, { url: `?dog=woof&${RESOURCE_DRAWER_QUERY_PARAM}=${resource.id}`, @@ -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, }) @@ -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(, { url: `?resource=${resource.id}`, }) diff --git a/frontends/main/src/page-components/LearningResourceDrawer/LearningResourceDrawerV2.tsx b/frontends/main/src/page-components/LearningResourceDrawer/LearningResourceDrawerV2.tsx index 9588be3a3f..a22f37ecae 100644 --- a/frontends/main/src/page-components/LearningResourceDrawer/LearningResourceDrawerV2.tsx +++ b/frontends/main/src/page-components/LearningResourceDrawer/LearningResourceDrawerV2.tsx @@ -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 @@ -108,8 +109,23 @@ const DrawerContent: React.FC<{ }, }, ]} + excludeResourceId={resourceId} /> ) + const topics = resource.data?.topics + ?.filter((topic) => topic.parent) + .slice(0, 2) + const topicCarousels = topics?.map((topic) => ( + + )) return ( <> @@ -117,7 +133,7 @@ const DrawerContent: React.FC<{ 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} diff --git a/frontends/main/src/page-components/ResourceCarousel/ResourceCarousel.test.tsx b/frontends/main/src/page-components/ResourceCarousel/ResourceCarousel.test.tsx index 6eb05a14a9..4bdab9c25c 100644 --- a/frontends/main/src/page-components/ResourceCarousel/ResourceCarousel.test.tsx +++ b/frontends/main/src/page-components/ResourceCarousel/ResourceCarousel.test.tsx @@ -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( + , + ) + 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() + }) }) diff --git a/frontends/main/src/page-components/ResourceCarousel/ResourceCarousel.tsx b/frontends/main/src/page-components/ResourceCarousel/ResourceCarousel.tsx index 125a4dcae8..ff2511f9d2 100644 --- a/frontends/main/src/page-components/ResourceCarousel/ResourceCarousel.tsx +++ b/frontends/main/src/page-components/ResourceCarousel/ResourceCarousel.tsx @@ -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. @@ -197,6 +198,7 @@ const ResourceCarousel: React.FC = ({ "data-testid": dataTestId, titleComponent = "h4", titleVariant = "h4", + excludeResourceId, }) => { const [tab, setTab] = React.useState("0") const [ref, setRef] = React.useState(null) @@ -306,13 +308,15 @@ const ResourceCarousel: React.FC = ({ {...tabConfig.cardProps} /> )) - : resources.map((resource) => ( - - ))} + : resources.map((resource) => + resource.id !== excludeResourceId ? ( + + ) : null, + )} )}