HornsAndHooves-slackiq 1.1.13 → 1.3.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0cbd140b134d6de7cf86dd0227b2cb33e9fddedd0e4b49a11b9dc5f5f27911c7
4
- data.tar.gz: 0f3fab8b618b37c1615e6e3e8fb156fccbd435ac9690f357b0c36260e5a0e43b
3
+ metadata.gz: 23e05e5344c4891a0825371e1d79342aedc903e109b0be8f4c79df75b2fe601e
4
+ data.tar.gz: 2d55c5017831b9041ddc6cd31d862c7a104a22c192762bf6836ba59f94be4312
5
5
  SHA512:
6
- metadata.gz: ad7c9fc78fcf4ad9a77896dcf9dcecf8bb1ee5f4989fb7a65cf1915ccb2fc5d97fcb94f77882dd9b5df59b33491baef93f1d3dea3afff6479d583720153251d7
7
- data.tar.gz: 29515290b0b20db4e94e5a0eab7e4961d3d43dfd3a304a399006d53878ea6133df4d4edf5da202f3e5aa8ea70ede885a7e3c450f147057663e20c2f39cceca86
6
+ metadata.gz: db63885b16aeaa6872dd46b35268d044be86f514432d6e97eaef6245a6987e797c8f4ea55d5648c77c288cbb342b95401a8076c6d4d7141ffcaa54b781bfb6b5
7
+ data.tar.gz: b3d3ae0dff813cd203df53c8d1d2f6902093457819e2e325885fae2a855de39d6676b0c9392ef25fc904477945a92cd8ba20e535e70f8ad4c4dec661a51c0250
data/Gemfile CHANGED
@@ -1,9 +1,6 @@
1
- source 'https://rubygems.org'
1
+ source "https://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in slackiq.gemspec
4
4
  gemspec
5
5
 
6
- group :development, :test do
7
- gem "sidekiq"
8
- gem "rspec-sidekiq"
9
- end
6
+ gem "sidekiq"
@@ -1,28 +1,26 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
 
3
- lib = File.expand_path('../lib', __FILE__)
3
+ lib = File.expand_path("lib", __dir__)
4
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
- require 'slackiq/version'
5
+ require "slackiq/version"
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "HornsAndHooves-slackiq"
9
9
  s.version = Slackiq::VERSION
10
10
  s.authors = ["HornsAndHooves", "Peter Maneykowski"]
11
- s.email = ['maneyko@integracredit.com']
12
- s.summary = 'HornsAndHooves: Slack and Sidekiq Pro integration'
11
+ s.email = ["maneyko@integracredit.com"]
12
+ s.summary = "HornsAndHooves: Slack and Sidekiq Pro integration"
13
13
  s.description = "Slackiq (by HornsAndHooves) integrates Slack and Sidekiq so that you can "\
14
14
  "have vital information about your Sidekiq jobs sent directly to your team's Slack."
15
- s.homepage = 'https://github.com/HornsAndHooves/slackiq'
16
- s.license = 'MIT'
15
+ s.homepage = "https://github.com/HornsAndHooves/slackiq"
16
+ s.license = "MIT"
17
17
 
18
18
  s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
19
19
  s.bindir = "exe"
20
20
  s.executables = s.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
21
  s.require_paths = ["lib"]
22
22
 
23
- s.add_dependency 'httparty'
24
- s.add_development_dependency "bundler", "~> 1.10"
25
- s.add_development_dependency "rake", "~> 10.0"
23
+ s.add_development_dependency "bundler"
24
+ s.add_development_dependency "rake"
26
25
  s.add_development_dependency "rspec"
27
- s.add_development_dependency "rspec-sidekiq"
28
26
  end
