Class: Wurk::ApiController

Inherits:
ApplicationController show all
Includes:
ActionController::Live
Defined in:
app/controllers/wurk/api_controller.rb

Overview

JSON APIs consumed by the React SPA. Action methods stay thin; mapping to the wire shape lives in ‘Wurk::Api::Serializers`, and pagination lives in `Wurk::Api::Pagination`. SSE lives in #stream.

Wire-compat: every payload field reads from the canonical Wurk inspector objects (Stats, Queue, RetrySet, ScheduledSet, DeadSet, ProcessSet, BatchSet, Cron::LoopSet) so dashboards stay aligned with the Redis schema in ‘docs/target/sidekiq-free,pro,ent.md`.

Constant Summary collapse

STREAM_TICK_SECONDS =
2.0
STREAM_MAX_DURATION =
600.0
HISTORY_WINDOW_UNITS =
{ 's' => 1, 'm' => 60, 'h' => 3600, 'd' => 86_400 }.freeze
DEFAULT_HISTORY_WINDOW =
24 * 3600

Instance Method Summary collapse

Instance Method Details

#batchObject



69
70
71
72
73
74
75
76
# File 'app/controllers/wurk/api_controller.rb', line 69

def batch
  status = ::Wurk::Batch::Status.new(params[:bid].to_s)
  return render(json: { error: 'unknown batch' }, status: :not_found) unless status.exists?

  render json: status.data.transform_keys(&:to_sym)
rescue ::ArgumentError
  render json: { error: 'unknown batch' }, status: :not_found
end

#batchesObject



62
63
64
65
66
67
# File 'app/controllers/wurk/api_controller.rb', line 62

def batches
  set = ::Wurk::BatchSet.new
  page = ::Wurk::Api::Pagination.window(params)
  rows = ::Wurk::Api::Pagination.slice(set, page) { |status| status.data.transform_keys(&:to_sym) }
  render json: { total: set.size, page: page[:page], count: page[:count], batches: rows }
end

#cronObject



89
90
91
92
# File 'app/controllers/wurk/api_controller.rb', line 89

def cron
  now = ::Time.now.to_i
  render json: ::Wurk::Cron::LoopSet.new.map { |lp| ::Wurk::Api::Serializers.cron_row(lp, now) }
end

#cron_historyObject



104
105
106
# File 'app/controllers/wurk/api_controller.rb', line 104

def cron_history
  render json: { lid: params[:lid].to_s, history: ::Wurk::Web::Enterprise::Periodic.history(params[:lid].to_s) }
end

#deadObject



56
# File 'app/controllers/wurk/api_controller.rb', line 56

def dead      = render_sorted_set(::Wurk::DeadSet.new)

#enqueue_cronObject



97
98
99
100
101
102
# File 'app/controllers/wurk/api_controller.rb', line 97

def enqueue_cron
  jid = ::Wurk::Web::Enterprise::Periodic.enqueue_now(params[:lid].to_s)
  return render(json: { error: 'unknown loop' }, status: :not_found) if jid.nil?

  render json: { ok: true, jid: jid }
end

#historyObject

Cluster-total throughput/failures time-series for the dashboard charts. ‘:bucket` is 1m/5m/1h; `?window=24h` (s/m/h/d suffix) is clamped to the bucket’s retention. Recharts-ready array under ‘series`.



129
130
131
132
133
134
135
# File 'app/controllers/wurk/api_controller.rb', line 129

def history
  window = parse_window(params[:window])
  series = ::Wurk::Web::Enterprise::Historical.history(params[:bucket].to_s, window: window)
  render json: { bucket: params[:bucket].to_s, window: window, series: series.map { |row| ::Wurk::Api::Serializers.history_point(row) } }
rescue ::ArgumentError => e
  render json: { error: e.message }, status: :bad_request
end

#limitersObject



78
79
80
81
82
# File 'app/controllers/wurk/api_controller.rb', line 78

def limiters
  names = ::Wurk::Web::Enterprise::Limits.list(filter: params[:substr])
  page = ::Wurk::Api::Pagination.window(params)
  render json: { total: names.size, page: page[:page], count: page[:count], limiters: limiter_rows(names, page) }
end

#metaObject

Boot-time flags the SPA reads once to shape the UI (e.g. hide destructive actions and show the read-only banner). Always a GET, so it stays reachable while read-only mode blocks mutations.



32
33
34
# File 'app/controllers/wurk/api_controller.rb', line 32

def meta
  render json: { read_only: ::Wurk::Web.config.read_only? }
end

#metricsObject



108
109
110
111
112
113
114
# File 'app/controllers/wurk/api_controller.rb', line 108

