Class: Karafka::Web::Ui::Lib::Filter

Inherits:
Object
  • Object
show all
Defined in:
lib/karafka/web/ui/lib/filter.rb

Overview

Note:

It handles filtering in place by mutating appropriate resources and sub-components, exactly like the [[Sorter]]. Because of that it must only be used on structures that are safe to mutate (per-request data), never on shared/live structures like the app routing.

Filtering engine for deep in-memory structures. It supports hashes, arrays and hash proxies. It is a companion to the [[Sorter]] and follows the same wiring (a per-controller allow-list plus an in-place call), but instead of reordering a structure it removes the elements that do not match a keyword query.

Matching is a case-insensitive substring check performed against the allowed attributes of each element (via public_send or hash lookup) and against hash keys (so structural labels such as topic or consumer group names are searchable).

It uses match-propagation: a container (array/hash) is kept when it matches directly or when any of its descendants match. That way filtering a nested structure by a topic name keeps that topic with all of its children while dropping the branches that have nothing in common with the query.

Instance Method Summary collapse

Constructor Details

#initialize(filter_query, allowed_attributes:, field: nil) ⇒ Filter

Returns a new instance of Filter.

Parameters:

  • filter_query (String)

    keyword based on which we filter or empty string when no filtering is needed

  • allowed_attributes (Array<String, #path>, Hash)

    attributes on which we allow to filter. Since we can filter on method invocations, this needs to be limited and provided on a per controller basis (same contract as the sorter). It can be an array of attribute names or a { attribute => label } hash (in which case only the keys matter here). An entry may also be a key-alias descriptor (any object responding to name/path), which matches nested hash keys at a path instead of record attributes (used for tree-shaped data whose labels are keys, e.g. the health stats).

  • field (String, nil) (defaults to: nil)

    when provided (and allowed), filtering is scoped to this single attribute (or key alias) instead of matching the query against every allowed attribute. Used by the field-selectable filter on flat record listings.



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/karafka/web/ui/lib/filter.rb', line 44

def initialize(filter_query, allowed_attributes:, field: nil)
  @query = filter_query.to_s.downcase.strip

  allowed = allowed_attributes.is_a?(Hash) ? allowed_attributes.keys : allowed_attributes

  # A declared field is either a plain attribute (matched on records) or a key alias
  # (matched on nested hash keys at a path). Key aliases are duck-typed: they respond to
  # `path` (a `KeyField`), which nothing else in the allow-list does.
  @allowed = []
  @aliases = {}

  Array(allowed).each do |attribute|
    if attribute.respond_to?(:path)
      @aliases[attribute.name.to_s] = attribute.path
    else
      # Normalize to strings so symbol keys from controllers and the string field coming
      # from the request params compare cleanly
      @allowed << attribute.to_s
    end
  end

  field = field.to_s

  # When the selected field is a key alias, filtering prunes hash keys at its path instead
  # of matching record attributes
  if @aliases.key?(field)
    @alias_path = @aliases[field]
  else
    @alias_path = nil
    @field = @allowed.include?(field) ? field : nil
  end

  # Things we have already seen and filtered. Prevents crashing (and infinite loops) on
  # circular dependencies when the same resources are present in different parts of the
  # tree. We cache the match result so a second visit returns the same answer.
  @seen = {}
end

Instance Method Details

#call(resource) ⇒ Hash, ...

Filters the structure in place and returns it.

Parameters:

  • resource (Hash, Array, Lib::HashProxy)

    structure we want to filter

Returns:

  • (Hash, Array, Lib::HashProxy)

    the same structure with non-matching elements removed



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/karafka/web/ui/lib/filter.rb', line 87

def call(resource)
  # Skip if there is nothing to filter on
  return resource if @query.empty?
  # Skip if there are no criteria to match against. Just like the sorter ignores a
  # disallowed field, we do not want to prune anything with nothing to match on.
  return resource if @allowed.empty? && @aliases.empty?

  if @alias_path
    # Field scoped to a key alias: prune the hash keys at the alias path
    keep_by_key_alias!(resource, @alias_path)
  else
    keep?(resource, 0)
  end

  resource
end