shipit-engine 0.29.0 → 0.30.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +37 -2
- data/app/assets/images/archive-solid.svg +1 -0
- data/app/assets/stylesheets/_pages/_stacks.scss +76 -0
- data/app/controllers/shipit/api/stacks_controller.rb +20 -1
- data/app/controllers/shipit/api_clients_controller.rb +49 -0
- data/app/controllers/shipit/merge_status_controller.rb +8 -4
- data/app/controllers/shipit/stacks_controller.rb +58 -9
- data/app/controllers/shipit/webhooks_controller.rb +2 -130
- data/app/helpers/shipit/stacks_helper.rb +4 -0
- data/app/jobs/shipit/background_job/unique.rb +3 -1
- data/app/jobs/shipit/continuous_delivery_job.rb +1 -0
- data/app/jobs/shipit/destroy_stack_job.rb +2 -2
- data/app/models/shipit/commit.rb +21 -9
- data/app/models/shipit/commit_deployment.rb +15 -11
- data/app/models/shipit/commit_deployment_status.rb +6 -2
- data/app/models/shipit/deploy.rb +48 -7
- data/app/models/shipit/deploy_stats.rb +57 -0
- data/app/models/shipit/repository.rb +38 -0
- data/app/models/shipit/stack.rb +41 -34
- data/app/models/shipit/task.rb +26 -4
- data/app/models/shipit/webhooks.rb +32 -0
- data/app/models/shipit/webhooks/handlers/check_suite_handler.rb +19 -0
- data/app/models/shipit/webhooks/handlers/handler.rb +40 -0
- data/app/models/shipit/webhooks/handlers/membership_handler.rb +45 -0
- data/app/models/shipit/webhooks/handlers/push_handler.rb +20 -0
- data/app/models/shipit/webhooks/handlers/status_handler.rb +26 -0
- data/app/serializers/shipit/stack_serializer.rb +6 -1
- data/app/validators/ascii_only_validator.rb +3 -3
- data/app/views/layouts/_head.html.erb +0 -0
- data/app/views/layouts/shipit.html.erb +4 -2
- data/app/views/shipit/api_clients/index.html.erb +36 -0
- data/app/views/shipit/api_clients/new.html.erb +33 -0
- data/app/views/shipit/api_clients/show.html.erb +35 -0
- data/app/views/shipit/merge_status/logged_out.erb +1 -1
- data/app/views/shipit/stacks/_header.html.erb +12 -7
- data/app/views/shipit/stacks/_links.html.erb +1 -0
- data/app/views/shipit/stacks/index.html.erb +7 -2
- data/app/views/shipit/stacks/settings.html.erb +19 -0
- data/app/views/shipit/stacks/statistics.html.erb +82 -0
- data/config/locales/en.yml +14 -2
- data/config/routes.rb +4 -0
- data/db/migrate/20191209231045_create_shipit_repositories.rb +12 -0
- data/db/migrate/20191209231307_add_repository_reference_to_stacks.rb +15 -0
- data/db/migrate/20191216162728_backfill_repository_data.rb +22 -0
- data/db/migrate/20191216163010_remove_repository_information_from_stacks.rb +20 -0
- data/db/migrate/20191219205202_add_archived_since_to_stacks.rb +6 -0
- data/db/migrate/20200102175621_optional_task_commits.rb +6 -0
- data/db/migrate/20200109132519_add_sha_to_commit_deployments.rb +5 -0
- data/lib/shipit/github_app.rb +32 -3
- data/lib/shipit/task_commands.rb +10 -2
- data/lib/shipit/version.rb +1 -1
- data/test/controllers/api/ccmenu_controller_test.rb +1 -1
- data/test/controllers/api/stacks_controller_test.rb +14 -6
- data/test/controllers/api_clients_controller_test.rb +103 -0
- data/test/controllers/merge_status_controller_test.rb +21 -4
- data/test/controllers/stacks_controller_test.rb +35 -0
- data/test/controllers/webhooks_controller_test.rb +26 -0
- data/test/dummy/config/environments/development.rb +22 -4
- data/test/dummy/db/schema.rb +17 -6
- data/test/dummy/db/seeds.rb +20 -6
- data/test/fixtures/shipit/commit_deployment_statuses.yml +4 -4
- data/test/fixtures/shipit/commit_deployments.yml +8 -8
- data/test/fixtures/shipit/commits.yml +23 -0
- data/test/fixtures/shipit/repositories.yml +23 -0
- data/test/fixtures/shipit/stacks.yml +100 -16
- data/test/fixtures/shipit/tasks.yml +66 -3
- data/test/jobs/destroy_stack_job_test.rb +9 -0
- data/test/models/commit_deployment_status_test.rb +33 -4
- data/test/models/commit_deployment_test.rb +8 -11
- data/test/models/commits_test.rb +22 -2
- data/test/models/deploy_stats_test.rb +112 -0
- data/test/models/deploys_test.rb +55 -17
- data/test/models/pull_request_test.rb +1 -1
- data/test/models/shipit/repository_test.rb +76 -0
- data/test/models/shipit/wehbooks/handlers_test.rb +26 -0
- data/test/models/stacks_test.rb +44 -51
- data/test/models/undeployed_commits_test.rb +13 -0
- data/test/test_helper.rb +3 -1
- data/test/unit/deploy_commands_test.rb +9 -0
- data/test/unit/github_app_test.rb +136 -0
- metadata +161 -128
data/app/models/shipit/task.rb
CHANGED
@@ -16,8 +16,8 @@ module Shipit
|
|
16
16
|
belongs_to :user, optional: true
|
17
17
|
belongs_to :aborted_by, class_name: 'User', optional: true
|
18
18
|
belongs_to :stack, counter_cache: true
|
19
|
-
belongs_to :until_commit, class_name: 'Commit'
|
20
|
-
belongs_to :since_commit, class_name: 'Commit'
|
19
|
+
belongs_to :until_commit, class_name: 'Commit', required: false
|
20
|
+
belongs_to :since_commit, class_name: 'Commit', required: false
|
21
21
|
|
22
22
|
deferred_touch stack: :updated_at
|
23
23
|
|
@@ -29,14 +29,18 @@ module Shipit
|
|
29
29
|
scope :success, -> { where(status: 'success') }
|
30
30
|
scope :completed, -> { where(status: COMPLETED_STATUSES) }
|
31
31
|
scope :active, -> { where(status: ACTIVE_STATUSES) }
|
32
|
+
scope :not_active, -> { where(status: COMPLETED_STATUSES + UNSUCCESSFUL_STATUSES) }
|
32
33
|
scope :exclusive, -> { where(allow_concurrency: false) }
|
33
34
|
scope :unsuccessful, -> { where(status: UNSUCCESSFUL_STATUSES) }
|
35
|
+
scope :last_seven_days, -> { where("created_at > ?", 7.days.ago) }
|
36
|
+
scope :previous_seven_days, -> { where(created_at: 14.days.ago..7.days.ago) }
|
34
37
|
|
35
38
|
scope :due_for_rollup, -> { completed.where(rolled_up: false).where('created_at <= ?', 1.hour.ago) }
|
36
39
|
|
37
40
|
after_save :record_status_change
|
38
41
|
after_create :prevent_concurrency, unless: :allow_concurrency?
|
39
|
-
after_commit :emit_hooks
|
42
|
+
after_commit :emit_hooks, on: :create
|
43
|
+
after_commit :emit_hooks_if_status_changed, on: :update
|
40
44
|
|
41
45
|
class << self
|
42
46
|
def durations
|
@@ -303,9 +307,13 @@ module Shipit
|
|
303
307
|
end
|
304
308
|
end
|
305
309
|
|
306
|
-
def
|
310
|
+
def emit_hooks_if_status_changed
|
307
311
|
return unless @status_changed
|
308
312
|
@status_changed = nil
|
313
|
+
emit_hooks
|
314
|
+
end
|
315
|
+
|
316
|
+
def emit_hooks
|
309
317
|
Hook.emit(hook_event, stack, hook_event => self, status: status, stack: stack)
|
310
318
|
end
|
311
319
|
|
@@ -327,6 +335,20 @@ module Shipit
|
|
327
335
|
end
|
328
336
|
end
|
329
337
|
|
338
|
+
def commit_range?
|
339
|
+
since_commit && until_commit
|
340
|
+
end
|
341
|
+
|
342
|
+
def includes_commit?(commit)
|
343
|
+
return false unless commit_range?
|
344
|
+
|
345
|
+
if since_commit == until_commit
|
346
|
+
commit.id == since_commit.id
|
347
|
+
else
|
348
|
+
commit.id > since_commit.id && commit.id <= until_commit.id
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
330
352
|
private
|
331
353
|
|
332
354
|
def prevent_concurrency
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Shipit
|
2
|
+
module Webhooks
|
3
|
+
class << self
|
4
|
+
def default_handlers
|
5
|
+
{
|
6
|
+
'push' => [Handlers::PushHandler],
|
7
|
+
'status' => [Handlers::StatusHandler],
|
8
|
+
'membership' => [Handlers::MembershipHandler],
|
9
|
+
'check_suite' => [Handlers::CheckSuiteHandler],
|
10
|
+
}
|
11
|
+
end
|
12
|
+
|
13
|
+
def handlers
|
14
|
+
@handlers ||= reset_handlers!
|
15
|
+
end
|
16
|
+
|
17
|
+
def reset_handlers!
|
18
|
+
@handlers = default_handlers
|
19
|
+
end
|
20
|
+
|
21
|
+
def register_handler(event, callable = nil, &block)
|
22
|
+
handlers[event] ||= []
|
23
|
+
handlers[event] << callable if callable
|
24
|
+
handlers[event] << block if block_given?
|
25
|
+
end
|
26
|
+
|
27
|
+
def for_event(event)
|
28
|
+
handlers.fetch(event) { [] }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Shipit
|
2
|
+
module Webhooks
|
3
|
+
module Handlers
|
4
|
+
class CheckSuiteHandler < Handler
|
5
|
+
params do
|
6
|
+
requires :check_suite do
|
7
|
+
requires :head_sha, String
|
8
|
+
requires :head_branch, String
|
9
|
+
end
|
10
|
+
end
|
11
|
+
def process
|
12
|
+
stacks.where(branch: params.check_suite.head_branch).each do |stack|
|
13
|
+
stack.commits.where(sha: params.check_suite.head_sha).each(&:schedule_refresh_check_runs!)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Shipit
|
2
|
+
module Webhooks
|
3
|
+
module Handlers
|
4
|
+
class Handler
|
5
|
+
class << self
|
6
|
+
attr_reader :param_parser
|
7
|
+
|
8
|
+
def params(&block)
|
9
|
+
@param_parser = ExplicitParameters::Parameters.define(&block)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.call(params)
|
14
|
+
new(params).process
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :params, :payload
|
18
|
+
|
19
|
+
def initialize(payload)
|
20
|
+
@payload = payload
|
21
|
+
@params = self.class.param_parser.parse!(payload)
|
22
|
+
end
|
23
|
+
|
24
|
+
def process
|
25
|
+
raise NotImplementedError
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def stacks
|
31
|
+
@stacks ||= Repository.from_github_repo_name(repository_name)&.stacks || Stack.none
|
32
|
+
end
|
33
|
+
|
34
|
+
def repository_name
|
35
|
+
payload.dig('repository', 'full_name')
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Shipit
|
2
|
+
module Webhooks
|
3
|
+
module Handlers
|
4
|
+
class MembershipHandler < Handler
|
5
|
+
params do
|
6
|
+
requires :action, String
|
7
|
+
requires :team do
|
8
|
+
requires :id, Integer
|
9
|
+
requires :name, String
|
10
|
+
requires :slug, String
|
11
|
+
requires :url, String
|
12
|
+
end
|
13
|
+
requires :organization do
|
14
|
+
requires :login, String
|
15
|
+
end
|
16
|
+
requires :member do
|
17
|
+
requires :login, String
|
18
|
+
end
|
19
|
+
end
|
20
|
+
def process
|
21
|
+
team = find_or_create_team!
|
22
|
+
member = User.find_or_create_by_login!(params.member.login)
|
23
|
+
|
24
|
+
case params.action
|
25
|
+
when 'added'
|
26
|
+
team.add_member(member)
|
27
|
+
when 'removed'
|
28
|
+
team.members.delete(member)
|
29
|
+
else
|
30
|
+
raise ArgumentError, "Don't know how to perform action: `#{action.inspect}`"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def find_or_create_team!
|
37
|
+
Team.find_or_create_by!(github_id: params.team.id) do |team|
|
38
|
+
team.github_team = params.team
|
39
|
+
team.organization = params.organization.login
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Shipit
|
2
|
+
module Webhooks
|
3
|
+
module Handlers
|
4
|
+
class PushHandler < Handler
|
5
|
+
params do
|
6
|
+
requires :ref
|
7
|
+
end
|
8
|
+
def process
|
9
|
+
stacks.where(branch: branch).each(&:sync_github)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def branch
|
15
|
+
params.ref.gsub('refs/heads/', '')
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Shipit
|
2
|
+
module Webhooks
|
3
|
+
module Handlers
|
4
|
+
class StatusHandler < Handler
|
5
|
+
params do
|
6
|
+
requires :sha, String
|
7
|
+
requires :state, String
|
8
|
+
accepts :description, String
|
9
|
+
accepts :target_url, String
|
10
|
+
accepts :context, String
|
11
|
+
accepts :created_at, String
|
12
|
+
|
13
|
+
accepts :branches, Array do
|
14
|
+
requires :name, String
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def process
|
19
|
+
Commit.where(sha: params.sha).each do |commit|
|
20
|
+
commit.create_status_from_github!(params)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -5,7 +5,8 @@ module Shipit
|
|
5
5
|
has_one :lock_author
|
6
6
|
attributes :id, :repo_owner, :repo_name, :environment, :html_url, :url, :tasks_url, :deploy_url, :pull_requests_url,
|
7
7
|
:deploy_spec, :undeployed_commits_count, :is_locked, :lock_reason, :continuous_deployment, :created_at,
|
8
|
-
:updated_at, :locked_since, :last_deployed_at, :branch, :merge_queue_enabled
|
8
|
+
:updated_at, :locked_since, :last_deployed_at, :branch, :merge_queue_enabled, :is_archived,
|
9
|
+
:archived_since
|
9
10
|
|
10
11
|
def url
|
11
12
|
api_stack_url(object)
|
@@ -39,6 +40,10 @@ module Shipit
|
|
39
40
|
object.locked?
|
40
41
|
end
|
41
42
|
|
43
|
+
def is_archived
|
44
|
+
object.archived?
|
45
|
+
end
|
46
|
+
|
42
47
|
def deploy_spec
|
43
48
|
object.cached_deploy_spec.cacheable.config
|
44
49
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
class AsciiOnlyValidator < ActiveModel::EachValidator
|
2
2
|
def validate_each(record, attribute, value)
|
3
|
-
value.
|
4
|
-
|
5
|
-
|
3
|
+
if value && !value.ascii_only?
|
4
|
+
record.errors.add(attribute, :ascii)
|
5
|
+
end
|
6
6
|
end
|
7
7
|
end
|
File without changes
|
@@ -12,6 +12,8 @@
|
|
12
12
|
<%= javascript_include_tag :shipit %>
|
13
13
|
<%= yield :update_subscription %>
|
14
14
|
<%= csrf_meta_tags %>
|
15
|
+
|
16
|
+
<%= render 'layouts/head' %>
|
15
17
|
</head>
|
16
18
|
<body>
|
17
19
|
<% flash.each do |name, msg| %>
|
@@ -52,11 +54,11 @@
|
|
52
54
|
<% end %>
|
53
55
|
|
54
56
|
<header class="header">
|
55
|
-
<%= link_to "Shipit v#{Shipit::VERSION}", "https://github.com/Shopify/shipit-engine/tree/v#{Shipit::VERSION}", class: 'powered-by' %>
|
57
|
+
<%= link_to "Powered by Shipit v#{Shipit::VERSION}", "https://github.com/Shopify/shipit-engine/tree/v#{Shipit::VERSION}", class: 'powered-by' %>
|
56
58
|
<div class="wrapper">
|
57
59
|
<div class="header__inner">
|
58
60
|
<a href="/" class="logo">
|
59
|
-
<span class="visually-hidden"
|
61
|
+
<span class="visually-hidden"><%= Shipit.app_name %></span>
|
60
62
|
</a>
|
61
63
|
<div class="header__page-title">
|
62
64
|
<%= yield :page_title %>
|
@@ -0,0 +1,36 @@
|
|
1
|
+
|
2
|
+
<div class="wrapper">
|
3
|
+
<section>
|
4
|
+
<%= link_to "Create new api client", new_api_client_path, class: 'btn'%>
|
5
|
+
</section>
|
6
|
+
</div>
|
7
|
+
<div class="wrapper">
|
8
|
+
<section>
|
9
|
+
<ul class="task-list">
|
10
|
+
<% @api_clients.each do |api_client|%>
|
11
|
+
<li>
|
12
|
+
<p>
|
13
|
+
<%= link_to api_client.name, api_client_path(api_client) %>
|
14
|
+
created by <%= api_client.creator.name %> on <%= api_client.created_at%>
|
15
|
+
</p>
|
16
|
+
</li>
|
17
|
+
<% end %>
|
18
|
+
</ul>
|
19
|
+
</section>
|
20
|
+
</div>
|
21
|
+
|
22
|
+
<div class="wrapper">
|
23
|
+
<section class="pagination">
|
24
|
+
<% if params[:since].present? %>
|
25
|
+
<%= link_to 'Start', @links[:first], class: 'header__btn btn' %>
|
26
|
+
<% else %>
|
27
|
+
<%= link_to 'Start', '', class: 'header__btn btn btn--disabled disabled' %>
|
28
|
+
<% end %>
|
29
|
+
|
30
|
+
<% if url = @links[:next] %>
|
31
|
+
<%= link_to 'Older', url, class: 'header__btn btn' %>
|
32
|
+
<% else %>
|
33
|
+
<%= link_to 'Older', '', class: 'header__btn btn btn--disabled disabled' %>
|
34
|
+
<% end %>
|
35
|
+
</section>
|
36
|
+
</div>
|
@@ -0,0 +1,33 @@
|
|
1
|
+
<div class="wrapper">
|
2
|
+
<section>
|
3
|
+
<header class="section-header">
|
4
|
+
<p>New Api Client</p>
|
5
|
+
</header>
|
6
|
+
</section>
|
7
|
+
|
8
|
+
<%= form_for @api_client, url: api_clients_path(@api_client) do |f| %>
|
9
|
+
<section>
|
10
|
+
<%= label_tag "App Name" %>
|
11
|
+
<%= f.text_field :name, placeholder: 'e.g. spy', required: true %>
|
12
|
+
</section>
|
13
|
+
|
14
|
+
<section>
|
15
|
+
<%= label_tag "Permissions" %>
|
16
|
+
<ul class="deploy-checklist">
|
17
|
+
<% Shipit::ApiClient::PERMISSIONS.each do |permission| %>
|
18
|
+
<li class="deploy-checklist__item">
|
19
|
+
<%= check_box_tag 'api_client[permissions][]', permission, false,
|
20
|
+
class: 'deploy-checklist__item__checkbox', id: "checkbox_" + permission %>
|
21
|
+
<label class="deploy-checklist__item__label" for="checkbox_<%= permission %>">
|
22
|
+
<%= permission %>
|
23
|
+
</label>
|
24
|
+
</li>
|
25
|
+
<% end %>
|
26
|
+
</ul>
|
27
|
+
</section>
|
28
|
+
|
29
|
+
<section class="submit-section">
|
30
|
+
<%= f.submit "Create", :class => ['btn', 'primary'] %>
|
31
|
+
</section>
|
32
|
+
<% end %>
|
33
|
+
</div>
|
@@ -0,0 +1,35 @@
|
|
1
|
+
<div class="wrapper">
|
2
|
+
<section>
|
3
|
+
<header class="section-header">
|
4
|
+
<h2>Api client: <b><%= @api_client.name %></b></h2>
|
5
|
+
</header>
|
6
|
+
|
7
|
+
<p>Created by <%= @api_client.creator.name %> on <%= @api_client.created_at %></p>
|
8
|
+
</section>
|
9
|
+
|
10
|
+
<section>
|
11
|
+
<h3>Authentication token:</h3>
|
12
|
+
<code style="background-color: yellow">
|
13
|
+
<b><%= @api_client.authentication_token %></b>
|
14
|
+
</code>
|
15
|
+
</section>
|
16
|
+
|
17
|
+
<section>
|
18
|
+
<%= form_for @api_client, url: api_client_path(@api_client) do |f| %>
|
19
|
+
<h3> Permissions </h3>
|
20
|
+
<ul class="deploy-checklist">
|
21
|
+
<% Shipit::ApiClient::PERMISSIONS.each do |permission| %>
|
22
|
+
<li class="deploy-checklist__item">
|
23
|
+
<%= check_box_tag 'api_client[permissions][]', permission, @api_client.permissions.include?(permission),
|
24
|
+
class: 'deploy-checklist__item__checkbox', id: "checkbox_" + permission %>
|
25
|
+
<label class="deploy-checklist__item__label" for="checkbox_<%= permission %>">
|
26
|
+
<%= permission %>
|
27
|
+
</label>
|
28
|
+
</li>
|
29
|
+
<% end %>
|
30
|
+
</ul>
|
31
|
+
<%= f.submit "Update", :class => ['btn', 'primary'] %>
|
32
|
+
<% end %>
|
33
|
+
</section>
|
34
|
+
|
35
|
+
</div>
|
@@ -3,7 +3,7 @@
|
|
3
3
|
<%= render 'anchor', color: '#bd2c00' %>
|
4
4
|
</div>
|
5
5
|
<h4 class="status-heading text-red">
|
6
|
-
Please log in to <%= link_to
|
6
|
+
Please log in to <%= link_to Shipit.app_name, '/', target: '_blank', rel: 'noopener' %>
|
7
7
|
</h4>
|
8
8
|
<span class="status-meta">
|
9
9
|
Here Comes The Walrus is unable to check merge status.
|
@@ -5,23 +5,26 @@
|
|
5
5
|
|
6
6
|
|
7
7
|
<% content_for :primary_navigation do %>
|
8
|
-
<%= link_to '
|
8
|
+
<%= link_to t('stack.nav.refresh'), stack_refresh_path(stack), method: 'post', class: "header__btn btn" %>
|
9
9
|
<% end %>
|
10
10
|
|
11
11
|
<% content_for :secondary_navigation do %>
|
12
12
|
<ul class="nav__list nav__list--primary">
|
13
13
|
<li class="nav__list__item">
|
14
|
-
<%= link_to
|
14
|
+
<%= link_to t('stack.nav.commits'), stack_path(stack) %>
|
15
15
|
</li>
|
16
16
|
<li class="nav__list__item">
|
17
|
-
<%= link_to '
|
17
|
+
<%= link_to t('stack.nav.settings'), stack_settings_path(stack) %>
|
18
18
|
</li>
|
19
19
|
<li class="nav__list__item">
|
20
|
-
<%= link_to '
|
20
|
+
<%= link_to t('stack.nav.timeline'), index_stack_tasks_path(stack) %>
|
21
|
+
</li>
|
22
|
+
<li class="nav__list__item">
|
23
|
+
<%= link_to t('stack.nav.statistics'), stack_statistics_path(stack) %>
|
21
24
|
</li>
|
22
25
|
<% if stack.merge_queue_enabled? %>
|
23
26
|
<li class="nav__list__item">
|
24
|
-
<%= link_to
|
27
|
+
<%= link_to t('stack.nav.merge_queue', count: stack.pull_requests.queued.count), stack_pull_requests_path(stack) %>
|
25
28
|
</li>
|
26
29
|
<% end %>
|
27
30
|
|
@@ -38,6 +41,8 @@
|
|
38
41
|
</ul>
|
39
42
|
</li>
|
40
43
|
<% end %>
|
44
|
+
|
45
|
+
<%= render partial: 'shipit/stacks/links', locals: { stack: stack } %>
|
41
46
|
</ul>
|
42
47
|
|
43
48
|
<ul class="nav__list nav__list--secondary">
|
@@ -49,11 +54,11 @@
|
|
49
54
|
<% end %>
|
50
55
|
<% end %>
|
51
56
|
<li class="nav__list__item">
|
52
|
-
<%= link_to '
|
57
|
+
<%= link_to t('stack.nav.view_on_github'), github_repo_url(stack.repo_owner, stack.repo_name) %>
|
53
58
|
</li>
|
54
59
|
<% if stack.deploy_url.present? %>
|
55
60
|
<li class="nav__list__item">
|
56
|
-
<%= sanitize link_to '
|
61
|
+
<%= sanitize link_to t('stack.nav.deploy_link'), stack.deploy_url, :target => '_blank', :class => 'deploy-url' %>
|
57
62
|
</li>
|
58
63
|
<% end %>
|
59
64
|
</ul>
|