refresh webhook status
This commit is contained in:
@@ -1,12 +1,10 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class WebhookEventsController < ApplicationController
|
class WebhookEventsController < ApplicationController
|
||||||
load_and_authorize_resource :webhook_url, parent: false, only: %i[show resend], id_param: :webhook_id
|
load_and_authorize_resource :webhook_url, parent: false, id_param: :webhook_id
|
||||||
|
before_action :load_webhook_event
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@webhook_event = @webhook_url.webhook_events.find_by!(uuid: params[:id])
|
|
||||||
@webhook_attempts = @webhook_event.webhook_attempts.order(created_at: :desc)
|
|
||||||
|
|
||||||
return unless current_ability.can?(:read, @webhook_event.record)
|
return unless current_ability.can?(:read, @webhook_event.record)
|
||||||
|
|
||||||
@data =
|
@data =
|
||||||
@@ -23,10 +21,10 @@ class WebhookEventsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def resend
|
def resend
|
||||||
@webhook_event = @webhook_url.webhook_events.find_by!(uuid: params[:id])
|
|
||||||
|
|
||||||
id_key = WebhookUrls::EVENT_TYPE_ID_KEYS.fetch(@webhook_event.event_type.split('.').first)
|
id_key = WebhookUrls::EVENT_TYPE_ID_KEYS.fetch(@webhook_event.event_type.split('.').first)
|
||||||
|
|
||||||
|
last_attempt_id = @webhook_event.webhook_attempts.maximum(:id)
|
||||||
|
|
||||||
WebhookUrls::EVENT_TYPE_TO_JOB_CLASS[@webhook_event.event_type].perform_async(
|
WebhookUrls::EVENT_TYPE_TO_JOB_CLASS[@webhook_event.event_type].perform_async(
|
||||||
id_key => @webhook_event.record_id,
|
id_key => @webhook_event.record_id,
|
||||||
'webhook_url_id' => @webhook_event.webhook_url_id,
|
'webhook_url_id' => @webhook_event.webhook_url_id,
|
||||||
@@ -35,6 +33,34 @@ class WebhookEventsController < ApplicationController
|
|||||||
'last_status' => 0
|
'last_status' => 0
|
||||||
)
|
)
|
||||||
|
|
||||||
head :ok
|
render turbo_stream: [
|
||||||
|
turbo_stream.after(
|
||||||
|
params[:button_id],
|
||||||
|
helpers.tag.submit_form(
|
||||||
|
helpers.button_to('', refresh_settings_webhook_event_path(@webhook_url.id, @webhook_event.uuid),
|
||||||
|
params: { last_attempt_id: }),
|
||||||
|
class: 'hidden', data: { interval: 3_000 }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
def refresh
|
||||||
|
return head :ok if @webhook_event.webhook_attempts.maximum(:id) == params[:last_attempt_id].to_i
|
||||||
|
|
||||||
|
render turbo_stream: [
|
||||||
|
turbo_stream.replace(helpers.dom_id(@webhook_event),
|
||||||
|
partial: 'event_row',
|
||||||
|
locals: { with_status: true, webhook_url: @webhook_url, webhook_event: @webhook_event }),
|
||||||
|
turbo_stream.replace("drawer_events_#{helpers.dom_id(@webhook_event)}",
|
||||||
|
partial: 'drawer_events',
|
||||||
|
locals: { webhook_url: @webhook_url, webhook_event: @webhook_event })
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def load_webhook_event
|
||||||
|
@webhook_event = @webhook_url.webhook_events.find_by!(uuid: params[:id])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ class WebhookSettingsController < ApplicationController
|
|||||||
def resend
|
def resend
|
||||||
submitter = current_account.submitters.where.not(completed_at: nil).order(:id).last
|
submitter = current_account.submitters.where.not(completed_at: nil).order(:id).last
|
||||||
|
|
||||||
|
authorize!(:read, submitter)
|
||||||
|
|
||||||
if submitter.blank? || @webhook_url.blank?
|
if submitter.blank? || @webhook_url.blank?
|
||||||
return redirect_back(fallback_location: settings_webhooks_path,
|
return redirect_back(fallback_location: settings_webhooks_path,
|
||||||
alert: I18n.t('unable_to_resend_webhook_request'))
|
alert: I18n.t('unable_to_resend_webhook_request'))
|
||||||
|
|||||||
@@ -1,5 +1,17 @@
|
|||||||
export default class extends HTMLElement {
|
export default class extends HTMLElement {
|
||||||
connectedCallback () {
|
connectedCallback () {
|
||||||
this.querySelector('form').requestSubmit()
|
if (this.dataset.interval) {
|
||||||
|
this.interval = setInterval(() => {
|
||||||
|
this.querySelector('form').requestSubmit()
|
||||||
|
}, parseInt(this.dataset.interval))
|
||||||
|
} else {
|
||||||
|
this.querySelector('form').requestSubmit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback () {
|
||||||
|
if (this.interval) {
|
||||||
|
clearInterval(this.interval)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,6 @@ class WebhookAttempt < ApplicationRecord
|
|||||||
belongs_to :webhook_event
|
belongs_to :webhook_event
|
||||||
|
|
||||||
def success?
|
def success?
|
||||||
response_status_code.to_i / 100 == 2
|
[2, 3].include?(response_status_code.to_i / 100)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
<div id="drawer_events_<%= dom_id(webhook_event) %>">
|
||||||
|
<ol class="relative border-s border-base-300 space-y-6 ml-3">
|
||||||
|
<% webhook_attempts = webhook_event.webhook_attempts.sort_by { |e| -e.id } %>
|
||||||
|
<% if webhook_event.status == 'error' %>
|
||||||
|
<% last_attempt = webhook_attempts.select { |e| e.attempt < SendWebhookRequest::MANUAL_ATTEMPT }.max_by(&:attempt) %>
|
||||||
|
<% if webhook_event.webhook_attempts.none?(&:success?) && last_attempt.attempt <= 10 %>
|
||||||
|
<li class="ml-7">
|
||||||
|
<span class="btn btn-outline btn-xs btn-circle pointer-events-none absolute justify-center border-base-content-/60 text-base-content/60 bg-base-100" style="left: -12px;">
|
||||||
|
<%= svg_icon('clock', class: 'w-4 h-4 shrink-0') %>
|
||||||
|
</span>
|
||||||
|
<p class="leading-none text-base-content/90 pt-1">
|
||||||
|
<%= t('next_attempt_in_time_in_words', time_in_words: distance_of_time_in_words(Time.current, last_attempt.created_at + (2**last_attempt.attempt).minutes)) %>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
<% if webhook_attempts.present? %>
|
||||||
|
<% webhook_attempts.each do |webhook_attempt| %>
|
||||||
|
<li class="ml-7">
|
||||||
|
<span class="btn btn-outline btn-xs btn-circle pointer-events-none absolute justify-center <%= webhook_attempt.success? ? 'btn-success bg-lime-50' : 'btn-error bg-red-50' %>" style="left: -12px;">
|
||||||
|
<%= svg_icon(webhook_attempt.success? ? 'check' : 'x', class: 'w-4 h-4 shrink-0') %>
|
||||||
|
</span>
|
||||||
|
<p class="leading-none text-sm text-base-content/60 pt-1">
|
||||||
|
<%= l(webhook_attempt.created_at.in_time_zone(current_account.timezone), format: :long, locale: current_account.locale) %>
|
||||||
|
</p>
|
||||||
|
<div class="mt-2">
|
||||||
|
<p class="text-sm font-bold text-base-content/80">
|
||||||
|
<span><%= Rack::Utils::HTTP_STATUS_CODES[webhook_attempt.response_status_code] %></span>
|
||||||
|
<% if webhook_attempt.response_status_code.positive? %>
|
||||||
|
<span>(<%= webhook_attempt.response_status_code %>)</span>
|
||||||
|
<% end %>
|
||||||
|
</p>
|
||||||
|
<% unless webhook_attempt.success? %>
|
||||||
|
<p class="text-sm text-base-content/80 mt-1">
|
||||||
|
<%= webhook_attempt.response_body.presence || Rack::Utils::HTTP_STATUS_CODES[webhook_attempt.response_status_code] %>
|
||||||
|
</p>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<% end %>
|
||||||
|
<% else %>
|
||||||
|
<li class="ml-7">
|
||||||
|
<span class="btn btn-outline btn-xs btn-circle pointer-events-none absolute justify-center btn-info bg-blue-50" style="left: -12px;">
|
||||||
|
<%= svg_icon('clock', class: 'w-4 h-4 shrink-0') %>
|
||||||
|
</span>
|
||||||
|
<p class="leading-none text-base-content/60 pt-1">
|
||||||
|
<%= l(webhook_event.created_at.in_time_zone(current_account.timezone), format: :long, locale: current_account.locale) %>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<% end %>
|
||||||
|
</ol>
|
||||||
|
<% unless webhook_event.status == 'pending' %>
|
||||||
|
<div class="absolute right-4 top-3">
|
||||||
|
<%= button_to button_title(title: t('resend'), disabled_with: t('awaiting'), icon: svg_icon('rotate', class: 'w-4 h-4'), icon_disabled: svg_icon('loader', class: 'w-4 h-4 animate-spin')), resend_settings_webhook_event_path(webhook_url.id, webhook_event.uuid), form: { id: button_uuid = SecureRandom.uuid }, params: { button_id: button_uuid }, class: 'btn btn-neutral btn-sm text-white', method: :post, onclick: '[this.form.requestSubmit(), this.disabled = true]' %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<div id="<%= dom_id(webhook_event) %>" class="group relative hover:cursor-pointer hover:bg-base-200">
|
||||||
|
<a href="<%= settings_webhook_event_path(webhook_url.id, webhook_event.uuid) %>" data-turbo-frame="drawer" class="top-0 bottom-0 left-0 right-0 absolute"></a>
|
||||||
|
<div class="min-h-12 flex flex-col md:flex-row md:items-center md:justify-between gap-2 px-3 py-2">
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<% if webhook_event.status == 'success' %>
|
||||||
|
<div class="btn btn-outline btn-xs btn-success bg-lime-50 gap-1">
|
||||||
|
<%= svg_icon('check', class: 'w-4 h-4 shrink-0 stroke-2') %>
|
||||||
|
<%= webhook_event.webhook_attempts.max_by(&:id)&.response_status_code if local_assigns[:with_status] %>
|
||||||
|
</div>
|
||||||
|
<% elsif webhook_event.status == 'pending' %>
|
||||||
|
<div class="btn btn-outline btn-xs btn-info bg-blue-50 gap-1">
|
||||||
|
<%= svg_icon('clock', class: 'w-4 h-4 shrink-0 stroke-2') %>
|
||||||
|
<%= webhook_event.webhook_attempts.max_by(&:id)&.response_status_code if local_assigns[:with_status] %>
|
||||||
|
</div>
|
||||||
|
<% elsif webhook_event.status == 'error' %>
|
||||||
|
<div class="btn btn-outline btn-xs btn-error bg-red-50 gap-1">
|
||||||
|
<%= svg_icon('x', class: 'w-4 h-4 shrink-0') %>
|
||||||
|
<%= webhook_event.webhook_attempts.max_by(&:id)&.response_status_code if local_assigns[:with_status] %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<div><%= webhook_event.event_type %></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<%= button_to button_title(title: t('resend'), disabled_with: t('awaiting'), icon: svg_icon('rotate', class: 'w-4 h-4'), icon_disabled: svg_icon('loader', class: 'w-4 h-4 animate-spin')), resend_settings_webhook_event_path(webhook_url.id, webhook_event.uuid), form: { id: button_uuid = SecureRandom.uuid }, params: { button_id: button_uuid }, class: 'btn btn-neutral btn-xs h-2 text-white relative z-[1] hidden md:group-hover:inline-block', data: { turbo_frame: :drawer }, method: :post, onclick: "[this.form.requestSubmit(), this.disabled = true, this.classList.remove('hidden')]" %>
|
||||||
|
<span><%= l(webhook_event.created_at, locale: current_account.locale, format: :short) %></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -1,57 +1,6 @@
|
|||||||
<%= render 'shared/turbo_drawer', title: @webhook_event.event_type, close_after_submit: false do %>
|
<%= render 'shared/turbo_drawer', title: @webhook_event.event_type, close_after_submit: false do %>
|
||||||
<div class="relative px-4 py-4">
|
<div class="relative px-4 py-4">
|
||||||
<ol class="relative border-s border-base-300 space-y-6 ml-3">
|
<%= render 'drawer_events', webhook_url: @webhook_url, webhook_event: @webhook_event %>
|
||||||
<% if @webhook_event.status == 'error' %>
|
|
||||||
<% last_attempt = @webhook_attempts.select { |e| SendWebhookRequest::AUTOMATED_RETRY_RANGE.cover?(e.attempt) }.max_by(&:attempt) %>
|
|
||||||
<% if SendWebhookRequest::AUTOMATED_RETRY_RANGE.cover?(last_attempt&.attempt) %>
|
|
||||||
<li class="ml-7">
|
|
||||||
<span class="btn btn-outline btn-xs btn-circle pointer-events-none absolute justify-center border-base-content-/60 text-base-content/60 bg-base-100" style="left: -12px;">
|
|
||||||
<%= svg_icon('clock', class: 'w-4 h-4 shrink-0') %>
|
|
||||||
</span>
|
|
||||||
<p class="leading-none text-base-content/90 pt-1">
|
|
||||||
<%= t('next_attempt_in_time_in_words', time_in_words: distance_of_time_in_words(Time.current, last_attempt.created_at + (2**last_attempt.attempt).minutes)) %>
|
|
||||||
</p>
|
|
||||||
</li>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
||||||
<% if @webhook_attempts.present? %>
|
|
||||||
<% @webhook_attempts.each do |webhook_attempt| %>
|
|
||||||
<li class="ml-7">
|
|
||||||
<span class="btn btn-outline btn-xs btn-circle pointer-events-none absolute justify-center <%= webhook_attempt.success? ? 'btn-success bg-lime-50' : 'btn-error bg-red-50' %>" style="left: -12px;">
|
|
||||||
<%= svg_icon(webhook_attempt.success? ? 'check' : 'x', class: 'w-4 h-4 shrink-0') %>
|
|
||||||
</span>
|
|
||||||
<p class="leading-none text-sm text-base-content/60 pt-1">
|
|
||||||
<%= l(webhook_attempt.created_at.in_time_zone(current_account.timezone), format: :long, locale: current_account.locale) %>
|
|
||||||
</p>
|
|
||||||
<div class="mt-2">
|
|
||||||
<p class="text-sm font-bold text-base-content/80">
|
|
||||||
<span><%= Rack::Utils::HTTP_STATUS_CODES[webhook_attempt.response_status_code] %></span>
|
|
||||||
<% if webhook_attempt.response_status_code.positive? %>
|
|
||||||
<span>(<%= webhook_attempt.response_status_code %>)</span>
|
|
||||||
<% end %>
|
|
||||||
</p>
|
|
||||||
<% unless webhook_attempt.success? %>
|
|
||||||
<p class="text-sm text-base-content/80 mt-1">
|
|
||||||
<%= webhook_attempt.response_body.presence || Rack::Utils::HTTP_STATUS_CODES[webhook_attempt.response_status_code] %>
|
|
||||||
</p>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<% end %>
|
|
||||||
<% else %>
|
|
||||||
<li class="ml-7">
|
|
||||||
<span class="btn btn-outline btn-xs btn-circle pointer-events-none absolute justify-center btn-info bg-blue-50" style="left: -12px;">
|
|
||||||
<%= svg_icon('clock', class: 'w-4 h-4 shrink-0') %>
|
|
||||||
</span>
|
|
||||||
<p class="leading-none text-base-content/60 pt-1">
|
|
||||||
<%= l(@webhook_event.created_at.in_time_zone(current_account.timezone), format: :long, locale: current_account.locale) %>
|
|
||||||
</p>
|
|
||||||
</li>
|
|
||||||
<% end %>
|
|
||||||
</ol>
|
|
||||||
<% unless @webhook_event.status == 'pending' %>
|
|
||||||
<%= button_to button_title(title: t('resend'), disabled_with: 'sending', icon: svg_icon('rotate', class: 'w-4 h-4'), icon_disabled: svg_icon('loader', class: 'w-4 h-4 animate-spin')), resend_settings_webhook_event_path(@webhook_url.id, @webhook_event.uuid), class: 'absolute right-4 top-3 btn btn-neutral btn-sm text-white', method: :post %>
|
|
||||||
<% end %>
|
|
||||||
<% if @data %>
|
<% if @data %>
|
||||||
<div class="mockup-code overflow-hidden relative pb-0 mt-6">
|
<div class="mockup-code overflow-hidden relative pb-0 mt-6">
|
||||||
<% response = JSON.pretty_generate({ event_type: @webhook_event.event_type, timestamp: @webhook_event.created_at.as_json, data: @data }) %>
|
<% response = JSON.pretty_generate({ event_type: @webhook_event.event_type, timestamp: @webhook_event.created_at.as_json, data: @data }) %>
|
||||||
|
|||||||
@@ -89,33 +89,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<% if @webhook_events.present? %>
|
<% if @webhook_events.present? %>
|
||||||
<div class="divide-y divide-base-300 rounded-lg">
|
<div class="divide-y divide-base-300 rounded-lg">
|
||||||
<% @webhook_events.each do |event| %>
|
<%= render partial: 'webhook_events/event_row', collection: @webhook_events, as: :webhook_event, locals: { webhook_url: @webhook_url } %>
|
||||||
<div class="group relative hover:cursor-pointer hover:bg-base-200">
|
|
||||||
<a href="<%= settings_webhook_event_path(@webhook_url.id, event.uuid) %>" data-turbo-frame="drawer" class="top-0 bottom-0 left-0 right-0 absolute"></a>
|
|
||||||
<div class="min-h-12 flex flex-col md:flex-row md:items-center md:justify-between gap-2 px-3 py-2">
|
|
||||||
<div class="flex items-center gap-4">
|
|
||||||
<% if event.status == 'success' %>
|
|
||||||
<div class="btn btn-outline btn-xs btn-success bg-lime-50 gap-2">
|
|
||||||
<%= svg_icon('check', class: 'w-4 h-4 shrink-0 stroke-2') %>
|
|
||||||
</div>
|
|
||||||
<% elsif event.status == 'pending' %>
|
|
||||||
<div class="btn btn-outline btn-xs btn-info bg-blue-50 gap-2">
|
|
||||||
<%= svg_icon('clock', class: 'w-4 h-4 shrink-0 stroke-2') %>
|
|
||||||
</div>
|
|
||||||
<% elsif event.status == 'error' %>
|
|
||||||
<div class="btn btn-outline btn-xs btn-error bg-red-50 gap-2">
|
|
||||||
<%= svg_icon('x', class: 'w-4 h-4 shrink-0') %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
<div><%= event.event_type %></div>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center gap-3">
|
|
||||||
<%= button_to button_title(title: t('resend'), disabled_with: t('sending'), icon: svg_icon('rotate', class: 'w-4 h-4'), icon_disabled: svg_icon('loader', class: 'w-4 h-4 animate-spin')), resend_settings_webhook_event_path(@webhook_url.id, event.uuid), class: 'btn btn-neutral btn-xs h-2 text-white relative z-[1] hidden md:group-hover:inline-block', data: { turbo_frame: :drawer }, method: :post %>
|
|
||||||
<span><%= l(event.created_at, locale: current_account.locale, format: :short) %></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="text-center py-4">
|
<div class="text-center py-4">
|
||||||
@@ -147,7 +121,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% elsif submitter = current_account.submitters.where.not(completed_at: nil).order(:id).last %>
|
<% elsif (submitter = current_account.submitters.where.not(completed_at: nil).order(:id).last) && can?(:read, submitter) %>
|
||||||
<div class="space-y-4 mt-4">
|
<div class="space-y-4 mt-4">
|
||||||
<div class="collapse collapse-open bg-base-200 px-1">
|
<div class="collapse collapse-open bg-base-200 px-1">
|
||||||
<div class="p-4 text-xl font-medium">
|
<div class="p-4 text-xl font-medium">
|
||||||
|
|||||||
@@ -182,6 +182,7 @@ Rails.application.routes.draw do
|
|||||||
|
|
||||||
resources :events, only: %i[show], controller: 'webhook_events' do
|
resources :events, only: %i[show], controller: 'webhook_events' do
|
||||||
post :resend, on: :member
|
post :resend, on: :member
|
||||||
|
post :refresh, on: :member
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
resource :account, only: %i[show update destroy]
|
resource :account, only: %i[show update destroy]
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ module SendWebhookRequest
|
|||||||
attempt:
|
attempt:
|
||||||
)
|
)
|
||||||
|
|
||||||
webhook_event.update!(status: response.success? ? 'success' : 'error')
|
webhook_event.update!(status: response.status.to_i >= 400 ? 'error' : 'success')
|
||||||
|
|
||||||
response
|
response
|
||||||
rescue StandardError
|
rescue StandardError
|
||||||
|
|||||||
Reference in New Issue
Block a user