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.
This commit is contained in:
Guhyoun Nam
2024-07-08 15:43:16 -05:00
committed by GitHub
parent 16a8a31c52
commit 784c04ea81
17 changed files with 305 additions and 4 deletions

View File

@@ -1,6 +1,10 @@
<li>
<div class="col first status">
<span class={{this.statusColorClasses}}>{{@event.status}}</span>
{{#if @event.redelivering}}
{{d-icon "sync"}}
{{else}}
<span class={{this.statusColorClasses}}>{{@event.status}}</span>
{{/if}}
</div>
<div class="col event-id">{{@event.id}}</div>

View File

@@ -13,6 +13,13 @@
class="delivery-status-filters"
/>
<DButton
@icon="sync"
@label="admin.web_hooks.events.redeliver_failed"
@action={{this.redeliverFailed}}
@disabled={{not this.redeliverEnabled}}
/>
<DButton
@icon="paper-plane"
@label="admin.web_hooks.events.ping"

View File

@@ -11,10 +11,12 @@ import I18n from "discourse-i18n";
export default class WebhookEvents extends Component {
@service messageBus;
@service store;
@service dialog;
@tracked pingEnabled = true;
@tracked events = [];
@tracked incomingEventIds = [];
@tracked redeliverEnabled = true;
@readOnly("incomingEventIds.length") incomingCount;
@gt("incomingCount", 0) hasIncoming;
@@ -37,6 +39,17 @@ export default class WebhookEvents extends Component {
} finally {
this.loading = false;
}
this.redeliverEnabled = this.failedEventIds.length;
}
get failedEventIds() {
return this.events.content
.filter(
(event) =>
(event.status < 200 || event.status > 299) && event.status !== 0
)
.map((event) => event.id);
}
get statuses() {
@@ -78,6 +91,24 @@ export default class WebhookEvents extends Component {
this.pingEnabled = true;
}
if (data.type === "redelivered") {
const event = this.events.find((e) => e.id === data.web_hook_event.id);
event.setProperties({
response_body: data.web_hook_event.response_body,
response_headers: data.web_hook_event.response_headers,
status: data.web_hook_event.status,
redelivering: false,
});
return;
}
if (data.type === "redelivery_failed") {
const event = this.events.find((e) => e.id === data.web_hook_event_id);
event.set("redelivering", false);
return;
}
if (!this.incomingEventIds.includes(data.web_hook_event_id)) {
this.incomingEventIds.pushObject(data.web_hook_event_id);
}
@@ -117,4 +148,43 @@ export default class WebhookEvents extends Component {
popupAjaxError(error);
}
}
@action
async redeliverFailed() {
if (!this.failedEventIds.length) {
this.dialog.alert(
I18n.t("admin.web_hooks.events.no_events_to_redeliver")
);
this.redeliverEnabled = false;
return;
}
return this.dialog.yesNoConfirm({
message: I18n.t("admin.web_hooks.events.redeliver_failed_confirm", {
count: this.failedEventIds.length,
}),
didConfirm: async () => {
try {
const response = await ajax(
`/admin/api/web_hooks/${this.args.webhookId}/events/failed_redeliver`,
{ type: "POST", data: { event_ids: this.failedEventIds } }
);
if (response.event_ids?.length) {
response.event_ids.map((id) => {
const event = this.events.find((e) => e.id === id);
event.set("redelivering", true);
});
} else {
this.dialog.alert(
I18n.t("admin.web_hooks.events.no_events_to_redeliver")
);
}
} catch (error) {
popupAjaxError(error);
} finally {
this.redeliverEnabled = false;
}
},
});
}
}