houston-core 0.5.6 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +94 -69
  3. data/app/adapters/houston/adapters/deployment/engineyard.rb +4 -3
  4. data/app/adapters/houston/adapters/version_control/git_adapter.rb +36 -42
  5. data/app/adapters/houston/adapters/version_control/git_adapter/github_repo.rb +2 -2
  6. data/app/adapters/houston/adapters/version_control/git_adapter/remote_repo.rb +23 -11
  7. data/app/adapters/houston/adapters/version_control/git_adapter/repo.rb +18 -8
  8. data/app/adapters/houston/adapters/version_control/null_repo.rb +8 -0
  9. data/app/assets/javascripts/core/handlebars_helpers.coffee +3 -3
  10. data/app/assets/stylesheets/application/github_repos.scss +77 -0
  11. data/app/assets/stylesheets/application/navigation.scss +2 -0
  12. data/app/assets/stylesheets/application/pull_requests.scss +44 -58
  13. data/app/assets/stylesheets/core/avatars.scss +5 -0
  14. data/app/assets/stylesheets/core/colors.scss.erb +10 -7
  15. data/app/concerns/commit_synchronizer.rb +3 -0
  16. data/app/controllers/github/pulls_controller.rb +12 -0
  17. data/app/controllers/project_tests_controller.rb +3 -3
  18. data/app/controllers/projects_controller.rb +45 -1
  19. data/app/controllers/releases_controller.rb +42 -26
  20. data/app/helpers/application_helper.rb +8 -0
  21. data/app/helpers/avatar_helper.rb +2 -2
  22. data/app/helpers/commit_helper.rb +2 -2
  23. data/app/helpers/oembed_helper.rb +8 -0
  24. data/app/helpers/project_helper.rb +4 -5
  25. data/app/helpers/release_helper.rb +11 -0
  26. data/app/helpers/timeline_helper.rb +1 -1
  27. data/app/helpers/url_helper.rb +0 -18
  28. data/app/interactors/cache_key_dependencies.rb +28 -0
  29. data/app/jobs/sync_all_tickets_job.rb +1 -0
  30. data/app/mailers/view_mailer.rb +0 -1
  31. data/app/models/commit.rb +1 -1
  32. data/app/models/github/pull_request.rb +82 -26
  33. data/app/models/github/pull_request_event.rb +2 -2
  34. data/app/models/milestone.rb +1 -0
  35. data/app/models/project.rb +14 -0
  36. data/app/models/project_dependencies.rb +5 -3
  37. data/app/models/task.rb +1 -1
  38. data/app/models/user.rb +41 -0
  39. data/app/views/commits/show.html.erb +9 -1
  40. data/app/views/github/pulls/index.html.erb +102 -0
  41. data/app/views/project_notification/new_release.html.erb +6 -0
  42. data/app/views/project_tickets/index.xls.erb +0 -7
  43. data/app/views/projects/_form.html.erb +29 -17
  44. data/app/views/projects/index.html.erb +3 -3
  45. data/app/views/projects/new_from_github.html.erb +67 -0
  46. data/app/views/releases/_commits.html.erb +1 -1
  47. data/app/views/releases/show.html.erb +9 -0
  48. data/app/views/users/_form.html.erb +35 -19
  49. data/config/application.rb +12 -0
  50. data/config/initializers/mime_types.rb +1 -0
  51. data/config/routes.rb +17 -3
  52. data/db/migrate/20151201042126_require_projects_to_have_name_and_slug.rb +6 -0
  53. data/db/migrate/20151202005557_add_head_sha_to_projects.rb +24 -0
  54. data/db/migrate/20151202011812_require_projects_to_have_color.rb +13 -0
  55. data/db/migrate/20151205204922_require_project_slugs_to_be_unique.rb +5 -0
  56. data/db/migrate/20151205214647_add_avatar_url_to_pull_requests.rb +5 -0
  57. data/db/migrate/20151209004458_add_json_labels_to_pull_requests.rb +5 -0
  58. data/db/migrate/20151209030113_add_timestamps_to_pull_requests.rb +6 -0
  59. data/db/structure.sql +31 -5
  60. data/houston.gemspec +7 -7
  61. data/lib/configuration.rb +3 -2
  62. data/lib/houston/version.rb +1 -1
  63. data/lib/rack/oembed.rb +23 -0
  64. data/templates/new-instance/config/jobs/cache_key_dependencies.rb +3 -0
  65. data/templates/new-instance/config/triggers/tests/slack_when_analyzed.rb +1 -4
  66. data/templates/new-instance/config/triggers/tests/slack_when_completed.rb +1 -1
  67. data/templates/new-instance/lib/slack_helpers.rb +1 -1
  68. data/test/integration/ticket_tasks_api_test.rb +1 -1
  69. data/test/unit/adapters/git_adapter_test.rb +29 -8
  70. data/test/unit/adapters/version_control_adapters_api_test.rb +2 -0
  71. data/test/unit/controllers/hooks_controller_test.rb +4 -4
  72. data/test/unit/models/commit_test.rb +2 -2
  73. data/test/unit/models/project_test.rb +2 -2
  74. data/test/unit/models/pull_request_test.rb +9 -4
  75. data/test/unit/models/task_test.rb +1 -1
  76. data/test/unit/models/ticket_test.rb +1 -1
  77. metadata +31 -16
