problem_child 1.1.0 → 2.0.0

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
  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