FEATURE: Add Filter for Webhook Events by Status (#27332)

* FEATURE: Add Filter for Webhook Events by Status

* Fixing multiple issues

* Lint

* Fixing multiple issues

* Change the range of the status for webhook events
This commit is contained in:
Guhyoun Nam 2024-06-07 10:26:00 -05:00 committed by GitHub
parent 970d7e9cd9
commit c13f64d35b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 207 additions and 19 deletions

View File

@ -4,4 +4,22 @@ export default class WebHookEvent extends RestAdapter {
basePath() {
return "/admin/api/";
}
appendQueryParams(path, findArgs, extension) {
const urlSearchParams = new URLSearchParams();
for (const [key, value] of Object.entries(findArgs)) {
if (value && key !== "webhookId") {
urlSearchParams.set(key, value);
}
}
const queryString = urlSearchParams.toString();
let url = `${path}/${findArgs.webhookId}${extension || ""}`;
if (queryString) {
url = `${url}?${queryString}`;
}
return url;
}
}

View File

@ -1,15 +1,26 @@
<div
class="web-hook-events-listing"
{{did-insert this.subscribe}}
{{did-update this.reloadEvents @status}}
{{will-destroy this.unsubscribe}}
>
<DButton
@icon="paper-plane"
@label="admin.web_hooks.events.ping"
@action={{this.ping}}
@disabled={{not this.pingEnabled}}
class="webhook-events__ping-button"
/>
<div class="web-hook-events-actions">
<ComboBox
@value={{@status}}
@content={{this.statuses}}
@onChange={{fn (mut @status)}}
@options={{hash none="admin.web_hooks.events.filter_status.all"}}
class="delivery-status-filters"
/>
<DButton
@icon="paper-plane"
@label="admin.web_hooks.events.ping"
@action={{this.ping}}
@disabled={{not this.pingEnabled}}
class="webhook-events__ping-button"
/>
</div>
{{#if this.events}}
<LoadMore @selector=".web-hook-events li" @action={{this.loadMore}}>

View File

@ -6,6 +6,7 @@ import { service } from "@ember/service";
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { bind } from "discourse-common/utils/decorators";
import I18n from "discourse-i18n";
export default class WebhookEvents extends Component {
@service messageBus;
@ -24,10 +25,40 @@ export default class WebhookEvents extends Component {
}
async loadEvents() {
this.events = await this.store.findAll(
"web-hook-event",
this.args.webhookId
);
this.loading = true;
try {
this.events = await this.store.findAll("web-hook-event", {
webhookId: this.args.webhookId,
status: this.args.status,
});
} catch (error) {
popupAjaxError(error);
} finally {
this.loading = false;
}
}
get statuses() {
return [
{
id: "successful",
name: I18n.t("admin.web_hooks.events.filter_status.successful"),
},
{
id: "failed",
name: I18n.t("admin.web_hooks.events.filter_status.failed"),
},
];
}
@bind
reloadEvents() {
if (this.loading) {
return;
}
this.loadEvents();
}
@bind

View File

@ -1,3 +1,4 @@
import { tracked } from "@glimmer/tracking";
import Controller, { inject as controller } from "@ember/controller";
import { action } from "@ember/object";
import { service } from "@ember/service";
@ -8,6 +9,9 @@ export default class AdminWebHooksShowController extends Controller {
@service dialog;
@service router;
@controller adminWebHooks;
@tracked status;
queryParams = ["status"];
@action
edit() {

View File

@ -31,4 +31,4 @@
</div>
</div>
<WebhookEvents @webhookId={{this.model.id}} />
<WebhookEvents @webhookId={{this.model.id}} @status={{this.status}} />

View File

@ -338,7 +338,10 @@ table.api-keys {
}
}
.webhook-events__ping-button {
.web-hook-events-actions {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 1rem;
}

View File

@ -87,14 +87,19 @@ class Admin::WebHooksController < Admin::AdminController
def list_events
limit = 50
offset = params[:offset].to_i
events = @web_hook.web_hook_events
if params[:status] == "successful"
events = events.successful
elsif params[:status] == "failed"
events = events.failed
end
total = events.count
events = events.limit(limit).offset(offset)
json = {
web_hook_events:
serialize_data(
@web_hook.web_hook_events.limit(limit).offset(offset),
AdminWebHookEventSerializer,
),
total_rows_web_hook_events: @web_hook.web_hook_events.count,
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, format: :json),
extras: {

View File

@ -1,6 +1,8 @@
# frozen_string_literal: true
class WebHookEvent < ActiveRecord::Base
scope :successful, -> { where("status >= 200 AND status <= 299") }
scope :failed, -> { where("status < 200 OR status > 299") }
belongs_to :web_hook
after_save :update_web_hook_delivery_status

View File

@ -5357,6 +5357,10 @@ en:
timestamp: "Created"
completion: "Completion Time"
actions: "Actions"
filter_status:
all: "All Events"
successful: "Delivered"
failed: "Failed"
home:
title: "Home"
account:

View File

@ -0,0 +1,7 @@
# frozen_string_literal: true
Fabricator(:web_hook_event) do
web_hook { Fabricate(:web_hook) }
payload { { some_key: "some_value" }.to_json }
status 200
end

View File

@ -197,6 +197,40 @@ RSpec.describe Admin::WebHooksController do
end
end
describe "#list_events" do
fab!(:web_hook_event1) { Fabricate(:web_hook_event, web_hook: web_hook, id: 1, status: 200) }
fab!(:web_hook_event2) { Fabricate(:web_hook_event, web_hook: web_hook, id: 2, status: 404) }
before { sign_in(admin) }
context "when status is 'successful'" do
it "lists the successfully delivered webhook events" do
get "/admin/api/web_hook_events/#{web_hook.id}.json", params: { status: "successful" }
expect(response.parsed_body["web_hook_events"].map { |c| c["id"] }).to eq(
[web_hook_event1.id],
)
end
end
context "when status is 'failed'" do
it "lists the failed webhook events" do
get "/admin/api/web_hook_events/#{web_hook.id}.json", params: { status: "failed" }
expect(response.parsed_body["web_hook_events"].map { |c| c["id"] }).to eq(
[web_hook_event2.id],
)
end
end
context "when there is no status param" do
it "lists all webhook events" do
get "/admin/api/web_hook_events/#{web_hook.id}.json"
expect(response.parsed_body["web_hook_events"].map { |c| c["id"] }).to match_array(
[web_hook_event1.id, web_hook_event2.id],
)
end
end
end
describe "#ping" do
context "when logged in as admin" do
before { sign_in(admin) }

View File

@ -0,0 +1,37 @@
# frozen_string_literal: true
describe "Admin WebHook Events", type: :system do
fab!(:web_hook)
fab!(:admin)
fab!(:web_hook_event1) { Fabricate(:web_hook_event, web_hook: web_hook, status: 200) }
fab!(:web_hook_event2) { Fabricate(:web_hook_event, web_hook: web_hook, status: 404) }
let(:admin_web_hooks_page) { PageObjects::Pages::AdminWebHookEvents.new }
before { sign_in(admin) }
it "shows all webhook events when filter is on 'All Events'" do
admin_web_hooks_page.visit(web_hook.id)
expect(admin_web_hooks_page).to have_web_hook_event(web_hook_event1.id)
expect(admin_web_hooks_page).to have_web_hook_event(web_hook_event2.id)
end
it "shows only successfully delivered webhook events when filter is on 'Delivered'" do
admin_web_hooks_page.visit(web_hook.id)
admin_web_hooks_page.click_filter_all
admin_web_hooks_page.click_filter_delivered
expect(admin_web_hooks_page).to have_web_hook_event(web_hook_event1.id)
expect(admin_web_hooks_page).to have_no_web_hook_event(web_hook_event2.id)
end
it "shows only webhook events that are failed to deliver when filter is on 'Failed'" do
admin_web_hooks_page.visit(web_hook.id)
admin_web_hooks_page.click_filter_all
admin_web_hooks_page.click_filter_failed
expect(admin_web_hooks_page).to have_no_web_hook_event(web_hook_event1.id)
expect(admin_web_hooks_page).to have_web_hook_event(web_hook_event2.id)
end
end

View File

@ -0,0 +1,32 @@
# frozen_string_literal: true
module PageObjects
module Pages
class AdminWebHookEvents < PageObjects::Pages::Base
def visit(id)
page.visit("/admin/api/web_hooks/#{id}")
self
end
def click_filter_all
find(".select-kit-header", text: "All Events").click
end
def click_filter_delivered
find(".select-kit-row", text: "Delivered").click
end
def click_filter_failed
find(".select-kit-row", text: "Failed").click
end
def has_web_hook_event?(id)
page.has_css?("li .event-id", text: id)
end
def has_no_web_hook_event?(id)
page.has_no_css?("li .event-id", text: id)
end
end
end
end