discourse/app/controllers/admin/web_hooks_controller.rb
Guhyoun Nam 784c04ea81
FEATURE: Add Mechanism to redeliver all failed webhook events (#27609)
Background:
In order to redrive failed webhook events, an operator has to go through and click on each. This PR is adding a mechanism to retry all failed events to help resolve issues quickly once the underlying failure has been resolved.

What is the change?:
Previously, we had to redeliver each webhook event. This merge is adding a 'Redeliver Failed' button next to the webhook event filter to redeliver all failed events. If there is no failed webhook events to redeliver, 'Redeliver Failed' gets disabled. If you click it, a window pops up to confirm the operator. Failed webhook events will be added to the queue and webhook event list will show the redelivering progress. Every minute, a job will be ran to go through 20 events to redeliver. Every hour, a job will cleanup the redelivering events which have been stored more than 8 hours.
2024-07-08 15:43:16 -05:00

194 lines
5.3 KiB
Ruby

# frozen_string_literal: true
class Admin::WebHooksController < Admin::AdminController
before_action :fetch_web_hook,
only: %i[show update destroy list_events bulk_events ping redeliver_failed_events]
def index
limit = 50
offset = params[:offset].to_i
web_hooks =
WebHook
.limit(limit)
.offset(offset)
.includes(:web_hook_event_types)
.includes(:categories)
.includes(:groups)
data = serialize_data(web_hooks, AdminWebHookSerializer, root: "web_hooks")
serialized_grouped_event_types =
WebHookEventType.active_grouped.transform_values do |array|
serialize_data(array, WebHookEventTypeSerializer)
end
json = {
web_hooks: data.delete("web_hooks"),
extras:
data.merge(
grouped_event_types: serialized_grouped_event_types,
default_event_types:
serialize_data(WebHook.default_event_types, WebHookEventTypeSerializer),
content_types: WebHook.content_types.map { |name, id| { id: id, name: name } },
delivery_statuses:
WebHook.last_delivery_statuses.map { |name, id| { id: id, name: name.to_s } },
),
total_rows_web_hooks: WebHook.count,
load_more_web_hooks:
admin_web_hooks_path(limit: limit, offset: offset + limit, format: :json),
}
render json: MultiJson.dump(json), status: 200
end
def show
data = serialize_data(@web_hook, AdminWebHookSerializer, root: "web_hook")
web_hook = data.delete("web_hook")
data = { "extras" => data, "web_hook" => web_hook }
render json: MultiJson.dump(data), status: 200
end
def edit
data = serialize_data(@web_hook, AdminWebHookSerializer, root: "web_hook")
data["extras"] = { "categories" => data.delete(:categories) }
render json: MultiJson.dump(data), status: 200
end
def create
web_hook = WebHook.new(web_hook_params)
if web_hook.save
StaffActionLogger.new(current_user).log_web_hook(
web_hook,
UserHistory.actions[:web_hook_create],
)
render_serialized(web_hook, AdminWebHookSerializer, root: "web_hook")
else
render_json_error web_hook.errors.full_messages
end
end
def update
if @web_hook.update(web_hook_params)
StaffActionLogger.new(current_user).log_web_hook(
@web_hook,
UserHistory.actions[:web_hook_update],
changes: @web_hook.saved_changes,
)
render_serialized(@web_hook, AdminWebHookSerializer, root: "web_hook")
else
render_json_error @web_hook.errors.full_messages
end
end
def destroy
@web_hook.destroy!
StaffActionLogger.new(current_user).log_web_hook(
@web_hook,
UserHistory.actions[:web_hook_destroy],
)
render json: success_json
end
def list_events
limit = 50
offset = params[:offset].to_i
events = @web_hook.web_hook_events.includes(:redelivering_webhook_event)
status = params[:status]
if status == "successful"
events = events.successful
elsif status == "failed"
events = events.failed
end
total = events.count
events = events.limit(limit).offset(offset)
json = {
web_hook_events: serialize_data(events, AdminWebHookEventSerializer),
total_rows_web_hook_events: total,
load_more_web_hook_events:
web_hook_events_admin_api_index_path(
limit: limit,
offset: offset + limit,
status: status,
format: :json,
),
extras: {
web_hook_id: @web_hook.id,
},
}
render json: MultiJson.dump(json), status: 200
end
def bulk_events
params.require(:ids)
web_hook_events = @web_hook.web_hook_events.where(id: params[:ids])
render_serialized(web_hook_events, AdminWebHookEventSerializer)
end
def redeliver_event
web_hook_event = WebHookEvent.find_by(id: params[:event_id])
if web_hook_event
web_hook = web_hook_event.web_hook
emitter = WebHookEmitter.new(web_hook, web_hook_event)
emitter.emit!(headers: MultiJson.load(web_hook_event.headers), body: web_hook_event.payload)
render_serialized(web_hook_event, AdminWebHookEventSerializer, root: "web_hook_event")
else
render json: failed_json
end
end
def redeliver_failed_events
web_hook_events =
@web_hook
.web_hook_events
.includes(:redelivering_webhook_event)
.not_ping
.where(id: params[:event_ids])
raise Discourse::InvalidParameters if web_hook_events.count.zero?
web_hook_events.each do |web_hook_event|
if !web_hook_event.redelivering_webhook_event
RedeliveringWebhookEvent.create!(web_hook_event: web_hook_event)
end
end
render json: { event_ids: web_hook_events.map(&:id) }
end
def ping
Jobs.enqueue(
:emit_web_hook_event,
web_hook_id: @web_hook.id,
event_type: "ping",
event_name: "ping",
)
render json: success_json
end
private
def web_hook_params
params.require(:web_hook).permit(
:payload_url,
:content_type,
:secret,
:wildcard_web_hook,
:active,
:verify_certificate,
web_hook_event_type_ids: [],
group_ids: [],
tag_names: [],
category_ids: [],
)
end
def fetch_web_hook
@web_hook = WebHook.find(params[:id])
end
end