data/README.md CHANGED
@@ -10,7 +10,7 @@ Slackiq (pronounced *slack-kick*) integrates [Slack](https://slack.com/) and [Si
10
10
  Add this line to your Gemfile:
11
11
 
12
12
  ```ruby
13
- gem 'slackiq'
13
+ gem "slackiq"
14
14
  ```
15
15
 
16
16
  Then run:
@@ -26,8 +26,8 @@ First, set up any number of Slack Incoming Webhooks [from your Slack](https://sl
26
26
  Then, you only need to call the `configure` method when your application launches to configure all of the webhooks to which you want to post. If you're using Rails, create an initializer at `config/initializers/slackiq.rb`. Here's an example:
27
27
 
28
28
  ```ruby
29
- Slackiq.configure( web_scrapes: 'https://hooks.slack.com/services/HA298HF2/ALSKF2451/lknsaHHA2323KKDKND',
30
- data_processing: 'https://hooks.slack.com/services/HA298HF2/ALSKF2451/H24dLKAHD22423')
29
+ Slackiq.configure( web_scrapes: "https://hooks.slack.com/services/HA298HF2/ALSKF2451/lknsaHHA2323KKDKND",
30
+ data_processing: "https://hooks.slack.com/services/HA298HF2/ALSKF2451/H24dLKAHD22423")
31
31
  ```
32
32
 
33
33
  `:web_scrapes` and `data_processing` are examples of keys. Use whatever keys you want.
@@ -57,7 +57,7 @@ class WebScraper
57
57
  # Scrape the first 100 URLs in the database
58
58
  def scrape_100
59
59
  batch = Sidekiq::Batch.new
60
- batch.description = 'Scrape the first 100 URLs!'
60
+ batch.description = "Scrape the first 100 URLs!"
61
61
  batch.on(:complete, self)
62
62
 
63
63
  batch.jobs do
@@ -72,22 +72,22 @@ class WebScraper
72
72
  end
73
73
 
74
74
  def on_complete(status, options)
75
- Slackiq.notify(webhook_name: :web_scrapes, status: status, title: 'Scrape Completed!',
76
- 'Total URLs in DB' => URL.count.to_s,
77
- 'Servers' => "#{Server.active_count} active, #{Server.inactive_count} inactive")
75
+ Slackiq.notify(webhook_name: :web_scrapes, status: status, title: "Scrape Completed!",
76
+ "Total URLs in DB" => URL.count.to_s,
77
+ "Servers" => "#{Server.active_count} active, #{Server.inactive_count} inactive")
78
78
  end
79
79
 
80
80
  end
81
81
  ```
82
82
 
83
- Note that in this case, `'Total URLs in DB'` and `'Servers'` are custom fields that will also appear in Slack!
83
+ Note that in this case, `"Total URLs in DB"` and `"Servers"` are custom fields that will also appear in Slack!
84
84
 
85
85
  ### Want to send a message to Slack that isn't Sidekiq-related?
86
86
 
87
87
  No prob. Just:
88
88
 
89
89
  ```ruby
90
- Slackiq.message('Server 5 is overloaded!', webhook_name: :data_processing)
90
+ Slackiq.message("Server 5 is overloaded!", webhook_name: :data_processing)
91
91
  ```
92
92
 
93
93
  ## Contributing
@@ -1,3 +1,3 @@
1
- module Slackiq
2
- VERSION = "1.1.13"
1
+ class Slackiq
2
+ VERSION = "1.3.1".freeze
3
3
  end
data/lib/slackiq.rb CHANGED
@@ -1,157 +1,197 @@
1
- require 'slackiq/version'
1
+ require "slackiq/version"
2
2
 
3
- require 'net/http'
4
- require 'json'
5
- require 'httparty'
3
+ require "net/http"
4
+ require "uri"
5
+ require "json"
6
+ require "date"
6
7
 
7
- require 'slackiq/time_helper'
8
- require 'slackiq/sidekiq_status'
8
+ class Slackiq
9
9
 
10
- require 'active_support' # For Hash#except
10
+ attr_reader :options
11
11
 
12
- module Slackiq
13
-
14
- class << self
15
-
16
- # Configure all of the webhook URLs you're going to use
17
- # @author Jason Lew
18
- def configure(webhook_urls={})
19
- raise 'Argument must be a Hash' unless webhook_urls.class == Hash
20
- @@webhook_urls = webhook_urls
21
- end
22
-
23
- # Send a notification to Slack with Sidekiq info about the batch
24
- # @author Jason Lew
25
- def notify(options={})
26
- url = @@webhook_urls[options[:webhook_name]]
27
- title = options[:title]
28
- # description = options[:description]
29
- status = options[:status]
30
-
31
- if (bid = options[:bid]) && status.nil?
32
- raise <<~EOT.chomp unless defined?(Sidekiq::Batch::Status)
33
- Sidekiq::Batch::Status is not defined. \
34
- Are you sure Sidekiq Pro is set up correctly?
35
- EOT
36
- status = Sidekiq::Batch::Status.new(bid)
37
- end
38
-
39
- color = options[:color] || color_for(status)
40
-
41
- extra_fields = options.except(:webhook_name, :title, :description, :status)
12
+ # @param options [Hash]
13
+ def self.notify(options)
14
+ raise "Need to run Slackiq.configure first" if @webhook_urls.nil?
15
+ new(options.merge(webhook_urls: @webhook_urls)).execute
16
+ end
42
17
 
43
- fields = []
18
+ # @param options [Hash]
19
+ def self.configure(webhook_urls={})
20
+ @webhook_urls = webhook_urls
21
+ end
44
22
 
45
- if status
46
- created_at = status.created_at
23
+ # @param options [Hash]
24
+ def initialize(options)
25
+ @options = options
26
+ end
47
27
 
48
- if created_at
49
- time_now = Time.now
50
- duration = Slackiq::TimeHelper.elapsed_time_humanized(created_at, time_now)
51
- time_now_title = (status.complete? ? 'Completed' : 'Now')
52
- end
28
+ # Send a notification to Slack with Sidekiq info about the batch
29
+ def execute
30
+ time_now = Time.now
53
31
 
54
- total_jobs = status.total
55
- failures = status.failures
56
- jobs_run = total_jobs - status.pending
57
-
58
- completion_percentage = (jobs_run/total_jobs.to_f)*100
59
- failure_percentage = (failures/total_jobs.to_f)*100 if total_jobs && failures
60
-
61
- # Round to two decimal places
62
- decimal_places = 2
63
- completion_percentage = completion_percentage.round(decimal_places)
64
- failure_percentage = failure_percentage.round(decimal_places)
65
-
66
- description = status.description
67
-
68
- fields += [
69
- {
70
- 'title' => 'Created',
71
- 'value' => Slackiq::TimeHelper.format(created_at),
72
- 'short' => true
73
- },
74
- {
75
- 'title' => time_now_title,
76
- 'value' => Slackiq::TimeHelper.format(time_now),
77
- 'short' => true
78
- },
79
- {
80
- 'title' => "Duration",
81
- 'value' => duration,
82
- 'short' => true
83
- },
84
- {
85
- 'title' => "Total Jobs",
86
- 'value' => total_jobs,
87
- 'short' => true
88
- },
89
- {
90
- 'title' => "Jobs Run",
91
- 'value' => jobs_run,
92
- 'short' => true
93
- },
94
- {
95
- 'title' => "Completion %",
96
- 'value' => "#{completion_percentage}%",
97
- 'short' => true
98
- },
99
- {
100
- 'title' => "Failures",
101
- 'value' => status.failures,
102
- 'short' => true
103
- },
104
- {
105
- 'title' => "Failure %",
106
- 'value' => "#{failure_percentage}%",
107
- 'short' => true
108
- },
109
- ]
110
- end
32
+ title = options[:title]
33
+ status = options[:status]
111
34
 
112
- # Add extra fields
113
- fields += extra_fields.map do |title, value|
114
- {
115
- 'title' => title,
116
- 'value' => value,
117
- 'short' => false
118
- }
119
- end
35
+ if (bid = options[:bid]) && status.nil?
36
+ raise <<~EOT.chomp unless defined?(Sidekiq::Batch::Status)
37
+ Sidekiq::Batch::Status is not defined. \
38
+ Are you sure Sidekiq Pro is set up correctly?
39
+ EOT
40
+ status = Sidekiq::Batch::Status.new(bid)
41
+ end
120
42
 
121
- attachments = [
122
- {
123
- 'fallback' => title,
124
- 'color' => color,
125
- 'title' => title,
126
- 'text' => description,
127
- 'fields' => fields,
128
- }
43
+ return if status.nil?
44
+
45
+ color = options[:color] || color_for(status)
46
+
47
+ duration = elapsed_time_humanized(status.created_at, time_now)
48
+ time_title = status.complete? ? "Completed" : "Now"
49
+ jobs_run = status.total - status.pending
50
+
51
+ return if status.total.to_f <= 0
52
+
53
+ completion_percentage = percentage(jobs_run / status.total.to_f)
54
+ failure_percentage = percentage(status.failures / status.total.to_f)
55
+
56
+ fields = [
57
+ {
58
+ title: title,
59
+ value: status.description,
60
+ short: false
61
+ },
62
+ {
63
+ title: "Batch ID",
64
+ value: status.bid,
65
+ short: false
66
+ },
67
+ {
68
+ title: "Created",
69
+ value: time_format(status.created_at),
70
+ short: true
71
+ },
72
+ {
73
+ title: time_title,
74
+ value: time_format(time_now),
75
+ short: true
76
+ },
77
+ {
78
+ title: "Duration",
79
+ value: duration,
80
+ short: true
81
+ },
82
+ {
83
+ title: "Total Jobs",
84
+ value: status.total,
85
+ short: true
86
+ },
87
+ {
88
+ title: "Jobs Run",
89
+ value: jobs_run,
90
+ short: true
91
+ },
92
+ {
93
+ title: "Completion %",
94
+ value: completion_percentage,
95
+ short: true
96
+ },
97
+ {
98
+ title: "Failures",
99
+ value: status.failures,
100
+ short: true
101
+ },
102
+ {
103
+ title: "Failure %",
104
+ value: failure_percentage,
105
+ short: true
106
+ }
107
+ ]
108
+
109
+ body = {
110
+ attachments: [
111
+ fields: fields,
112
+ color: color
129
113
  ]
114
+ }
115
+ http_post(body)
116
+ end
130
117
 
131
- body = { attachments: attachments }.to_json
118
+ # @param data [Hash]
119
+ private def http_post(data)
120
+ url = options[:webhook_urls].fetch(options[:webhook_name])
121
+ uri = URI.parse(url)
122
+ http = Net::HTTP.new(uri.host, uri.port)
123
+ http.use_ssl = true if uri.port == 443
124
+
125
+ header = {"Content-Type": "application/json"}
126
+ request = Net::HTTP::Post.new(uri.request_uri, header)
127
+ request.body = data.to_json
128
+ http.request(request)
129
+ end
132
130
 
133
- HTTParty.post(url, body: body)
134
- end
131
+ # @param number [Numeric]
132
+ # @param precision [Integer]
133
+ # @param multiply100 [Boolean]
134
+ private def percentage(number, precision: 2, multiply100: true)
135
+ number = number * 100 if multiply100
136
+ rounded = number.to_f.round(precision)
137
+ format = number == rounded.to_i ? "%.f" : "%.#{precision}f"
138
+ (format % rounded) + "%"
139
+ end
135
140
 
136
- # Send a notification without Sidekiq batch info
137
- # @author Jason Lew
138
- def message(text, options)
139
- url = @@webhook_urls[options[:webhook_name]]
140
- body = { 'text' => text }.to_json
141
- HTTParty.post(url, body: body)
141
+ # @param status [Sidekiq::Batch::Status]
142
+ private def color_for(status)
143
+ colors = {
144
+ red: "f00000",
145
+ yellow: "ffc000",
146
+ green: "009800"
147
+ }
148
+
149
+ if status.total == 0
150
+ colors[:yellow]
151
+ elsif status.failures > 0
152
+ colors[:red]
153
+ elsif status.failures == 0
154
+ colors[:green]
155
+ else
156
+ colors[:yellow]
142
157
  end
158
+ end
159
+
160
+ # @param t0 [DateTime]
161
+ # @param t1 [DateTime]
162
+ private def elapsed_time_humanized(t0, t1, precision: 2)
163
+ time_humanize(
164
+ elapsed_seconds(t0, t1, precision: precision),
165
+ precision: precision
166
+ )
167
+ end
143
168
 
144
- private
145
- def color_for(status)
146
- if status.total == 0
147
- '#FBBD08' # yellow
148
- elsif status.failures > 0
149
- '#FF0000' # red
150
- elsif status.failures == 0
151
- '#1C9513' # green
152
- else
153
- '#FBBD08' # yellow
169
+ # @param t0 [DateTime]
170
+ # @param t1 [DateTime]
171
+ private def elapsed_seconds(t0, t1, precision: 2)
172
+ dt0 = t0.to_datetime
173
+ dt1 = t1.to_datetime
174
+ ((dt1 - dt0) * 24 * 60 * 60).to_f.round(precision)
175
+ end
176
+
177
+ # http://stackoverflow.com/questions/4136248/how-to-generate-a-human-readable-time-range-using-ruby-on-rails
178
+ # @param secs [Integer]
179
+ private def time_humanize(secs, precision: 2)
180
+ [[60, :s], [60, :m], [24, :h], [1000, :d]].map do |count, name|
181
+ if secs > 0
182
+ secs, n = secs.divmod(count)
183
+ if name == :s
184
+ num = n.to_f == n.to_i ? n.to_i : n.to_f
185
+ "%.#{precision}f#{name}" % num
186
+ else
187
+ "#{n.to_i}#{name}"
188
+ end
154
189
  end
155
- end
190
+ end.compact.reverse.join(" ")
191
+ end
192
+
193
+ # @param time [DateTime]
194
+ private def time_format(time)
195
+ time.strftime("%D @ %I:%M:%S %P")
156
196
  end
157
197
  end
metadata CHANGED
@@ -1,60 +1,32 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: HornsAndHooves-slackiq
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.13
4
+ version: 1.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - HornsAndHooves
8
8
  - Peter Maneykowski
9
- autorequire:
9
+ autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2020-03-31 00:00:00.000000000 Z
12
+ date: 2022-01-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
- name: httparty
15
+ name: bundler
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  requirements:
18
18
  - - ">="
19
19
  - !ruby/object:Gem::Version
20
20
  version: '0'
21
- type: :runtime
21
+ type: :development
22
22
  prerelease: false
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
25
  - - ">="
26
26
  - !ruby/object:Gem::Version
27
27
  version: '0'
28
- - !ruby/object:Gem::Dependency
29
- name: bundler
30
- requirement: !ruby/object:Gem::Requirement
31
- requirements:
32
- - - "~>"
33
- - !ruby/object:Gem::Version
34
- version: '1.10'
35
- type: :development
36
- prerelease: false
37
- version_requirements: !ruby/object:Gem::Requirement
38
- requirements:
39
- - - "~>"
40
- - !ruby/object:Gem::Version
41
- version: '1.10'
42
28
  - !ruby/object:Gem::Dependency
43
29
  name: rake
44
- requirement: !ruby/object:Gem::Requirement
45
- requirements:
46
- - - "~>"
47
- - !ruby/object:Gem::Version
48
- version: '10.0'
49
- type: :development
50
- prerelease: false
51
- version_requirements: !ruby/object:Gem::Requirement
52
- requirements:
53
- - - "~>"
54
- - !ruby/object:Gem::Version
55
- version: '10.0'
56
- - !ruby/object:Gem::Dependency
57
- name: rspec
58
30
  requirement: !ruby/object:Gem::Requirement
59
31
  requirements:
60
32
  - - ">="
@@ -68,7 +40,7 @@ dependencies:
68
40
  - !ruby/object:Gem::Version
69
41
  version: '0'
70
42
  - !ruby/object:Gem::Dependency
71
- name: rspec-sidekiq
43
+ name: rspec
72
44
  requirement: !ruby/object:Gem::Requirement
73
45
  requirements:
74
46
  - - ">="
@@ -100,14 +72,12 @@ files:
100
72
  - bin/console
101
73
  - bin/setup
102
74
  - lib/slackiq.rb
103
- - lib/slackiq/sidekiq_status.rb
104
- - lib/slackiq/time_helper.rb
105
75
  - lib/slackiq/version.rb
106
76
  homepage: https://github.com/HornsAndHooves/slackiq
107
77
  licenses:
108
78
  - MIT
109
79
  metadata: {}
110
- post_install_message:
80
+ post_install_message:
111
81
  rdoc_options: []
112
82
  require_paths:
113
83
  - lib
@@ -122,8 +92,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
122
92
  - !ruby/object:Gem::Version
123
93
  version: '0'
124
94
  requirements: []
125
- rubygems_version: 3.0.6
126
- signing_key:
95
+ rubygems_version: 3.0.9
96
+ signing_key:
127
97
  specification_version: 4
128
98
  summary: 'HornsAndHooves: Slack and Sidekiq Pro integration'
129
99
  test_files: []
@@ -1,24 +0,0 @@
1
- # RSpec::Sidekiq::Batch::Status does not support batch failures.
2
- # Here, FailureStatus and SuccessStatus circumvent this problem.
3
-
4
- if defined?(RSpec)
5
- require 'rspec-sidekiq'
6
-
7
- module RSpec
8
- module Sidekiq
9
- class Batch
10
- class FailureStatus < NullStatus
11
- def failures
12
- 1
13
- end
14
- end
15
-
16
- class SuccessStatus < NullStatus
17
- def failures
18
- 0
19
- end
20
- end
21
- end
22
- end
23
- end
24
- end
@@ -1,37 +0,0 @@
1
- require 'date'
2
-
3
- module Slackiq
4
- module TimeHelper
5
-
6
- class << self
7
-
8
- def elapsed_time_humanized(t0, t1)
9
- humanize(elapsed_seconds(t0, t1))
10
- end
11
-
12
- def elapsed_seconds(t0, t1)
13
- dt0 = t0.to_datetime
14
- dt1 = t1.to_datetime
15
- ((dt1-dt0)*24*60*60).to_f.round(2)
16
- end
17
-
18
- # http://stackoverflow.com/questions/4136248/how-to-generate-a-human-readable-time-range-using-ruby-on-rails
19
- def humanize(secs)
20
- [[60, :s], [60, :m], [24, :h], [1000, :d]].map{ |count, name|
21
- if secs > 0
22
- secs, n = secs.divmod(count)
23
- if name == :s
24
- "%.2f#{name}" % [n.to_f]
25
- else
26
- "#{n.to_i}#{name}"
27
- end
28
- end
29
- }.compact.reverse.join(' ')
30
- end
31
-
32
- def format(time)
33
- time.strftime('%D @ %r').gsub('PM', 'pm').gsub('AM', 'am')
34
- end
35
- end
36
- end
37
- end