Module: RoundhouseUi::JobSetBrowsing

Extended by:
ActiveSupport::Concern
Included in:
DeadController, RetriesController, ScheduledController
Defined in:
app/controllers/concerns/roundhouse_ui/job_set_browsing.rb

Overview

Shared search + pagination over a Sidekiq job set (dead, retry, scheduled). Keeps the controllers from duplicating the scan/filter/window logic.

Constant Summary collapse

PER_PAGE =
25
BULK_CAP =

safety ceiling on a single match-set action

1_000

Instance Method Summary collapse

Instance Method Details

#browse(set, query, page, per = PER_PAGE, tag: nil) ⇒ Object



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'app/controllers/concerns/roundhouse_ui/job_set_browsing.rb', line 24

def browse(set, query, page, per = PER_PAGE, tag: nil)
  start = (page - 1) * per
  jobs = []
  has_next = false
  matched = 0
  cache = tag_cache_for(tag)

  set.each do |entry|
    next unless entry_selected?(entry, query, tag, cache)

    if matched < start
      matched += 1
    elsif jobs.size < per
      jobs << entry
      matched += 1
    else
      has_next = true
      break
    end
  end

  [ jobs, has_next ]
end

#bulk_apply(set, query, op, cap = BULK_CAP, tag: nil) ⇒ Object

Apply an op ("retry"/"delete") to every entry matching the query, capped at BULK_CAP. Entries are collected first, then acted on — mutating a Sidekiq set mid-iteration skips entries. Returns [count_acted_on, capped?].



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'app/controllers/concerns/roundhouse_ui/job_set_browsing.rb', line 51

def bulk_apply(set, query, op, cap = BULK_CAP, tag: nil)
  matches = []
  capped = false
  cache = tag_cache_for(tag)
  set.each do |entry|
    next unless entry_selected?(entry, query, tag, cache)

    matches << entry
    if matches.size >= cap
      capped = true
      break
    end
  end
  matches.each { |entry| op == "delete" ? entry.delete : entry.retry }
  [ matches.size, capped ]
end

#entry_matches?(entry, query, tags = Tags::EMPTY) ⇒ Boolean

Tag values are part of the haystack, so typing a squad name finds its jobs without reaching for the structured filter. Safe to widen here only because browse and bulk_apply share this predicate — if they diverged, a search would show one set of rows and "delete all matching" would act on another.

Returns:

  • (Boolean)


116
117
118
119
120
121
122
123
124
125
126
# File 'app/controllers/concerns/roundhouse_ui/job_set_browsing.rb', line 116

def entry_matches?(entry, query, tags = Tags::EMPTY)
  needle = query.downcase
  # Queue matches on equality, not substring: typing a queue name should
  # find its jobs, but this predicate also drives bulk_apply, so "default"
  # must never additionally select "default_low".
  return true if entry.queue.to_s.downcase == needle

  [ entry.klass, entry.jid, entry.item["error_class"], entry.item["error_message"], entry.args.to_s,
    *tags.values ]
    .any? { |hay| hay.to_s.downcase.include?(needle) }
end

#entry_selected?(entry, query, tag, cache) ⇒ Boolean

Both the browse and bulk paths run every candidate through this, so the rows an operator sees are exactly the rows a bulk action will touch — including when a tag value is what matched the free-text search.

Returns:

  • (Boolean)


71
72
73
74
75
76
77
78
79
# File 'app/controllers/concerns/roundhouse_ui/job_set_browsing.rb', line 71

def entry_selected?(entry, query, tag, cache)
  return false if @queue_filter.present? && entry.queue.to_s != @queue_filter

  tags = (entry, cache)
  return false if query.present? && !entry_matches?(entry, query, tags)
  return true if tag.nil?

  (tags, tag)
end

#entry_tagged?(tags, key, value) ⇒ Boolean

Returns:

  • (Boolean)


89
90
91
92
93
94
95
96
# File 'app/controllers/concerns/roundhouse_ui/job_set_browsing.rb', line 89

def (tags, (key, value))
  # A declared vocabulary is authoritative: filtering on a key the host
  # never declared matches nothing rather than everything.
  declared = Tags.filters
  return false if declared && !declared.key?(key)

  Tags.match?(tags, key, value)
end

#entry_tags(entry, cache) ⇒ Object



98
99
100
101
102
# File 'app/controllers/concerns/roundhouse_ui/job_set_browsing.rb', line 98

def (entry, cache)
  return Tags::EMPTY unless RoundhouseUi.job_tags

  Tags.for(klass: entry.klass, item: entry.item, cache: cache)
end

#queue_filterObject

?queue=name — exact match, so clicking a queue pill or picking one from the palette narrows to that queue. Exact rather than substring because this feeds bulk_apply too, and "default" must never also select "default_low".



85
86
87
# File 'app/controllers/concerns/roundhouse_ui/job_set_browsing.rb', line 85

def queue_filter
  params[:queue].to_s.strip.presence
end

#tag_cache_for(_tag) ⇒ Object

Shares the request memo with TagsHelper — controller ivars carry into the view, so an entry resolved while scanning is not resolved again when its badge renders. Tags.for picks the key: class name normally, jid in per-job mode.



108
109
110
# File 'app/controllers/concerns/roundhouse_ui/job_set_browsing.rb', line 108

def tag_cache_for(_tag)
  @rh_tag_cache ||= {}
end

#tag_filterObject

Returns [entries_for_page, has_next?]. Scans only far enough to fill the requested page plus one (to know if a next page exists) — never loads the whole set, so a 50k dead set stays cheap to page through. ?tag=key:value — an exact match against a host-defined tag (ADR 0002), parsed once per request. Deliberately structured rather than folded into the free-text query: substring search feeding bulk_apply would silently widen destructive bulk actions.



17
18
19
20
21
22
# File 'app/controllers/concerns/roundhouse_ui/job_set_browsing.rb', line 17

def tag_filter
  key, value = params[:tag].to_s.split(":", 2)
  return nil if key.blank? || value.blank?

  [ key, value ]
end