chat_notifier 0.2.5 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a442afb94e03ba8a40be94d62cb0043abe9a4a88430028bb79a5655ebe2d1f7e
4
- data.tar.gz: 2070d04462fc7d9fec4daf08b25e900f03d5d7ce735aa5ac51eace3acc6429c6
3
+ metadata.gz: 62958f54e981fe03f20e8e7b954b18b0cc7f404542698d58db36a3fd7911b3ed
4
+ data.tar.gz: 42760f3c980906d660c601a984719ea551d137e6db064545ef9fcc446442a6d0
5
5
  SHA512:
6
- metadata.gz: e892f14d366c45b01b3665523ee7b47eba8ad9b15021351b3f626763a08bacce3b72bdd5a6425338e5e1a5b3d94c3ae2294c3c92809060607b9bfa9b8aeaa273
7
- data.tar.gz: 4a7cdd3397d62a9c7a9e25ea9b1bbf669a9e1c92eac97b95900764df0bc62b878cbd7e7942612852199a8ecd6efff13b0c6ed3604a7160b3d40b8da709e86c3d
6
+ metadata.gz: 4c37610777e3d4cc791791339e8b39ea7cacc386fb4032290a29040c0ef0184ee87d0d5ae26d3d072b16e9ed85d6abbd668e37b46adbaff98faeab1aff2a9396
7
+ data.tar.gz: d70529078358faeb2d419bae2951958b0ca94fdcaec00bfe5d820092dd4e366e784b5825ea1f0541eb041d91758427265256f30efd1f487e9427b6022e19c382
data/CHANGELOG.md CHANGED
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.3.0] - 2026-07-24
9
+
10
+ ### Added
11
+
12
+ - Threaded Slack failure notifications via a Slack bot token (NOTIFY_SLACK_BOT_TOKEN), grouping failing files into batched thread replies (e453cd0)
13
+
8
14
  ## [0.2.5] - 2026-02-02
9
15
 
10
16
  ### Added
@@ -19,10 +25,3 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
19
25
  ### Removed
20
26
 
21
27
  - OpenStruct dependency
22
-
23
- ## [0.2.4] - 2025-01-16
24
-
25
- ### Changed
26
-
27
- - Add support for Ruby 3.4
28
- - Check for Rails application before using Rails.application
data/README.md CHANGED
@@ -51,6 +51,22 @@ If you are _not_ using Rails, you will need to add this ENV variable:
51
51
  NOTIFY_APP_NAME
