perchfall-notify 0.1.0
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 +7 -0
- data/CHANGELOG.md +14 -0
- data/LICENSE.txt +21 -0
- data/README.md +179 -0
- data/lib/perchfall/notify/channel.rb +30 -0
- data/lib/perchfall/notify/channels/slack.rb +52 -0
- data/lib/perchfall/notify/errors.rb +14 -0
- data/lib/perchfall/notify/net_http_client.rb +41 -0
- data/lib/perchfall/notify/notifier.rb +52 -0
- data/lib/perchfall/notify/slack_formatter.rb +79 -0
- data/lib/perchfall/notify/version.rb +7 -0
- data/lib/perchfall/notify.rb +25 -0
- metadata +112 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 794750f0863a55a3da86f6127a9cee66b340bb551ab16a7d69181a7b793e09a6
|
|
4
|
+
data.tar.gz: 6b7591466107ed3c03581e817e079c6eb08b307eee7db7fde2f9f175dfe39e69
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: cee07f7fcb3ab999a3dde58e0dd9f679c2cc4447711b437ff8872baa4b166fc08561e7f20ff54451bd807761f2f5343626af284e50c8d53a327e1fa7b24d0d67
|
|
7
|
+
data.tar.gz: ecf544f28317029bd970a63571a8107200e024cb9827aecf5fe7a6888ae5ce6809b7924082c044b7103cb83a62772cdb84d25b3c4861e2c9d362fbf5b2120e8e
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.1.0] - 2026-03-20
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `Perchfall::Notify.deliver` convenience method for one-line delivery
|
|
8
|
+
- `Perchfall::Notify::Notifier` — delivers reports to multiple channels, attempting all before raising
|
|
9
|
+
- `Perchfall::Notify::Channel` — base class for custom channel implementations
|
|
10
|
+
- `Perchfall::Notify::Channels::Slack` — Slack incoming webhook channel with Block Kit formatting
|
|
11
|
+
- `Perchfall::Notify::SlackFormatter` — default formatter; injectable for custom message shapes
|
|
12
|
+
- `Perchfall::Notify::DeliveryError` and `Perchfall::Notify::ConfigurationError`
|
|
13
|
+
|
|
14
|
+
[0.1.0]: https://github.com/beflagrant/perchfall-notify/releases/tag/v0.1.0
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jim Remsik
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# perchfall-notify
|
|
2
|
+
|
|
3
|
+
[](https://github.com/beflagrant/perchfall-notify/actions/workflows/ci.yml)
|
|
4
|
+
[](https://badge.fury.io/rb/perchfall-notify)
|
|
5
|
+
|
|
6
|
+
**Notification channels for [Perchfall](https://github.com/beflagrant/perchfall) synthetic monitoring.** Get alerted in Slack — or anywhere you like — the moment a check fails.
|
|
7
|
+
|
|
8
|
+
```ruby
|
|
9
|
+
slack = Perchfall::Notify::Channels::Slack.new(
|
|
10
|
+
webhook_url: ENV["SLACK_WEBHOOK_URL"]
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
report = Perchfall.run(url: "https://example.com")
|
|
14
|
+
Perchfall::Notify.deliver(report, channels: [slack])
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Why perchfall-notify
|
|
20
|
+
|
|
21
|
+
Perchfall tells you what a real browser saw. perchfall-notify tells your team about it.
|
|
22
|
+
|
|
23
|
+
- **Fail-open delivery** — if one channel errors, the others still fire. A Slack outage won't suppress your SMS alert.
|
|
24
|
+
- **Ships with Slack** — Block Kit messages with status, URL, HTTP code, duration, and a full list of network and console errors.
|
|
25
|
+
- **Bring your own channel** — implement one method and plug anything in: webhooks, PagerDuty, SMS, email.
|
|
26
|
+
- **No framework required** — works in Sidekiq jobs, Rake tasks, plain Ruby scripts, or anywhere Perchfall runs.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Requirements
|
|
31
|
+
|
|
32
|
+
| Dependency | Version |
|
|
33
|
+
| --- | --- |
|
|
34
|
+
| Ruby | ≥ 3.2 |
|
|
35
|
+
| perchfall | ≥ 0.3.0 |
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
bundle add perchfall-notify
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Quickstart
|
|
48
|
+
|
|
49
|
+
```ruby
|
|
50
|
+
require "perchfall/notify"
|
|
51
|
+
|
|
52
|
+
slack = Perchfall::Notify::Channels::Slack.new(
|
|
53
|
+
webhook_url: ENV["SLACK_WEBHOOK_URL"]
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
report = Perchfall.run(url: "https://example.com")
|
|
57
|
+
|
|
58
|
+
Perchfall::Notify.deliver(report, channels: [slack])
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
That's it. If the report has network errors or console errors, they'll appear in the Slack message.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Slack channel
|
|
66
|
+
|
|
67
|
+
Create a [Slack incoming webhook](https://api.slack.com/messaging/webhooks) and pass the URL:
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
slack = Perchfall::Notify::Channels::Slack.new(
|
|
71
|
+
webhook_url: "https://hooks.slack.com/services/..."
|
|
72
|
+
)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The default message format uses Block Kit and includes:
|
|
76
|
+
|
|
77
|
+
- Pass/fail header with optional scenario name
|
|
78
|
+
- URL, HTTP status, and duration
|
|
79
|
+
- Network errors with method, URL, and failure reason
|
|
80
|
+
- Console errors with type and message
|
|
81
|
+
|
|
82
|
+
### Custom message format
|
|
83
|
+
|
|
84
|
+
Inject a formatter to control message shape:
|
|
85
|
+
|
|
86
|
+
```ruby
|
|
87
|
+
class MyFormatter
|
|
88
|
+
def format(report)
|
|
89
|
+
{
|
|
90
|
+
text: report.ok? ? "✅ #{report.url} OK" : "🚨 #{report.url} FAILED"
|
|
91
|
+
}
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
slack = Perchfall::Notify::Channels::Slack.new(
|
|
96
|
+
webhook_url: ENV["SLACK_WEBHOOK_URL"],
|
|
97
|
+
formatter: MyFormatter.new
|
|
98
|
+
)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Delivering to multiple channels
|
|
104
|
+
|
|
105
|
+
Pass any number of channels. All are attempted — a failure in one does not skip the others.
|
|
106
|
+
|
|
107
|
+
```ruby
|
|
108
|
+
Perchfall::Notify.deliver(report, channels: [slack, pagerduty, sms])
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
If any channels fail, a single `Perchfall::Notify::DeliveryError` is raised after all have been attempted, with a summary of every failure.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Building a custom channel
|
|
116
|
+
|
|
117
|
+
Subclass `Perchfall::Notify::Channel` and implement `#deliver`:
|
|
118
|
+
|
|
119
|
+
```ruby
|
|
120
|
+
class WebhookChannel < Perchfall::Notify::Channel
|
|
121
|
+
def initialize(url)
|
|
122
|
+
@url = url
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def deliver(report)
|
|
126
|
+
# post report.to_json to @url
|
|
127
|
+
rescue => e
|
|
128
|
+
raise Perchfall::Notify::DeliveryError, "Webhook failed: #{e.message}"
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Raise `DeliveryError` on failure so `Notifier` can accumulate it alongside other channel failures.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Use in a background job
|
|
138
|
+
|
|
139
|
+
```ruby
|
|
140
|
+
class SyntheticCheckJob
|
|
141
|
+
include Sidekiq::Job
|
|
142
|
+
|
|
143
|
+
def perform(url)
|
|
144
|
+
channels = [
|
|
145
|
+
Perchfall::Notify::Channels::Slack.new(webhook_url: ENV.fetch("SLACK_WEBHOOK_URL"))
|
|
146
|
+
]
|
|
147
|
+
report = Perchfall.run(url: url)
|
|
148
|
+
Perchfall::Notify.deliver(report, channels: channels)
|
|
149
|
+
rescue Perchfall::Errors::PageLoadError => e
|
|
150
|
+
Perchfall::Notify.deliver(e.report, channels: channels)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Errors
|
|
158
|
+
|
|
159
|
+
| Exception | When |
|
|
160
|
+
| --- | --- |
|
|
161
|
+
| `Perchfall::Notify::ConfigurationError` | A channel was constructed with invalid configuration (e.g. missing webhook URL) |
|
|
162
|
+
| `Perchfall::Notify::DeliveryError` | One or more channels failed to deliver |
|
|
163
|
+
| `Perchfall::Notify::Error` | Base class — catches any perchfall-notify error |
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Development
|
|
168
|
+
|
|
169
|
+
```sh
|
|
170
|
+
bundle install
|
|
171
|
+
bundle exec rspec
|
|
172
|
+
bin/console
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## License
|
|
178
|
+
|
|
179
|
+
MIT
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Perchfall
|
|
4
|
+
module Notify
|
|
5
|
+
# Base interface for notification channels.
|
|
6
|
+
#
|
|
7
|
+
# A channel is responsible for delivering a formatted message derived from
|
|
8
|
+
# a Perchfall::Report to an external destination (Slack, SMS, webhook, etc.).
|
|
9
|
+
#
|
|
10
|
+
# Implement #deliver(report) in subclasses. Raise DeliveryError on failure.
|
|
11
|
+
#
|
|
12
|
+
# Example:
|
|
13
|
+
#
|
|
14
|
+
# class MyChannel < Perchfall::Notify::Channel
|
|
15
|
+
# def deliver(report)
|
|
16
|
+
# # send the report somewhere
|
|
17
|
+
# end
|
|
18
|
+
# end
|
|
19
|
+
class Channel
|
|
20
|
+
# Deliver a notification for the given report.
|
|
21
|
+
#
|
|
22
|
+
# @param report [Perchfall::Report]
|
|
23
|
+
# @raise [NotImplementedError] if subclass does not implement this method
|
|
24
|
+
# @raise [DeliveryError] if delivery fails
|
|
25
|
+
def deliver(report)
|
|
26
|
+
raise NotImplementedError, "#{self.class}#deliver is not implemented"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Perchfall
|
|
4
|
+
module Notify
|
|
5
|
+
module Channels
|
|
6
|
+
# Delivers Perchfall::Report notifications to a Slack incoming webhook.
|
|
7
|
+
#
|
|
8
|
+
# Requires a webhook URL. Accepts an optional formatter to control
|
|
9
|
+
# message shape, and an optional http_client for testability.
|
|
10
|
+
#
|
|
11
|
+
# Example:
|
|
12
|
+
#
|
|
13
|
+
# slack = Perchfall::Notify::Channels::Slack.new(
|
|
14
|
+
# webhook_url: "https://hooks.slack.com/services/..."
|
|
15
|
+
# )
|
|
16
|
+
# slack.deliver(report)
|
|
17
|
+
#
|
|
18
|
+
class Slack < Channel
|
|
19
|
+
# @param webhook_url [String] Slack incoming webhook URL
|
|
20
|
+
# @param formatter [#format] converts a Report to a Slack payload Hash
|
|
21
|
+
# @param http_client [#post] sends the payload; defaults to NetHttpClient
|
|
22
|
+
def initialize(webhook_url:, formatter: SlackFormatter.new, http_client: NetHttpClient.new)
|
|
23
|
+
super()
|
|
24
|
+
raise ConfigurationError, "webhook_url is required" if webhook_url.nil? || webhook_url.strip.empty?
|
|
25
|
+
|
|
26
|
+
uri = begin
|
|
27
|
+
URI.parse(webhook_url)
|
|
28
|
+
rescue URI::InvalidURIError
|
|
29
|
+
raise ConfigurationError, "webhook_url is not a valid URL"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
raise ConfigurationError, "webhook_url must use https" unless uri.is_a?(URI::HTTPS)
|
|
33
|
+
|
|
34
|
+
@webhook_url = webhook_url
|
|
35
|
+
@formatter = formatter
|
|
36
|
+
@http_client = http_client
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# @param report [Perchfall::Report]
|
|
40
|
+
# @raise [DeliveryError] if the HTTP request fails or Slack rejects the payload
|
|
41
|
+
def deliver(report)
|
|
42
|
+
payload = @formatter.format(report)
|
|
43
|
+
@http_client.post(@webhook_url, payload)
|
|
44
|
+
rescue DeliveryError
|
|
45
|
+
raise
|
|
46
|
+
rescue StandardError => e
|
|
47
|
+
raise DeliveryError, "Slack delivery failed: #{e.message}"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Perchfall
|
|
4
|
+
module Notify
|
|
5
|
+
# Base error for all perchfall-notify failures.
|
|
6
|
+
Error = Class.new(StandardError)
|
|
7
|
+
|
|
8
|
+
# Raised when a channel fails to deliver a notification.
|
|
9
|
+
DeliveryError = Class.new(Error)
|
|
10
|
+
|
|
11
|
+
# Raised when a channel is constructed with invalid configuration.
|
|
12
|
+
ConfigurationError = Class.new(Error)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "json"
|
|
5
|
+
require "uri"
|
|
6
|
+
|
|
7
|
+
module Perchfall
|
|
8
|
+
module Notify
|
|
9
|
+
# Sends a JSON payload to a URL via HTTP POST using Ruby's net/http.
|
|
10
|
+
#
|
|
11
|
+
# This is the default http_client for Channels::Slack. Inject a fake or
|
|
12
|
+
# stub in tests — do not mock this class directly.
|
|
13
|
+
class NetHttpClient
|
|
14
|
+
OPEN_TIMEOUT = 5 # seconds
|
|
15
|
+
READ_TIMEOUT = 10 # seconds
|
|
16
|
+
|
|
17
|
+
# @param url [String] destination URL
|
|
18
|
+
# @param payload [Hash] will be serialized to JSON
|
|
19
|
+
# @raise [DeliveryError] on network failure or non-2xx response
|
|
20
|
+
def post(url, payload)
|
|
21
|
+
uri = URI.parse(url)
|
|
22
|
+
response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |http|
|
|
23
|
+
http.open_timeout = OPEN_TIMEOUT
|
|
24
|
+
http.read_timeout = READ_TIMEOUT
|
|
25
|
+
request = Net::HTTP::Post.new(uri.request_uri)
|
|
26
|
+
request["Content-Type"] = "application/json"
|
|
27
|
+
request.body = JSON.generate(payload)
|
|
28
|
+
http.request(request)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
return if response.is_a?(Net::HTTPSuccess)
|
|
32
|
+
|
|
33
|
+
raise DeliveryError, "HTTP #{response.code}: #{response.body}"
|
|
34
|
+
rescue DeliveryError
|
|
35
|
+
raise
|
|
36
|
+
rescue StandardError => e
|
|
37
|
+
raise DeliveryError, "HTTP request failed: #{e.message}"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Perchfall
|
|
4
|
+
module Notify
|
|
5
|
+
# Delivers a Perchfall::Report to one or more notification channels.
|
|
6
|
+
#
|
|
7
|
+
# Notifier is the primary entry point. It accepts an array of Channel
|
|
8
|
+
# objects and calls #deliver on each one in order. All channels are
|
|
9
|
+
# attempted even if one fails; DeliveryError is raised at the end if
|
|
10
|
+
# any channel failed, with all failures captured.
|
|
11
|
+
#
|
|
12
|
+
# Example:
|
|
13
|
+
#
|
|
14
|
+
# slack = Perchfall::Notify::Channels::Slack.new(webhook_url: "https://...")
|
|
15
|
+
# notifier = Perchfall::Notify::Notifier.new(channels: [slack])
|
|
16
|
+
# notifier.notify(report)
|
|
17
|
+
#
|
|
18
|
+
class Notifier
|
|
19
|
+
# @param channels [Array<Channel>] one or more delivery channels
|
|
20
|
+
def initialize(channels:)
|
|
21
|
+
raise ConfigurationError, "at least one channel is required" if channels.empty?
|
|
22
|
+
|
|
23
|
+
@channels = channels
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Deliver the report to all configured channels.
|
|
27
|
+
#
|
|
28
|
+
# @param report [Perchfall::Report]
|
|
29
|
+
# @raise [DeliveryError] if one or more channels fail
|
|
30
|
+
def notify(report)
|
|
31
|
+
failures = []
|
|
32
|
+
|
|
33
|
+
@channels.each do |channel|
|
|
34
|
+
channel.deliver(report)
|
|
35
|
+
rescue DeliveryError => e
|
|
36
|
+
failures << e
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
raise_if_failed(failures)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def raise_if_failed(failures)
|
|
45
|
+
return if failures.empty?
|
|
46
|
+
|
|
47
|
+
messages = failures.map(&:message).join("; ")
|
|
48
|
+
raise DeliveryError, "#{failures.size} channel(s) failed: #{messages}"
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Perchfall
|
|
4
|
+
module Notify
|
|
5
|
+
# Converts a Perchfall::Report into a Slack Block Kit payload Hash.
|
|
6
|
+
#
|
|
7
|
+
# The default format posts a concise status message with:
|
|
8
|
+
# - pass/fail indicator
|
|
9
|
+
# - URL and scenario name (if present)
|
|
10
|
+
# - HTTP status and duration
|
|
11
|
+
# - counts of network and console errors (if any)
|
|
12
|
+
#
|
|
13
|
+
# Inject a custom formatter into Channels::Slack to change message shape.
|
|
14
|
+
class SlackFormatter
|
|
15
|
+
# @param report [Perchfall::Report]
|
|
16
|
+
# @return [Hash] Slack Block Kit payload
|
|
17
|
+
def format(report)
|
|
18
|
+
{
|
|
19
|
+
blocks: [
|
|
20
|
+
header_block(report),
|
|
21
|
+
details_block(report),
|
|
22
|
+
*error_blocks(report)
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def header_block(report)
|
|
30
|
+
icon = report.ok? ? ":white_check_mark:" : ":rotating_light:"
|
|
31
|
+
label = report.scenario_name ? " — #{report.scenario_name}" : ""
|
|
32
|
+
{
|
|
33
|
+
type: "header",
|
|
34
|
+
text: { type: "plain_text", text: "#{icon} Perchfall#{label}" }
|
|
35
|
+
}
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def details_block(report)
|
|
39
|
+
status_text = report.ok? ? "OK" : "FAILED"
|
|
40
|
+
lines = [
|
|
41
|
+
"*Status:* #{status_text}",
|
|
42
|
+
"*URL:* #{report.url}",
|
|
43
|
+
"*HTTP:* #{report.http_status}",
|
|
44
|
+
"*Duration:* #{report.duration_ms}ms"
|
|
45
|
+
]
|
|
46
|
+
lines << "*Error:* #{escape(report.error)}" if report.error
|
|
47
|
+
|
|
48
|
+
{
|
|
49
|
+
type: "section",
|
|
50
|
+
text: { type: "mrkdwn", text: lines.join("\n") }
|
|
51
|
+
}
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def error_blocks(report)
|
|
55
|
+
blocks = []
|
|
56
|
+
|
|
57
|
+
if report.network_errors.any?
|
|
58
|
+
lines = report.network_errors.map { |e| "• `#{e.http_method} #{e.url}` — #{escape(e.failure)}" }
|
|
59
|
+
blocks << section("*Network errors (#{report.network_errors.size}):*\n#{lines.join("\n")}")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
if report.console_errors.any?
|
|
63
|
+
lines = report.console_errors.map { |e| "• `#{e.type}` #{escape(e.text)}" }
|
|
64
|
+
blocks << section("*Console errors (#{report.console_errors.size}):*\n#{lines.join("\n")}")
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
blocks
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def section(text)
|
|
71
|
+
{ type: "section", text: { type: "mrkdwn", text: text } }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def escape(str)
|
|
75
|
+
str.to_s.gsub("&", "&").gsub("<", "<").gsub(">", ">")
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "perchfall"
|
|
4
|
+
|
|
5
|
+
require_relative "notify/version"
|
|
6
|
+
require_relative "notify/errors"
|
|
7
|
+
require_relative "notify/channel"
|
|
8
|
+
require_relative "notify/slack_formatter"
|
|
9
|
+
require_relative "notify/net_http_client"
|
|
10
|
+
require_relative "notify/notifier"
|
|
11
|
+
require_relative "notify/channels/slack"
|
|
12
|
+
|
|
13
|
+
module Perchfall
|
|
14
|
+
module Notify
|
|
15
|
+
# Convenience method. Delivers a report to one or more channels.
|
|
16
|
+
#
|
|
17
|
+
# Perchfall::Notify.deliver(report, channels: [slack_channel])
|
|
18
|
+
#
|
|
19
|
+
# @param report [Perchfall::Report]
|
|
20
|
+
# @param channels [Array<Channel>]
|
|
21
|
+
def self.deliver(report, channels:)
|
|
22
|
+
Notifier.new(channels: channels).notify(report)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: perchfall-notify
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Jim Remsik
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: perchfall
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: 0.3.0
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: 0.3.0
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: rspec
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '3.13'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '3.13'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: rubocop
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '1.70'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '1.70'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: simplecov
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0.22'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0.22'
|
|
68
|
+
description: |
|
|
69
|
+
Delivers Perchfall check reports via configurable notification channels.
|
|
70
|
+
Ships with Slack support; extensible to SMS, webhooks, and beyond.
|
|
71
|
+
email:
|
|
72
|
+
- jim@beflagrant.com
|
|
73
|
+
executables: []
|
|
74
|
+
extensions: []
|
|
75
|
+
extra_rdoc_files: []
|
|
76
|
+
files:
|
|
77
|
+
- CHANGELOG.md
|
|
78
|
+
- LICENSE.txt
|
|
79
|
+
- README.md
|
|
80
|
+
- lib/perchfall/notify.rb
|
|
81
|
+
- lib/perchfall/notify/channel.rb
|
|
82
|
+
- lib/perchfall/notify/channels/slack.rb
|
|
83
|
+
- lib/perchfall/notify/errors.rb
|
|
84
|
+
- lib/perchfall/notify/net_http_client.rb
|
|
85
|
+
- lib/perchfall/notify/notifier.rb
|
|
86
|
+
- lib/perchfall/notify/slack_formatter.rb
|
|
87
|
+
- lib/perchfall/notify/version.rb
|
|
88
|
+
homepage: https://github.com/beflagrant/perchfall-notify
|
|
89
|
+
licenses:
|
|
90
|
+
- MIT
|
|
91
|
+
metadata:
|
|
92
|
+
source_code_uri: https://github.com/beflagrant/perchfall-notify
|
|
93
|
+
changelog_uri: https://github.com/beflagrant/perchfall-notify/blob/main/CHANGELOG.md
|
|
94
|
+
rubygems_mfa_required: 'true'
|
|
95
|
+
rdoc_options: []
|
|
96
|
+
require_paths:
|
|
97
|
+
- lib
|
|
98
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - ">="
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: 3.2.0
|
|
103
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
104
|
+
requirements:
|
|
105
|
+
- - ">="
|
|
106
|
+
- !ruby/object:Gem::Version
|
|
107
|
+
version: '0'
|
|
108
|
+
requirements: []
|
|
109
|
+
rubygems_version: 4.0.6
|
|
110
|
+
specification_version: 4
|
|
111
|
+
summary: Notification channels for Perchfall synthetic monitoring
|
|
112
|
+
test_files: []
|