@@ -14,6 +14,8 @@ module CommitSynchronizer
14
14
 
15
15
  unreachable_commits = project.commits.unreachable.pluck(:sha)
16
16
  flag_reachable_commits! unreachable_commits & expected_commits
17
+
18
+ project.update_column :head_sha, project.repo.branch("master")
17
19
  end
18
20
  end
19
21
 
@@ -82,6 +84,7 @@ private
82
84
  commit = find_by_sha(native_commit.sha)
83
85
  return commit if commit
84
86
 
87
+ $!.additional_information["project"] = project.slug
85
88
  $!.additional_information["native_commit.sha"] = native_commit.sha
86
89
  raise
87
90
  end
@@ -0,0 +1,12 @@
1
+ class Github::PullsController < ApplicationController
2
+
3
+ def index
4
+ @pulls = Github::PullRequest.order(created_at: :desc).preload(:project, :user)
5
+ @labels = @pulls.flat_map(&:labels).uniq { |label| label["name"] }.sort_by { |label| label["name"] }
6
+ @selected_labels = params.fetch(:only, "").split(/,\s*/)
7
+ @selected_labels = @labels.map { |label| label["name"] } if @selected_labels.none?
8
+ @selected_labels -= params.fetch(:except, "").split(/,\s*/)
9
+ @title = "Pull Requests (#{@pulls.count})"
10
+ end
11
+
12
+ end
@@ -3,7 +3,7 @@ class ProjectTestsController < ApplicationController
3
3
  def index
4
4
  @project = Project.find_by_slug! params[:slug]
5
5
 
6
- head = params.fetch :at, @project.repo.branch("master")
6
+ head = params.fetch :at, @project.head_sha
7
7
  commits = params.fetch(:limit, 500).to_i
8
8
 
