shipit-engine 0.15.0 → 0.16.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.
Files changed (111) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +34 -1
  3. data/app/assets/javascripts/shipit/page_updater.js.coffee +63 -0
  4. data/app/assets/javascripts/shipit/stacks.js.coffee +9 -21
  5. data/app/assets/stylesheets/_base/_base.scss +2 -2
  6. data/app/assets/stylesheets/_base/_colors.scss +0 -1
  7. data/app/assets/stylesheets/_base/_forms.scss +14 -0
  8. data/app/assets/stylesheets/_pages/_commits.scss +16 -6
  9. data/app/assets/stylesheets/_pages/_settings.scss +8 -0
  10. data/app/assets/stylesheets/_pages/_stacks.scss +1 -1
  11. data/app/controllers/shipit/api/base_controller.rb +7 -3
  12. data/app/controllers/shipit/api/ccmenu_controller.rb +33 -0
  13. data/app/controllers/shipit/api/pull_requests_controller.rb +36 -0
  14. data/app/controllers/shipit/api/stacks_controller.rb +1 -0
  15. data/app/controllers/shipit/ccmenu_url_controller.rb +22 -0
  16. data/app/controllers/shipit/pull_requests_controller.rb +30 -0
  17. data/app/controllers/shipit/stacks_controller.rb +7 -2
  18. data/app/controllers/shipit/webhooks_controller.rb +1 -2
  19. data/app/helpers/shipit/github_url_helper.rb +8 -2
  20. data/app/helpers/shipit/shipit_helper.rb +9 -0
  21. data/app/helpers/shipit/stacks_helper.rb +22 -7
  22. data/app/jobs/shipit/background_job/unique.rb +19 -1
  23. data/app/jobs/shipit/cache_deploy_spec_job.rb +1 -1
  24. data/app/jobs/shipit/merge_pull_requests_job.rb +26 -0
  25. data/app/jobs/shipit/perform_task_job.rb +1 -1
  26. data/app/jobs/shipit/refresh_pull_request_job.rb +8 -0
  27. data/app/models/concerns/shipit/deferred_touch.rb +6 -1
  28. data/app/models/shipit/anonymous_user.rb +4 -0
  29. data/app/models/shipit/application_record.rb +5 -0
  30. data/app/models/shipit/commit.rb +51 -49
  31. data/app/models/shipit/commit_message.rb +32 -0
  32. data/app/models/shipit/deploy.rb +5 -0
  33. data/app/models/shipit/deploy_spec.rb +26 -1
  34. data/app/models/shipit/deploy_spec/file_system.rb +6 -1
  35. data/app/models/shipit/deploy_spec/kubernetes_discovery.rb +10 -13
  36. data/app/models/shipit/deploy_spec/npm_discovery.rb +2 -1
  37. data/app/models/shipit/duration.rb +3 -1
  38. data/app/models/shipit/hook.rb +1 -0
  39. data/app/models/shipit/pull_request.rb +252 -0
  40. data/app/models/shipit/stack.rb +33 -17
  41. data/app/models/shipit/status.rb +1 -16
  42. data/app/models/shipit/status/common.rb +45 -0
  43. data/app/models/shipit/status/group.rb +82 -0
  44. data/app/models/shipit/status/missing.rb +30 -0
  45. data/app/models/shipit/status/unknown.rb +33 -0
  46. data/app/models/shipit/unlimited_api_client.rb +10 -0
  47. data/app/serializers/shipit/commit_serializer.rb +1 -1
  48. data/app/serializers/shipit/pull_request_serializer.rb +20 -0
  49. data/app/serializers/shipit/stack_serializer.rb +6 -2
  50. data/app/views/layouts/shipit.html.erb +41 -39
  51. data/app/views/shipit/ccmenu/project.xml.builder +13 -0
  52. data/app/views/shipit/commits/_commit.html.erb +1 -1
  53. data/app/views/shipit/deploys/_deploy.html.erb +1 -1
  54. data/app/views/shipit/pull_requests/_pull_request.html.erb +29 -0
  55. data/app/views/shipit/pull_requests/index.html.erb +20 -0
  56. data/app/views/shipit/shared/_author.html.erb +7 -0
  57. data/app/views/shipit/stacks/_header.html.erb +5 -0
  58. data/app/views/shipit/stacks/settings.html.erb +13 -0
  59. data/app/views/shipit/stacks/show.html.erb +3 -2
  60. data/app/views/shipit/statuses/_group.html.erb +1 -1
  61. data/app/views/shipit/tasks/_task.html.erb +1 -1
  62. data/config/initializers/inflections.rb +3 -0
  63. data/config/locales/en.yml +1 -3
  64. data/config/routes.rb +8 -0
  65. data/db/migrate/20170130113633_create_shipit_pull_requests.rb +25 -0
  66. data/db/migrate/20170208143657_add_pull_request_number_and_title_to_commits.rb +7 -0
  67. data/db/migrate/20170208154609_backfill_merge_commits.rb +13 -0
  68. data/db/migrate/20170209160355_add_branch_to_pull_requests.rb +5 -0
  69. data/db/migrate/20170215123538_add_merge_queue_enabled_to_stacks.rb +5 -0
  70. data/db/migrate/20170220152410_improve_users_indexing.rb +6 -0
  71. data/db/migrate/20170221102128_improve_tasks_indexing.rb +8 -0
  72. data/db/migrate/20170221130336_add_last_revalidated_at_on_pull_requests.rb +10 -0
  73. data/lib/shipit.rb +2 -0
  74. data/lib/shipit/version.rb +1 -1
  75. data/lib/tasks/cron.rake +1 -0
  76. data/test/controllers/api/ccmenu_controller_test.rb +57 -0
  77. data/test/controllers/api/commits_controller_test.rb +1 -1
  78. data/test/controllers/api/pull_requests_controller_test.rb +59 -0
  79. data/test/controllers/ccmenu_controller_test.rb +33 -0
  80. data/test/controllers/pull_requests_controller_test.rb +31 -0
  81. data/test/controllers/webhooks_controller_test.rb +3 -4
  82. data/test/dummy/config/environments/development.rb +3 -1
  83. data/test/dummy/data/stacks/shopify/junk/production/git/README.md +8 -0
  84. data/test/dummy/data/stacks/shopify/junk/production/git/circle.yml +4 -0
  85. data/test/dummy/data/stacks/shopify/junk/production/git/shipit.yml +4 -0
  86. data/test/dummy/db/development.sqlite3 +0 -0
  87. data/test/dummy/db/schema.rb +45 -11
  88. data/test/dummy/db/seeds.rb +33 -10
  89. data/test/dummy/db/test.sqlite3 +0 -0
  90. data/test/fixtures/shipit/commits.yml +14 -0
  91. data/test/fixtures/shipit/pull_requests.yml +56 -0
  92. data/test/fixtures/shipit/stacks.yml +5 -1
  93. data/test/fixtures/shipit/statuses.yml +8 -0
  94. data/test/helpers/json_helper.rb +16 -14
  95. data/test/jobs/merge_pull_requests_job_test.rb +59 -0
  96. data/test/models/commits_test.rb +104 -49
  97. data/test/{unit → models}/deploy_spec_test.rb +138 -12
  98. data/test/models/deploys_test.rb +10 -4
  99. data/test/models/pull_request_test.rb +197 -0
  100. data/test/models/stacks_test.rb +46 -53
  101. data/test/models/status/group_test.rb +44 -0
  102. data/test/models/status/missing_test.rb +23 -0
  103. data/test/models/status_test.rb +3 -6
  104. data/test/unit/csv_serializer_test.rb +10 -2
  105. metadata +57 -12
  106. data/app/models/shipit/missing_status.rb +0 -21
  107. data/app/models/shipit/status_group.rb +0 -35
  108. data/app/models/shipit/unknown_status.rb +0 -48
  109. data/app/views/shipit/commits/_commit_author.html.erb +0 -7
  110. data/test/models/missing_status_test.rb +0 -23
  111. data/test/models/status_group_test.rb +0 -26
