Design - General design principles

This covers some high level design principles that don’t seem to fit anywhere else.

When possible, Sort/Filter in the Backend rather than the Frontend

If you have a page where you are presenting information that always needs to be presented to the user initially in a certain order, you have two choices:

  • Sort/filter the data in the backend at the API endpoint
  • Sort/filter the data on the frontend

It will almost always be a better choice to sort the data in the backend.

  • It’s typically easier to code and test.
  • It’s typically faster, performance wise

You have two choices in the backend:

Choice 1: Let the database do the sorting/filtering

This is almost always the better option when it’s possible: For example, consider this example PR from proj-dining

Moving Filtering to backend:

Before:

 Iterable<Review> reviews = reviewRepository.findAll();

After:

 Iterable<Review> reviews = reviewRepository.findByItemAndStatus(item, ModerationStatus.APPROVED);

This eliminates the need for frontend filters such as:

const filteredReviews =
    reviews?.filter((review) => review.status === "AWAITING_REVIEW") || [];

or

const filteredReviews =
    reviews?.filter((review) => review.item.id === Number(itemid)) || [];

For sorting:

Consider this example from proj-courses:

Before:

    Iterable<Job> jobs = jobsRepository.findAll();

After:

    Iterable<Job> jobs = jobsRepository.findAllByOrderByIdDesc();

Now there’s no need to apply an additional sort in the frontend.

Choice 2: Do it with Java collection utilities

Sorting of Java Collections is described in this article on the CS156 website.

In this case, we had to define a new collection by wrapping the result before sorting (since the result returned by convertedSectionCollection.findByQuarterRangeAndBuildingCode was an immutable collection):

Before:

List<ConvertedSection> courseResults = 
                convertedSectionCollection.findByQuarterRangeAndBuildingCode(
                        startQtr, endQtr, buildingCode);

After:

List<ConvertedSection> courseResults = new java.util.ArrayList<>(
                convertedSectionCollection.findByQuarterRangeAndBuildingCode(
                        startQtr, endQtr, buildingCode));

courseResults.sort(new ConvertedSection.ConvertedSectionSortDescendingByQuarterComparator());

The comparator is defined here as an inner class of ConvertedSection:

   public static class ConvertedSectionSortDescendingByQuarterComparator implements java.util.Comparator<ConvertedSection> {
        @Override
        public int compare(ConvertedSection o1, ConvertedSection o2) {
            return o2.getCourseInfo().getQuarter().compareTo(o1.getCourseInfo().getQuarter());
        }
    }

Considerations

Consider whether the backend endpoint used by only one page in the app (which means you can sort any way you please for that page), or multiple pages in the app? If it’s used by multiple pages, consider defining a new endpoint, or adding a new optional parameter to endpoint to specify the sort or filtering criteria.

If you must do it in the frontend, use React-Table

If you have to do it in the frontend, you can do it with React-Table.

Here’s an example of sorting a table descending by it’s id from a version of JobsTable (before we refactored it to sort on the backend):

 const sortByIdDescending = {
    sorting: [
      {
        id: "id",
        desc: true, // sort by name in descending order by default
      },
    ],
  };

  return (
    <OurTable
      data={jobs}
      columns={columns}
      testid={testid}
      initialState={sortByIdDescending}
    />
  );

But then you have to cover it in tests: for example:

    // Check that rows are sorted by id in descending order
    const rows = screen.getAllByRole("row");
    expect(rows).toHaveLength(7); // 6 jobs + 1 header row
    expect(rows[1]).toHaveTextContent("6");
    expect(rows[2]).toHaveTextContent("5");
    expect(rows[3]).toHaveTextContent("4");
    expect(rows[4]).toHaveTextContent("3");
    expect(rows[5]).toHaveTextContent("2");
    expect(rows[6]).toHaveTextContent("1");