Facet JSON Structure

Structure

The overall structure of filters is as follows. Each facet term combines a data source and constraint spec, and so all combinations of constraint kind and data source path are possible in the syntax.

<FILTERS>:  { <logical-operator>: <TERMSET> }

<TERMSET>: '[' <TERM> [, <TERM>]* ']'

<TERM>:     { <logical-operator>: <TERMSET> }
            or
            { "source": <data-source>, <constraint(s)>, <extra-properties(s)> }
            or
            { "sourcekey": <source-key>, <constraint(s)>, <extra-properties(s)> }

In the following sections each of location operators, data source, constraints, and extra properties are explained. You can also find some examples at the end of this document.

Logical operators

We want the structure to be as general as possible, so we don’t need to redesign the whole structure when we need to support more complex queries. Therefore as the top level, we have logical operators.

{ "and": [ term... ] }
{ "or": [ term... ]}
{ "not": term }

The current implementation of faceting in Chaise only supports and. The rest of logical operators are currently not supported.

Source path

Source path captures the source of filter. It can either be

  • one of current table’s column.
  • a column in a table that has a valid foreign key relationship with the current table.

Even if we are faceting on a vocabulary concept and just want the user to pick values by displayed row name and we substitute the actual entity keys in the ERMrest query, we must record this column choice explicitly in the facet spec so that the resulting faceting app URL is unambiguous even if there have been subtle model changes in the interim, which might change the default key selection heuristics etc.

Based on this, we are not supporting filtering on foreign keys with composite keys.

For more information about source path refer to column directive’s documentation in here.

Source key

Instead of defining a new source, you can refer to the sources that are defined in 2019:source-definitions by using sourcekey attribute. For instance, assuming path_to_table_1 is a valid source definition, you can do

{"sourcekey": "path_to_table_1"}

Fast filter source

While defining the source of a column directive, you must be mindful of the structure of the path and the projected table. This can cause performance issues as ERMrestJS would introduce joins for filtering matching results. If you would like to improve the performance of your queries, you could define an alternative fast_filter_source that will only be used for filtering. With this alternative source, you can denormalize the tables and create data warehouses that are more optimized for filtering.

  • The defined fast_filter_source attribute supports the same syntax as source path.
  • As the name suggests, the new source will only be used for filtering requests and won’t be used for defining a projection. That’s why we’ll only use this property while parsing the facets.
  • ERMrestJS only ensures that the given path exists and won’t do any further checks. It’s your responsibility to ensure the projected columns of both source and fast_filter_source have compatible values.
  • To make sure ERMrestJS is enabling this feature, you need to set "aggressive_facet_lookup": true in table-config annotation as well.

In the following you can see an example of this feature:

{
   "source": [
     {"sourcekey": "S_core_fact"},
     {"inbound": ["CFDE", "core_fact_assay_type_core_fact_fkey"]},
     {"outbound": ["CFDE", "core_fact_assay_type_assay_type_fkey"]},
     "nid"
   ],
   "fast_filter_source": [
     "assay_types"
   ]
}

Notes:

  • When we want to show the facet panel, we use the source for fetching the options. This path is important as it will dictate the entity mode as well as the displayed table, etc.
  • After users select any of the displayed options in this facet, instead of sending a request will multiple join, we’re going to filter based on the local assay_types column. The assay_types column has been specifically populated for each row to work with this given path.

Constraints

There are three kinds of constraint right now:

  1. Discrete choice e.g. maps to a checklist or similar UX
  2. Half-open or closed intervals, e.g. maps to a slider or similar UX
  3. Substring search, e.g. maps to a search box UX
  4. Match any record with value (not-null).

Conceptually, this should correspond to three possible syntactic forms:

{"choices": [ value, ... ]}
{"ranges": [ {"min": lower, "max": upper}, ...]}
{"search": [ "box content" ]}
{"not_null": true}

A half-open range might be {"min" lower} or {"max": upper}. By default both min and max are inclusive. To use exclusive ranges you can use min_exclusive:true or max_exclusive: true.

Extra properties

entity v.s. scalar facet

If the facet can be treated as entity (the column that is being used for facet is key of the table), setting entity attribute to false will force the facet to show scalar mode.

"entity": false