smart_todo 1.6.0 → 1.8.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: c17d1471d893e5a764dda2fa9adcfc73049b5c5a45d6678feca40104879065fc
4
- data.tar.gz: 85411f13bc189f060372e90a72fc1a545cd0030b6f67cf0fd75f1ff97dbeb755
3
+ metadata.gz: a7cac40289c5fb2fdf4bb143ee7cca9b1c74ce41c7e1fa426f7efc17364a2683
4
+ data.tar.gz: c93946f4131f64068e9c249c760829a33f3e88ae669f322bb00ea0647f68eb20
5
5
  SHA512:
6
- metadata.gz: bfe190ce26c9b3e65aea86f3d23e40a400c14edcac8c465b361341ac00a7ab7d59498c9c3edda92d7e180659cad96240148bce26dc3a1db77a92ee2e03575204
7
- data.tar.gz: f1c0386fbb5346a74c3f9c0c3b6228a9af29a810241793a5f44db5cbe7d7873c24e9dda987b65aff0d4984193be3652bec472431a48407badb2eb7610da506d6
6
+ metadata.gz: a6a84fd8bd731f7b31fe0033be57a50ba018af724ae29cfa16a4c9a0a96766e6335ee1c1145976002519aff1f69d6a28a3bf220963e42f55c28b051f02c85d80
7
+ data.tar.gz: 562151dd8c4fc2b8d55d72c004a701ef2c10f01d3e84f632b1a945ee969469e7dffd5473c7f109548ca105175b1a893f1e039a9ee091b3cb8034af7e47116082
@@ -12,7 +12,7 @@ jobs:
12
12
  name: Ruby ${{ matrix.version }}
13
13
  strategy:
14
14
  matrix:
15
- version: [3.0, 3.1, 3.2]
15
+ version: [3.0, 3.1, 3.2, 3.3]
16
16
 
17
17
  steps:
18
18
  - uses: actions/checkout@v2
@@ -8,10 +8,9 @@ jobs:
8
8
 
9
9
  steps:
10
10
  - uses: actions/checkout@v2
11
- - name: Set up Ruby 3.0
11
+ - name: Set up Ruby
12
12
  uses: ruby/setup-ruby@v1
13
13
  with:
14
- ruby-version: 3.0
15
14
  bundler-cache: true
16
15
  - name: Install gems
17
16
  run: |
data/.rubocop.yml CHANGED
@@ -2,7 +2,6 @@ inherit_gem:
2
2
  rubocop-shopify: rubocop.yml
3
3
 
4
4
  AllCops:
5
- TargetRubyVersion: 3.0
6
5
  NewCops: disable
7
6
  SuggestExtensions: false
8
7
  Exclude:
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.3.0
data/Gemfile.lock CHANGED
@@ -1,8 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- smart_todo (1.6.0)
5
- rexml
4
+ smart_todo (1.8.0)
5
+ prism (~> 0.15)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
@@ -13,34 +13,37 @@ GEM
13
13
  crack (0.4.5)
14
14
  rexml
15
15
  hashdiff (1.0.1)
16
- json (2.6.3)
16
+ json (2.7.1)
17
+ language_server-protocol (3.17.0.3)
17
18
  minitest (5.14.4)
18
- parallel (1.23.0)
19
- parser (3.2.2.3)
19
+ parallel (1.24.0)
20
+ parser (3.3.0.5)
20
21
  ast (~> 2.4.1)
21
22
  racc
23
+ prism (0.17.0)
22
24
  public_suffix (4.0.6)
23
- racc (1.7.0)
25
+ racc (1.7.3)
24
26
  rainbow (3.1.1)
25
27
  rake (13.0.6)
26
- regexp_parser (2.8.1)
27
- rexml (3.2.5)
28
- rubocop (1.52.1)
28
+ regexp_parser (2.9.0)
29
+ rexml (3.2.6)
30
+ rubocop (1.62.1)
29
31
  json (~> 2.3)
32
+ language_server-protocol (>= 3.17.0)
30
33
  parallel (~> 1.10)
31
- parser (>= 3.2.2.3)
34
+ parser (>= 3.3.0.2)
32
35
  rainbow (>= 2.2.2, < 4.0)