@@ -1,5 +1,6 @@
1
1
  module Shipit
2
2
  class Status < ActiveRecord::Base
3
+ include Common
3
4
  include DeferredTouch
4
5
 
5
6
  STATES = %w(pending success failure error).freeze
@@ -30,22 +31,6 @@ module Shipit
30
31
  end
31
32
  end
32
33
 
33
- def unknown?
34
- false
35
- end
36
-
37
- def ignored?
38
- stack.soft_failing_statuses.include?(context)
39
- end
40
-
41
- def group?
42
- false
43
- end
44
-
45
- def simple_state
46
- state == 'error' ? 'failure' : state
47
- end
48
-
49
34
  private
50
35
 
51
36
  def enable_ci_on_stack
@@ -0,0 +1,45 @@
1
+ module Shipit
2
+ class Status
3
+ module Common
4
+ def unknown?
5
+ state == 'unknown'.freeze
6
+ end
7
+
8
+ def pending?
9
+ state == 'pending'.freeze
10
+ end
11
+
12
+ def success?
13
+ state == 'success'.freeze
14
+ end
15
+
16
+ def error?
17
+ state == 'error'.freeze
18
+ end
19
+
20
+ def failure?
21
+ state == 'failure'.freeze
22
+ end
23
+
24
+ def group?
25
+ false
26
+ end
27
+
28
+ def simple_state
29
+ state == 'error'.freeze ? 'failure'.freeze : state
30
+ end
31
+
32
+ def allowed_to_fail?
33
+ commit.soft_failing_statuses.include?(context)
34
+ end
35
+
36
+ def hidden?
37
+ commit.hidden_statuses.include?(context)
38
+ end
39
+
40
+ def required?
41
+ commit.required_statuses.include?(context)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,82 @@
1
+ module Shipit
2
+ class Status
3
+ class Group
4
+ include Common
5
+
6
+ attr_reader :commit, :statuses
7
+
8
+ class << self
9
+ def compact(commit, statuses)
10
+ group = new(commit, statuses)
11
+ case group.size
12
+ when 0
13
+ Status::Unknown.new(commit)
14
+ when 1
15
+ group.statuses.first
16
+ else
17
+ group
18
+ end
19
+ end
20
+ end
21
+
22
+ def initialize(commit, statuses)
23
+ @commit = commit
24
+
25
+ visible_statuses = reject_hidden(statuses.to_a.uniq(&:context))
26
+ missing_contexts = required_statuses - visible_statuses.map(&:context)
27
+ visible_statuses += missing_contexts.map { |c| Status::Missing.new(commit, c) }
28
+
29
+ @statuses = visible_statuses.sort_by!(&:context)
30
+ end
31
+
32
+ delegate :pending?, :success?, :error?, :failure?, :unknown?, :state, :simple_state, to: :significant_status
33
+ delegate :each, :size, :map, to: :statuses
34
+ delegate :required_statuses, to: :commit
35
+
36
+ def to_a
37
+ @statuses.dup
38
+ end
39
+
40
+ def description
41
+ "#{success_count} / #{statuses.count} checks OK"
42
+ end
43
+
44
+ def target_url
45
+ end
46
+
47
+ def to_partial_path
48
+ 'statuses/group'
49
+ end
50
+
51
+ def group?
52
+ true
53
+ end
54
+
55
+ private
56
+
57
+ def reject_hidden(statuses)
58
+ statuses.reject(&:hidden?)
59
+ end
60
+
61
+ def reject_allowed_to_fail(statuses)
62
+ statuses.reject(&:allowed_to_fail?)
63
+ end
64
+
65
+ def significant_status
66
+ @significant_status ||= select_significant_status(statuses)
67
+ end
68
+
69
+ def select_significant_status(statuses)
70
+ statuses = reject_allowed_to_fail(statuses)
71
+ return Status::Unknown.new(commit) if statuses.empty?
72
+ non_success_statuses = statuses.reject(&:success?)
73
+ return statuses.first if non_success_statuses.empty?
74
+ non_success_statuses.reject(&:pending?).first || non_success_statuses.first || Status::Unknown.new(commit)
75
+ end
76
+
77
+ def success_count
78
+ @statuses.count(&:success?)
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,30 @@
1
+ module Shipit
2
+ class Status
3
+ class Missing
4
+ include Common
5
+
6
+ attr_reader :commit, :context
7
+
8
+ def initialize(commit, context)
9
+ @commit = commit
10
+ @context = context
11
+ end
12
+
13
+ def target_url
14
+ nil
15
+ end
16
+
17
+ def state
18
+ 'pending'.freeze
19
+ end
20
+
21
+ def description
22
+ I18n.t('missing_status.description', context: context)
23
+ end
24
+
25
+ def to_partial_path
26
+ 'shipit/statuses/status'
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,33 @@
1
+ module Shipit
2
+ class Status
3
+ class Unknown
4
+ include Common
5
+
6
+ attr_reader :commit
7
+
8
+ def initialize(commit)
9
+ @commit = commit
10
+ end
11
+
12
+ def state
13
+ 'unknown'.freeze
14
+ end
15
+
16
+ def target_url
17
+ nil
18
+ end
19
+
20
+ def description
21
+ ''
22
+ end
23
+
24
+ def context
25
+ 'ci/unknown'
26
+ end
27
+
28
+ def to_partial_path
29
+ 'shipit/statuses/status'
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,10 @@
1
+ module Shipit
2
+ class UnlimitedApiClient
3
+ def stack_id?
4
+ false
5
+ end
6
+
7
+ def check_permissions!(*)
8
+ end
9
+ end
10
+ end
@@ -13,7 +13,7 @@ module Shipit
13
13
  end
