Skip to content

Commit

Permalink
switch typesense sorting to presets (#1458)
Browse files Browse the repository at this point in the history
  • Loading branch information
zacjones93 authored Jun 28, 2024
1 parent ef3026c commit 1cfce3d
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 60 deletions.
84 changes: 56 additions & 28 deletions src/components/search/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
Configure,
InstantSearch,
ClearRefinements,
SortBy,
useInstantSearch,
} from 'react-instantsearch'
import {get, isEmpty} from 'lodash'
import {useToggle} from 'react-use'
Expand All @@ -28,7 +28,11 @@ import cx from 'classnames'
import NewCuratedTopicPage from './curated/[slug]'
import Link from 'next/link'
import analytics from '@/utils/analytics'
import {SORT_PRESETS} from '@/utils/typesense'
import {
typsenseAdapterConfig,
TYPESENSE_COLLECTION_NAME,
} from '@/utils/typesense'
import {typesenseAdapter} from '@/pages/q/[[...all]]'

type SearchProps = {
searchClient?: any
Expand Down Expand Up @@ -157,6 +161,53 @@ const Search: FunctionComponent<React.PropsWithChildren<SearchProps>> = ({
)
}

const PresetOptions = () => {
const {refresh} = useInstantSearch()

return (
<select
className="border-0 flex items-center flex-shrink-0 space-x-2 flex-nowrap dark:bg-gray-900 bg-white h-full"
defaultValue="popular"
onChange={(e) => {
typesenseAdapter.updateConfiguration({
...typsenseAdapterConfig,
additionalSearchParameters: {
query_by: 'title,description,_tags,instructor_name,contributors',
preset: e.target.value,
},
})

refresh()
}}
>
<option
className="border-opacity-0 dark:border-gray-800 border-gray-100"
value="popular"
>
Most Popular
</option>
<option
className="border-opacity-0 dark:border-gray-800 border-gray-100"
value="rating"
>
Highest Rated
</option>
<option
className="border-opacity-0 dark:border-gray-800 border-gray-100"
value="created_at"
>
Recently Added
</option>
<option
className="border-opacity-0 dark:border-gray-800 border-gray-100"
value="most_watched"
>
Most Watched
</option>
</select>
)
}

const RefinementsDesktop = () => {
return (
<aside className="col-span-2 md:block hidden relative flex-shrink-0 dark:bg-gray-1000 bg-gray-100 pl-4">
Expand Down Expand Up @@ -245,13 +296,12 @@ const Search: FunctionComponent<React.PropsWithChildren<SearchProps>> = ({
</Head>
<div className="dark:bg-gray-1000 bg-gray-100 relative">
<InstantSearch
indexName={'content_production'} // CREE: Replace with env
indexName={TYPESENSE_COLLECTION_NAME}
searchClient={searchClient}
onStateChange={onSearchStateChange}
initialUiState={{
content_production: {
TYPESENSE_COLLECTION_NAME: {
...searchState,
sortBy: SORT_PRESETS.POPULAR,
},
}}
{...rest}
Expand All @@ -271,29 +321,7 @@ const Search: FunctionComponent<React.PropsWithChildren<SearchProps>> = ({
<div className="dark:bg-gray-900 bg-white sticky top-0 z-40 shadow-smooth flex items-center w-full border-b dark:border-white border-gray-900 dark:border-opacity-5 border-opacity-5">
<SearchBox placeholder={searchBoxPlaceholder} />
<div className="border-l dark:border-gray-800 border-gray-100 flex items-center flex-shrink-0 space-x-2 flex-nowrap h-full">
<SortBy
classNames={{
root: 'border-opacity-0',
select:
' flex items-center flex-shrink-0 space-x-2 flex-nowrap dark:bg-gray-900 bg-white h-full',
option: 'dark:border-gray-800 border-gray-100',
}}
items={[
{
value: SORT_PRESETS.POPULAR,
label: 'Most Popular',
},
{value: SORT_PRESETS.RATING, label: 'Highest Rated'},
{
value: SORT_PRESETS.CREATED_AT,
label: 'Recently Added',
},
{
value: SORT_PRESETS.MOST_WATCHED,
label: 'Most Watched',
},
]}
/>
<PresetOptions />
</div>
</div>
<NoSearchResults searchQuery={searchState.query} />
Expand Down
4 changes: 3 additions & 1 deletion src/pages/q/[[...all]].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ const tracer = getTracer('search-page')

const createURL = (state: any) => `?${qs.stringify(state)}`

const searchClient = typesenseInstantsearchAdapter().searchClient
export const typesenseAdapter = typesenseInstantsearchAdapter()

const searchClient = typesenseAdapter.searchClient

const defaultProps = {
searchClient,
Expand Down
7 changes: 5 additions & 2 deletions src/server/routers/topics.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import {router, baseProcedure} from '../trpc'
import {z} from 'zod'
import {typesenseInstantsearchAdapter} from '@/utils/typesense'
import {
typesenseInstantsearchAdapter,
TYPESENSE_COLLECTION_NAME,
} from '@/utils/typesense'

const searchClient = typesenseInstantsearchAdapter().searchClient

Expand All @@ -14,7 +17,7 @@ export const topicRouter = router({
.optional(),
)
.query(async ({input, ctx}) => {
const index = searchClient.initIndex('content_production')
const index = searchClient.initIndex(TYPESENSE_COLLECTION_NAME)
// top 10 free playlists for a topic
const filters = `access_state:free AND type:playlist ${
input?.topic ? ` AND _tags:${input.topic}` : ''
Expand Down
53 changes: 24 additions & 29 deletions src/utils/typesense.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,29 @@
import TypesenseInstantSearchAdapter from 'typesense-instantsearch-adapter'

export const typsenseAdapterConfig = {
server: {
apiKey: process.env.NEXT_PUBLIC_TYPESENSE_API_KEY ?? '', // Be sure to use an API key that only allows search operations
nodes: [
{
host: process.env.NEXT_PUBLIC_TYPESENSE_HOST ?? 'test',
path: '',
port: Number(process.env.NEXT_PUBLIC_TYPESENSE_PORT) ?? 8108,
protocol: 'https',
},
],

cacheSearchResultsForSeconds: 2 * 60,
},
// The following parameters are directly passed to Typesense's search API endpoint.
// So you can pass any parameters supported by the search endpoint below.
// query_by is required.
additionalSearchParameters: {
query_by: 'title,description,_tags,instructor_name,contributors',
preset: 'popular',
},
}

export const typesenseInstantsearchAdapter = () =>
new TypesenseInstantSearchAdapter({
server: {
apiKey: process.env.NEXT_PUBLIC_TYPESENSE_API_KEY ?? '', // Be sure to use an API key that only allows search operations
nodes: [
{
host: process.env.NEXT_PUBLIC_TYPESENSE_HOST ?? 'test',
path: '',
port: Number(process.env.NEXT_PUBLIC_TYPESENSE_PORT) ?? 8108,
protocol: 'https',
},
],
cacheSearchResultsForSeconds: 2 * 60,
},
// The following parameters are directly passed to Typesense's search API endpoint.
// So you can pass any parameters supported by the search endpoint below.
// query_by is required.
additionalSearchParameters: {
query_by: 'title,description,_tags,instructor_name,contributors',
},
})
new TypesenseInstantSearchAdapter({...typsenseAdapterConfig})
export const TYPESENSE_COLLECTION_NAME =
process.env.NEXT_PUBLIC_TYPESENSE_COLLECTION_NAME || 'content_production'

const BASE_SORT = `${TYPESENSE_COLLECTION_NAME}/sort/_eval([ (type:playlist):4, (type:lesson):3, (type:podcast):2], (type:talk):1):desc`

export const SORT_PRESETS = {
POPULAR: `${BASE_SORT},search_research_sort:asc,rank:asc`,
RATING: `${BASE_SORT},average_rating_out_of_5:desc,rank:asc`,
CREATED_AT: `${BASE_SORT},published_at_timestamp:desc,rank:asc`,
MOST_WATCHED: `${BASE_SORT},watched_count:desc,rank:asc`,
}

0 comments on commit 1cfce3d

Please sign in to comment.