problem_child 1.1.0 → 2.0.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
  SHA1:
3
- metadata.gz: 6b194f981f4da29f466015a0147e28f2ea7f98fe
4
- data.tar.gz: a8fca157ebe6c7718ba59b46692c63d103c690ab
3
+ metadata.gz: e282b0323f0d971146fde3f02147126f0438a506
4
+ data.tar.gz: 3b16247f802e2e3f03ce29f8ca6168fe155b9ddd
5
5
  SHA512:
6
- metadata.gz: b1202f58aef1959d41fb1cba180e127e389ddf26148af0ef42ac53036f18abf3eb64ed7a8c9d21cca833baf8f5e9e04143192921500f493fa03b865a272670f4
7
- data.tar.gz: b472d792040dd8e939461402c16ca3e3bd4a40b9258734e62846440c2e4c5f087f42873bd09e78601bd2d987a552898e68c5f37680ad144de56fc350a27888bf
6
+ metadata.gz: f9fea889ff77fccd9640e02f278ca3399a5ca00629773c1b3954f661ccc20af84c3e6505f79cf516904a5a32f1ca568025f80d1e4bb18c85509d271dbbbe4bda
7
+ data.tar.gz: 37edf7116f0aaa52c0e0e83dc073b1faf7621d72617d5581920a0308ea342cca047329145940be156324e1a241175da0accdd808d5373f8b8064e80c4b266ffe
data/.travis.yml CHANGED
@@ -7,8 +7,8 @@ rvm:
7
7
  sudo: false
8
8
  cache: bundler
9
9
  services:
10
- - memcached
11
-
10
+ - redis
11
+
12
12
  env:
13
13
  global:
14
14
  GITHUB_CLIENT_ID: "1234"
data/README.md CHANGED
@@ -22,11 +22,11 @@ Allows authenticated or anonymous users to fill out a standard web form to creat
22
22
 
23
23
  ## Requirements
24
24
 
25
- You'll need to have [Memcache](http://memcached.org/) running.
25
+ You'll need to have [Redis](http://redis.io/) running.
26
26
 
27
- On OS X, run `brew install memcached` to install, followed by `memcached` to run the memcache server.
27
+ On OS X, run `brew install redis` to install, followed by `redis-server` to run the redis server.
28
28
 
29
- On Heroku you'll want to run `heroku addons:add memcachier:dev` to add a free Memecache instance to your app.
29
+ On Heroku you'll want to run `heroku addons:create heroku-redis:hobby-dev` to add a free Redis instance to your app.
30
30
 
31
31
  ## Configuring
32
32
 
@@ -73,6 +73,10 @@ or as a checkbox:
73
73
  <input type="checkbox" name="labels[]" value="suggestion" />
