Skip to content

Commit bffe823

Browse files
authored
Server render search results (search and channel pages) (#1836)
* Warn on API calls during initial render not prefetched * Full prefetch for homepage (commented) * Prefetch utility * Check for queries prefetched that are not needed during render and warn * No need to stringify * Replace useQuery overrides with decoupled cache check (wip) * Observer count for unnecessary prefetch warnings * Remove useQuery override * Test prefetch warnings * Remove inadvertent/unnecessary diff * Remove comments * Prefetch homepage. Invalidate learning resource cache items * Remove comment * Update comment * Temp remove featured resource list shuffle * Remove unused * Prefetch calls * Prefetch topics page * Rename key factory re-exports * Units page prefetch * Single request for all unit channel details * Update unit listing page tests * Page arg types * Optional route params * Server rendered search page results * Unit channel page and search prefetch * Featured list and testimonials only for unit channels * Remove comment * Remove unused * Map address search params * Search params test * URL search param validation anf transforms to align with course-search-utils * Update to use validators from course-search-utils * Install published @mitodl/course-search-utils * Remove comment * Fix export missed from merge * Remove cache invalidation
1 parent 51710b3 commit bffe823

File tree

16 files changed

+524
-252
lines changed

16 files changed

+524
-252
lines changed

frontends/api/src/ssr/prefetch.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ export const prefetch = async (
1212
queryClient = queryClient || new QueryClient()
1313

1414
await Promise.all(
15-
queries.map((query) => queryClient.prefetchQuery(query as Query)),
15+
queries
16+
.filter(Boolean)
17+
.map((query) => queryClient.prefetchQuery(query as Query)),
1618
)
1719

1820
return { dehydratedState: dehydrate(queryClient), queryClient }

frontends/main/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"dependencies": {
1313
"@ebay/nice-modal-react": "^1.2.13",
1414
"@emotion/cache": "^11.13.1",
15-
"@mitodl/course-search-utils": "^3.3.1",
15+
"@mitodl/course-search-utils": "3.3.2",
1616
"@next/bundle-analyzer": "^14.2.15",
1717
"@remixicon/react": "^4.2.0",
1818
"@sentry/nextjs": "^8.36.0",

frontends/main/src/app-pages/ChannelPage/ChannelPage.tsx

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,9 @@ import { useParams } from "next/navigation"
66
import { ChannelPageTemplate } from "./ChannelPageTemplate"
77
import { useChannelDetail } from "api/hooks/channels"
88
import ChannelSearch from "./ChannelSearch"
9-
import type {
10-
Facets,
11-
FacetKey,
12-
BooleanFacets,
13-
} from "@mitodl/course-search-utils"
149
import { ChannelTypeEnum } from "api/v0"
1510
import { Typography } from "ol-components"
11+
import { getConstantSearchParams } from "./searchRequests"
1612

1713
type RouteParams = {
1814
channelType: ChannelTypeEnum
@@ -22,20 +18,11 @@ type RouteParams = {
2218
const ChannelPage: React.FC = () => {
2319
const { channelType, name } = useParams<RouteParams>()
2420
const channelQuery = useChannelDetail(String(channelType), String(name))
25-
const searchParams: Facets & BooleanFacets = {}
2621
const publicDescription = channelQuery.data?.public_description
2722

28-
if (channelQuery.data?.search_filter) {
29-
const urlParams = new URLSearchParams(channelQuery.data.search_filter)
30-
for (const [key, value] of urlParams.entries()) {
31-
const paramEntry = searchParams[key as FacetKey]
32-
if (paramEntry !== undefined) {
33-
paramEntry.push(value)
34-
} else {
35-
searchParams[key as FacetKey] = [value]
36-
}
37-
}
38-
}
23+
const channelSearchFilter = channelQuery.data?.search_filter
24+
25+
const searchParams = getConstantSearchParams(channelSearchFilter)
3926

4027
return (
4128
name &&
@@ -46,9 +33,9 @@ const ChannelPage: React.FC = () => {
4633
{publicDescription && (
4734
<Typography variant="body1">{publicDescription}</Typography>
4835
)}
49-
{channelQuery.data?.search_filter && (
36+
{channelSearchFilter && (
5037
<ChannelSearch
51-
channelTitle={channelQuery.data.title}
38+
channelTitle={channelQuery.data!.title}
5239
constantSearchParams={searchParams}
5340
channelType={channelType}
5441
/>

frontends/main/src/app-pages/ChannelPage/ChannelSearch.tsx

Lines changed: 5 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,13 @@
11
import React, { useCallback, useMemo } from "react"
2-
import { LearningResourceOfferor } from "api"
32
import { ChannelTypeEnum } from "api/v0"
43
import { useOfferorsList } from "api/hooks/learningResources"
5-
6-
import {
7-
useResourceSearchParams,
8-
UseResourceSearchParamsProps,
9-
} from "@mitodl/course-search-utils"
10-
import type {
11-
Facets,
12-
BooleanFacets,
13-
FacetManifest,
14-
} from "@mitodl/course-search-utils"
4+
import { useResourceSearchParams } from "@mitodl/course-search-utils"
5+
import type { Facets, BooleanFacets } from "@mitodl/course-search-utils"
156
import { useSearchParams } from "@mitodl/course-search-utils/next"
167
import SearchDisplay from "@/page-components/SearchDisplay/SearchDisplay"
178
import { Container, styled, VisuallyHidden } from "ol-components"
189
import { SearchField } from "@/page-components/SearchField/SearchField"
19-
20-
import { getFacetManifest } from "@/app-pages/SearchPage/SearchPage"
10+
import { getFacets } from "./searchRequests"
2111

2212
import _ from "lodash"
2313

@@ -35,34 +25,6 @@ const StyledSearchField = styled(SearchField)({
3525
width: "624px",
3626
})
3727

38-
const FACETS_BY_CHANNEL_TYPE: Record<ChannelTypeEnum, string[]> = {
39-
[ChannelTypeEnum.Topic]: [
40-
"free",
41-
"resource_type",
42-
"certification_type",
43-
"delivery",
44-
"offered_by",
45-
"department",
46-
],
47-
[ChannelTypeEnum.Department]: [
48-
"free",
49-
"resource_type",
50-
"certification_type",
51-
"topic",
52-
"delivery",
53-
"offered_by",
54-
],
55-
[ChannelTypeEnum.Unit]: [
56-
"free",
57-
"resource_type",
58-
"topic",
59-
"certification_type",
60-
"delivery",
61-
"department",
62-
],
63-
[ChannelTypeEnum.Pathway]: [],
64-
}
65-
6628
const SHOW_PROFESSIONAL_TOGGLE_BY_CHANNEL_TYPE: Record<
6729
ChannelTypeEnum,
6830
boolean
@@ -73,24 +35,6 @@ const SHOW_PROFESSIONAL_TOGGLE_BY_CHANNEL_TYPE: Record<
7335
[ChannelTypeEnum.Pathway]: false,
7436
}
7537

76-
const getFacetManifestForChannelType = (
77-
channelType: ChannelTypeEnum,
78-
offerors: Record<string, LearningResourceOfferor>,
79-
constantSearchParams: Facets,
80-
resourceCategory: string | null,
81-
): FacetManifest => {
82-
const facets = FACETS_BY_CHANNEL_TYPE[channelType] || []
83-
return getFacetManifest(offerors, resourceCategory)
84-
.filter(
85-
(facetSetting) =>
86-
!Object.keys(constantSearchParams).includes(facetSetting.name) &&
87-
facets.includes(facetSetting.name),
88-
)
89-
.sort(
90-
(a, b) => facets.indexOf(a.name) - facets.indexOf(b.name),
91-
) as FacetManifest
92-
}
93-
9438
interface ChannelSearchProps {
9539
constantSearchParams: Facets & BooleanFacets
9640
channelType: ChannelTypeEnum
@@ -110,14 +54,9 @@ const ChannelSearch: React.FC<ChannelSearchProps> = ({
11054
const [searchParams, setSearchParams] = useSearchParams()
11155
const resourceCategory = searchParams.get("resource_category")
11256

113-
const facetManifest = useMemo(
57+
const { facetNames, facetManifest } = useMemo(
11458
() =>
115-
getFacetManifestForChannelType(
116-
channelType,
117-
offerors,
118-
constantSearchParams,
119-
resourceCategory,
120-
),
59+
getFacets(channelType, offerors, constantSearchParams, resourceCategory),
12160
[offerors, channelType, constantSearchParams, resourceCategory],
12261
)
12362

@@ -140,18 +79,6 @@ const ChannelSearch: React.FC<ChannelSearchProps> = ({
14079
setPage(1)
14180
}, [setPage])
14281

143-
const facetNames = Array.from(
144-
new Set(
145-
facetManifest.flatMap((facet) => {
146-
if (facet.type === "group") {
147-
return facet.facets.map((subfacet) => subfacet.name)
148-
} else {
149-
return [facet.name]
150-
}
151-
}),
152-
),
153-
) as UseResourceSearchParamsProps["facets"]
154-
15582
const {
15683
hasFacets,
15784
params,
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import type {
2+
Facets,
3+
FacetKey,
4+
BooleanFacets,
5+
FacetManifest,
6+
UseResourceSearchParamsProps,
7+
} from "@mitodl/course-search-utils"
8+
import { LearningResourceOfferor } from "api"
9+
import { ChannelTypeEnum } from "api/v0"
10+
import { getFacetManifest } from "@/page-components/SearchDisplay/getFacetManifest"
11+
12+
export const getConstantSearchParams = (searchFilter?: string) => {
13+
const searchParams: Facets & BooleanFacets = {}
14+
15+
if (searchFilter) {
16+
const urlParams = new URLSearchParams(searchFilter)
17+
for (const [key, value] of urlParams.entries()) {
18+
const paramEntry = searchParams[key as FacetKey]
19+
if (paramEntry !== undefined) {
20+
paramEntry.push(value)
21+
} else {
22+
searchParams[key as FacetKey] = [value]
23+
}
24+
}
25+
}
26+
27+
return searchParams
28+
}
29+
30+
const FACETS_BY_CHANNEL_TYPE: Record<ChannelTypeEnum, string[]> = {
31+
[ChannelTypeEnum.Topic]: [
32+
"free",
33+
"resource_type",
34+
"certification_type",
35+
"delivery",
36+
"offered_by",
37+
"department",
38+
],
39+
[ChannelTypeEnum.Department]: [
40+
"free",
41+
"resource_type",
42+
"certification_type",
43+
"topic",
44+
"delivery",
45+
"offered_by",
46+
],
47+
[ChannelTypeEnum.Unit]: [
48+
"free",
49+
"resource_type",
50+
"topic",
51+
"certification_type",
52+
"delivery",
53+
"department",
54+
],
55+
[ChannelTypeEnum.Pathway]: [],
56+
}
57+
58+
const getFacetManifestForChannelType = (
59+
channelType: ChannelTypeEnum,
60+
offerors: Record<string, LearningResourceOfferor>,
61+
constantSearchParams: Facets,
62+
resourceCategory: string | null,
63+
): FacetManifest => {
64+
const facets = FACETS_BY_CHANNEL_TYPE[channelType] || []
65+
return getFacetManifest(offerors, resourceCategory)
66+
.filter(
67+
(facetSetting) =>
68+
!Object.keys(constantSearchParams).includes(facetSetting.name) &&
69+
facets.includes(facetSetting.name),
70+
)
71+
.sort(
72+
(a, b) => facets.indexOf(a.name) - facets.indexOf(b.name),
73+
) as FacetManifest
74+
}
75+
76+
export const getFacets = (
77+
channelType: ChannelTypeEnum,
78+
offerors: Record<string, LearningResourceOfferor>,
79+
constantSearchParams: Facets,
80+
resourceCategory: string | null,
81+
) => {
82+
const facetManifest = getFacetManifestForChannelType(
83+
channelType,
84+
offerors,
85+
constantSearchParams,
86+
resourceCategory,
87+
)
88+
89+
const facetNames = Array.from(
90+
new Set(
91+
facetManifest.flatMap((facet) => {
92+
if (facet.type === "group") {
93+
return facet.facets.map((subFacet) => subFacet.name)
94+
} else {
95+
return [facet.name]
96+
}
97+
}),
98+
),
99+
) as UseResourceSearchParamsProps["facets"]
100+
101+
return { facetNames, facetManifest }
102+
}

0 commit comments

Comments
 (0)