def metrics
  minutes = ::Wurk::Api::Pagination.clamp_int(params[:minutes], 1, ::Wurk::Metrics::Query::MAX_MINUTES, 60)
  rows = ::Wurk::Web::Enterprise::Historical.top(minutes: minutes, class_filter: params[:substr])
  render json: { minutes: minutes, top_jobs: rows.map { |(klass, totals)| ::Wurk::Api::Serializers.metric_row(klass, totals) } }
rescue ::Wurk::Metrics::Query::WindowTooWide => e
  render json: { error: e.message }, status: :bad_request
end

#metrics_for_jobObject



116
117
118
119
120
121
122
123
124
# File 'app/controllers/wurk/api_controller.rb', line 116

def metrics_for_job
  klass = params[:klass].to_s
  minutes, hours = metrics_window(params)
  rows = ::Wurk::Web::Enterprise::Historical.for_job(klass, minutes: minutes, hours: hours)
  series = rows.map { |row| row.merge(at: row[:at].to_f) }
  render json: { klass: klass, minutes: minutes, hours: hours, series: series }
rescue ::ArgumentError, ::Wurk::Metrics::Query::WindowTooWide => e
  render json: { error: e.message }, status: :bad_request
end

#pause_cronObject



94
# File 'app/controllers/wurk/api_controller.rb', line 94

def pause_cron   = render_cron_action(::Wurk::Web::Enterprise::Periodic.pause(params[:lid].to_s))

#processesObject



58
59
60
# File 'app/controllers/wurk/api_controller.rb', line 58

def processes
  render json: ::Wurk::ProcessSet.new.map { |p| ::Wurk::Api::Serializers.process_row(p) }
end

#queueObject



44
45
46
47
48
49
50
51
52
# File 'app/controllers/wurk/api_controller.rb', line 44

def queue
  q = ::Wurk::Queue.new(params[:name].to_s)
  page = ::Wurk::Api::Pagination.window(params)
  jobs = ::Wurk::Api::Pagination.slice(q, page) { |rec| ::Wurk::Api::Serializers.job_record(rec) }
  render json: {
    name: q.name, size: q.size, latency: q.latency, paused: q.paused?,
    page: page[:page], count: page[:count], jobs: jobs
  }
end

#queuesObject



40
41
42
# File 'app/controllers/wurk/api_controller.rb', line 40

def queues
  render json: ::Wurk::Stats.new.queue_summaries.map { |q| ::Wurk::Api::Serializers.queue_summary(q) }
end

#reset_limiterObject



84
85
86
87
# File 'app/controllers/wurk/api_controller.rb', line 84

def reset_limiter
  ::Wurk::Web::Enterprise::Limits.reset(params[:name].to_s)
  render json: { ok: true }
end

#retriesObject



54
# File 'app/controllers/wurk/api_controller.rb', line 54

def retries   = render_sorted_set(::Wurk::RetrySet.new)

#scheduledObject



55
# File 'app/controllers/wurk/api_controller.rb', line 55

def scheduled = render_sorted_set(::Wurk::ScheduledSet.new)

#searchObject



137
138
139
140
141
142
143
# File 'app/controllers/wurk/api_controller.rb', line 137

def search
  substr = params[:substr].to_s
  return render(json: { substr: substr, total: 0, hits: [] }) if substr.empty?

  hits = ::Wurk::Web::Search.new(substr, kinds: parse_search_kinds(params), limit: parse_search_limit(params)).to_a
  render json: { substr: substr, total: hits.size, hits: hits }
end

#statsObject



36
37
38
# File 'app/controllers/wurk/api_controller.rb', line 36

def stats
  render json: ::Wurk::Api::Serializers.stats_payload(::Wurk::Stats.new)
end

#streamObject

SSE: one ‘event: stats` per tick with a fresh Stats snapshot. Caps at `STREAM_MAX_DURATION` so a stale browser tab can’t tie a Rails worker forever — the client reconnects automatically when the stream closes.

‘?max_duration=` and `?tick=` are test/debug knobs; the SPA never sets them. `?max_duration=0` emits one tick and closes.



151
152
153
154
155
156
157
158
# File 'app/controllers/wurk/api_controller.rb', line 151

def stream
  stream_headers!
  clamp = ::Wurk::Api::Pagination.method(:clamp_float)
  tick = clamp.call(params[:tick], 0.0, STREAM_TICK_SECONDS, STREAM_TICK_SECONDS)
  max_dur = clamp.call(params[:max_duration], 0.0, STREAM_MAX_DURATION, STREAM_MAX_DURATION)
  sse = ::ActionController::Live::SSE.new(response.stream, retry: (STREAM_TICK_SECONDS * 1000).to_i)
  drive_stream(sse, tick, max_dur)
end

#unpause_cronObject



95
# File 'app/controllers/wurk/api_controller.rb', line 95

def unpause_cron = render_cron_action(::Wurk::Web::Enterprise::Periodic.unpause(params[:lid].to_s))