33
36
  regexp_parser (>= 1.8, < 3.0)
34
37
  rexml (>= 3.2.5, < 4.0)
35
- rubocop-ast (>= 1.28.0, < 2.0)
38
+ rubocop-ast (>= 1.31.1, < 2.0)
36
39
  ruby-progressbar (~> 1.7)
37
40
  unicode-display_width (>= 2.4.0, < 3.0)
38
- rubocop-ast (1.29.0)
39
- parser (>= 3.2.1.0)
41
+ rubocop-ast (1.31.2)
42
+ parser (>= 3.3.0.4)
40
43
  rubocop-shopify (2.14.0)
41
44
  rubocop (~> 1.51)
42
45
  ruby-progressbar (1.13.0)
43
- unicode-display_width (2.4.2)
46
+ unicode-display_width (2.5.0)
44
47
  webmock (3.11.2)
45
48
  addressable (>= 2.3.6)
46
49
  crack (>= 0.3.2)
@@ -58,4 +61,4 @@ DEPENDENCIES
58
61
  webmock
59
62
 
60
63
  BUNDLED WITH
61
- 2.2.25
64
+ 2.5.7
data/bin/profile ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "smart_todo"
6
+
7
+ class NullDispatcher < SmartTodo::Dispatchers::Base
8
+ class << self
9
+ def validate_options!(_); end
10
+ end
11
+
12
+ def dispatch
13
+ end
14
+ end
15
+
16
+ exit SmartTodo::CLI.new(NullDispatcher).run
data/dev.yml CHANGED
@@ -4,7 +4,7 @@ type:
4
4
  - ruby
5
5
 
6
6
  up:
7
- - ruby: 3.0.2
7
+ - ruby
8
8
  - bundler
9
9
 
10
10
  console:
@@ -1,15 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "optionparser"
4
+ require "etc"
4
5
 
5
6
  module SmartTodo
6
7
  # This class is the entrypoint of the SmartTodo library and is responsible
7
8
  # to retrieve the command line options as well as iterating over each files/directories
8
9
  # to run the +CommentParser+ on.
9
10
  class CLI
10
- def initialize
11
+ def initialize(dispatcher = nil)
11
12
  @options = {}
12
13
  @errors = []
14
+ @dispatcher = dispatcher
13
15
  end
14
16
 
15
17
  # @param args [Array<String>]
@@ -19,15 +21,18 @@ module SmartTodo
19
21
 
20
22
  paths << "." if paths.empty?
21
23
 
24
+ comment_parser = CommentParser.new
22
25
  paths.each do |path|
23
- normalize_path(path).each do |file|
24
- parse_file(file)
26
+ normalize_path(path).each do |filepath|
27
+ comment_parser.parse_file(filepath)
25
28
 
26
29
  $stdout.print(".")
27
30
  $stdout.flush
28
31
  end
29
32
  end
30
33
 
34
+ process_dispatches(process_todos(comment_parser.todos))
35
+
31
36
  if @errors.empty?
32
37
  0
33
38
  else
@@ -79,14 +84,17 @@ module SmartTodo
79
84
  end
80
85
  end
81
86
 
82
- # @param file [String] a path to a file
83
- def parse_file(file)
84
- Parser::CommentParser.new(File.read(file, encoding: "UTF-8")).parse.each do |todo_node|
87
+ def process_todos(todos)
88
+ events = Events.new
89
+ dispatches = []
90
+
91
+ todos.each do |todo|
85
92
  event_message = nil
86
- event_met = todo_node.metadata.events.find do |event|
87
- event_message = Events.public_send(event.method_name, *event.arguments)
93
+ event_met = todo.events.find do |event|
94
+ event_message = events.public_send(event.method_name, *event.arguments)
88
95
  rescue => e
89
- message = "Error while parsing #{file} on event `#{event.method_name}` with arguments #{event.arguments}: " \
96
+ message = "Error while parsing #{todo.filepath} on event `#{event.method_name}` " \
97
+ "with arguments #{event.arguments.map(&:inspect)}: " \
90
98
  "#{e.message}"
91
99
 
