Gitpro.Filter (gitpro v1.0.0)

Copy Markdown View Source

What is shown, as data.

A filter is a query string and a set of switches on four axes — the card's state, its column on the board, its labels and its assignees. apply/2 is a pure function from a filter and every card to the cards that pass it, which is the whole of the filtering logic and is tested without a terminal or a network in sight.

The query

Whitespace splits the query into terms and every term has to match somewhere in the row — its number, title, repository, column, author, milestone, labels, assignees, state or kind. That makes narrowing additive: typing another word can only ever shorten the list, which is what a search field that filters as you type has to do to stay predictable.

"bug ready"     both words, in any field, in any order
"#42"           the number, because "#42" is part of the haystack
"e-matrix/eec"  the repository, on a board that spans several

The switches

Every switch is on until it is turned off, which is why the filter stores what is off rather than what is on. A board that grows a column between one reload and the next, or a card that arrives wearing a label nobody has seen before, is then shown rather than silently dropped — the alternative is a filter that hides things because of a switch that did not exist yet.

An axis is either single-valued or multi-valued, and they are filtered differently:

  • state and column — a card is in exactly one, so it passes when that one is on.
  • labels and assignees — a card can be in several, so it passes when any of them is on. Turning one label off therefore hides the cards that wear only that label, not every card that happens to wear it too.

Each axis has a :none bucket — no column, unlabelled, unassigned — so the cards that nobody has got to yet are a switch of their own rather than a gap.

Where the options come from

The columns come from the board, so a column with nothing in it is still a switch you can turn off. The states, labels and assignees come from the cards, because nothing else knows what is on the board — put_options/3 is how the list view hands them over once they have loaded, and it keeps the switches that were already set.

Summary

Functions

The cards that pass the filter, in the order they were given.

The axes, in the order the popup lists them.

How an axis is titled on screen.

Every switch the filter knows about, as {axis, key} pairs.

True while a switch is off — the query is not counted.

How a key is written on screen, without its axis to name it by.

How a switch's key is written on screen.

True when this filter can remove a row at all.

A filter that hides nothing, with a switch for each of the board's columns.

Whether a switch is on. A switch nobody has touched is on.

The switches on axis, in the order they are listed.

Replaces one axis's switches, keeping the ones already set.

Replaces the query string.

Turns every switch on or off at once, the query untouched.

A one-line description of the switches, for the footer.

Splits a query into the lowercased terms every row has to match.

Flips one switch.

Types

axis()

@type axis() :: :state | :column | :label | :assignee

flag()

@type flag() :: {axis(), key()}

key()

@type key() :: String.t() | atom()

t()

@type t() :: %Gitpro.Filter{
  off: MapSet.t(flag()),
  options: %{required(axis()) => [key()]},
  query: String.t()
}

Functions

apply(filter, items)

@spec apply(t(), [Gitpro.Item.t()]) :: [Gitpro.Item.t()]

The cards that pass the filter, in the order they were given.

Order is the board's, not the filter's: a list that reshuffles itself as you type is unusable, so filtering only ever takes rows away.

axes()

@spec axes() :: [axis()]

The axes, in the order the popup lists them.

axis_title(atom)

@spec axis_title(axis()) :: String.t()

How an axis is titled on screen.

flags(filter)

@spec flags(t()) :: [flag()]

Every switch the filter knows about, as {axis, key} pairs.

flags_narrowed?(filter)

@spec flags_narrowed?(t()) :: boolean()

True while a switch is off — the query is not counted.

label(key)

@spec label(key()) :: String.t()

How a key is written on screen, without its axis to name it by.

label(arg1, key)

@spec label(axis(), key()) :: String.t()

How a switch's key is written on screen.

The empty bucket is named for its axis — "Unassigned" says what it holds, and four switches all called "(none)" would not.

narrowed?(filter)

@spec narrowed?(t()) :: boolean()

True when this filter can remove a row at all.

new(columns \\ [])

@spec new([String.t()]) :: t()

A filter that hides nothing, with a switch for each of the board's columns.

The other three axes start empty and are filled in by put_options/3 once the cards are in — until then they hide nothing, which is what an axis with no switches on it should do.

on?(filter, flag)

@spec on?(t(), flag()) :: boolean()

Whether a switch is on. A switch nobody has touched is on.

options(filter, axis)

@spec options(t(), axis()) :: [key()]

The switches on axis, in the order they are listed.

put_options(filter, axis, keys)

@spec put_options(t(), axis(), [key()]) :: t()

Replaces one axis's switches, keeping the ones already set.

The :none bucket is appended rather than expected in keys: a card with no column, no label or no assignee is always possible, whatever the cards that have loaded happen to show. State has no such bucket — every card is in one of the states, and a switch that can never match anything is a switch that only wastes a row.

put_query(filter, query)

@spec put_query(t(), String.t()) :: t()

Replaces the query string.

set_all(filter, bool)

@spec set_all(t(), boolean()) :: t()

Turns every switch on or off at once, the query untouched.

set_all(filter, false) can only turn off the switches the filter knows about, so a label that arrives afterwards is on — see the note on where the options come from.

summary(filter)

@spec summary(t()) :: String.t() | nil

A one-line description of the switches, for the footer.

Answers nil when nothing is narrowed, so the footer can leave the space to the shortcuts rather than saying "all".

terms(query)

@spec terms(String.t()) :: [String.t()]

Splits a query into the lowercased terms every row has to match.

toggle(filter, flag)

@spec toggle(t(), flag()) :: t()

Flips one switch.