52
52
  ```
53
53
 
54
+ ### Threaded failure notifications (optional)
55
+
56
+ Incoming webhooks post a single message. If you instead provide a Slack **bot
57
+ token**, ChatNotifier uses the Slack Web API and threads failures: it posts the
58
+ branch/run summary as a parent message, then posts the failing files — grouped
59
+ into reasonably sized batches — as threaded replies.
60
+
61
+ ```
62
+ NOTIFY_SLACK_BOT_TOKEN # xoxb-… bot token; enables threading
63
+ NOTIFY_SLACK_NOTIFY_CHANNEL # channel id or name to post to
64
+ NOTIFY_SLACK_THREAD_GROUP_SIZE # optional, files per reply (default 10)
65
+ ```
66
+
67
+ When both `NOTIFY_SLACK_BOT_TOKEN` and `NOTIFY_SLACK_WEBHOOK_URL` are set, the
68
+ bot token is preferred. The bot needs the `chat:write` scope.
69
+
54
70
  ### Debug your Slack setup
55
71
 
56
72
  Create rake task to test the connection to your Slack channel
@@ -1,10 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../failure_groups"
4
+
3
5
  module ChatNotifier
4
6
  class Chatter
5
7
  class Slack < self
6
8
  Chatter.register self
7
9
 
10
+ API_URL = "https://slack.com/api/chat.postMessage"
11
+ DEFAULT_THREAD_GROUP_SIZE = 10
12
+
8
13
  class << self
9
14
  def handles?(settings)
10
15
  !settings.keys.grep(/NOTIFY_SLACK/).empty?
@@ -15,14 +20,71 @@ module ChatNotifier
15
20
  settings.fetch("NOTIFY_SLACK_WEBHOOK_URL", nil)
16
21
  end
17
22
 
23
+ def bot_token
24
+ settings.fetch("NOTIFY_SLACK_BOT_TOKEN", nil)
25
+ end
26
+
18
27
  def channel
19
28
  settings.fetch("NOTIFY_SLACK_NOTIFY_CHANNEL", nil)
20
29
  end
21
30
 
31
+ def thread_group_size
32
+ Integer(settings.fetch("NOTIFY_SLACK_THREAD_GROUP_SIZE", DEFAULT_THREAD_GROUP_SIZE))
33
+ end
34
+
35
+ # When a bot token is configured we use the Slack Web API, which can
36
+ # thread failures. The token path is preferred over an incoming webhook.
37
+ def post(messenger, process: Net::HTTP.method(:post))
38
+ return super unless bot_token
39
+
40
+ post_via_api(messenger, process:)
41
+ end
42
+
22
43
  def payload(data)
23
44
  super(Configuration.for(data, self).to_h)
24
45
  end
25
46
 
47
+ private
48
+
49
+ def post_via_api(messenger, process:)
50
+ parent_text = messenger.failure? ? messenger.lede : messenger.message
51
+ parent = post_message(text: parent_text, process:)
52
+
53
+ return parent unless messenger.failure? && ok?(parent)
54
+
55
+ thread_ts = response_ts(parent)
56
+ FailureGroups.new(messenger.failures, group_size: thread_group_size).reply_texts.each do |text|
57
+ post_message(text:, thread_ts:, process:)
58
+ end
59
+ end
60
+
61
+ def post_message(text:, process:, thread_ts: nil)
62
+ body = {channel: channel, text: text}
63
+ body[:thread_ts] = thread_ts if thread_ts
64
+ process.call(URI(API_URL), body.to_json, api_headers)
65
+ end
66
+
67
+ def api_headers
68
+ {
69
+ "Content-Type" => "application/json; charset=utf-8",
70
+ "Authorization" => "Bearer #{bot_token}"
71
+ }
72
+ end
73
+
74
+ def ok?(response)
75
+ parsed_response(response)["ok"] == true
76
+ end
77
+
78
+ def response_ts(response)
79
+ parsed_response(response)["ts"]
80
+ end
81
+
82
+ def parsed_response(response)
83
+ JSON.parse(response.body.to_s)
84
+ rescue JSON::ParserError
85
+ {}
86
+ end
87
+
26
88
  class Configuration
27
89
  def self.for(messenger, communicator)
28
90
  if messenger.failure?
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ChatNotifier
4
+ # Groups test failures by file and packs them into reasonably sized
5
+ # batches suitable for posting as individual Slack thread replies.
6
+ class FailureGroups
7
+ def initialize(failures, group_size: 10, char_limit: 3000)
8
+ @failures = failures
9
+ @group_size = group_size
10
+ @char_limit = char_limit
11
+ end
12
+
13
+ attr_reader :failures, :group_size, :char_limit
14
+
15
+ # One rendered string per thread reply: file blocks packed into batches
16
+ # bounded by group_size (file count) and char_limit (rendered length).
17
+ def reply_texts
18
+ batches.map { |blocks| blocks.join("\n") }
19
+ end
20
+
21
+ private
22
+
23
+ def batches
24
+ file_blocks.each_with_object([]) do |block, packed|
25
+ current = packed.last
26
+ if current.nil? ||
27
+ current.size >= group_size ||
28
+ (current.join("\n").length + 1 + block.length) > char_limit
29
+ packed << [block]
30
+ else
31
+ current << block
32
+ end
33
+ end
34
+ end
35
+
36
+ # Render each file (with its failing lines) into a text block.
37
+ def file_blocks
38
+ by_file.map do |file, lines|
39
+ bullets = lines.map { |line| " • #{File.basename(file)}:#{line}" }
40
+ ([file] + bullets).join("\n")
41
+ end
42
+ end
43
+
44
+ # { "path/to/file.rb" => ["42", "88"], ... } preserving first-seen order.
45
+ def by_file
46
+ failures.each_with_object({}) do |failure, groups|
47
+ file, line = normalize(failure.location)
48
+ (groups[file] ||= []) << line
49
+ end
50
+ end
51
+
52
+ # RSpec locations are "path:line" strings; Minitest locations are
53
+ # [file, line] arrays. Normalize both to [file, line].
54
+ def normalize(location)
55
+ if location.is_a?(Array)
56
+ [location[0], location[1]]
57
+ else
58
+ file, _, line = location.to_s.rpartition(":")
59
+ [file, line]
60
+ end
61
+ end
62
+ end
63
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ChatNotifier
4
- VERSION = "0.2.5"
4
+ VERSION = "0.3.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chat_notifier
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.5
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jim Gay
@@ -9,7 +9,21 @@ authors:
9
9
  bindir: exe
10
10
  cert_chain: []
11
11
  date: 1980-01-02 00:00:00.000000000 Z
12
- dependencies: []
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: logger
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  description: Send test results to chat
14
28
  email:
15
29
  - jim@saturnflyer.com
@@ -25,6 +39,7 @@ files:
25
39
  - lib/chat_notifier/chatter.rb
26
40
  - lib/chat_notifier/chatter/debug.rb
27
41
  - lib/chat_notifier/chatter/slack.rb
42
+ - lib/chat_notifier/failure_groups.rb
28
43
  - lib/chat_notifier/messenger.rb
29
44
  - lib/chat_notifier/repository.rb
30
45
  - lib/chat_notifier/repository/debug.rb
@@ -46,7 +61,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
46
61
  requirements:
47
62
  - - ">="
48
63
  - !ruby/object:Gem::Version
49
- version: 3.2.0
64
+ version: 3.3.0
50
65
  required_rubygems_version: !ruby/object:Gem::Requirement
51
66
  requirements:
52
67
  - - ">="