Module: Wurk::Api::Serializers
- Defined in:
- app/controllers/wurk/api/serializers.rb
Overview
Pure mapping from inspector objects → JSON-shaped Hashes for the dashboard SPA. Keeping the serializers out of the controller lets the action methods stay tiny and lets future endpoints share the same field shapes without re-implementing them.
Class Method Summary collapse
- .cron_row(loop_obj, now_epoch) ⇒ Object
-
.flow_detail(status) ⇒ Object
The graph.
-
.flow_node(node) ⇒ Object
errorcarries a broken pipe's reason (slice 11 decision 2) — the one node state whose cause is not discoverable from the job itself, because no job ever ran. -
.flow_row(status) ⇒ Object
One row of the flow listing.
-
.history_point(row) ⇒ Object
One point in a cluster-total time-series (Wurk::Metrics::Query.history).
-
.job_record(record) ⇒ Object
Host-registered custom job-info rows (spec §25.2) ride along as
custom_rowsfor the SPA's job-detail modal (see Config#job_info_pairs, which gates on registration so the common no-extension case is free). - .limiter_row(name, meta) ⇒ Object
-
.limiter_status(name, meta) ⇒ Object
Reconstruct the limiter (read-only,
register: false) just to read its uniform{ used, limit, reset_at, available? }status for the Limits tab. - .metric_row(klass, totals) ⇒ Object
- .parse_options(raw) ⇒ Object
-
.process_row(process, leader_identity: nil) ⇒ Object
leader_identityis the cluster'sdear-leadervalue (ProcessSet#leader, memoized to one Redis GET per request) — comparing here avoids the N+1Process#leader?GET-per-row that a per-process lookup would cost. -
.profile_record(rec) ⇒ Object
One profile row for the Profiles pane.
-
.queue_history_series(row) ⇒ Object
One queue's size/latency gauge series (Wurk::Metrics::Query.queue_history).
- .queue_summary(summary) ⇒ Object
- .sorted_entry(entry) ⇒ Object
-
.stats_payload(stats) ⇒ Object
Wire-shape consumed by the SolidJS dashboard's landing page + SSE feed.
-
.work_row(process_id, thread_id, work) ⇒ Object
One in-flight job (WorkSet row) for the Busy page's process detail.
Class Method Details
.cron_row(loop_obj, now_epoch) ⇒ Object
128 129 130 131 132 133 134 135 136 137 138 139 140 |
# File 'app/controllers/wurk/api/serializers.rb', line 128 def cron_row(loop_obj, now_epoch) { lid: loop_obj.lid, schedule: loop_obj.schedule, klass: loop_obj.klass, queue: loop_obj.queue, tz: loop_obj.tz_name, paused: loop_obj.paused?, args: loop_obj.args, last_fire_at: loop_obj.last_fired_at, next_fire_at: loop_obj.next_fire_at(now_epoch) } end |
.flow_detail(status) ⇒ Object
The graph. Nodes ride inside the same document rather than behind a
second request: the SPA lays the DAG out from depends_on, and a
header fetched separately from its edges can describe a different
revision of the flow than the one being drawn.
165 166 167 168 169 170 |
# File 'app/controllers/wurk/api/serializers.rb', line 165 def flow_detail(status) flow_row(status).merge( dead_nodes: status.dead_indexes, nodes: status.nodes.map { |node| flow_node(node) } ) end |
.flow_node(node) ⇒ Object
error carries a broken pipe's reason (slice 11 decision 2) — the one
node state whose cause is not discoverable from the job itself, because
no job ever ran.
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 |
# File 'app/controllers/wurk/api/serializers.rb', line 175 def flow_node(node) { index: node.index, name: node.name, klass: node.klass, queue: node.queue, jid: node.jid, bid: node.bid, state: node.state, depends_on: node.dependencies, dependents: node.dependents, remaining: node.remaining, piped: node.piped?, error: node.error } end |
.flow_row(status) ⇒ Object
One row of the flow listing. Header fields only: Flow::Status reads its node records lazily, and a page of 25 flows that each fetched a thousand of them would be 25,000 HMGETs to render a progress bar.
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 |
# File 'app/controllers/wurk/api/serializers.rb', line 145 def flow_row(status) { fid: status.fid, state: status.state, total: status.total, pending: status.pending, succeeded: status.succeeded_count, depth: status.depth, width: status.width, created_at: status.created_at, finished_at: status.finished_at, failed_at: status.failed_at, abandoned_at: status.abandoned_at } end |
.history_point(row) ⇒ Object
One point in a cluster-total time-series (Wurk::Metrics::Query.history).
at is epoch seconds at the bucket start.
198 199 200 |
# File 'app/controllers/wurk/api/serializers.rb', line 198 def history_point(row) { at: row[:at], processed: row[:p], failed: row[:f], runtime_ms: row[:ms] } end |
.job_record(record) ⇒ Object
Host-registered custom job-info rows (spec §25.2) ride along as
custom_rows for the SPA's job-detail modal (see Config#job_info_pairs,
which gates on registration so the common no-extension case is free).
Divergence: wurk evaluates these during job-list serialization — the SPA
renders job detail client-side — not in a dedicated server detail view.
42 43 44 45 46 47 48 49 50 51 52 53 |
# File 'app/controllers/wurk/api/serializers.rb', line 42 def job_record(record) base = { jid: record.jid, klass: record.display_class, args: record.display_args, queue: record.queue, enqueued_at: record.enqueued_at&.to_f, created_at: record.created_at&.to_f } rows = ::Wurk::Web.config.job_info_pairs(record) rows.empty? ? base : base.merge(custom_rows: rows) end |
.limiter_row(name, meta) ⇒ Object
107 108 109 110 111 112 113 114 115 |
# File 'app/controllers/wurk/api/serializers.rb', line 107 def limiter_row(name, ) { name: name, type: ['type'].to_s, fingerprint: ['fingerprint'].to_s, options: (['options']), status: limiter_status(name, ) } end |
.limiter_status(name, meta) ⇒ Object
Reconstruct the limiter (read-only, register: false) just to read
its uniform { used, limit, reset_at, available? } status for the
Limits tab. Best-effort: a malformed meta hash yields nil rather than
500-ing the whole list.
121 122 123 124 125 126 |
# File 'app/controllers/wurk/api/serializers.rb', line 121 def limiter_status(name, ) limiter = ::Wurk::Limiter.build(name, ['type'], (['options'])) limiter&.status rescue StandardError nil end |
.metric_row(klass, totals) ⇒ Object
192 193 194 |
# File 'app/controllers/wurk/api/serializers.rb', line 192 def metric_row(klass, totals) { klass: klass, processed: totals[:p], failed: totals[:f], runtime_ms: totals[:ms] } end |
.parse_options(raw) ⇒ Object
209 210 211 212 213 214 215 |
# File 'app/controllers/wurk/api/serializers.rb', line 209 def (raw) return {} if raw.nil? || raw.to_s.empty? ::JSON.parse(raw) rescue ::JSON::ParserError {} end |
.process_row(process, leader_identity: nil) ⇒ Object
leader_identity is the cluster's dear-leader value (ProcessSet#leader,
memoized to one Redis GET per request) — comparing here avoids the N+1
Process#leader? GET-per-row that a per-process lookup would cost.
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
# File 'app/controllers/wurk/api/serializers.rb', line 69 def process_row(process, leader_identity: nil) # rubocop:disable Metrics/AbcSize { identity: process.identity, hostname: process['hostname'], pid: process['pid'], tag: process.tag, concurrency: process['concurrency'], busy: process['busy'], beat: process['beat'], quiet: process.stopping?, leader: !leader_identity.to_s.empty? && process.identity == leader_identity, rss: process['rss'], rtt_us: process['rtt_us'], started_at: process['started_at'], cpu_model: process['cpu_model'], cores: process['cores'], memory_total_kb: process['memory_total_kb'], labels: process.labels, queues: process.queues, version: process.version, embedded: process. } end |
.profile_record(rec) ⇒ Object
One profile row for the Profiles pane. key drives the view/data links.
218 219 220 221 222 223 224 225 226 227 228 |
# File 'app/controllers/wurk/api/serializers.rb', line 218 def profile_record(rec) { key: rec.key, jid: rec.jid, token: rec.token, type: rec.type, size: rec.size, elapsed: rec.elapsed, started_at: rec.started_at&.to_i } end |
.queue_history_series(row) ⇒ Object
One queue's size/latency gauge series (Wurk::Metrics::Query.queue_history).
points are oldest→newest; at is epoch seconds at the bucket start,
size is queue depth, latency is head-of-line wait in seconds.
205 206 207 |
# File 'app/controllers/wurk/api/serializers.rb', line 205 def queue_history_series(row) { name: row[:name], points: row[:points].map { |p| { at: p[:at], size: p[:size], latency: p[:latency] } } } end |
.queue_summary(summary) ⇒ Object
33 34 35 |
# File 'app/controllers/wurk/api/serializers.rb', line 33 def queue_summary(summary) { name: summary.name, size: summary.size, latency: summary.latency, paused: summary.paused? } end |
.sorted_entry(entry) ⇒ Object
55 56 57 58 59 60 61 62 63 64 |
# File 'app/controllers/wurk/api/serializers.rb', line 55 def sorted_entry(entry) job_record(entry).merge( score: entry.score, at: entry.at.to_f, error_class: entry['error_class'].to_s, error_message: entry['error_message'].to_s, retry_count: entry['retry_count'], error_backtrace: entry.error_backtrace ) end |
.stats_payload(stats) ⇒ Object
Wire-shape consumed by the SolidJS dashboard's landing page + SSE feed.
Field names match the SPA's StatsSnapshot interface in
frontend/src/hooks/useSSE.ts — keep them in sync. The canonical
Sidekiq-compatible accessors on Wurk::Stats use _size suffixes;
this serializer renames them for the dashboard's wire shape only.
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
# File 'app/controllers/wurk/api/serializers.rb', line 17 def stats_payload(stats) { processed: stats.processed, failed: stats.failed, expired: stats.expired, enqueued: stats.enqueued, busy: stats.workers_size, scheduled: stats.scheduled_size, retries: stats.retry_size, dead: stats.dead_size, processes: stats.processes_size, latency: stats.default_queue_latency, queues: stats.queue_summaries.map { |q| queue_summary(q) } } end |
.work_row(process_id, thread_id, work) ⇒ Object
One in-flight job (WorkSet row) for the Busy page's process detail.
94 95 96 97 98 99 100 101 102 103 104 105 |
# File 'app/controllers/wurk/api/serializers.rb', line 94 def work_row(process_id, thread_id, work) record = work.job { process_id: process_id, thread_id: thread_id, queue: work.queue, klass: record.display_class, args: record.display_args, jid: record.jid, run_at: work.run_at.to_f } end |