Skip to content

Server rendering for homepage, units and topics listing pages #1822

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 32 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
4721190
Warn on API calls during initial render not prefetched
jonkafton Nov 6, 2024
96f6c54
Merge branch 'main' into jk/5919-prefetch-mechanism
jonkafton Nov 6, 2024
fc4dcb7
Full prefetch for homepage (commented)
jonkafton Nov 6, 2024
52e6cf7
Prefetch utility
jonkafton Nov 7, 2024
bae8e93
Check for queries prefetched that are not needed during render and warn
jonkafton Nov 7, 2024
73209e0
No need to stringify
jonkafton Nov 7, 2024
9f25822
Replace useQuery overrides with decoupled cache check (wip)
jonkafton Nov 7, 2024
d34bd26
Observer count for unnecessary prefetch warnings
jonkafton Nov 8, 2024
98843c2
Remove useQuery override
jonkafton Nov 8, 2024
23e328e
Add warnings hook
jonkafton Nov 8, 2024
db5916b
Test prefetch warnings
jonkafton Nov 8, 2024
cd9ad5a
Remove inadvertent/unnecessary diff
jonkafton Nov 8, 2024
c41d537
Remove comments
jonkafton Nov 8, 2024
e4c19b7
Prefetch homepage. Invalidate learning resource cache items
jonkafton Nov 12, 2024
573e416
Remove comment
jonkafton Nov 12, 2024
e17f352
Update comment
jonkafton Nov 12, 2024
2cf4ab3
Temp remove featured resource list shuffle
jonkafton Nov 12, 2024
e015ac3
Merge branch 'jk/5919-prefetch-mechanism' into jk/5920-prefetch-homepage
jonkafton Nov 12, 2024
c10f677
Remove unused
jonkafton Nov 12, 2024
af74cb4
Prefetch calls
jonkafton Nov 12, 2024
d3b7fe3
Prefetch topics page
jonkafton Nov 12, 2024
c8cde78
Rename key factory re-exports
jonkafton Nov 13, 2024
12c6955
Merge branch 'main' into jk/5920-prefetch-homepage
jonkafton Nov 13, 2024
0c46451
Units page prefetch
jonkafton Nov 14, 2024
2bdea92
Single request for all unit channel details
jonkafton Nov 14, 2024
84eb78a
Update unit listing page tests
jonkafton Nov 15, 2024
14d49ae
Page arg types
jonkafton Nov 15, 2024
11d76cf
Optional route params
jonkafton Nov 15, 2024
1ac7666
Remove comment
jonkafton Nov 18, 2024
b713b53
Remove unused
jonkafton Nov 18, 2024
546c0ce
Merge branch 'main' into jk/5920-prefetch-homepage
jonkafton Nov 19, 2024
f4b10d6
Remove comment
jonkafton Nov 19, 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
2 changes: 1 addition & 1 deletion frontends/api/src/hooks/channels/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,5 @@ export {
useChannelsList,
useChannelPartialUpdate,
useChannelCounts,
channels as channelsKeyFactory,
channels,
}
2 changes: 1 addition & 1 deletion frontends/api/src/hooks/learningResources/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -531,5 +531,5 @@ export {
useListItemMove,
usePlatformsList,
useSchoolsList,
learningResources as learningResourcesKeyFactory,
learningResources,
}
8 changes: 7 additions & 1 deletion frontends/api/src/hooks/learningResources/keyFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ import type {

import { createQueryKeys } from "@lukemorales/query-key-factory"

/* TODO we can add this back in once resource user list membership is implemented in the front end
* Currently we fetch on the server for public resources and then refetch on the client for the
* user lists parents. Randomizing therefore causes a content flicker as the client loaded
* sequence replaces the server rendered sequence.

const shuffle = ([...arr]) => {
let m = arr.length
while (m) {
Expand Down Expand Up @@ -61,6 +66,7 @@ const randomizeResults = ([...results]) => {
})
return randomizedResults
}
*/

const learningResources = createQueryKeys("learningResources", {
detail: (id: number) => ({
Expand All @@ -81,7 +87,7 @@ const learningResources = createQueryKeys("learningResources", {
queryKey: [params],
queryFn: () => {
return featuredApi.featuredList(params).then((res) => {
res.data.results = randomizeResults(res.data?.results)
// res.data.results = randomizeResults(res.data?.results)
return res.data
})
},
Expand Down
2 changes: 1 addition & 1 deletion frontends/api/src/hooks/newsEvents/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@ export {
useNewsEventsList,
useNewsEventsDetail,
NewsEventsListFeedTypeEnum,
newsEvents as newsEventsKeyFactory,
newsEvents,
}
6 changes: 1 addition & 5 deletions frontends/api/src/hooks/testimonials/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,4 @@ const useTestimonialDetail = (id: number | undefined) => {
})
}

export {
useTestimonialDetail,
useTestimonialList,
testimonials as testimonialsKeyFactory,
}
export { useTestimonialDetail, useTestimonialList, testimonials }
14 changes: 10 additions & 4 deletions frontends/api/src/ssr/prefetch.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import { QueryClient, dehydrate } from "@tanstack/react-query"
import type { Query } from "@tanstack/react-query"

// Utility to avoid repetition in server components
export const prefetch = async (queries: (Query | unknown)[]) => {
const queryClient = new QueryClient()
/* Utility to avoid repetition in server components
* Optionally pass the queryClient returned from a previous prefetch
* where queries are dependent on previous results
*/
export const prefetch = async (
queries: (Query | unknown)[],
queryClient?: QueryClient,
) => {
queryClient = queryClient || new QueryClient()

await Promise.all(
queries.map((query) => queryClient.prefetchQuery(query as Query)),
)

return dehydrate(queryClient)
return { dehydratedState: dehydrate(queryClient), queryClient }
}
12 changes: 6 additions & 6 deletions frontends/api/src/ssr/usePrefetchWarnings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { usePrefetchWarnings } from "./usePrefetchWarnings"
import { setupReactQueryTest } from "../hooks/test-utils"
import { urls, factories, setMockResponse } from "../test-utils"
import {
learningResourcesKeyFactory,
learningResources,
useLearningResourcesDetail,
} from "../hooks/learningResources"

Expand Down Expand Up @@ -45,7 +45,7 @@ describe("SSR prefetch warnings", () => {
expect.objectContaining({
disabled: false,
initialStatus: "loading",
key: learningResourcesKeyFactory.detail(1).queryKey,
key: learningResources.detail(1).queryKey,
observerCount: 1,
}),
],
Expand All @@ -65,7 +65,7 @@ describe("SSR prefetch warnings", () => {
wrapper,
initialProps: {
queryClient,
exemptions: [learningResourcesKeyFactory.detail(1).queryKey],
exemptions: [learningResources.detail(1).queryKey],
},
})

Expand All @@ -83,7 +83,7 @@ describe("SSR prefetch warnings", () => {
const { unmount } = renderHook(
() =>
useQuery({
...learningResourcesKeyFactory.detail(1),
...learningResources.detail(1),
initialData: data,
}),
{ wrapper },
Expand All @@ -105,9 +105,9 @@ describe("SSR prefetch warnings", () => {
[
{
disabled: false,
hash: JSON.stringify(learningResourcesKeyFactory.detail(1).queryKey),
hash: JSON.stringify(learningResources.detail(1).queryKey),
initialStatus: "success",
key: learningResourcesKeyFactory.detail(1).queryKey,
key: learningResources.detail(1).queryKey,
observerCount: 0,
status: "success",
},
Expand Down
7 changes: 7 additions & 0 deletions frontends/api/src/ssr/usePrefetchWarnings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ export const usePrefetchWarnings = ({
*/
useEffect(
() => {
/* Invalidate all learning resource queries so they are re-fetched in the client
* for session specific data (user list memberships).
* This is interim, until the memberships endpoint is implemented in the frontend
* TODO remove once complete (see https://github.com/mitodl/hq/issues/5159)
*/
queryClient.invalidateQueries(["learningResources"])

if (process.env.NODE_ENV === "production") {
return
}
Expand Down
1 change: 1 addition & 0 deletions frontends/api/src/test-utils/factories/channels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ const _channelShared = (): Partial<Omit<Channel, "channel_type">> => {
key: faker.lorem.slug(),
value: faker.lorem.slug(),
}),
channel_url: `${faker.internet.url({ appendSlash: false })}${faker.system.directoryPath()}`,
}
}

Expand Down
3 changes: 3 additions & 0 deletions frontends/api/src/test-utils/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import type {
NewsEventsApiNewsEventsListRequest,
TestimonialsApi,
ChannelsApi,
} from "../generated/v0"
import type {
LearningResourcesApi as LRApi,
Expand Down Expand Up @@ -179,6 +180,8 @@ const channels = {
details: (channelType: string, name: string) =>
`${API_BASE_URL}/api/v0/channels/type/${channelType}/${name}/`,
patch: (id: number) => `${API_BASE_URL}/api/v0/channels/${id}/`,
list: (params?: Paramsv0<ChannelsApi, "channelsList">) =>
`${API_BASE_URL}/api/v0/channels/${query(params)}`,
}

const widgetLists = {
Expand Down
29 changes: 13 additions & 16 deletions frontends/main/src/app-pages/UnitsListingPage/UnitCard.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from "react"
import { LearningResourceOfferorDetail, OfferedByEnum } from "api"
import type { OfferedByEnum } from "api"
import type { UnitChannel } from "api/v0"
import {
Card,
Skeleton,
Expand All @@ -8,7 +9,6 @@ import {
theme,
UnitLogo,
} from "ol-components"
import { useChannelDetail } from "api/hooks/channels"
import Link from "next/link"

const CardStyled = styled(Card)({
Expand Down Expand Up @@ -102,25 +102,23 @@ const CountsText = styled(Typography)(({ theme }) => ({
}))

interface UnitCardsProps {
units: LearningResourceOfferorDetail[] | undefined
channels: UnitChannel[] | undefined
courseCounts: Record<string, number>
programCounts: Record<string, number>
}

interface UnitCardProps {
unit: LearningResourceOfferorDetail
channel: UnitChannel
courseCount: number
programCount: number
}

const UnitCard: React.FC<UnitCardProps> = (props) => {
const { unit, courseCount, programCount } = props
const channelDetailQuery = useChannelDetail("unit", unit.code)
const channelDetail = channelDetailQuery.data
const unitUrl = channelDetail?.channel_url
const { channel, courseCount, programCount } = props
const unit = channel.unit_detail.unit

if (!unitUrl) return null
const href = unitUrl && new URL(unitUrl).pathname
if (!channel.channel_url) return null
const href = new URL(channel.channel_url).pathname

return (
<CardStyled forwardClicksToLink data-testid={`unit-card-${unit.code}`}>
Expand All @@ -134,9 +132,7 @@ const UnitCard: React.FC<UnitCardProps> = (props) => {
</LogoContainer>
<CardBottom>
<ValuePropContainer>
<HeadingText>
{channelDetail?.configuration?.heading}
</HeadingText>
<HeadingText>{channel?.configuration?.heading}</HeadingText>
</ValuePropContainer>
<CountsTextContainer>
<CountsText data-testid={`course-count-${unit.code}`}>
Expand Down Expand Up @@ -174,17 +170,18 @@ export const UnitCardLoading = () => {
}

export const UnitCards: React.FC<UnitCardsProps> = (props) => {
const { units, courseCounts, programCounts } = props
const { channels, courseCounts, programCounts } = props
return (
<>
{units?.map((unit) => {
{channels?.map((channel) => {
const unit = channel.unit_detail.unit
const courseCount = courseCounts[unit.code] || 0
const programCount = programCounts[unit.code] || 0

return unit.value_prop ? (
<UnitCard
key={unit.code}
unit={unit}
channel={channel}
courseCount={courseCount}
programCount={programCount}
/>
Expand Down
Loading