shipit-engine 0.15.0 → 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
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>