92
100
  @errors << message
@@ -94,10 +102,36 @@ module SmartTodo
94
102
  nil
95
103
  end
96
104
 
97
- @errors.concat(todo_node.metadata.errors)
98
-
99
- dispatcher.new(event_message, todo_node, file, @options).dispatch if event_met
105
+ @errors.concat(todo.errors)
106
+ dispatches << [event_message, todo] if event_met
100
107
  end
108
+
109
+ dispatches
110
+ end
111
+
112
+ def process_dispatches(dispatches)
113
+ queue = Queue.new
114
+ dispatches.each { |dispatch| queue << dispatch }
115
+
116
+ thread_count = Etc.nprocessors
117
+ thread_count.times { queue << nil }
118
+
119
+ threads =
120
+ thread_count.times.map do
121
+ Thread.new do
122
+ Thread.current.abort_on_exception = true
123
+
124
+ loop do
125
+ dispatch = queue.pop
126
+ break if dispatch.nil?
127
+
128
+ (event_message, todo) = dispatch
129
+ dispatcher.new(event_message, todo, todo.filepath, @options).dispatch
130
+ end
131
+ end
132
+ end
133
+
134
+ threads.each(&:join)
101
135
  end
102
136
  end
103
137
  end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartTodo
4
+ class CommentParser
5
+ attr_reader :todos
6
+
7
+ def initialize
8
+ @todos = []
9
+ end
10
+
11
+ if Prism.respond_to?(:parse_comments)
12
+ def parse(source, filepath = "-e")
13
+ parse_comments(Prism.parse_comments(source), filepath)
14
+ end
15
+
16
+ def parse_file(filepath)
17
+ parse_comments(Prism.parse_file_comments(filepath), filepath)
18
+ end
19
+ else
20
+ def parse(source, filepath = "-e")
21
+ parse_comments(Prism.parse(source, filepath).comments, filepath)
22
+ end
23
+
24
+ def parse_file(filepath)
25
+ parse_comments(Prism.parse_file(filepath).comments, filepath)
26
+ end
27
+ end
28
+
29
+ class << self
30
+ def parse(source)
31
+ parser = new
32
+ parser.parse(source)
33
+ parser.todos
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ if defined?(Prism::InlineComment)
40
+ def inline?(comment)
41
+ comment.is_a?(Prism::InlineComment)
42
+ end
43
+ else
44
+ def inline?(comment)
45
+ comment.type == :inline
46
+ end
47
+ end
48
+
49
+ def parse_comments(comments, filepath)
50
+ current_todo = nil
51
+
52
+ comments.each do |comment|
53
+ next unless inline?(comment)
54
+
55
+ source = comment.location.slice
56
+
57
+ if source.match?(/^#\sTODO\(/)
58
+ todos << current_todo if current_todo
59
+ current_todo = Todo.new(source, filepath)
60
+ elsif current_todo && (indent = source[/^#(\s*)/, 1].length) && (indent - current_todo.indent == 2)
61
+ current_todo << "#{source[(indent + 1)..]}\n"
62
+ else
63
+ todos << current_todo if current_todo
64
+ current_todo = nil
65
+ end
66
+ end
67
+
68
+ todos << current_todo if current_todo
69
+ end
70
+ end
71
+ end
@@ -40,7 +40,7 @@ module SmartTodo
40
40
  @todo_node = todo_node
41
41
  @options = options
42
42
  @file = file
43
- @assignees = @todo_node.metadata.assignees
43
+ @assignees = @todo_node.assignees
44
44
  end
45
45
 
46
46
  # This method gets called when a TODO reminder is expired and needs to be delivered.
@@ -1,5 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ gem("bundler")
4
+ require "bundler"
5
+ require "net/http"
6
+ require "time"
7
+ require "json"
8
+
3
9
  module SmartTodo
4
10
  # This module contains all the methods accessible for SmartTodo comments.
5
11
  # It is meant to be reopened by the host application in order to define
@@ -10,7 +16,7 @@ module SmartTodo
10
16
  #
11
17
  # @example Adding a custom event
12
18
  # module SmartTodo
13
- # module Events
19
+ # class Events
14
20
  # def trello_card_close(card)
15
21
  # ...
16
22
  # end
@@ -18,33 +24,87 @@ module SmartTodo
18
24
  # end
19
25
  #
20
26
  # TODO(on: trello_card_close(381), to: 'john@example.com')
21
- module Events
22
- extend self
27
+ #
28
+ class Events
29
+ def initialize(now: nil, spec_set: nil, current_ruby_version: nil)
30
+ @now = now
31
+ @spec_set = spec_set
32
+ @rubygems_client = nil
33
+ @github_client = nil
34
+ @current_ruby_version = current_ruby_version
35
+ end
23
36
 
24
37
  # Check if the +date+ is in the past
25
38
  #
26
- # @param date [String] a correctly formatted date
39
+ # @param on_date [String] a string parsable by Time.parse
27
40
  # @return [false, String]
28
- def date(date)
29
- Date.met?(date)
41
+ def date(on_date)
42
+ if now >= Time.parse(on_date)
43
+ "We are past the *#{on_date}* due date and " \
44
+ "your TODO is now ready to be addressed."
45
+ else
46
+ false
47
+ end
30
48
  end
31
49
 
32
50
  # Check if a new version of +gem_name+ was released with the +requirements+ expected
33
51
  #
52
+ # @example Expecting a specific version
53
+ # gem_release('rails', '6.0')
54
+ #
55
+ # @example Expecting a version in the 5.x.x series
56
+ # gem_release('rails', '> 5.2', '< 6')
57
+ #
34
58
  # @param gem_name [String]
35
59
  # @param requirements [Array<String>] a list of version specifiers
36
60
  # @return [false, String]
37
61
  def gem_release(gem_name, *requirements)
38
- GemRelease.new(gem_name, requirements).met?
62
+ response = rubygems_client.get("/api/v1/versions/#{gem_name}.json")
63
+
64
+ if response.code_type < Net::HTTPClientError
65
+ "The gem *#{gem_name}* doesn't seem to exist, I can't determine if " \
66
+ "your TODO is ready to be addressed."
67
+ else
68
+ requirement = Gem::Requirement.new(requirements)
69
+ version = JSON.parse(response.body).find { |gem| requirement.satisfied_by?(Gem::Version.new(gem["number"])) }
70
+
71
+ if version
72
+ "The gem *#{gem_name}* was released to version *#{version["number"]}* and " \
73
+ "your TODO is now ready to be addressed."
74
+ else
75
+ false
76
+ end
77
+ end
39
78
  end
40
79
 
41
80
  # Check if +gem_name+ was bumped to the +requirements+ expected
42
81
  #
82
+ # @example Expecting a specific version
83
+ # gem_bump('rails', '6.0')
84
+ #
85
+ # @example Expecting a version in the 5.x.x series
86
+ # gem_bump('rails', '> 5.2', '< 6')
87
+ #
43
88
  # @param gem_name [String]
44
89
  # @param requirements [Array<String>] a list of version specifiers
45
90
  # @return [false, String]
46
91
  def gem_bump(gem_name, *requirements)
47
- GemBump.new(gem_name, requirements).met?
92
+ specs = spec_set[gem_name]
93
+
94
+ if specs.empty?
95
+ "The gem *#{gem_name}* is not in your dependencies, I can't determine if " \
96
+ "your TODO is ready to be addressed."
97
+ else
98
+ requirement = Gem::Requirement.new(requirements)
99
+ version = specs.first.version
100
+
101
+ if requirement.satisfied_by?(version)
102
+ "The gem *#{gem_name}* was updated to version *#{version}* and " \
103
+ "your TODO is now ready to be addressed."
104
+ else
105
+ false
106
+ end
107
+ end
48
108
  end
49
109
 
50
110
  # Check if the issue +issue_number+ is closed
@@ -54,7 +114,22 @@ module SmartTodo
54
114
  # @param issue_number [String, Integer]
55
115
  # @return [false, String]
56
116
  def issue_close(organization, repo, issue_number)
57
- IssueClose.new(organization, repo, issue_number, type: "issues").met?
117
+ headers = github_headers(organization, repo)
118
+ response = github_client.get("/repos/#{organization}/#{repo}/issues/#{issue_number}", headers)
119
+
120
+ if response.code_type < Net::HTTPClientError
121
+ <<~EOM
122
+ I can't retrieve the information from the issue *#{issue_number}* in the *#{organization}/#{repo}* repository.
123
+
124
+ If the repository is a private one, make sure to export the `#{GITHUB_TOKEN}`
125
+ environment variable with a correct GitHub token.
126
+ EOM
127
+ elsif JSON.parse(response.body)["state"] == "closed"
128
+ "The issue https://github.com/#{organization}/#{repo}/issues/#{issue_number} is now closed, " \
129
+ "your TODO is ready to be addressed."
130
+ else
131
+ false
132
+ end
58
133
  end
59
134
 
60
135
  # Check if the pull request +pr_number+ is closed
@@ -64,7 +139,22 @@ module SmartTodo
64
139
  # @param pr_number [String, Integer]
65
140
  # @return [false, String]
66
141
  def pull_request_close(organization, repo, pr_number)
67
- IssueClose.new(organization, repo, pr_number, type: "pulls").met?
142
+ headers = github_headers(organization, repo)
143
+ response = github_client.get("/repos/#{organization}/#{repo}/pulls/#{pr_number}", headers)
144
+
145
+ if response.code_type < Net::HTTPClientError
146
+ <<~EOM
147
+ I can't retrieve the information from the PR *#{pr_number}* in the *#{organization}/#{repo}* repository.
148
+
149
+ If the repository is a private one, make sure to export the `#{GITHUB_TOKEN}`
150
+ environment variable with a correct GitHub token.
151
+ EOM
152
+ elsif JSON.parse(response.body)["state"] == "closed"
153
+ "The pull request https://github.com/#{organization}/#{repo}/pull/#{pr_number} is now closed, " \
154
+ "your TODO is ready to be addressed."
155
+ else
156
+ false
157
+ end
68
158
  end
69
159
 
70
160
  # Check if the installed ruby version meets requirements.
@@ -72,7 +162,61 @@ module SmartTodo
72
162
  # @param requirements [Array<String>] a list of version specifiers
73
163
  # @return [false, String]
74
164
  def ruby_version(*requirements)
75
- RubyVersion.new(requirements).met?
165
+ requirement = Gem::Requirement.new(requirements)
166
+
167
+ if requirement.satisfied_by?(current_ruby_version)
168
+ "The currently installed version of Ruby #{current_ruby_version} is #{requirement}."
169
+ else
170
+ false
171
+ end
172
+ end
173
+
174
+ private
175
+
176
+ def now
177
+ @now ||= Time.now
178
+ end
179
+
180
+ def spec_set
181
+ @spec_set ||= Bundler.load.specs
182
+ end
183
+
184
+ def rubygems_client
185
+ @rubygems_client ||= HttpClientBuilder.build("rubygems.org")
186
+ end
187
+
188
+ def github_client
189
+ @github_client ||= HttpClientBuilder.build("api.github.com")
190
+ end
191
+
192
+ def github_headers(organization, repo)
193
+ headers = { "Accept" => "application/vnd.github.v3+json" }
194
+
195
+ token = github_authorization_token(organization, repo)
196
+ headers["Authorization"] = "token #{token}" if token
197
+
198
+ headers
199
+ end
200
+
201
+ GITHUB_TOKEN = "SMART_TODO_GITHUB_TOKEN"
202
+
203
+ # @return [String, nil]
204
+ def github_authorization_token(organization, repo)
205
+ organization_name = organization.upcase.gsub(/[^A-Z0-9]/, "_")
206
+ repo_name = repo.upcase.gsub(/[^A-Z0-9]/, "_")
207
+
208
+ [
209
+ "#{GITHUB_TOKEN}__#{organization_name}__#{repo_name}",
210
+ "#{GITHUB_TOKEN}__#{organization_name}",
211
+ GITHUB_TOKEN,
212
+ ].find do |key|
213
+ token = ENV[key]
214
+ break token unless token.nil? || token.empty?
215
+ end
216
+ end
217
+
218
+ def current_ruby_version
219
+ @current_ruby_version ||= Gem::Version.new(RUBY_VERSION)
76
220
  end
77
221
  end
78
222
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+
5
+ module SmartTodo
6
+ # @api private
7
+ class HttpClientBuilder
8
+ class << self
9
+ def build(endpoint)
10
+ Net::HTTP.new(endpoint, Net::HTTP.https_default_port).tap do |client|
11
+ client.use_ssl = true
12
+ client.read_timeout = 30
13
+ client.ssl_timeout = 15
14
+ client.max_retries = 2
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -26,11 +26,7 @@ module SmartTodo
26
26
  # @param slack_token [String]
27
27
  def initialize(slack_token)
28
28
  @slack_token = slack_token
29
- @client = Net::HTTP.new("slack.com", Net::HTTP.https_default_port).tap do |client|
30
- client.use_ssl = true
31
- client.read_timeout = 30
32
- client.ssl_timeout = 15
33
- end
29
+ @client = HttpClientBuilder.build("slack.com")
34
30
  end
35
31
 
36
32
  # Retrieve the Slack ID of a user from his email
@@ -43,9 +39,9 @@ module SmartTodo
43
39
  #
44
40
  # @see https://api.slack.com/methods/users.lookupByEmail
45
41
  def lookup_user_by_email(email)
46
- headers = { "Content-Type" => "application/x-www-form-urlencoded" }
47
-
48
- request(:get, "/api/users.lookupByEmail?email=#{CGI.escape(email)}", nil, headers)
42
+ headers = default_headers.merge("Content-Type" => "application/x-www-form-urlencoded")
43
+ request = Net::HTTP::Get.new("/api/users.lookupByEmail?email=#{CGI.escape(email)}", headers)
44
+ dispatch(request)
49
45
  end
50
46
 
51
47
  # Send a message to a Slack channel or to a user
@@ -59,7 +55,10 @@ module SmartTodo
59
55
  #
60
56
  # @see https://api.slack.com/methods/chat.postMessage
61
57
  def post_message(channel, text)
62
- request(:post, "/api/chat.postMessage", JSON.dump(channel: channel, text: text))
58
+ headers = default_headers
59
+ request = Net::HTTP::Post.new("/api/chat.postMessage", headers)
60
+ request.body = JSON.dump(channel: channel, text: text)
61
+ dispatch(request)
63
62
  end
64
63
 
65
64
  private
@@ -71,27 +70,19 @@ module SmartTodo
71
70
  #
72
71
  # @raise [Net::HTTPError] in case the request to Slack failed
73
72
  # @raise [SlackClient::Error] in case Slack returns a { ok: false } in the body
74
- def request(method, endpoint, data = nil, headers = {})
75
- response = case method
76
- when :post, :patch
77
- @client.public_send(method, endpoint, data, default_headers.merge(headers))
78
- else
79
- @client.public_send(method, endpoint, default_headers.merge(headers))
80
- end
73
+ def dispatch(request, max_attempts = 5)
74
+ response = @client.request(request)
75
+ attempts = 1
81
76
 
82
- slack_response!(response)
83
- end
77
+ while response.is_a?(Net::HTTPTooManyRequests) && attempts < max_attempts
78
+ sleep([Integer(response["Retry-After"]), 600].min)
79
+ response = @client.request(request)
80
+ attempts += 1
81
+ end
84
82
 
85
- # Check if the response to Slack was a 200 and the Slack API request was successful
86
- #
87
- # @param response [Net::HTTPResponse] a net Net::HTTPResponse subclass
88
- # (Net::HTTPOK, Net::HTTPNotFound ...)
89
- # @return [Hash]
90
- #
91
- # @raise [Net::HTTPError] in case the request to Slack failed
92
- # @raise [SlackClient::Error] in case Slack returns a { ok: false } in the body
93
- def slack_response!(response)
94
- raise(Net::HTTPError.new("Request to slack failed", response)) unless response.code_type < Net::HTTPSuccess
83
+ unless response.code_type < Net::HTTPSuccess
84
+ raise(Net::HTTPError.new("Request to slack failed", response))
85
+ end
95
86
 
96
87
  body = JSON.parse(response.body)
97
88