Search lectures functionality
Let's begin from the backend. The /backend/cds/views.py
has one of the most important roles of the search functionality.
class LectureViewSet(viewsets.ModelViewSet):
# Calls all the lectures
queryset = Lecture.objects.all()
...
class LectureDocumentView(DocumentViewSet):
document = LectureDocument
...
filter_backends = [
FilteringFilterBackend,
OrderingFilterBackend,
DefaultOrderingFilterBackend,
SimpleQueryStringSearchFilterBackend,
]
# The default operator for search is AND
simple_query_string_options = {
"default_operator": "and",
}
# Fields for the search are defined here
filter_fields = {
"lecture_id": None,
"type": None,
"keywords": None,
"series": None,
"sponsor": None,
"speaker": None,
"subject_category": None,
}
# Fields for the ordering are defined here
ordering_fields = {
"lecture_id": None,
"title": None,
"date": None,
"score": None,
"series": None,
"subject_category": None
}
# `boost` gives score to the variables,
# meaning that the most important ones are going to be displayed first
simple_query_string_search_fields = {
"title": {"boost": 5},
"abstract": {"boost": 1},
"type": {"boost": 5},
"keywords": {"boost": 6},
"sponsor": {"boost": 10},
"speaker": {"boost": 10},
"series": {"boost": 10},
"subject_category": {"boost": 10},
}
# Sorts by the score of the lectures and the date
ordering = (
"_score",
"-date",
)
The main code for the search within the UI can be found at /ui/src/routes/results.tsx
.
Help for search
A tooltip has been added next to the search bar to help users how to search in /ui/src/components/SEARCH_BAR.tsx
.
<Tooltip
color="#0033a0"
key="#0033a0"
placement="bottomRight"
title={
<span style={{ whiteSpace: "pre-line" }}>
<strong>How to search:</strong>
{helpText}
</span>
}
arrowPointAtCenter
>
// The (?) icon
<QuestionCircleOutlined />
</Tooltip>
View search results
The overall code for the UI can be found at /ui/src/routes/results.tsx
.
First, the searched lectures are fetched from the API root:
const searchLectures = useCallback(async () => {
try {
...
const response = await getApiRoot().get(`/search/lectures/`, {
params: {
...
search_simple_query_string: searchValue,
...
},
});
...
setLectures(response.data.results);
...
} catch (error) {
setLectures([]);
}
}, [searchValue, ...]);
Thumbnails are filtered the following way:
{
// Lecture that has a thumbnail
lecture.thumbnail_picture && (
<div className="list-thumbnail">
<img alt="thumbnail" src={lecture.thumbnail_picture} />
</div>
);
}
{
// Lecture that doesn't have a thumbnail
!lecture.thumbnail_picture && (
<div className="list-thumbnail">
<div className="blank-thumbnail">
<FileFilled
style={{
fontSize: "350%",
opacity: "0.6",
}}
/>
</div>
</div>
);
}
View number of results
Under /ui/src/common/utils.ts
:
export const pluralizeUnlessSingle = (singularWord: string, count: number) =>
count !== 1 ? `${singularWord}s` : singularWord;
Then go back to /ui/src/routes/results.tsx
:
...
const [total, setTotal] = useState<number>(0);
...
const searchLectures = useCallback(async () => {
try {
...
// This is where it counts the total amount of lectures (!)
setTotal(response.data.count);
...
}
...
});
...
<Content id="atc-content">
...
<Title>
{total} Search {pluralizeUnlessSingle('result', total)}:{" "}
...
</Title>
...
</Content>
Sort results by relevance
const [order, setOrder] = useState<SortOptions>(SortOptions.Default);
const searchLectures = useCallback(async () => {
...
const response = await getApiRoot().get(`/search/lectures/`, {
params: {
ordering: order === SortOptions.Default ? undefined : order,
...
},
});
...
}, [..., order]);
This component displays a dropdown menu for sorting.
<SortMenu sortMethod={order} handleChange={setOrder} />
The SortMenu compontent can be found at /ui/src/components/SORT_MENU.tsx
. The options for the dropdown menu are included.
import { Select } from "antd";
import "./SORT_MENU.css";
import { SortMethodType } from "../models/lectures";
export const SORT_MENU = ({
handleChange,
sortMethod,
}: {
handleChange: (value: SortMethodType) => void;
sortMethod: SortMethodType;
}) => {
const { Option } = Select;
return (
<div className="sort-container">
<Select value={sortMethod} onChange={handleChange}>
<Option value="relevance" key="relevance">
Most relevant
</Option>
<Option value="newest" key="newest">
Newest first
</Option>
<Option value="oldest" key="oldest">
Oldest first
</Option>
</Select>
</div>
);
};
Lecture interface at /ui/src/models/lectures.ts
not only exports the lectures, but the sort options as well.
export enum SortOptions {
Default = "relevance",
Oldest = "date",
Newest = "-date",
}
What if no results are present?
This component displays an empty container.
<Empty className="empty" description="No results found" />{" "}