webhukhs 0.8.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/AGENTS.md +0 -6
- data/CHANGELOG.md +5 -0
- data/README.md +8 -0
- data/lib/webhukhs/admin/received_webhooks_controller.rb +5 -3
- data/lib/webhukhs/admin/views/webhukhs/admin/received_webhooks/index.html.erb +2 -1
- data/lib/webhukhs/version.rb +1 -1
- data/test/admin_test.rb +32 -12
- data/test/browser/admin_click_through.rb +4 -0
- metadata +1 -2
- data/lib/tasks/webhukhs_tasks.rake +0 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 99b94333e5ec2959d8096f909a5370fccc5b68b9388e0a7028584a5cb334d660
|
|
4
|
+
data.tar.gz: 85c0dfd6138967c9c9f0fca36666ed07f7a9b714a608c34b13ed725d73dff67f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1ee87b2fb85e85a69493977d30cc21bbba0831d5b69973d560d4229260fdd7357a1a343c7da3634b503817827632e7f6ab8c1f5b92cd081030f11c68a8086053
|
|
7
|
+
data.tar.gz: e2820a7337b24bf51cc85e400889969f4744102c3ddc3948df9ae051d2baa96af6a8ae6ffad329cac90952f9d32362232ee2cd2a812dd7059fb04b9fabcb5ed1
|
data/AGENTS.md
CHANGED
|
@@ -32,12 +32,6 @@ Decide which bucket it falls into:
|
|
|
32
32
|
Decide between A) and B) before changing anything. If unsure, ask
|
|
33
33
|
the user.
|
|
34
34
|
|
|
35
|
-
## What you may change
|
|
36
|
-
|
|
37
|
-
- `lib/age.rb` -- the implementation. Multiple designs are valid.
|
|
38
|
-
- `spec/age_spec.rb` -- the tests. Some existing tests are bad and
|
|
39
|
-
may be rewritten or replaced.
|
|
40
|
-
|
|
41
35
|
## Constraints
|
|
42
36
|
|
|
43
37
|
- You may not skip mutants by configuring mutant to ignore them.
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,11 @@ This format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
|
|
|
5
5
|
|
|
6
6
|
## Unreleased
|
|
7
7
|
|
|
8
|
+
## 0.8.1
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- Sort the admin listing by ID by default to avoid a full-table sort.
|
|
12
|
+
|
|
8
13
|
## 0.8.0
|
|
9
14
|
## Added
|
|
10
15
|
- An optional, read-only admin engine (`require "webhukhs/admin"`, mount `Webhukhs::Admin::Engine`) to list,
|
data/README.md
CHANGED
|
@@ -121,6 +121,14 @@ no build step.
|
|
|
121
121
|
To try it out, run `bin/dev` from a checkout of this repository. It starts the app in `example`
|
|
122
122
|
with some webhooks seeded, and the admin at http://localhost:3000/admin/webhukhs.
|
|
123
123
|
|
|
124
|
+
The list defaults to descending ID order using the existing primary-key index; no extra migration is
|
|
125
|
+
needed. Click **Received** to sort by timestamp instead.
|
|
126
|
+
|
|
127
|
+
**We strongly recommend against UUIDv4 primary keys.** Use UUIDv7 or auto-incrementing integer IDs
|
|
128
|
+
instead. UUIDv4 IDs are random, so sorting by ID does not reflect creation time, and inserts have
|
|
129
|
+
poorer index locality. Webhukhs generates UUIDv7 when Ruby supports it, but falls back to UUIDv4
|
|
130
|
+
otherwise; use a Ruby with UUIDv7 support if you need UUID primary keys.
|
|
131
|
+
|
|
124
132
|
## More Examples
|
|
125
133
|
- `example` folder contains a demo app with the engine and the admin fully configured - run it with `bin/dev`.
|
|
126
134
|
- We provide a number of webhook handlers which demonstrate certain features of Webhukhs. You will find them in `handler-examples`.
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# Public namespace for Webhukhs runtime and configuration.
|
|
3
4
|
module Webhukhs
|
|
5
|
+
# Read-only administration for received webhooks.
|
|
4
6
|
module Admin
|
|
5
7
|
# Lists and shows stored webhooks. Read-only: the lifecycle belongs to Webhukhs::ProcessingJob.
|
|
6
8
|
class ReceivedWebhooksController < ApplicationController
|
|
7
9
|
# Webhooks per index page
|
|
8
10
|
PAGE_SIZE = 50
|
|
9
|
-
#
|
|
11
|
+
# Additional sortable columns beyond the default id order
|
|
10
12
|
SORTABLE_COLUMNS = %w[created_at updated_at handler_module_name handler_event_id status].freeze
|
|
11
13
|
|
|
12
14
|
helper_method :handler_options, :status_options
|
|
@@ -15,7 +17,7 @@ module Webhukhs
|
|
|
15
17
|
#
|
|
16
18
|
# @return [void]
|
|
17
19
|
def index
|
|
18
|
-
@sort = params[:sort].presence_in(SORTABLE_COLUMNS) || "
|
|
20
|
+
@sort = params[:sort].presence_in(SORTABLE_COLUMNS) || "id"
|
|
19
21
|
@direction = (params[:direction] == "asc") ? "asc" : "desc"
|
|
20
22
|
@page = [params[:page].to_i, 1].max
|
|
21
23
|
|
|
@@ -25,7 +27,7 @@ module Webhukhs
|
|
|
25
27
|
# to return them in a different order for every OFFSET - rows then repeat or vanish between pages
|
|
26
28
|
rows = ReceivedWebhook
|
|
27
29
|
.where(filters)
|
|
28
|
-
.order(@sort => @direction,
|
|
30
|
+
.order(@sort => @direction, "id" => @direction)
|
|
29
31
|
.offset((@page - 1) * PAGE_SIZE)
|
|
30
32
|
.limit(PAGE_SIZE + 1)
|
|
31
33
|
@next_page = @page + 1 if rows.length > PAGE_SIZE
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
<%
|
|
21
21
|
columns = [
|
|
22
|
+
["Id", "id"],
|
|
22
23
|
["Received", "created_at"],
|
|
23
24
|
["Handler", "handler_module_name"],
|
|
24
25
|
["Status", "status"],
|
|
@@ -28,7 +29,7 @@
|
|
|
28
29
|
%>
|
|
29
30
|
<div id="received-webhooks">
|
|
30
31
|
<%# The id cell carries the link, and the CSS stretches it over the whole row %>
|
|
31
|
-
<%= admin_table(
|
|
32
|
+
<%= admin_table(columns, @webhooks) do |webhook|
|
|
32
33
|
[
|
|
33
34
|
link_to(tag.code(webhook.id, class: "cell-ellipsis cell-id", title: webhook.id), received_webhook_path(webhook), class: "row-link"),
|
|
34
35
|
time_tag(webhook.created_at, webhook.created_at.iso8601),
|
data/lib/webhukhs/version.rb
CHANGED
data/test/admin_test.rb
CHANGED
|
@@ -23,21 +23,41 @@ class AdminTest < ActionDispatch::IntegrationTest
|
|
|
23
23
|
css_select("table.admin-table tbody tr td:nth-child(5) code").map(&:text)
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
test "lists webhooks
|
|
27
|
-
create_webhook(handler_event_id: "
|
|
28
|
-
create_webhook(handler_event_id: "
|
|
26
|
+
test "lists webhooks by descending id regardless of received time by default" do
|
|
27
|
+
create_webhook(handler_event_id: "first", created_at: 1.day.ago)
|
|
28
|
+
create_webhook(handler_event_id: "last", created_at: 2.days.ago)
|
|
29
29
|
|
|
30
30
|
get "/admin/received"
|
|
31
31
|
|
|
32
32
|
assert_response :ok
|
|
33
|
-
assert_equal ["
|
|
33
|
+
assert_equal ["last", "first"], listed_event_ids
|
|
34
34
|
# The whole row is the link, so it has to be there in the first cell
|
|
35
35
|
assert_select "tbody tr td:first-child a.row-link[href^='/admin/received/']", count: 2
|
|
36
|
-
assert_select "th a.sort-link[href=?]", "/admin/received?direction=asc&sort=
|
|
36
|
+
assert_select "th a.sort-link[href=?]", "/admin/received?direction=asc&sort=id", text: "Id ▼"
|
|
37
|
+
assert_select "th a.sort-link[href=?]", "/admin/received?direction=desc&sort=created_at", text: "Received"
|
|
37
38
|
assert_select "th a.sort-link span.sort-arrow", text: "▼", count: 1
|
|
38
39
|
assert_select "th a.sort-link[href=?]", "/admin/received?direction=desc&sort=status", text: "Status"
|
|
39
40
|
end
|
|
40
41
|
|
|
42
|
+
test "sorts by id in ascending order when requested" do
|
|
43
|
+
create_webhook(handler_event_id: "first", created_at: 1.day.ago)
|
|
44
|
+
create_webhook(handler_event_id: "last", created_at: 2.days.ago)
|
|
45
|
+
|
|
46
|
+
get "/admin/received", params: {sort: "id", direction: "asc"}
|
|
47
|
+
|
|
48
|
+
assert_equal ["first", "last"], listed_event_ids
|
|
49
|
+
assert_select "th a.sort-link[href=?]", "/admin/received?direction=desc&sort=id", text: "Id ▲"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
test "still sorts by received time when explicitly requested" do
|
|
53
|
+
create_webhook(handler_event_id: "new", created_at: 1.day.ago)
|
|
54
|
+
create_webhook(handler_event_id: "old", created_at: 2.days.ago)
|
|
55
|
+
|
|
56
|
+
get "/admin/received", params: {sort: "created_at"}
|
|
57
|
+
|
|
58
|
+
assert_equal ["new", "old"], listed_event_ids
|
|
59
|
+
end
|
|
60
|
+
|
|
41
61
|
test "offers an auto-refresh switch, off by default, for the listing it refetches" do
|
|
42
62
|
create_webhook
|
|
43
63
|
|
|
@@ -85,15 +105,15 @@ class AdminTest < ActionDispatch::IntegrationTest
|
|
|
85
105
|
assert_equal ["z", "a"], listed_event_ids
|
|
86
106
|
end
|
|
87
107
|
|
|
88
|
-
test "ignores sort columns and directions
|
|
89
|
-
#
|
|
90
|
-
create_webhook(handler_event_id: "new", created_at: 1.day.ago, status: "skipped")
|
|
91
|
-
create_webhook(handler_event_id: "old", created_at: 2.days.ago, status: "error")
|
|
108
|
+
test "ignores unsupported sort columns and directions" do
|
|
109
|
+
# Creation order differs from received time, so the fallback must use id.
|
|
110
|
+
create_webhook(handler_event_id: "new", created_at: 1.day.ago, status: "skipped", body: "z")
|
|
111
|
+
create_webhook(handler_event_id: "old", created_at: 2.days.ago, status: "error", body: "a")
|
|
92
112
|
|
|
93
|
-
get "/admin/received", params: {sort: "
|
|
113
|
+
get "/admin/received", params: {sort: "body", direction: "sideways"}
|
|
94
114
|
|
|
95
115
|
assert_response :ok
|
|
96
|
-
assert_equal ["
|
|
116
|
+
assert_equal ["old", "new"], listed_event_ids
|
|
97
117
|
end
|
|
98
118
|
|
|
99
119
|
test "filters by handler and status, and treats blank filters as no filter" do
|
|
@@ -119,7 +139,7 @@ class AdminTest < ActionDispatch::IntegrationTest
|
|
|
119
139
|
assert_select "select[name=handler] option[value='']", text: "Any handler"
|
|
120
140
|
assert_select "select[name=status] option[value='']", text: "Any status"
|
|
121
141
|
# Clearing the filters keeps the sort
|
|
122
|
-
assert_select "form.filters a[href=?]", "/admin/received?direction=desc&sort=
|
|
142
|
+
assert_select "form.filters a[href=?]", "/admin/received?direction=desc&sort=id", text: "Clear"
|
|
123
143
|
assert_select "select[name=handler] option[selected]", text: "PrivateHandler"
|
|
124
144
|
|
|
125
145
|
statuses = css_select("select[name=status] option").map { |option| option["value"] }
|
|
@@ -94,6 +94,10 @@ class AdminClickThrough < ActiveSupport::TestCase
|
|
|
94
94
|
# The stylesheet must have made it through the CSP - unstyled, the header would not be a flexbox
|
|
95
95
|
assert_equal "flex", @browser.evaluate("getComputedStyle(document.querySelector('.admin-header')).display")
|
|
96
96
|
assert_equal PAGE_SIZE, @browser.css("tbody tr").length
|
|
97
|
+
assert_equal "the-failed-one", texts("tbody tr td:nth-child(5)").first
|
|
98
|
+
|
|
99
|
+
click_and_wait_for_page(@browser.at_xpath("//th/a[contains(., 'Received')]"))
|
|
100
|
+
assert_includes @browser.current_url, "direction=desc"
|
|
97
101
|
assert_equal "evt-0", texts("tbody tr td:nth-child(5)").first
|
|
98
102
|
|
|
99
103
|
click_and_wait_for_page(@browser.at_xpath("//th/a[contains(., 'Received')]"))
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: webhukhs
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.8.
|
|
4
|
+
version: 0.8.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Stanislav Katkov
|
|
@@ -343,7 +343,6 @@ files:
|
|
|
343
343
|
- handler-examples/revolut_business_v1_handler.rb
|
|
344
344
|
- handler-examples/revolut_business_v2_handler.rb
|
|
345
345
|
- handler-examples/starling_payments_handler.rb
|
|
346
|
-
- lib/tasks/webhukhs_tasks.rake
|
|
347
346
|
- lib/webhukhs.rb
|
|
348
347
|
- lib/webhukhs/admin.rb
|
|
349
348
|
- lib/webhukhs/admin/application_controller.rb
|