14
14
 
15
15
  def status
16
- object.significant_status.state
16
+ object.status.state
17
17
  end
18
18
 
19
19
  def html_url
@@ -0,0 +1,20 @@
1
+ module Shipit
2
+ class PullRequestSerializer < ActiveModel::Serializer
3
+ include GithubUrlHelper
4
+ include ConditionalAttributes
5
+
6
+ has_one :merge_requested_by
7
+ has_one :head, serializer: ShortCommitSerializer
8
+
9
+ attributes :id, :number, :title, :github_id, :additions, :deletions, :state, :merge_status, :mergeable,
10
+ :merge_requested_at, :rejection_reason, :html_url
11
+
12
+ def html_url
13
+ github_pull_request_url(object)
14
+ end
15
+
16
+ def include_rejection_reason?
17
+ object.rejection_reason?
18
+ end
19
+ end
20
+ end
@@ -3,8 +3,8 @@ module Shipit
3
3
  include ConditionalAttributes
4
4
 
5
5
  has_one :lock_author
6
- attributes :id, :repo_owner, :repo_name, :environment, :html_url, :url, :tasks_url, :deploy_url, :deploy_spec,
7
- :undeployed_commits_count, :is_locked, :lock_reason, :continuous_deployment, :created_at,
6
+ attributes :id, :repo_owner, :repo_name, :environment, :html_url, :url, :tasks_url, :deploy_url, :pull_requests_url,
7
+ :deploy_spec, :undeployed_commits_count, :is_locked, :lock_reason, :continuous_deployment, :created_at,
8
8
  :updated_at, :locked_since