9
9
  @commits = Houston.benchmark("[project_tests#index] fetch commits") {
@@ -35,7 +35,7 @@ class ProjectTestsController < ApplicationController
35
35
  @totals = Hash[@test.test_results.group(:status).pluck(:status, "COUNT(*)")]
36
36
 
37
37
  begin
38
- head = params.fetch :at, @project.repo.branch("master")
38
+ head = params.fetch :at, @project.head_sha
39
39
  stop_shas = @test.introduced_in_shas
40
40
  @commits = Houston.benchmark("[project_tests#show] fetch commits") {
41
41
  @project.repo.ancestors(head, including_self: true, limit: 100, hide: stop_shas) }
@@ -54,7 +54,7 @@ class ProjectTestsController < ApplicationController
54
54
 
55
55
  @results = @test.test_results.where(test_run_id: @runs.map(&:id))
56
56
  .joins(:test_run)
57
- .select("test_runs.sha", :*)
57
+ .select("test_runs.sha", "test_results.*")
58
58
  .index_by { |result| result[:sha] }
59
59
  @runs = @runs.index_by(&:sha)
60
60
  end
@@ -9,8 +9,8 @@ class ProjectsController < ApplicationController
9
9
  @projects = Project \
10
10
  .includes(:owners)
11
11
  .includes(:maintainers)
12
+ .includes(:head)
12
13
  .unretired
13
- .map { |project| ProjectDependencies.new(project) }
14
14
  @test_runs = TestRun.most_recent.index_by(&:project_id)
15
15
  @releases = Release.where(environment_name: "production").most_recent.index_by(&:project_id)
16
16
  end
@@ -28,6 +28,50 @@ class ProjectsController < ApplicationController
28
28
  @project.roles.build(user: current_user) if @project.roles.none?
29
29
  end
30
30
 
31
+ def new_from_github
32
+ authorize! :create, Project
33
+
34
+ existing_projects = Project.unscoped.where("extended_attributes->'git_location' LIKE '%github.com%'")
35
+ github_repos = Houston.benchmark "Fetching repos" do
36
+ Houston.github.repos
37
+ end
38
+ @repos = github_repos.map do |repo|
39
+ project = existing_projects.detect { |project|
40
+ [repo.git_url, repo.ssh_url, repo.clone_url].member?(project.extended_attributes["git_location"]) }
41
+ { name: repo.name,
42
+ owner: repo.owner.login,
43
+ full_name: repo.full_name,
44
+ private: repo[:private],
45
+ git_location: repo.ssh_url,
46
+ project: project }
47
+ end
48
+ end
49
+
50
+
51
+ def create_from_github
52
+ authorize! :create, Project
53
+
54
+ repos = params.fetch(:repos, [])
55
+ projects = Project.transaction do
56
+ repos.map do |repo|
57
+ owner, name = repo.split("/")
58
+ title = name.humanize.gsub(/\b(?<!['’.`])[a-z]/) { $&.capitalize }.gsub("-", "::")
59
+ Project.create!(
60
+ name: title,
61
+ slug: name,
62
+ version_control_name: "Git",
63
+ extended_attributes: {"git_location" => "git@github.com:#{repo}.git"})
64
+ end
65
+ end
66
+
67
+ flash[:notice] = "#{projects.count} projects added"
68
+ redirect_to projects_path
69
+
70
+ rescue ActiveRecord::RecordInvalid
71
+ flash[:error] = $!.message
72
+ redirect_to :back
73
+ end
74
+
31
75
 
32
76
  def edit
33
77
  @project = Project.find_by_slug!(params[:id])
@@ -1,20 +1,17 @@
1
1
  class ReleasesController < ApplicationController
2
2
  include UrlHelper
3
- before_filter :get_project_and_environment
3
+ include ReleaseHelper
4
+ before_filter :get_release_and_project, only: [:show, :edit, :update, :destroy]
5
+ before_filter :get_project_and_environment, only: [:index, :new, :create]
4
6
  before_filter :load_tickets, only: [:new, :edit, :create, :update]
5
7
 
8
+
9
+
6
10
  def index
7
11
  @title = "Releases • #{@project.name}"
8
12
  @title << " (#{@environment})" if @environment
9
13
  end
10
14
 
11
- def show
12
- @release = @releases.find(params[:id])
13
- authorize! :show, @release
14
-
15
- @title = "Release #{@release.release_date.strftime("%b %-d")} • #{@project.name}"
16
- end
17
-
18
15
  def new
19
16
  @title = "New Release (#{@environment}) • #{@project.name}"
20
17
 
@@ -42,22 +39,6 @@ class ReleasesController < ApplicationController
42
39
  end
43
40
  end
44
41
 
45
- def edit
46
- @release = @releases.find(params[:id])
47
- authorize! :update, @release
48
-
49
- if params[:recreate]
50
- if @release.can_read_commits?
51
- @release.load_commits!
52
- @release.load_tickets!
53
- @release.build_changes_from_commits
54
- end
55
- end
56
-
57
- @release.release_changes = [ReleaseChange.new(@release, "", "")] if @release.release_changes.none?
58
- @release.valid?
59
- end
60
-
61
42
  def create
62
43
  @release = @releases.new(params[:release])
63
44
  @release.user = current_user
@@ -81,8 +62,39 @@ class ReleasesController < ApplicationController
81
62
  end
82
63
  end
83
64
 
65
+
66
+
67
+ def show
68
+ authorize! :show, @release
69
+
70
+ @title = "Release #{@release.release_date.strftime("%b %-d")} • #{@project.name}"
71
+
72
+ if request.format.oembed?
73
+ render json: {
74
+ version: "1.0",
75
+ type: "link",
76
+ author_name: "#{@project.slug} / #{@release.environment_name}",
77
+ title: format_release_subject(@release),
78
+ html: format_release_description(@release) }
79
+ end
80
+ end
81
+
82
+ def edit
83
+ authorize! :update, @release
84
+
85
+ if params[:recreate]
86
+ if @release.can_read_commits?
87
+ @release.load_commits!
88
+ @release.load_tickets!
89
+ @release.build_changes_from_commits
90
+ end
91
+ end
92
+
93
+ @release.release_changes = [ReleaseChange.new(@release, "", "")] if @release.release_changes.none?
94
+ @release.valid?
95
+ end
96
+
84
97
  def update
85
- @release = @releases.find(params[:id])
86
98
  authorize! :update, @release
87
99
 
88
100
  if @release.update_attributes(params[:release])
@@ -93,7 +105,6 @@ class ReleasesController < ApplicationController
93
105
  end
94
106
 
95
107
  def destroy
96
- @release = @releases.find(params[:id])
97
108
  authorize! :destroy, @release
98
109
 
99
110
  @release.destroy
@@ -103,6 +114,11 @@ class ReleasesController < ApplicationController
103
114
 
104
115
  private
105
116
 
117
+ def get_release_and_project
118
+ @release = Release.find(params[:id])
119
+ @project = @release.project
120
+ end
121
+
106
122
  def get_project_and_environment
107
123
  @project = Project.find_by_slug!(params[:project_id])
108
124
  @environment = params[:environment] || @project.environments_with_release_notes.first
@@ -75,6 +75,14 @@ module ApplicationHelper
75
75
 
76
76
 
77
77
 
78
+ def pull_request_label(label)
79
+ background = "##{label["color"]}"
80
+ foreground = "#fff"
81
+ foreground = "#333" if %w{#f7c6c7 #d4c5f9 #fbca04 #fad8c7 #bfe5bf}.member? background
82
+ "<span class=\"label\" style=\"background: #{background}; color: #{foreground};\">#{label["name"]}</span>".html_safe
83
+ end
84
+
85
+
78
86
  end
79
87
 
80
88
 
@@ -22,11 +22,11 @@ module AvatarHelper
22
22
  # http://en.gravatar.com/site/implement/ruby
23
23
  # http://en.gravatar.com/site/implement/url
24
24
  def gravatar_url(email, options={})
25
- url = "//www.gravatar.com/avatar/#{Digest::MD5::hexdigest(email)}?r=g&d=retro"
25
+ url = "https://www.gravatar.com/avatar/#{Digest::MD5::hexdigest(email)}?r=g&d=retro"
26
26
  url << "&s=#{options[:size]}" if options.key?(:size)
27
27
  url
28
28
  end
29
29
 
30
30
 
31
31
 
32
- end
32
+ end
@@ -13,8 +13,8 @@ module CommitHelper
13
13
  project = commit.project
14
14
  content = block_given? ? yield : "<span class=\"commit-sha\">#{commit.sha[0...7]}</span>".html_safe
15
15
 
16
- return content unless github_url?(project)
17
- link_to content, github_commit_url(project, commit.sha), options.reverse_merge(target: "_blank")
16
+ return content unless url = github_commit_url(project, commit.sha)
17
+ link_to content, url, options.reverse_merge(target: "_blank")
18
18
  end
19
19
 
20
20
  def link_to_release_commit_range(release)
@@ -0,0 +1,8 @@
1
+ module OembedHelper
2
+
3
+ def link_to_oembed(url)
4
+ url = "#{main_app.root_url}oembed/1.0?url=#{CGI.escape(url)}"
5
+ tag "link", rel: "alternate", type: "application/json+oembed", href: url
6
+ end
7
+
8
+ end
@@ -1,10 +1,9 @@
1
1
  module ProjectHelper
2
2
 
3
3
  def with_most_recent_commit(project)
4
- return if project.repo.nil?
5
- commit = project.find_commit_by_sha project.repo.branch("master")
4
+ commit = project.head
6
5
  if commit
7
- commit.project = project.model # so that _Commit_ doesn't load project again
6
+ commit.project = project # so that _Commit_ doesn't load project again
8
7
  yield commit
9
8
  end
10
9
  end
@@ -12,7 +11,7 @@ module ProjectHelper
12
11
  def with_most_recent_release(project)
13
12
  release = @releases[project.id]
14
13
  if release
15
- release.project = project.model # so that _Release_ doesn't load project again
14
+ release.project = project # so that _Release_ doesn't load project again
16
15
  yield release
17
16
  end
18
17
  end
@@ -20,7 +19,7 @@ module ProjectHelper
20
19
  def with_most_recent_test_run(project)
21
20
  test_run = @test_runs[project.id]
22
21
  if test_run
23
- test_run.project = project.model # so that _TestRun_ doesn't load project again
22
+ test_run.project = project # so that _TestRun_ doesn't load project again
24
23
  yield test_run
25
24
  end
26
25
  end
@@ -4,6 +4,17 @@ module ReleaseHelper
4
4
  "<span class=\"weekday\">#{date.strftime("%A")}</span> #{date.strftime("%b %e, %Y")}".html_safe
5
5
  end
6
6
 
7
+ def format_release_subject(release)
8
+ release.date.strftime("%b %e, %Y • ") + release.released_at.strftime("%-I:%M%p").downcase
9
+ end
10
+
11
+ def format_release_description(release)
12
+ ordered_by_tag(release.release_changes)
13
+ .map { |change| "#{change.tag.name.upcase}&nbsp;&nbsp;&nbsp;#{change.description}" }
14
+ .join("\r\n")
15
+ .html_safe
16
+ end
17
+
7
18
  def ordered_by_tag(changes)
8
19
  changes.sort_by { |change| change.tag ? change.tag.position : 99 }
9
20
  end
@@ -3,7 +3,7 @@ module TimelineHelper
3
3
  def render_timeline_gap_for(date_range)
4
4
  days = date_range.end - date_range.begin
5
5
  if days < 3
6
- date_range.inject("") { |html, date| html << render_timeline_date(date) }.html_safe
6
+ date_range.to_a.reverse.inject("") { |html, date| html << render_timeline_date(date) }.html_safe
7
7
  else
8
8
  <<-HTML.html_safe
9
9
  <div class="timeline-date-gap"></div>
@@ -48,24 +48,6 @@ module UrlHelper
48
48
  end
49
49
  end
50
50
 
51
- def release_path(release, options={})
52
- super(release.project.to_param, release.environment_name, release, options)
53
- end
54
-
55
- def edit_release_path(release, options={})
56
- super(release.project.to_param, release.environment_name, release, options)
57
- end
58
-
59
-
60
-
61
- def release_url(release, options={})
62
- super(release.project.to_param, release.environment_name, release, options)
63
- end
64
-
65
- def edit_release_url(release, options={})
66
- super(release.project.to_param, release.environment_name, release, options)
67
- end
68
-
69
51
  def new_release_url(release, options={})
70
52
  super(release.project.to_param, release.environment_name, options.merge(deploy_id: release.deploy_id))
71
53
  end
@@ -0,0 +1,28 @@
1
+ class CacheKeyDependencies
2
+ attr_reader :project
3
+
4
+ def self.for(*projects)
5
+ projects = projects[0] if projects.length == 1 && projects[0].respond_to?(:each)
6
+ projects.each do |project|
7
+ begin
8
+ self.new(project).perform!
9
+ rescue StandardError => e
10
+ Houston.report_exception(e)
11
+ end
12
+ end
13
+ end
14
+
15
+ def initialize(project)
16
+ @project = ProjectDependencies.new(project)
17
+ end
18
+
19
+ def perform!
20
+ KeyDependency.all.each do |dependency|
21
+ version = ProjectDependency.new(project, dependency).version
22
+ project.extended_attributes = project.extended_attributes.merge(
23
+ "key_dependency.#{dependency.slug}" => version)
24
+ end
25
+ project.update_column :extended_attributes, project.extended_attributes
26
+ end
27
+
28
+ end
@@ -16,6 +16,7 @@ class SyncAllTicketsJob
16
16
  end
17
17
 
18
18
  def update_tickets_for_project!(project)
19
+ connection_retry_count ||= 0
19
20
  SyncProjectTicketsJob.new(project).run!
20
21
 
21
22
  rescue Houston::Adapters::TicketTracker::ConnectionError
@@ -23,7 +23,6 @@ class ViewMailer < ActionMailer::Base
23
23
  application/markdown.scss
24
24
  application/test_run.scss
25
25
  application/releases.scss
26
- application/pull_requests.scss
27
26
  application/follow_up.scss
28
27
  }
29
28
 
@@ -206,7 +206,7 @@ class Commit < ActiveRecord::Base
206
206
  self.tasks = identify_tasks
207
207
 
208
208
  tasks.each do |task|
209
- task.committed!(self)
209
+ task.mark_committed!(self)
210
210
  end
211
211
  end
212
212
 
@@ -21,21 +21,63 @@ module Github
21
21
  validates :number, uniqueness: { scope: :project_id }
22
22
 
23
23
  class << self
24
- def fetch!
24
+ # Makes X + Y requests to GitHub
25
+ # where X is the number of projects in Houston on GitHub
26
+ # and Y is the number of pull requests for those projects
27
+ #
28
+ # We _could_ group repos by their owner and fetch `org_issues`
29
+ # but that will only work for organizations, not personal
30
+ # accounts.
31
+ #
32
+ # This method can chomp through your rate limit rather quickly.
33
+ # Also, on my computer it took 19 seconds to fetch 39 pull
34
+ # requests from 52 repos.
35
+ def fetch!(projects = Project.unretired)
36
+ repos = projects
37
+ .where("extended_attributes->'git_location' LIKE '%github.com%'")
38
+ .pluck("extended_attributes->'git_location'")
39
+ .map { |url| _repo_name_from_url(url) }
40
+ .compact
41
+
25
42
  Houston.benchmark "Fetching pull requests" do
26
- Houston.github.org_issues(Houston.config.github[:organization], filter: "all", state: "open")
43
+ requests = 0
44
+ issues = repos.flat_map do |repo|
45
+ _fetch_issues_for!(repo).tap do |results|
46
+ requests += 1 + (results.length / 30)
47
+ end
48
+ end
49
+
50
+ pulls = issues
27
51
  .select { |issue| !issue.pull_request.nil? }
28
- .map { |issue| Houston.github.pull_request(issue.repository.full_name, issue.number)
29
- .to_h
30
- .merge(labels: issue.labels.map(&:name))
31
- .with_indifferent_access }
52
+ .map { |issue|
53
+ requests += 1
54
+ repo = issue.pull_request.url[/https:\/\/api.github.com\/repos\/(.*)\/pulls\/\d+/, 1]
55
+ Houston.github.pull_request(repo, issue.number)
56
+ .to_h
57
+ .merge(labels: issue.labels)
58
+ .with_indifferent_access }
59
+
60
+ Rails.logger.info "[pulls] #{requests} requests; #{Houston.github.last_response.headers["x-ratelimit-remaining"]} remaining"
61
+ pulls
62
+ end
63
+ end
64
+
65
+ def _repo_name_from_url(url)
66
+ url[/\Agit@github\.com:(.*)\.git\Z/, 1] || url[/\Agit:\/\/github.com\/(.*)\.git\Z/, 1]
67
+ end
68
+
69
+ def _fetch_issues_for!(repo)
70
+ if repo.end_with? "/*"
71
+ Houston.github.org_issues(repo[0...-2], filter: "all", state: "open")
72
+ else
73
+ Houston.github.issues(repo, filter: "all", state: "open")
32
74
  end
75
+ rescue Octokit::NotFound
76
+ []
33
77
  end
34
78
 
35
- def sync!
36
- expected_pulls = fetch!
37
- expected_pulls.select! { |pr| pr["base"]["repo"]["name"] == pr["head"]["repo"]["name"] }
38
- # select only ones where head and base are the same repo
79
+ def sync!(projects = Project.unretired)
80
+ expected_pulls = fetch!(projects)
39
81
  Houston.benchmark "Syncing pull requests" do
40
82
  existing_pulls = all.to_a
41
83
 
@@ -56,7 +98,9 @@ module Github
56
98
 
57
99
  existing_pr ||= Github::PullRequest.new
58
100
  existing_pr.merge_attributes(expected_pr)
59
- existing_pr.save if existing_pr.valid?
101
+ unless existing_pr.save
102
+ Rails.logger.warn "\e[31m[pulls] Invalid PR: #{existing_pr.errors.full_messages.join("; ")}\e[0m"
103
+ end
60
104
  existing_pr
61
105
  end
62
106
  end
@@ -93,42 +137,52 @@ module Github
93
137
  end
94
138
 
95
139
  def labeled(*labels)
96
- where(["labels && ARRAY[?]", labels])
140
+ where(["exists (select 1 from jsonb_array_elements(pull_requests.json_labels) as \"label\" where \"label\"->>'name' IN (?))", labels])
97
141
  end
98
142
  end
99
143
 
100
144
 
101
145
 
102
146
  def labels=(value)
103
- super Array(value).uniq
147
+ self.json_labels = value.map { |label| label.to_h.stringify_keys.pick("name", "color") }
148
+ end
149
+
150
+ def labels
151
+ json_labels
104
152
  end
105
153
 
106
154
  def add_label!(label, options={})
155
+ label = label.to_h.stringify_keys.pick("name", "color")
156
+
107
157
  transaction do
108
158
  pr = self.class.lock.find id
109
- pr.update_attributes! labels: pr.labels + [label], actor: options[:as]
159
+ new_labels = pr.json_labels.reject { |l| l["name"] == label["name"] } + [label]
160
+ pr.update_attributes! json_labels: new_labels, actor: options[:as]
110
161
  end
111
162
  end
112
163
 
113
164
  def remove_label!(label, options={})
165
+ label = label.to_h.stringify_keys.pick("name", "color")
166
+
114
167
  transaction do
115
168
  pr = self.class.lock.find id
116
- pr.update_attributes! labels: pr.labels - [label], actor: options[:as]
169
+ new_labels = pr.json_labels.reject { |l| l["name"] == label["name"] }
170
+ pr.update_attributes! json_labels: new_labels, actor: options[:as]
117
171
  end
118
172
  end
119
173
 
120
174
 
121
175
 
122
176
  def merge_attributes(pr)
123
- if new_record?
124
- self.repo = pr["base"]["repo"]["name"]
125
- self.number = pr["number"]
126
- self.username = pr["user"]["login"]
127
- self.url = pr["html_url"]
128
- self.base_sha = pr["base"]["sha"]
129
- self.base_ref = pr["base"]["ref"]
130
- end
131
-
177
+ self.repo = pr["base"]["repo"]["name"] unless repo
178
+ self.number = pr["number"] unless number
179
+ self.username = pr["user"]["login"] unless username
180
+ self.avatar_url = pr["user"]["avatar_url"] unless avatar_url
181
+ self.url = pr["html_url"] unless url
182
+ self.base_sha = pr["base"]["sha"] unless base_sha
183
+ self.base_ref = pr["base"]["ref"] unless base_ref
184
+
185
+ self.created_at = pr["created_at"]
132
186
  self.title = pr["title"]
133
187
  self.body = pr["body"]
134
188
  self.head_sha = pr["head"]["sha"]
@@ -145,11 +199,13 @@ module Github
145
199
  end
146
200
 
147
201
  def associate_user_with_self
148
- self.user = User.find_by_nickname(username)
202
+ self.user = User.find_by_github_username(username)
149
203
  end
150
204
 
151
205
  def associate_commits_with_self
152
- self.commits = project.commits.between(base_sha, head_sha)
206
+ Houston.try({max_tries: 2, base: 0}, ActiveRecord::RecordNotUnique) do
207
+ self.commits = project.commits.between(base_sha, head_sha)
208
+ end
153
209
  end
154
210
 
155
211
  end