Module: SourceMonitor::ApplicationHelper
- Includes:
- HealthBadgeHelper, TableSortHelper
- Defined in:
- app/helpers/source_monitor/application_helper.rb
Constant Summary collapse
- ITEM_SCRAPE_STATUS_LABELS =
Unified status badge helper for both fetch and scrape operations
{ "pending" => "Pending", "processing" => "Processing", "success" => "Scraped", "failed" => "Failed", "partial" => "Partial", "disabled" => "Disabled", "idle" => "Not scraped" }.freeze
Instance Method Summary collapse
-
#async_status_badge(status, show_spinner: true) ⇒ Object
Maps asynchronous workflow states to badge styling/labels shared across the engine.
- #compact_blank_hash(hash) ⇒ Object
-
#domain_from_url(url) ⇒ Object
Extracts the domain from a URL, returning nil if parsing fails.
-
#external_link_to(label, url, **options) ⇒ Object
Renders a clickable link that opens in a new tab with an external-link icon.
- #fetch_interval_bucket_path(bucket, search_params, selected: false) ⇒ Object
- #fetch_interval_bucket_query(bucket, search_params, selected: false) ⇒ Object
- #fetch_interval_filter_label(bucket, filter) ⇒ Object
- #fetch_schedule_window_label(group) ⇒ Object
-
#fetch_status_badge_classes(status) ⇒ Object
Legacy helper for backwards compatibility.
- #format_schedule_time(time) ⇒ Object
- #formatted_setting_value(value) ⇒ Object
- #heatmap_bucket_classes(count, max_count) ⇒ Object
- #human_fetch_interval(minutes) ⇒ Object
-
#item_scrape_status_badge(item:, source: nil, show_spinner: true) ⇒ Object
Returns a normalized badge payload for the source show/item pages.
-
#loading_spinner_svg(css_class: "mr-1 h-4 w-4 animate-spin text-blue-500") ⇒ Object
Helper to render the loading spinner SVG via IconComponent.
- #pagination_page_numbers(current_page:, total_pages:, window: 2) ⇒ Object
-
#source_favicon_tag(source, size: 24, **options) ⇒ Object
Renders the source favicon as an <img> tag or a colored-circle initials placeholder when no favicon is attached.
- #source_monitor_javascript_bundle_tag ⇒ Object
- #source_monitor_stylesheet_bundle_tag ⇒ Object
Methods included from HealthBadgeHelper
#interactive_health_status?, #source_health_actions, #source_health_badge
Methods included from TableSortHelper
#table_sort_aria, #table_sort_arrow, #table_sort_direction, #table_sort_link
Instance Method Details
#async_status_badge(status, show_spinner: true) ⇒ Object
Maps asynchronous workflow states to badge styling/labels shared across the engine. Item scraping builds on these core states, reusing the same colors so the UI stays consistent across sources, items, and job dashboards.
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
# File 'app/helpers/source_monitor/application_helper.rb', line 140 def async_status_badge(status, show_spinner: true) status_str = status.to_s label, classes, spinner = case status_str when "queued" [ "Queued", "bg-amber-100 text-amber-700", show_spinner ] when "pending" [ "Pending", "bg-amber-100 text-amber-700", show_spinner ] when "fetching", "processing" [ "Processing", "bg-blue-100 text-blue-700", show_spinner ] when "success" [ "Completed", "bg-green-100 text-green-700", false ] when "failed" [ "Failed", "bg-rose-100 text-rose-700", false ] when "partial" [ "Partial", "bg-amber-100 text-amber-700", false ] when "disabled" [ "Disabled", "bg-slate-200 text-slate-600", false ] when "idle" [ "Idle", "bg-slate-100 text-slate-600", false ] else [ "Ready", "bg-slate-100 text-slate-600", false ] end { label: label, classes: classes, show_spinner: spinner } end |
#compact_blank_hash(hash) ⇒ Object
38 39 40 41 42 43 44 45 46 |
# File 'app/helpers/source_monitor/application_helper.rb', line 38 def compact_blank_hash(hash) return {} if hash.blank? if hash.respond_to?(:compact_blank) hash.compact_blank else hash.reject { |_key, value| value.respond_to?(:blank?) ? value.blank? : value.nil? } end end |
#domain_from_url(url) ⇒ Object
Extracts the domain from a URL, returning nil if parsing fails.
225 226 227 228 229 230 231 |
# File 'app/helpers/source_monitor/application_helper.rb', line 225 def domain_from_url(url) return nil if url.blank? URI.parse(url.to_s).host rescue URI::InvalidURIError nil end |
#external_link_to(label, url, **options) ⇒ Object
Renders a clickable link that opens in a new tab with an external-link icon. Returns the label as plain text if the URL is blank.
215 216 217 218 219 220 221 222 |
# File 'app/helpers/source_monitor/application_helper.rb', line 215 def external_link_to(label, url, **) return label if url.blank? css = .delete(:class) || "text-blue-600 hover:text-blue-500" link_to(url, target: "_blank", rel: "noopener noreferrer", class: css, title: url, **) do safe_join([ label, " ", external_link_icon ]) end end |
#fetch_interval_bucket_path(bucket, search_params, selected: false) ⇒ Object
48 49 50 51 52 53 |
# File 'app/helpers/source_monitor/application_helper.rb', line 48 def fetch_interval_bucket_path(bucket, search_params, selected: false) query = fetch_interval_bucket_query(bucket, search_params, selected: selected) route_helpers = SourceMonitor::Engine.routes.url_helpers query.empty? ? route_helpers.sources_path : route_helpers.sources_path(q: query) end |
#fetch_interval_bucket_query(bucket, search_params, selected: false) ⇒ Object
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'app/helpers/source_monitor/application_helper.rb', line 55 def fetch_interval_bucket_query(bucket, search_params, selected: false) base = (search_params || {}).dup base = base.except("fetch_interval_minutes_gteq", "fetch_interval_minutes_lt", "fetch_interval_minutes_lteq") query = if selected base else updated = base.dup updated["fetch_interval_minutes_gteq"] = bucket.min.to_i.to_s if bucket.respond_to?(:min) && bucket.min if bucket.respond_to?(:max) && bucket.max updated["fetch_interval_minutes_lt"] = bucket.max.to_i.to_s else updated.delete("fetch_interval_minutes_lt") updated.delete("fetch_interval_minutes_lteq") end updated end compact_blank_hash(query) end |
#fetch_interval_filter_label(bucket, filter) ⇒ Object
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
# File 'app/helpers/source_monitor/application_helper.rb', line 78 def fetch_interval_filter_label(bucket, filter) return bucket.label if bucket&.respond_to?(:label) return unless filter min = filter[:min] max = filter[:max] if min && max "#{min}-#{max} min" elsif min "#{min}+ min" else "Any interval" end end |
#fetch_schedule_window_label(group) ⇒ Object
94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'app/helpers/source_monitor/application_helper.rb', line 94 def fetch_schedule_window_label(group) start_time = group.respond_to?(:window_start) ? group.window_start : nil end_time = group.respond_to?(:window_end) ? group.window_end : nil return unless start_time || end_time if start_time && end_time "#{format_schedule_time(start_time)} – #{format_schedule_time(end_time)}" elsif start_time "After #{format_schedule_time(start_time)}" else nil end end |
#fetch_status_badge_classes(status) ⇒ Object
Legacy helper for backwards compatibility
186 187 188 |
# File 'app/helpers/source_monitor/application_helper.rb', line 186 def fetch_status_badge_classes(status) async_status_badge(status) end |
#format_schedule_time(time) ⇒ Object
109 110 111 112 113 |
# File 'app/helpers/source_monitor/application_helper.rb', line 109 def format_schedule_time(time) return unless time l(time.in_time_zone, format: :short) end |
#formatted_setting_value(value) ⇒ Object
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 |
# File 'app/helpers/source_monitor/application_helper.rb', line 196 def formatted_setting_value(value) case value when TrueClass "Enabled" when FalseClass "Disabled" when Hash value.to_json when Array value.join(", ") when NilClass "—" else value end end |
#heatmap_bucket_classes(count, max_count) ⇒ Object
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
# File 'app/helpers/source_monitor/application_helper.rb', line 21 def heatmap_bucket_classes(count, max_count) return "bg-slate-100 text-slate-500" if max_count.to_i.zero? || count.to_i.zero? ratio = count.to_f / max_count case ratio when 0...0.25 "bg-blue-100 text-blue-800" when 0.25...0.5 "bg-blue-200 text-blue-900" when 0.5...0.75 "bg-blue-400 text-white" else "bg-blue-600 text-white" end end |
#human_fetch_interval(minutes) ⇒ Object
115 116 117 118 119 120 121 122 123 124 |
# File 'app/helpers/source_monitor/application_helper.rb', line 115 def human_fetch_interval(minutes) return "—" if minutes.blank? total_minutes = minutes.to_i hours, remaining = total_minutes.divmod(60) parts = [] parts << "#{hours}h" if hours.positive? parts << "#{remaining}m" if remaining.positive? || parts.empty? parts.join(" ") end |
#item_scrape_status_badge(item:, source: nil, show_spinner: true) ⇒ Object
Returns a normalized badge payload for the source show/item pages. The status derives from the item’s recorded scrape_status, falls back to the source configuration, and always lands inside the known status set: pending, processing, success, failed, partial, disabled, or idle.
171 172 173 174 175 176 177 178 179 180 181 182 183 |
# File 'app/helpers/source_monitor/application_helper.rb', line 171 def item_scrape_status_badge(item:, source: nil, show_spinner: true) status = derive_item_scrape_status(item:, source: source) base_badge = async_status_badge(status, show_spinner: show_spinner) label = ITEM_SCRAPE_STATUS_LABELS.fetch(status) { base_badge[:label] } spinner = base_badge[:show_spinner] && %w[pending processing].include?(status) { status: status, label: label, classes: base_badge[:classes], show_spinner: spinner } end |
#loading_spinner_svg(css_class: "mr-1 h-4 w-4 animate-spin text-blue-500") ⇒ Object
Helper to render the loading spinner SVG via IconComponent. Accepts a custom css_class to override the default spinner styling.
192 193 194 |
# File 'app/helpers/source_monitor/application_helper.rb', line 192 def loading_spinner_svg(css_class: "mr-1 h-4 w-4 animate-spin text-blue-500") render SourceMonitor::IconComponent.new(:spinner, size: nil, css_class: css_class) end |
#pagination_page_numbers(current_page:, total_pages:, window: 2) ⇒ Object
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 |
# File 'app/helpers/source_monitor/application_helper.rb', line 250 def pagination_page_numbers(current_page:, total_pages:, window: 2) return [ 1 ] if total_pages <= 1 # When total pages fit without gaps, show them all if total_pages <= (2 * window) + 3 return (1..total_pages).to_a end pages = [ 1, total_pages ] ((current_page - window)..(current_page + window)).each do |p| pages << p if p >= 1 && p <= total_pages end pages = pages.uniq.sort result = [] last = 0 pages.each do |p| result << :gap if p > last + 1 result << p last = p end result end |
#source_favicon_tag(source, size: 24, **options) ⇒ Object
Renders the source favicon as an <img> tag or a colored-circle initials placeholder when no favicon is attached. Handles the case where ActiveStorage is not loaded (host app without AS).
Options:
size: pixel dimension for width/height (default: 24)
class: additional CSS classes
240 241 242 243 244 245 246 247 248 |
# File 'app/helpers/source_monitor/application_helper.rb', line 240 def source_favicon_tag(source, size: 24, **) css = .delete(:class) || "" if favicon_attached?(source) favicon_image_tag(source, size: size, css: css) else favicon_placeholder_tag(source, size: size, css: css) end end |
#source_monitor_javascript_bundle_tag ⇒ Object
14 15 16 17 18 19 |
# File 'app/helpers/source_monitor/application_helper.rb', line 14 def source_monitor_javascript_bundle_tag javascript_include_tag("source_monitor/application", "data-turbo-track": "reload", type: "module") rescue StandardError => error log_source_monitor_asset_error(:javascript, error) nil end |
#source_monitor_stylesheet_bundle_tag ⇒ Object
7 8 9 10 11 12 |
# File 'app/helpers/source_monitor/application_helper.rb', line 7 def source_monitor_stylesheet_bundle_tag stylesheet_link_tag("source_monitor/application", "data-turbo-track": "reload") rescue StandardError => error log_source_monitor_asset_error(:stylesheet, error) nil end |