9
9
 
10
10
  def url
@@ -19,6 +19,10 @@ module Shipit
19
19
  api_stack_tasks_url(object)
20
20
  end
21
21
 
22
+ def pull_requests_url
23
+ api_stack_pull_requests_url(object)
24
+ end
25
+
22
26
  def is_locked
23
27
  object.locked?
24
28
  end
@@ -5,7 +5,7 @@
5
5
  <%= favicon_link_tag %>
6
6
  <%= stylesheet_link_tag :shipit, media: 'all' %>
7
7
  <%= javascript_include_tag :shipit %>
8
-
8
+ <%= yield :update_subscription %>
9
9
  <%= csrf_meta_tags %>
10
10
  </head>
11
11
  <body>
@@ -26,51 +26,53 @@
26
26
  </div>
27
27
  </div>
28
28
 
29
- <% github_status = Shipit::GithubStatus.status
30
- unless github_status.nil? || github_status[:status] == 'good' %>
31
- <div class="banner github-status banner--orange hidden">
32
- <div class="banner__inner wrapper">
33
- <div class="banner__content">
34
- <h2 class="banner__title">GitHub is having issues</h2>
35
- <% if github_status[:body].present? %>
36
- "<i><%= github_status[:body] %></i>"
37
- <% end %>
38
- <% if github_status[:last_updated].present? %>
39
- <%= time_ago_in_words(github_status[:last_updated]) %> ago
40
- <% end %>
41
- </div>
29
+ <div id="layout-content">
30
+ <% github_status = Shipit::GithubStatus.status
31
+ unless github_status.nil? || github_status[:status] == 'good' %>
32
+ <div class="banner github-status banner--orange hidden">
33
+ <div class="banner__inner wrapper">
34
+ <div class="banner__content">
35
+ <h2 class="banner__title">GitHub is having issues</h2>
36
+ <% if github_status[:body].present? %>
37
+ "<i><%= github_status[:body] %></i>"
38
+ <% end %>
39
+ <% if github_status[:last_updated].present? %>
40
+ <%= time_ago_in_words(github_status[:last_updated]) %> ago
41
+ <% end %>
42
+ </div>
42
43
 
