Skip to content

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.

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" />{" "}