roundhouse_ui 0.10.0 → 0.11.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 +563 -50
- data/app/controllers/concerns/roundhouse_ui/job_set_browsing.rb +190 -23
- data/app/controllers/roundhouse_ui/application_controller.rb +73 -0
- data/app/controllers/roundhouse_ui/audit_controller.rb +1 -0
- data/app/controllers/roundhouse_ui/busy_controller.rb +25 -5
- data/app/controllers/roundhouse_ui/dashboard_controller.rb +23 -4
- data/app/controllers/roundhouse_ui/dead_controller.rb +26 -12
- data/app/controllers/roundhouse_ui/errors_controller.rb +30 -8
- data/app/controllers/roundhouse_ui/jobs_controller.rb +5 -1
- data/app/controllers/roundhouse_ui/queues_controller.rb +56 -7
- data/app/controllers/roundhouse_ui/recurring_controller.rb +12 -0
- data/app/controllers/roundhouse_ui/retries_controller.rb +33 -12
- data/app/controllers/roundhouse_ui/scheduled_controller.rb +3 -8
- data/app/controllers/roundhouse_ui/settings_controller.rb +15 -0
- data/app/controllers/roundhouse_ui/snapshots_controller.rb +5 -6
- data/app/controllers/roundhouse_ui/workers_controller.rb +2 -7
- data/app/helpers/roundhouse_ui/application_helper.rb +253 -10
- data/app/helpers/roundhouse_ui/nav_helper.rb +3 -3
- data/app/helpers/roundhouse_ui/observability_helper.rb +113 -27
- data/app/helpers/roundhouse_ui/tags_helper.rb +30 -3
- data/app/views/layouts/roundhouse_ui/application.html.erb +1181 -92
- data/app/views/roundhouse_ui/busy/index.html.erb +30 -6
- data/app/views/roundhouse_ui/dashboard/show.html.erb +62 -17
- data/app/views/roundhouse_ui/dead/index.html.erb +23 -15
- data/app/views/roundhouse_ui/errors/index.html.erb +28 -9
- data/app/views/roundhouse_ui/jobs/show.html.erb +7 -3
- data/app/views/roundhouse_ui/metrics/show.html.erb +26 -3
- data/app/views/roundhouse_ui/queues/index.html.erb +53 -13
- data/app/views/roundhouse_ui/queues/show.html.erb +55 -0
- data/app/views/roundhouse_ui/recurring/index.html.erb +71 -0
- data/app/views/roundhouse_ui/retries/index.html.erb +17 -15
- data/app/views/roundhouse_ui/scheduled/index.html.erb +10 -10
- data/app/views/roundhouse_ui/settings/show.html.erb +91 -0
- data/app/views/roundhouse_ui/shared/_filter_error.html.erb +23 -0
- data/app/views/roundhouse_ui/shared/_pager.html.erb +2 -2
- data/app/views/roundhouse_ui/shared/_search_bar.html.erb +55 -0
- data/app/views/roundhouse_ui/shared/_search_help.html.erb +37 -0
- data/app/views/roundhouse_ui/shared/_tag_filter.html.erb +2 -4
- data/app/views/roundhouse_ui/shared/bulk_preview.html.erb +69 -0
- data/config/routes.rb +9 -0
- data/lib/roundhouse_ui/backends/sidekiq.rb +124 -5
- data/lib/roundhouse_ui/backends/solid_queue.rb +54 -3
- data/lib/roundhouse_ui/demo.rb +130 -0
- data/lib/roundhouse_ui/error_groups.rb +13 -3
- data/lib/roundhouse_ui/filter_query.rb +504 -0
- data/lib/roundhouse_ui/health.rb +43 -4
- data/lib/roundhouse_ui/history.rb +54 -0
- data/lib/roundhouse_ui/icons.rb +104 -0
- data/lib/roundhouse_ui/marks/datadog-lockup-white.svg +30 -0
- data/lib/roundhouse_ui/marks/datadog-lockup.svg +41 -0
- data/lib/roundhouse_ui/observability.rb +55 -7
- data/lib/roundhouse_ui/pause.rb +6 -11
- data/lib/roundhouse_ui/queue_summary.rb +11 -0
- data/lib/roundhouse_ui/recurring.rb +137 -0
- data/lib/roundhouse_ui/runbooks.rb +75 -0
- data/lib/roundhouse_ui/snapshots.rb +13 -2
- data/lib/roundhouse_ui/tags.rb +8 -11
- data/lib/roundhouse_ui/theme.rb +298 -0
- data/lib/roundhouse_ui/version.rb +1 -1
- data/lib/roundhouse_ui.rb +200 -5
- data/lib/tasks/roundhouse_ui_tasks.rake +84 -4
- metadata +29 -7
|
@@ -1,48 +1,134 @@
|
|
|
1
1
|
module RoundhouseUi
|
|
2
|
+
# Links out to whatever APM the host configured. Four entry points — job or
|
|
3
|
+
# error, by historical call-site name — and exactly one appearance. They were
|
|
4
|
+
# four hand-rolled copies once, and only some got updated: two still emitted a
|
|
5
|
+
# literal ↗ after the rest moved to SVG. Later the split survived as two
|
|
6
|
+
# different shapes, a text link and a bordered box, which is not "one
|
|
7
|
+
# appearance" however the helper described itself.
|
|
2
8
|
module ObservabilityHelper
|
|
3
|
-
# Renders a deep-link to the configured observability tool for a job, or
|
|
4
|
-
# nothing when no adapter is configured (the default).
|
|
5
9
|
def trace_link(klass:, jid:, queue: nil)
|
|
6
|
-
|
|
7
|
-
url = adapter.job_url(klass: klass, jid: jid, queue: queue)
|
|
8
|
-
return unless url
|
|
9
|
-
|
|
10
|
-
link_to "↗ #{adapter.label}", url, target: "_blank", rel: "noopener", class: "rh-trace"
|
|
10
|
+
trace_affordance(job_trace_url(klass: klass, jid: jid, queue: queue), as: :mark)
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
# Icon-only variant for table rows. The adapter's name is worth reading once
|
|
14
|
-
# in a header or a detail page, not once per row — there it is width spent
|
|
15
|
-
# repeating a word the operator already knows.
|
|
16
13
|
def trace_icon(klass:, jid:, queue: nil)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return unless url
|
|
14
|
+
trace_affordance(job_trace_url(klass: klass, jid: jid, queue: queue), as: :control)
|
|
15
|
+
end
|
|
20
16
|
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
def error_trace_link(klass:, error: nil)
|
|
18
|
+
trace_affordance(error_trace_url(klass: klass, error: error), as: :mark)
|
|
23
19
|
end
|
|
24
20
|
|
|
25
21
|
def error_trace_icon(klass:, error: nil)
|
|
22
|
+
trace_affordance(error_trace_url(klass: klass, error: error), as: :control, compact: true)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Says once, above a table, where every row's trace glyph goes. Takes the rows
|
|
26
|
+
# it is describing, because a legend above an empty table explains a control
|
|
27
|
+
# that is not on the page — which is exactly what "Nothing scheduled" under
|
|
28
|
+
# "Traces open in Datadog" looked like.
|
|
29
|
+
#
|
|
30
|
+
# Returns nil unless the adapter has a mark of its own, too: a plain adapter
|
|
31
|
+
# already labels every row, so there would be nothing to hoist.
|
|
32
|
+
def trace_legend(rows)
|
|
33
|
+
return nil if rows.blank?
|
|
34
|
+
|
|
26
35
|
adapter = RoundhouseUi.observability
|
|
27
|
-
return unless
|
|
36
|
+
return nil unless wordmark?(adapter)
|
|
37
|
+
return nil if adapter.job_url(klass: "Probe", jid: "probe").blank?
|
|
38
|
+
|
|
39
|
+
content_tag(:div, class: "rh-trace-legend") do
|
|
40
|
+
safe_join([ content_tag(:span, "Traces open in"), trace_glyph(adapter) ], " ")
|
|
41
|
+
end
|
|
42
|
+
end
|
|
28
43
|
|
|
29
|
-
|
|
30
|
-
|
|
44
|
+
# An adapter's own mark, when it supplies one. Three shapes are accepted, in
|
|
45
|
+
# descending order of how much we trust them:
|
|
46
|
+
#
|
|
47
|
+
# :name → one of Roundhouse's shipped icons
|
|
48
|
+
# "<svg …>" → the adapter's markup, rendered as-is
|
|
49
|
+
# nil → the default trace glyph
|
|
50
|
+
#
|
|
51
|
+
# The markup case is not escaped, because escaping renders the tags as text
|
|
52
|
+
# and the feature does not work at all. That is the same trust level as the
|
|
53
|
+
# adapter object itself, which can already run arbitrary code. Anything that
|
|
54
|
+
# is neither markup nor a known name falls back rather than printing stray
|
|
55
|
+
# text into a table.
|
|
56
|
+
def trace_glyph(adapter)
|
|
57
|
+
supplied = adapter.respond_to?(:icon) ? adapter.icon : nil
|
|
58
|
+
return icon(:trace_out) if supplied.blank?
|
|
59
|
+
return icon(supplied) if supplied.is_a?(Symbol)
|
|
31
60
|
|
|
32
|
-
|
|
33
|
-
title: "Open in #{adapter.label}", "aria-label": "Open in #{adapter.label}"
|
|
61
|
+
supplied.to_s.strip.start_with?("<") ? supplied.to_s.html_safe : icon(:trace_out)
|
|
34
62
|
end
|
|
35
63
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def job_trace_url(klass:, jid:, queue: nil)
|
|
67
|
+
RoundhouseUi.observability.job_url(klass: klass, jid: jid, queue: queue)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def error_trace_url(klass:, error: nil)
|
|
39
71
|
adapter = RoundhouseUi.observability
|
|
40
|
-
return unless adapter.respond_to?(:error_url)
|
|
72
|
+
return nil unless adapter.respond_to?(:error_url)
|
|
41
73
|
|
|
42
|
-
|
|
43
|
-
|
|
74
|
+
adapter.error_url(klass: klass, error: error)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Two contexts, one principle: a dense list gets a control, a single item
|
|
78
|
+
# gets the vendor's mark.
|
|
79
|
+
#
|
|
80
|
+
# In a table the mark would repeat on every row — 366 copies of someone
|
|
81
|
+
# else's logo on one Retries page, and the only unboxed thing in a column of
|
|
82
|
+
# bordered buttons. So rows get a Roundhouse glyph shaped exactly like the
|
|
83
|
+
# buttons beside it, and the page says once, above the table, where that
|
|
84
|
+
# glyph goes. On a page showing one job there is nothing to tile, so the
|
|
85
|
+
# mark itself goes inline where it has room to read.
|
|
86
|
+
#
|
|
87
|
+
# This is not the old `labelled` split, which was two arbitrary shapes with
|
|
88
|
+
# no stated rule. The distinction here is repetition, and `trace_legend` is
|
|
89
|
+
# what makes the row glyph legible without restating the brand per row.
|
|
90
|
+
def trace_affordance(url, as:, compact: false)
|
|
91
|
+
return nil if url.blank?
|
|
92
|
+
|
|
93
|
+
adapter = RoundhouseUi.observability
|
|
94
|
+
return trace_control(url, adapter, compact: compact) if as == :control
|
|
95
|
+
|
|
96
|
+
link_to trace_body(adapter), url, **trace_attrs(adapter),
|
|
97
|
+
class: "rh-trace#{' rh-trace-mark' if wordmark?(adapter)}"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Shaped like whatever it sits beside. In an Actions column that is .rh-btn,
|
|
101
|
+
# so it lands level with Run now and Delete. Inline in a text cell it is
|
|
102
|
+
# .rh-runbook's smaller pill, because a 28px-tall button next to an 11.5px
|
|
103
|
+
# "runbook" link puts three different heights in one table cell.
|
|
104
|
+
#
|
|
105
|
+
# Never carries the vendor mark either way: boxing a logo is against most
|
|
106
|
+
# brand guidelines, Datadog's included.
|
|
107
|
+
def trace_control(url, adapter, compact: false)
|
|
108
|
+
# One class. Borrowing .rh-btn's or .rh-runbook's box was the previous fix
|
|
109
|
+
# for a 2px drift, and it still left two definitions to keep in step — the
|
|
110
|
+
# compact variant came out 2px taller than the runbook pill it sat beside.
|
|
111
|
+
# The control scale (#65) gives every control the same box from the same
|
|
112
|
+
# tokens, so there is nothing to borrow and nothing to synchronise.
|
|
113
|
+
base = compact ? "rh-trace-btn rh-btn--sm" : "rh-trace-btn"
|
|
114
|
+
link_to icon(:trace_out), url, **trace_attrs(adapter), class: base
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def trace_body(adapter)
|
|
118
|
+
glyph = trace_glyph(adapter)
|
|
119
|
+
return glyph if wordmark?(adapter)
|
|
120
|
+
|
|
121
|
+
safe_join([ glyph, content_tag(:span, adapter.label) ])
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def trace_attrs(adapter)
|
|
125
|
+
{ target: "_blank", rel: "noopener noreferrer",
|
|
126
|
+
title: "Open in #{adapter.label}",
|
|
127
|
+
aria: { label: "Open in #{adapter.label}" } }
|
|
128
|
+
end
|
|
44
129
|
|
|
45
|
-
|
|
130
|
+
def wordmark?(adapter)
|
|
131
|
+
adapter.respond_to?(:wordmark?) && adapter.wordmark?
|
|
46
132
|
end
|
|
47
133
|
end
|
|
48
134
|
end
|
|
@@ -78,18 +78,45 @@ module RoundhouseUi
|
|
|
78
78
|
# Every active constraint must appear here. A confirm that names only the
|
|
79
79
|
# text query while a queue or tag also narrows the set understates what is
|
|
80
80
|
# about to be destroyed.
|
|
81
|
-
|
|
81
|
+
# `filter` is the controller's parse, reached through the helper. One source for
|
|
82
|
+
# the prose, the predicate and the pill — a description built from a second
|
|
83
|
+
# reading of the filter is a description that can disagree with what was applied.
|
|
84
|
+
def rh_filter
|
|
85
|
+
controller.respond_to?(:filter, true) ? controller.send(:filter) : RoundhouseUi::FilterQuery.none
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def filter_description(query, tag, queue = rh_filter.queue)
|
|
82
89
|
parts = []
|
|
83
90
|
parts << "matching “#{query}”" if query.present?
|
|
84
91
|
parts << "tagged #{tag[0]}: #{tag[1]}" if tag
|
|
85
92
|
parts << "in queue #{queue}" if queue.present?
|
|
93
|
+
parts << like_description if rh_filter.klass.present? || rh_filter.error.present?
|
|
86
94
|
parts.join(" and ")
|
|
87
95
|
end
|
|
88
96
|
|
|
97
|
+
# "find more like this", said back to you. The wording has to be exact about
|
|
98
|
+
# what it selected, because the bulk Delete sitting next to it acts on
|
|
99
|
+
# precisely this set and nothing wider.
|
|
100
|
+
def like_description
|
|
101
|
+
# Either half stands on its own. "every Timeout::Error, whatever the class"
|
|
102
|
+
# is a question worth asking, so clearing the class must not take the error
|
|
103
|
+
# with it — which is what it used to do.
|
|
104
|
+
return "failing with #{rh_filter.error}" if rh_filter.klass.blank?
|
|
105
|
+
return "like #{rh_filter.klass}" if rh_filter.error.blank?
|
|
106
|
+
|
|
107
|
+
"like #{rh_filter.klass} failing with #{rh_filter.error}"
|
|
108
|
+
end
|
|
109
|
+
|
|
89
110
|
# Any filter active? Bulk-on-match is filter-gated, so this decides whether
|
|
90
111
|
# the bulk bar renders at all — and which empty-state copy is honest.
|
|
91
|
-
|
|
92
|
-
|
|
112
|
+
# Delegates to the concern's predicate where it is available, so the button the
|
|
113
|
+
# view offers and the scope the route enforces cannot drift apart. They did:
|
|
114
|
+
# this returned true only for the view, while bulk_all had no gate at all.
|
|
115
|
+
def any_filter?(query, tag, queue = rh_filter.queue)
|
|
116
|
+
return controller.send(:bulk_filter_present?, query, tag) if controller.respond_to?(:bulk_filter_present?, true)
|
|
117
|
+
|
|
118
|
+
query.present? || !tag.nil? || queue.present? ||
|
|
119
|
+
rh_filter.klass.present? || rh_filter.error.present?
|
|
93
120
|
end
|
|
94
121
|
|
|
95
122
|
# Value only — the key is near-constant down a column, so repeating it is
|