43
- <a class="banner__dismiss">&times;</a>
44
+ <a class="banner__dismiss">&times;</a>
45
+ </div>
44
46
  </div>
45
- </div>
46
- <% end %>
47
+ <% end %>
47
48
 
48
- <header class="header">
49
- <%= link_to "Shipit v#{Shipit::VERSION}", "https://github.com/Shopify/shipit-engine/tree/v#{Shipit::VERSION}", class: 'powered-by' %>
50
- <div class="wrapper">
51
- <div class="header__inner">
52
- <a href="/" class="logo">
53
- <span class="visually-hidden">Shipit</span>
54
- </a>
55
- <div class="header__page-title">
56
- <%= yield :page_title %>
57
- </div>
58
- <% if content_for? :primary_navigation %>
59
- <div class="header__page-actions">
60
- <%= yield :primary_navigation %>
49
+ <header class="header">
50
+ <%= link_to "Shipit v#{Shipit::VERSION}", "https://github.com/Shopify/shipit-engine/tree/v#{Shipit::VERSION}", class: 'powered-by' %>
51
+ <div class="wrapper">
52
+ <div class="header__inner">
53
+ <a href="/" class="logo">
54
+ <span class="visually-hidden">Shipit</span>
55
+ </a>
56
+ <div class="header__page-title">
57
+ <%= yield :page_title %>
61
58
  </div>
59
+ <% if content_for? :primary_navigation %>
60
+ <div class="header__page-actions">
61
+ <%= yield :primary_navigation %>
62
+ </div>
63
+ <% end %>
64
+ </div>
65
+ <% if content_for? :secondary_navigation %>
66
+ <nav class="nav">
67
+ <%= yield :secondary_navigation %>
68
+ </nav>
62
69
  <% end %>
63
70
  </div>
