AlexsLemonade / refinebio-web

Refinebio Web
https://staging.web.refine.bio
BSD 3-Clause "New" or "Revised" License
1 stars 0 forks source link

Implement SearchManagerContext and useSearchManager #122

Closed nozomione closed 1 year ago

nozomione commented 1 year ago

Context

As with the existing refine.bio, when users perform searches within our application, we want to be able to manage their specified queries and provides accurate search results.

Problem or idea

Using the context API, we'll create a search manager context SearchManagerContext and implement its hook useSearchManager which includes methods that allow us to interact with the search query object and to update search results.

The general strategy should be to have a single context that;

This context and its hook should manage the following:

Each exposed method of this manager should correspond to the behavior of the application and indirectly communicates with the API using the refinebio-js helper.

The implementation details and pseudocode is as follows:

❚ API Supported Query Parameters

(keys marked with * are required):

// search query object
{
 limit*: number; 
 offset*: number;
 ordering*: number;
 num_downloadable_samples__gt*: number
 search: string; 
 [key: string]: string[] // selected filters if any
}

Common

These are common queries that are always sent to the API. Key Value Description
limit* number the number of results per page with predefined options 10 (by default), 20, and 50. On the client side, the query string 'size=SIZE' (the currently specified page size) will be appended to the browser's URL to preserve the limit value when refetching the API results only if SIZE is not the default option.
offset* number the starting index for returning API results (0 by default). On the client side, the query string 'p=PAGE' (the currently specified page in the search result pagination) will be appended to the browser's URL to calculate the offset value when refetching the API results only if PAGE > 1.
ordering* string the sort order with predefined options _score (by default), -num_downloadable_samples, num_downloadable_samples, -soiurce_first_published, and source_first_published. On the client side, the query string 'ordering=ORDER' (the currently specified sort order) will be appended to the browser's URL to preserve the sort order when refetching API results only if ORDER is not the default option.
num_downloadable_samples__gt* number the query for toggling non-downloadable samples. The default value is 0 which excludes non-downloadable samples from API results. If the value is set to -1, it includes non-downloadable samples in API results. On the client side, the query string 'empty=true' will be appended to the browser's URL to preserve the user's preference and is used to refetch the API results with non-downloadable samples.

Filter options

These are currently supported filter options. Key Value Description
downloadable_organism string the results by downloadable organism. Multiple values are supported.
platform string the results by platform. Multiple values are supported.
technology string the results by technology. Multiple values are supported.
has_publication boolean the flag for only returning results with associated publications (false by default).

Search terms

Key Value Description
search string a search term used to return the results.
Supported prefix:

These are prefixes for returning explicit search results for a specific category.

Prefix Description
accession_code the results by the accession code(s). In order to fetch data associated with accession code(s), currently we need to make two separate API calls.
e.g.) Search for "GSE116436"
A query string appended to the browse's URL:
?search=GSE116436
A query send to API:
(1st call)
?search=GSE116436&limit=10&offset=0&ordering=_score&num_downloadable_samples__gt=0
(2nd call)
?search=accession_code:GSE24528&search=alternate_accession_code:GSE24528
alternate_accession_code the results by the alternative accession code(s)
description the results by the description
platform_names the results by the platform name(s)
publication_authors the results by the publication author's name(s)
publication_doi the results by the publication DOI
publication_title the results by the publication title
pubmed_id the results by the publication med ID
sample_keywords the results by the sample keyword(s)
sample_metadata_fields the results by the sample metadata field(s)
submitter_institution the results by the submitter institution name(s)
title the results by the title

❚ State Management

Context State

The SearchManagerContext contains the following states that are shared across the application.

config: (an object) the latest data retrieved from the API (e.g, available facet names) setConfig: an async method for setting config

search: (an object) the currently defined search query object setSearch: an async method for setting search

Local State

The following states are local to the search page component and are required to build the search results pagination, the sort order select menu, the page size select menu, and the common query parameters.

page: (number) the currently selected page (1 by default) setPage: an async method for setting page

pageSize: (number) the currently selected predefined page limit (10 by default) setPageSize: an async method for setting pageSize

sortBy: (string) the currently selected predefined sort order (_score by default) setSortBy: an async method for setting sortBy

❚ Method

The useSearchManager hook contains the helper methods that are necessary to perform searches and maintain the accuracy of the results. These methods are primarily called in the UI and each one corresponds to a specific application behavior.

(Parameters marked with * are required).

Common

resetPage() return void resets the current page number to the default value

updatePage(newPage*) return void

updatePageSize(newPageSize*) return void

updateSortBy(newSortOrder*) return void

Filters

clearAllFilters() return void

hasAppliedFilters() return boolean returns true if any currently applied filters, otherwise false

isFilterChecked(key*, value*) return boolean returns true if the filter is selected, otherwise false

toggleFilter(checked*, key*, val*, updateQuery = true) return void

Search Terms

updateSearchTerm(newTerm*) return void

Other

formatFacetNames(facetNames*) return string[]

getSearchQueryParam(queryParams*) return object returns client-only query parameters from URL

navigateToSearch(newQuery*) return void handles search requests from non-search page and navigates a user to the search page

updateSearchQuery(reset = false) return void

NOTE: The API requests are made in the search page component's getInitialProps using the application's fetchSearch helper which calls the refinebio-js helper's get method.

Example:

Search.getInitialProps = async (ctx) => {
  const { query } = ctx
  const queryString = {
    ...query,
    limit: query.limit || 10,
    offset: query.offset  || 0,
    ordering: query.ordering || '_score',
    num_downloadable_samples__gt: !query.empty ? 0 : -1
  }
  const response = fetchSearch(queryString)

  return {
     query, 
     results: response 
  }
}

Solution or next step

Based on the above requirements, implement the manager and its hook.

davidsmejia commented 1 year ago

I think this is moving in the right direction. Three comments for now:

nozomione commented 1 year ago

We will want to move to using the search query param for free text search and should have a fallback to redirect q params to search / this could probably in the context of the search page for now

Currently(in refinebio-web, PRs created for the search terms), the key name search is used for free text search as we briefly chatted to replace q that is used in current refinebio.

I think we should move away from supporting the aliased empty

Please take a look at the updated num_downloadable_samples__gt description. This query string empty is necessary to maintain the user-requested query. For instance, if users filtered the desired search results with non downloadable samples to bookmark the URL for future reference or to share with colleges, this query string enables them to do so (the same logic applies to limit, offset, ordering - also updated the descriptions). Perhaps, we could use more descriptive key names or if there is a better approach to achieving this, that would be great πŸ’‘

Filters will be arrays of values, so toggling them should clone the array and either add or remove the value to that array

Please take a look at the updated filters description. It's an object that contains nested object(s). The key of each object is one of the supported filter options and its value as an array of selected items (using the datatype object, since it prevents duplicate keys and easy to search πŸ” for specific filter options to add / remove items). And yes, those items are added or removed from filters accordingly based on a user's selection (by checking / unchecking the checkbox βœ… in UI).

nozomione commented 1 year ago

πŸ“ @davidsmejia I updated the description for num_downloadable_samples__gt and now rather than removing it, it uses the value "-1" to include the non-downloadable samples in API results. I also included the mobile solution (for toggleFilter) and added a new method navigateToSearch.

nozomione commented 1 year ago

πŸ“ @davidsmejia I updated the implementation so that it no longer uses the filters state and any selected filters will be directly added to the search state. The filterList ( for the available filter options that was defined in config/options) was also removed and now the config state is used to store all the available filter options fetched from the latest API request.