74
74
  ```
75
75
 
76
+ ## Creating pull requests
77
+
78
+ Problem child can also be used to create pull requests. Simply add one or more file inputs to your form. The uploaded files will be committed to a newly created feature branch, and a pull request will be created against the repo's primary branch with the rest of the form content.
79
+
76
80
  ## Contributing
77
81
 
78
82
  1. Fork it ( https://github.com/[my-github-username]/problem_child/fork )
@@ -26,7 +26,7 @@ module ProblemChild
26
26
  end
27
27
 
28
28
  def issue_body
29
- form_data.reject { |key, value| key == "title" || value.empty? || key == "labels" }.map { |key,value| "* **#{key.humanize}**: #{value}"}.join("\n")
29
+ form_data.reject { |key, value| key == "title" || value.empty? || key == "labels" || value.is_a?(Hash) }.map { |key,value| "* **#{key.humanize}**: #{value}"}.join("\n")
30
30
  end
31
31
 
32
32
  # abstraction to allow cached form data to be used in place of default params
@@ -38,11 +38,71 @@ module ProblemChild
38
38
  form_data["labels"].join(",") if form_data["labels"]
39
39
  end
40
40
 
41
+ def uploads
42
+ form_data.select do |key, value|
43
+ value.is_a?(Hash) && ( value.has_key?("filename") || value.has_key?(:filename) )
44
+ end
45
+ end
46
+
41
47
  def create_issue
42
48
  issue = client.create_issue(repo, form_data["title"], issue_body, :labels => labels)
43
49
  issue["number"] if issue
44
50
  end
45
51
 
52
+ # Returns array of Octokit branch objects
53
+ def branches
54
+ client.branches(repo)
55
+ end
56
+
57
+ # Head SHA of default branch, used for creating new branches
58
+ def base_sha
59
+ default_branch = client.repo(repo)[:default_branch]
60
+ branches.find { |branch| branch[:name] == default_branch }[:commit][:sha]
61
+ end
62
+
63
+ def branch_exists?(branch)
64
+ branches.any? { |b| b.name == branch }
65
+ end
66
+
67
+ # Name of branch to submit pull request from
68
+ # Starts with patch-1 and keeps going until it finds one not taken
69
+ def patch_branch
70
+ num = 1
71
+ branch_name = form_data["title"].parameterize
72
+ return branch_name unless branch_exists?(branch_name)
73
+ branch = "#{branch_name}-#{num}"
74
+ while branch_exists?(branch) do
75
+ num = num + 1
76
+ branch = "#{branch_name}-#{num}"
77
+ end
78
+ branch
79
+ end
80
+
81
+ # Create a branch with the given name, based off of HEAD of the defautl branch
82
+ def create_branch(branch)
83
+ client.create_ref repo, "heads/#{branch}", base_sha
84
+ end
85
+
86
+ # Create a pull request with the form contents
87
+ def create_pull_request
88
+ unless uploads.empty?
89
+ branch = patch_branch
90
+ create_branch(branch)
91
+ uploads.each do |key, upload|
92
+ client.create_contents(
93
+ repo,
94
+ upload["filename"],
95
+ "Create #{upload["filename"]}",
96
+ session["file_#{key}"],
97
+ :branch => branch
98
+ )
99
+ session["file_#{key}"] = nil
100
+ end
101
+ end
102
+ pr = client.create_pull_request(repo, "master", branch, form_data["title"], issue_body, :labels => labels)
103
+ pr["number"] if pr
104
+ end
105
+
46
106
  def repo_access?
47
107
  return true unless anonymous_submissions?
48
108
  !client.repository(repo)["private"]
@@ -50,6 +110,13 @@ module ProblemChild
50
110
  false
51
111
  end
52
112
 
113
+ def cache_form_data
114
+ uploads.each do |key, upload|
115
+ session["file_#{key}"] = File.open(upload[:tempfile]).read
116
+ end
117
+ session[:form_data] = params.to_json
118
+ end
119
+
53
120
  def auth!
54
121
  if anonymous_submissions?
55
122
  true
@@ -1,3 +1,3 @@
1
1
  module ProblemChild
2
- VERSION = "1.1.0"
2
+ VERSION = "2.0.0"
3
3
  end
@@ -20,6 +20,6 @@
20
20
  <label for="body">Body</label>
21
21
  <textarea class="form-control" rows="10" id="body" name="body"></textarea>
22
22
  </div>
23
-
23
+
24
24
  <button type="submit" class="btn btn-default">Submit</button>
25
25
  </form>
data/lib/problem_child.rb CHANGED
@@ -3,13 +3,12 @@ require 'sinatra'
3
3
  require 'sinatra_auth_github'
4
4
  require 'dotenv'
5
5
  require 'json'
6
- require 'dalli'
7
- require 'rack/session/dalli'
6
+ require 'redis'
7
+ require 'rack/session/moneta'
8
8
  require 'active_support'
9
9
  require 'active_support/core_ext/string'
10
10
  require "problem_child/version"
11
11
  require "problem_child/helpers"
12
- require "problem_child/memcache"
13
12
 
14
13
  module ProblemChild
15
14
 
@@ -29,35 +28,27 @@ module ProblemChild
29
28
 
30
29
  include ProblemChild::Helpers
31
30
 
32
- configure do
33
- use Rack::Session::Dalli, cache: ProblemChild::Memcache.client
34
- end
35
-
36
31
  set :github_options, {
37
32
  :scopes => "repo,read:org"
38
33
  }
39
34
 
40
- ENV['WARDEN_GITHUB_VERIFIER_SECRET'] ||= SecureRandom.hex
41
- register Sinatra::Auth::Github
42
-
43
- enable :sessions
44
- use Rack::Session::Cookie, {
45
- :http_only => true,
46
- :secret => ENV['SESSION_SECRET'] || SecureRandom.hex
47
- }
35
+ use Rack::Session::Moneta, store: :Redis, url: ENV["REDIS_URL"]
48
36
 
49
37
  configure :production do
50
38
  require 'rack-ssl-enforcer'
51
39
  use Rack::SslEnforcer
52
40
  end
53
41
 
42
+ ENV['WARDEN_GITHUB_VERIFIER_SECRET'] ||= SecureRandom.hex
43
+ register Sinatra::Auth::Github
44
+
54
45
  set :views, Proc.new { ProblemChild.views_dir }
55
46
  set :root, Proc.new { ProblemChild.root }
56
47
  set :public_folder, Proc.new { File.expand_path "public", ProblemChild.root }
57
48
 
58
49
  get "/" do
59
50
  if session[:form_data]
60
- issue = create_issue
51
+ issue = uploads.empty? ? create_issue : create_pull_request
61
52
  session[:form_data] = nil
62
53
  access = repo_access?
63
54
  else
@@ -69,7 +60,7 @@ module ProblemChild
69
60
  end
70
61
 
71
62
  post "/" do
72
- session[:form_data] = params.to_json
63
+ cache_form_data
73
64
  auth! unless anonymous_submissions?
74
65
  halt redirect "/"
75
66
  end
@@ -24,7 +24,8 @@ Gem::Specification.new do |spec|
24
24
  spec.add_dependency "sinatra_auth_github", "~> 1.0"
25
25
  spec.add_dependency "activesupport", "~> 4.2"
26
26
  spec.add_dependency "rack", "1.5.2"
27
- spec.add_dependency "dalli", "~> 2.7"
27
+ spec.add_dependency "redis", "~> 3.2"
28
+ spec.add_dependency "moneta", "~> 0.8"
28
29
 
29
30
  spec.add_development_dependency "rspec", "~> 3.1"
30
31
  spec.add_development_dependency "rack-test", "~> 0.6"
@@ -0,0 +1 @@
1
+ FOO
@@ -57,6 +57,12 @@ describe "ProblemChild::Helpers" do
57
57
  end
58
58
  end
59
59
 
60
+ it "caches the form data" do
61
+ @helper.params = {"title" => "title", "foo" => "bar"}
62
+ @helper.cache_form_data
63
+ expect(@helper.form_data["foo"]).to eql("bar")
64
+ end
65
+
60
66
  it "grabs the form data from the session" do
61
67
  expected = {"title" => "title", "foo" => "bar"}
62
68
  @helper.session["form_data"] = expected.to_json
@@ -127,4 +133,124 @@ describe "ProblemChild::Helpers" do
127
133
  @helper.params["labels"] = ["foo", "bar"]
128
134
  expect(@helper.labels).to eql("foo,bar")
129
135
  end
136
+
137
+ context "uploads" do
138
+ it "can identify uploads" do
139
+ @helper.params = {"title" => "title", "file" => { :filename => "foo.md"} }
140
+ expect(@helper.uploads.first[0]).to eql("file")
141
+ end
142
+
143
+ it "fetches the branches" do
144
+ stub_request(:get, "https://api.github.com/repos/benbalter/test-repo-ignore-me/branches").
145
+ to_return(:status => 200, :body => [{ :name => "master", :commit => { :sha => "1" } }])
146
+
147
+ with_env "GITHUB_TOKEN", "1234" do
148
+ with_env "GITHUB_REPO", "benbalter/test-repo-ignore-me" do
149
+ expect(@helper.branches[0][:name]).to eql("master")
150
+ end
151
+ end
152
+ end
153
+
154
+ it "fetches the base sha" do
155
+ stub_request(:get, "https://api.github.com/repos/benbalter/test-repo-ignore-me/branches").
156
+ to_return(:status => 200, :body => [{ :name => "master", :commit => { :sha => "123" } }])
157
+
158
+ stub_request(:get, "https://api.github.com/repos/benbalter/test-repo-ignore-me").
159
+ to_return(:status => 200, :body => {:default_branch => "master"}.to_json,
160
+ :headers => { "Content-Type" => "application/json" })
161
+
162
+ with_env "GITHUB_TOKEN", "1234" do
163
+ with_env "GITHUB_REPO", "benbalter/test-repo-ignore-me" do
164
+ expect(@helper.base_sha).to eql("123")
165
+ end
166
+ end
167
+ end
168
+
169
+ it "knows if a branch exists" do
170
+ stub_request(:get, "https://api.github.com/repos/benbalter/test-repo-ignore-me/branches").
171
+ to_return(:status => 200, :body => [{ :name => "master", :commit => { :sha => "123" } }].to_json,
172
+ :headers => { "Content-Type" => "application/json" })
173
+
174
+ with_env "GITHUB_TOKEN", "1234" do
175
+ with_env "GITHUB_REPO", "benbalter/test-repo-ignore-me" do
176
+ expect(@helper.branch_exists?("master")).to eql(true)
177
+ expect(@helper.branch_exists?("master2")).to eql(false)
178
+ end
179
+ end
180
+ end
181
+
182
+ it "creates the branch name" do
183
+ stub_request(:get, "https://api.github.com/repos/benbalter/test-repo-ignore-me/branches").
184
+ to_return(:status => 200, :body => [{ :name => "master", :commit => { :sha => "123" } }].to_json,
185
+ :headers => { "Content-Type" => "application/json" })
186
+
187
+ with_env "GITHUB_TOKEN", "1234" do
188
+ with_env "GITHUB_REPO", "benbalter/test-repo-ignore-me" do
189
+ @helper.params = {"title" => "My title" }
190
+ expect(@helper.patch_branch).to eql("my-title")
191
+
192
+ @helper.params = {"title" => "master" }
193
+ expect(@helper.patch_branch).to eql("master-1")
194
+ end
195
+ end
196
+ end
197
+
198
+ it "creates a branch" do
199
+ stub_request(:get, "https://api.github.com/repos/benbalter/test-repo-ignore-me").
200
+ to_return(:status => 200, :body => {:default_branch => "master"}.to_json,
201
+ :headers => { "Content-Type" => "application/json" })
202
+
203
+ stub_request(:get, "https://api.github.com/repos/benbalter/test-repo-ignore-me/branches").
204
+ to_return(:status => 200, :body => [{ :name => "master", :commit => { :sha => "123" } }].to_json,
205
+ :headers => { "Content-Type" => "application/json" })
206
+
207
+ stub = stub_request(:post, "https://api.github.com/repos/benbalter/test-repo-ignore-me/git/refs").
208
+ with(:body => "{\"ref\":\"refs/heads/my-branch\",\"sha\":\"123\"}")
209
+
210
+ with_env "GITHUB_TOKEN", "1234" do
211
+ with_env "GITHUB_REPO", "benbalter/test-repo-ignore-me" do
212
+ @helper.create_branch("my-branch")
213
+ expect(stub).to have_been_requested
214
+ end
215
+ end
216
+ end
217
+
218
+ it "caches uploads" do
219
+ path = File.expand_path "./fixtures/file.txt", File.dirname(__FILE__)
220
+ @helper.params = { "title" => "title", "some_file" => { :filename => "file.txt", :tempfile => path } }
221
+ @helper.cache_form_data
222
+ expect(@helper.session["file_some_file"]).to eql("FOO\n")
223
+ end
224
+
225
+ it "creates the pull request" do
226
+ stub_request(:get, "https://api.github.com/repos/benbalter/test-repo-ignore-me").
227
+ to_return(:status => 200, :body => {:default_branch => "master"}.to_json,
228
+ :headers => { "Content-Type" => "application/json" })
229
+
230
+ stub_request(:get, "https://api.github.com/repos/benbalter/test-repo-ignore-me/branches").
231
+ to_return(:status => 200, :body => [{ :name => "master", :commit => { :sha => "123" } }].to_json,
232
+ :headers => { "Content-Type" => "application/json" })
233
+
234
+ stub_request(:post, "https://api.github.com/repos/benbalter/test-repo-ignore-me/git/refs").
235
+ with(:body => "{\"ref\":\"refs/heads/title\",\"sha\":\"123\"}").
236
+ to_return(:status => 200)
237
+
238
+ push = stub_request(:put, "https://api.github.com/repos/benbalter/test-repo-ignore-me/contents/").
239
+ with(:body => "{\"branch\":\"title\",\"content\":\"Rk9PCg==\",\"message\":\"Create \"}")
240
+
241
+ pr = stub_request(:post, "https://api.github.com/repos/benbalter/test-repo-ignore-me/pulls").
242
+ with(:body => "{\"labels\":null,\"base\":\"master\",\"head\":\"title\",\"title\":\"title\",\"body\":\"* **Foo**: bar\"}")
243
+
244
+ with_env "GITHUB_TOKEN", "1234" do
245
+ with_env "GITHUB_REPO", "benbalter/test-repo-ignore-me" do
246
+ path = File.expand_path "./fixtures/file.txt", File.dirname(__FILE__)
247
+ @helper.params = { "title" => "title", "some_file" => { :filename => "file.txt", :tempfile => path }, "foo" => "bar" }
248
+ @helper.cache_form_data
249
+ @helper.create_pull_request
250
+ expect(push).to have_been_requested
251
+ expect(pr).to have_been_requested
252
+ end
253
+ end
254
+ end
255
+ end
130
256
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: problem_child
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Balter
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-02-26 00:00:00.000000000 Z
11
+ date: 2015-06-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sinatra
@@ -109,19 +109,33 @@ dependencies:
109
109
  - !ruby/object:Gem::Version
110
110
  version: 1.5.2
111
111
  - !ruby/object:Gem::Dependency
112
- name: dalli
112
+ name: redis
113
113
  requirement: !ruby/object:Gem::Requirement
114
114
  requirements:
115
115
  - - "~>"
116
116
  - !ruby/object:Gem::Version
117
- version: '2.7'
117
+ version: '3.2'
118
118
  type: :runtime
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
- version: '2.7'
124
+ version: '3.2'
125
+ - !ruby/object:Gem::Dependency
126
+ name: moneta
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '0.8'
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '0.8'
125
139
  - !ruby/object:Gem::Dependency
126
140
  name: rspec
127
141
  requirement: !ruby/object:Gem::Requirement
@@ -239,7 +253,6 @@ files:
239
253
  - config.ru
240
254
  - lib/problem_child.rb
241
255
  - lib/problem_child/helpers.rb
242
- - lib/problem_child/memcache.rb
243
256
  - lib/problem_child/public/vendor/bootstrap/.bower.json
244
257
  - lib/problem_child/public/vendor/bootstrap/Gruntfile.js
245
258
  - lib/problem_child/public/vendor/bootstrap/LICENSE
@@ -292,8 +305,8 @@ files:
292
305
  - script/console
293
306
  - script/release
294
307
  - script/server
308
+ - spec/fixtures/file.txt
295
309
  - spec/problem_child_helpers_spec.rb
296
- - spec/problem_child_memcache_spec.rb
297
310
  - spec/problem_child_spec.rb
298
311
  - spec/spec_helper.rb
299
312
  homepage: https://github.com/benbalter/problem_child
@@ -316,13 +329,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
316
329
  version: '0'
317
330
  requirements: []
318
331
  rubyforge_project:
319
- rubygems_version: 2.2.0
332
+ rubygems_version: 2.2.3
320
333
  signing_key:
321
334
  specification_version: 4
322
335
  summary: Allows authenticated or anonymous users to fill out a standard web form to
323
336
  creat GitHub issues.
324
337
  test_files:
338
+ - spec/fixtures/file.txt
325
339
  - spec/problem_child_helpers_spec.rb
326
- - spec/problem_child_memcache_spec.rb
327
340
  - spec/problem_child_spec.rb
328
341
  - spec/spec_helper.rb
@@ -1,22 +0,0 @@
1
- module ProblemChild
2
- class Memcache
3
-
4
- def self.client
5
- Dalli::Client.new(server, options)
6
- end
7
-
8
- def self.options
9
- {
10
- :username => ENV["MEMCACHIER_USERNAME"],
11
- :password => ENV["MEMCACHIER_PASSWORD"],
12
- :failover => true,
13
- :socket_timeout => 1.5,
14
- :socket_failure_delay => 0.2
15
- }
16
- end
17
-
18
- def self.server
19
- ENV["MEMCACHIER_SERVERS"].split(",") unless ENV["MEMCACHIER_SERVERS"].to_s.blank?
20
- end
21
- end
22
- end
@@ -1,35 +0,0 @@
1
- require "spec_helper"
2
-
3
- describe "ProblemChild::MemcacheHelper" do
4
-
5
- it "knows the server" do
6
- with_env "MEMCACHIER_SERVERS", "localhost:1234" do
7
- expect(ProblemChild::Memcache.server).to eql(["localhost:1234"])
8
- end
9
- end
10
-
11
- it "can process multiple servers" do
12
- with_env "MEMCACHIER_SERVERS", "localhost:1234, localhost:4567" do
13
- expect(ProblemChild::Memcache.server).to eql(["localhost:1234", " localhost:4567"])
14
- end
15
- end
16
-
17
- it "pulls the username and password" do
18
- with_env "MEMCACHIER_USERNAME", "user" do
19
- with_env "MEMCACHIER_PASSWORD", "pass" do
20
- expected = {
21
- :username => "user",
22
- :password => "pass",
23
- :failover => true,
24
- :socket_timeout => 1.5,
25
- :socket_failure_delay => 0.2
26
- }
27
- expect(ProblemChild::Memcache.options).to eql(expected)
28
- end
29
- end
30
- end
31
-
32
- it "returns the client" do
33
- expect(ProblemChild::Memcache.client.class).to eql(Dalli::Client)
34
- end
35
- end