64
- <% if content_for? :secondary_navigation %>
65
- <nav class="nav">
66
- <%= yield :secondary_navigation %>
67
- </nav>
68
- <% end %>
69
- </div>
70
- </header>
71
+ </header>
71
72
 
72
- <div class="main <%= content_for(:main_classes) %>" data-layout-content>
73
- <%= yield %>
73
+ <div class="main <%= content_for(:main_classes) %>">
74
+ <%= yield %>
75
+ </div>
74
76
  </div>
75
77
  </body>
76
78
  </html>
@@ -0,0 +1,13 @@
1
+ # Derived from http://timnew.me/blog/2013/04/07/multiple-project-summary-reporting-standard-cctray-xml-feed/
2
+ status_map = {'backlogged' => 'failure', 'locked' => 'failure'}
3
+ xml.instruct!
4
+ xml.Projects do
5
+ xml.Project '', {
6
+ :name => stack.to_param,
7
+ :lastBuildStatus => status_map.fetch(stack.merge_status, stack.merge_status).capitalize,
8
+ :activity => deploy.running? ? 'Building' : 'Sleeping',
9
+ :lastBuildTime => deploy.ended_at || deploy.started_at || deploy.created_at,
10
+ :lastBuildLabel => deploy.id,
11
+ :webUrl => stack_url(stack),
12
+ }
13
+ end
@@ -1,5 +1,5 @@
1
1
  <li class="commit" id="commit-<%= commit.id %>">
2
- <%= render 'shipit/commits/commit_author', commit: commit %>
2
+ <%= render 'shipit/shared/author', author: commit.author %>
3
3
  <%= render commit.status %>
4
4
  <div class="commit-details">
5
5
  <span class="commit-title"><%= render_commit_message_with_link commit %></span>
@@ -1,7 +1,7 @@
1
1
  <%- read_only ||= false -%>
2
2
 
3
3
  <li class="task deploy" id="task-<%= deploy.id %>" data-status="<%= deploy.status %>">
4
- <%= render 'shipit/commits/commit_author', commit: deploy %>
4
+ <%= render 'shipit/shared/author', author: deploy.author %>
5
5
  <a href="<%= stack_deploy_path(@stack, deploy) %>" class="status status--<%= deploy.status %>" data-tooltip="<%= deploy.status.capitalize %>">
6
6
  <i class="status__icon"></i>
7
7
  <span class="visually-hidden"><%= deploy.status %></span>
@@ -0,0 +1,29 @@
1
+ <li class="pr" id="pr-<%= pull_request.id %>">
2
+ <%= render 'shipit/shared/author', author: pull_request.merge_requested_by %>
3
+ <div class="pr-details">
4
+ <span class="pr-number">
5
+ <%= pull_request_link(pull_request) %>
6
+ </span>
7
+ <span class="pr-title">
8
+ <%= render_pull_request_title_with_link pull_request %>
9
+ </span>
10
+ <p class="pr-meta">
11
+ <span class="code-additions">+<%= pull_request.additions %></span>
12
+ <span class="code-deletions">-<%= pull_request.deletions %></span>
13
+ </p>
14
+ <p class="pr-meta">
15
+ Enqueued <%= timeago_tag(pull_request.merge_requested_at, force: true) %>
16
+ <% if pull_request.revalidating? %>
17
+ <em class="warning">Need revalidation.</em>
18
+ <% end %>
19
+ </p>
20
+ </div>
21
+ <% if pull_request.revalidating? %>
22
+ <div class="commit-actions">
23
+ <%= button_to 'Confirm', stack_pull_requests_path(pull_request.stack, number_or_url: pull_request.number), class: 'btn btn--warning', method: 'post' %>
24
+ </div>
25
+ <% end %>
26
+ <div class="commit-actions">
27
+ <%= button_to 'Cancel', stack_pull_request_path(pull_request.stack, pull_request), class: 'btn btn--warning', method: 'delete' %>
28
+ </div>
29
+ </li>