roundhouse_ui 0.9.1 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +61 -0
- data/app/controllers/concerns/roundhouse_ui/job_set_browsing.rb +73 -6
- data/app/controllers/roundhouse_ui/application_controller.rb +1 -0
- data/app/controllers/roundhouse_ui/dashboard_controller.rb +6 -1
- data/app/controllers/roundhouse_ui/dead_controller.rb +5 -2
- data/app/controllers/roundhouse_ui/errors_controller.rb +37 -0
- data/app/controllers/roundhouse_ui/queues_controller.rb +2 -0
- data/app/controllers/roundhouse_ui/retries_controller.rb +5 -2
- data/app/controllers/roundhouse_ui/scheduled_controller.rb +3 -1
- data/app/helpers/roundhouse_ui/application_helper.rb +50 -0
- data/app/helpers/roundhouse_ui/observability_helper.rb +23 -0
- data/app/helpers/roundhouse_ui/tags_helper.rb +101 -0
- data/app/views/layouts/roundhouse_ui/application.html.erb +108 -4
- data/app/views/roundhouse_ui/dashboard/show.html.erb +1 -1
- data/app/views/roundhouse_ui/dead/index.html.erb +41 -27
- data/app/views/roundhouse_ui/errors/index.html.erb +28 -7
- data/app/views/roundhouse_ui/jobs/show.html.erb +1 -0
- data/app/views/roundhouse_ui/queues/index.html.erb +6 -2
- data/app/views/roundhouse_ui/retries/index.html.erb +23 -12
- data/app/views/roundhouse_ui/scheduled/index.html.erb +12 -6
- data/app/views/roundhouse_ui/shared/_pager.html.erb +2 -2
- data/app/views/roundhouse_ui/shared/_tag_filter.html.erb +30 -0
- data/lib/roundhouse_ui/error_groups.rb +12 -1
- data/lib/roundhouse_ui/tags.rb +115 -0
- data/lib/roundhouse_ui/version.rb +1 -1
- data/lib/roundhouse_ui.rb +35 -0
- metadata +5 -2
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
module RoundhouseUi
|
|
2
|
+
# Resolves host-defined tags for a job at read time (no middleware, no
|
|
3
|
+
# storage — works on every backend and applies retroactively to jobs already
|
|
4
|
+
# in the sets). The host supplies the resolver; see ADR 0002.
|
|
5
|
+
#
|
|
6
|
+
# RoundhouseUi.job_tags = RoundhouseUi::Tags.from_constant(:OWNER, as: :squad)
|
|
7
|
+
# # or any callable:
|
|
8
|
+
# RoundhouseUi.job_tags = ->(klass:, item:) { { squad: :growth } }
|
|
9
|
+
#
|
|
10
|
+
# Contract: resolver output is normalized to string keys/values, masked via
|
|
11
|
+
# Redaction (redact_args patterns apply to tag keys), and a raising resolver
|
|
12
|
+
# yields no tags — tagging must never break a page.
|
|
13
|
+
module Tags
|
|
14
|
+
EMPTY = {}.freeze
|
|
15
|
+
|
|
16
|
+
module_function
|
|
17
|
+
|
|
18
|
+
# Tags for one job entry, as a { "key" => "value" } Hash (EMPTY when no
|
|
19
|
+
# resolver is configured, the resolver declines, or it raises).
|
|
20
|
+
#
|
|
21
|
+
# `klass`/`item` come from the backend entry (entry.klass / entry.item);
|
|
22
|
+
# the ActiveJob adapter wrapper is unwrapped here, so resolvers always see
|
|
23
|
+
# the real job class. Pass a Hash as `cache` to memoize per class across a
|
|
24
|
+
# request — unused (and unneeded) in per-job mode.
|
|
25
|
+
def for(klass:, item:, cache: nil)
|
|
26
|
+
resolver = RoundhouseUi.job_tags
|
|
27
|
+
return EMPTY unless resolver
|
|
28
|
+
|
|
29
|
+
effective = effective_klass(klass, item)
|
|
30
|
+
return EMPTY unless effective
|
|
31
|
+
|
|
32
|
+
if RoundhouseUi.job_tags_per_job
|
|
33
|
+
# Per-job mode still memoizes, keyed by jid rather than class: a job's
|
|
34
|
+
# tags cannot change within one request, and the same entry is resolved
|
|
35
|
+
# twice otherwise — once while scanning, once when its badge renders.
|
|
36
|
+
jid = item["jid"] if item.is_a?(Hash)
|
|
37
|
+
return resolve(resolver, effective, item) unless cache && jid
|
|
38
|
+
|
|
39
|
+
cache.key?(jid) ? cache[jid] : cache[jid] = resolve(resolver, effective, item)
|
|
40
|
+
elsif cache
|
|
41
|
+
# Class-cached mode: item is withheld (deliberately — see resolve).
|
|
42
|
+
cache.key?(effective) ? cache[effective] : cache[effective] = resolve(resolver, effective, nil)
|
|
43
|
+
else
|
|
44
|
+
resolve(resolver, effective, nil)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# The class-constant convention (Trainual's OWNER pattern) as a resolver:
|
|
49
|
+
#
|
|
50
|
+
# RoundhouseUi.job_tags = RoundhouseUi::Tags.from_constant(:OWNER, as: :squad)
|
|
51
|
+
#
|
|
52
|
+
# Tags every job whose class (or ancestor — inherited constants count, so a
|
|
53
|
+
# base-class OWNER covers subclasses) defines the constant.
|
|
54
|
+
def from_constant(const_name, as: const_name.to_s.downcase)
|
|
55
|
+
lambda do |klass:, item:|
|
|
56
|
+
k = klass.to_s.safe_constantize
|
|
57
|
+
{ as => k.const_get(const_name) } if k&.const_defined?(const_name)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# The declared filter vocabulary, normalized to { "key" => ["value", ...] },
|
|
62
|
+
# or nil when the host declared none (the filter UI then discovers values
|
|
63
|
+
# from the entries it scans). Both the whole setting and individual values
|
|
64
|
+
# may be callables, so vocabularies can be dynamic.
|
|
65
|
+
def filters
|
|
66
|
+
declared = RoundhouseUi.tag_filters
|
|
67
|
+
declared = declared.call if declared.respond_to?(:call)
|
|
68
|
+
return nil unless declared.is_a?(Hash)
|
|
69
|
+
|
|
70
|
+
declared.each_with_object({}) do |(key, values), out|
|
|
71
|
+
values = values.call if values.respond_to?(:call)
|
|
72
|
+
out[key.to_s] = Array(values).map(&:to_s)
|
|
73
|
+
end
|
|
74
|
+
rescue => e
|
|
75
|
+
warn_once("tag_filters failed: #{e.message}")
|
|
76
|
+
nil
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Does a resolved tag Hash match a key/value filter? Exact match on the
|
|
80
|
+
# normalized (post-redaction) value — so a redacted tag matches only its
|
|
81
|
+
# mask, and the filter can't be used to probe redacted values.
|
|
82
|
+
def match?(tags, key, value)
|
|
83
|
+
tags[key.to_s] == value.to_s
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# -- internals ----------------------------------------------------------
|
|
87
|
+
|
|
88
|
+
# ActiveJob-on-Sidekiq stores the adapter's JobWrapper in item["class"] and
|
|
89
|
+
# the real job class in item["wrapped"]; Solid Queue and raw Sidekiq
|
|
90
|
+
# workers put the real class in klass. Resolvers always get the real one.
|
|
91
|
+
def effective_klass(klass, item)
|
|
92
|
+
wrapped = item["wrapped"] if item.is_a?(Hash)
|
|
93
|
+
(wrapped || klass)&.to_s
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# `item` is nil in class-cached mode: an args-reading resolver cached by
|
|
97
|
+
# class would poison the cache with first-job-wins values — withholding the
|
|
98
|
+
# payload makes it fail deterministically (rescued → no tags) instead.
|
|
99
|
+
# Hosts that need the payload set RoundhouseUi.job_tags_per_job = true.
|
|
100
|
+
def resolve(resolver, klass, item)
|
|
101
|
+
tags = resolver.call(klass: klass, item: item)
|
|
102
|
+
return EMPTY unless tags.is_a?(Hash)
|
|
103
|
+
|
|
104
|
+
normalized = tags.each_with_object({}) { |(k, v), out| out[k.to_s] = v.to_s }
|
|
105
|
+
Redaction.apply(normalized)
|
|
106
|
+
rescue => e
|
|
107
|
+
warn_once("job_tags resolver failed for #{klass}: #{e.message}")
|
|
108
|
+
EMPTY
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def warn_once(message)
|
|
112
|
+
Rails.logger&.warn("[roundhouse] #{message}") if defined?(Rails)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
data/lib/roundhouse_ui.rb
CHANGED
|
@@ -7,6 +7,7 @@ require "roundhouse_ui/snapshots"
|
|
|
7
7
|
require "roundhouse_ui/observability"
|
|
8
8
|
require "roundhouse_ui/audit"
|
|
9
9
|
require "roundhouse_ui/redaction"
|
|
10
|
+
require "roundhouse_ui/tags"
|
|
10
11
|
require "roundhouse_ui/cancellation"
|
|
11
12
|
require "roundhouse_ui/cancel_middleware"
|
|
12
13
|
require "roundhouse_ui/metrics"
|
|
@@ -56,6 +57,39 @@ module RoundhouseUi
|
|
|
56
57
|
# e.g. RoundhouseUi.redact_args = %w[password token secret]. Default: none.
|
|
57
58
|
attr_accessor :redact_args
|
|
58
59
|
|
|
60
|
+
# Host-defined job tags, resolved at read time (see ADR 0002): a callable
|
|
61
|
+
# given the job's class name and payload, returning a Hash of tags or nil.
|
|
62
|
+
# `klass` is always the real job class (the ActiveJob adapter wrapper is
|
|
63
|
+
# unwrapped first). For the class-constant convention there's a shorthand:
|
|
64
|
+
#
|
|
65
|
+
# RoundhouseUi.job_tags = RoundhouseUi::Tags.from_constant(:OWNER, as: :squad)
|
|
66
|
+
# # equivalent to:
|
|
67
|
+
# RoundhouseUi.job_tags = ->(klass:, item:) {
|
|
68
|
+
# k = klass.safe_constantize
|
|
69
|
+
# { squad: k.const_get(:OWNER) } if k&.const_defined?(:OWNER)
|
|
70
|
+
# }
|
|
71
|
+
#
|
|
72
|
+
# Tag values render in the UI and pass through redact_args masking (by tag
|
|
73
|
+
# key). Must be cheap: by default it's memoized per class per request and
|
|
74
|
+
# called with item: nil. Default: nil (no tags anywhere).
|
|
75
|
+
attr_accessor :job_tags
|
|
76
|
+
|
|
77
|
+
# Set true when job_tags derives tags from the payload (args, tenant, …):
|
|
78
|
+
# the resolver is then called once per job with the full item, and nothing
|
|
79
|
+
# is cached — a 1,000-entry scan means 1,000 calls, so keep it fast. Leave
|
|
80
|
+
# false for class-derived tags (OWNER-style constants). Default: false.
|
|
81
|
+
attr_accessor :job_tags_per_job
|
|
82
|
+
|
|
83
|
+
# Optional declared filter vocabulary, so tag filter dropdowns are stable
|
|
84
|
+
# instead of discovered from whatever jobs happen to be visible:
|
|
85
|
+
#
|
|
86
|
+
# RoundhouseUi.tag_filters = { squad: %w[core training growth platform ops ai] }
|
|
87
|
+
#
|
|
88
|
+
# Values (or the whole setting) may be callables for dynamic vocabularies.
|
|
89
|
+
# When set, filtering by an undeclared key matches nothing (fail-closed).
|
|
90
|
+
# Default: nil — the filter UI discovers values from the entries it scans.
|
|
91
|
+
attr_accessor :tag_filters
|
|
92
|
+
|
|
59
93
|
# Opt-in: fold failures recorded by the `sidekiq-failures` gem (its `failed`
|
|
60
94
|
# sorted set) into the grouped Errors view. Off by default, and a no-op
|
|
61
95
|
# unless sidekiq-failures is loaded. Jobs with `retry: false` never enter
|
|
@@ -112,4 +146,5 @@ module RoundhouseUi
|
|
|
112
146
|
self.pause_enabled = true
|
|
113
147
|
self.poll_interval = 5
|
|
114
148
|
self.collect_durations = false
|
|
149
|
+
self.job_tags_per_job = false
|
|
115
150
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: roundhouse_ui
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.10.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- R.J. Robinson
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-08-17 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -72,6 +72,7 @@ files:
|
|
|
72
72
|
- app/helpers/roundhouse_ui/application_helper.rb
|
|
73
73
|
- app/helpers/roundhouse_ui/nav_helper.rb
|
|
74
74
|
- app/helpers/roundhouse_ui/observability_helper.rb
|
|
75
|
+
- app/helpers/roundhouse_ui/tags_helper.rb
|
|
75
76
|
- app/views/layouts/roundhouse_ui/application.html.erb
|
|
76
77
|
- app/views/roundhouse_ui/audit/index.html.erb
|
|
77
78
|
- app/views/roundhouse_ui/busy/index.html.erb
|
|
@@ -89,6 +90,7 @@ files:
|
|
|
89
90
|
- app/views/roundhouse_ui/retries/index.html.erb
|
|
90
91
|
- app/views/roundhouse_ui/scheduled/index.html.erb
|
|
91
92
|
- app/views/roundhouse_ui/shared/_pager.html.erb
|
|
93
|
+
- app/views/roundhouse_ui/shared/_tag_filter.html.erb
|
|
92
94
|
- app/views/roundhouse_ui/snapshots/index.html.erb
|
|
93
95
|
- app/views/roundhouse_ui/workers/index.html.erb
|
|
94
96
|
- config/routes.rb
|
|
@@ -108,6 +110,7 @@ files:
|
|
|
108
110
|
- lib/roundhouse_ui/pause.rb
|
|
109
111
|
- lib/roundhouse_ui/redaction.rb
|
|
110
112
|
- lib/roundhouse_ui/snapshots.rb
|
|
113
|
+
- lib/roundhouse_ui/tags.rb
|
|
111
114
|
- lib/roundhouse_ui/version.rb
|
|
112
115
|
- lib/tasks/roundhouse_ui_tasks.rake
|
|
113
116
|
homepage: https://github.com/rjrobinson/roundhouse_ui
|