diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb
index 8e19752a8a1142565450fe635345ca277e52cd70..d9f5e01f0dcb0a9a8a11c73e228fb8e336b27957 100644
--- a/app/helpers/ci_status_helper.rb
+++ b/app/helpers/ci_status_helper.rb
@@ -4,25 +4,7 @@ module CiStatusHelper
     builds_namespace_project_commit_path(project.namespace, project, pipeline.sha)
   end
 
-  def ci_status_with_icon(status, target = nil)
-    content = ci_icon_for_status(status) + ci_text_for_status(status)
-    klass = "ci-status ci-#{status}"
-
-    if target
-      link_to content, target, class: klass
-    else
-      content_tag :span, content, class: klass
-    end
-  end
-
-  def ci_text_for_status(status)
-    if detailed_status?(status)
-      status.text
-    else
-      status
-    end
-  end
-
+  # Is used by Commit and Merge Request Widget
   def ci_label_for_status(status)
     if detailed_status?(status)
       return status.label
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 88c46076df612c996114946927f4aeed6bf42d2e..e7cf606a7aed36e767ebd03f38751d2377023a6f 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -100,6 +100,12 @@ module Ci
       end
     end
 
+    def detailed_status(current_user)
+      Gitlab::Ci::Status::Build::Factory
+        .new(self, current_user)
+        .fabricate!
+    end
+
     def manual?
       self.when == 'manual'
     end
@@ -123,8 +129,13 @@ module Ci
       end
     end
 
+    def cancelable?
+      active?
+    end
+
     def retryable?
-      project.builds_enabled? && commands.present? && complete?
+      project.builds_enabled? && commands.present? &&
+        (success? || failed? || canceled?)
     end
 
     def retried?
@@ -148,7 +159,7 @@ module Ci
     end
 
     def environment_action
-      self.options.fetch(:environment, {}).fetch(:action, 'start')
+      self.options.fetch(:environment, {}).fetch(:action, 'start') if self.options
     end
 
     def outdated_deployment?
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index fda8228a1e988dc778fbc445353401281742b8c3..54f73171fd467a027e9b93b17c20f15f5c80f706 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -336,8 +336,10 @@ module Ci
         .select { |merge_request| merge_request.head_pipeline.try(:id) == self.id }
     end
 
-    def detailed_status
-      Gitlab::Ci::Status::Pipeline::Factory.new(self).fabricate!
+    def detailed_status(current_user)
+      Gitlab::Ci::Status::Pipeline::Factory
+        .new(self, current_user)
+        .fabricate!
     end
 
     private
diff --git a/app/models/ci/stage.rb b/app/models/ci/stage.rb
index d2a37c0a827a49f3a7ff6a5c08d52496c9a452c1..7ef59445d777354bd6e78b1969dc74f64b1d5849 100644
--- a/app/models/ci/stage.rb
+++ b/app/models/ci/stage.rb
@@ -22,8 +22,10 @@ module Ci
       @status ||= statuses.latest.status
     end
 
-    def detailed_status
-      Gitlab::Ci::Status::Stage::Factory.new(self).fabricate!
+    def detailed_status(current_user)
+      Gitlab::Ci::Status::Stage::Factory
+        .new(self, current_user)
+        .fabricate!
     end
 
     def statuses
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index cf90475f4d4502cd173cdec37156a1cf1aff7643..31cd381dcd2d0b16934d5cf717ed5bc6e4cc2cf1 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -131,4 +131,10 @@ class CommitStatus < ActiveRecord::Base
   def has_trace?
     false
   end
+
+  def detailed_status(current_user)
+    Gitlab::Ci::Status::Factory
+      .new(self, current_user)
+      .fabricate!
+  end
 end
diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml
index 73038164056f7be693c666305358da2b631f3f8b..ca503e35623a5f24650160d3c31300cc9854159d 100644
--- a/app/views/admin/runners/show.html.haml
+++ b/app/views/admin/runners/show.html.haml
@@ -91,7 +91,7 @@
               %strong ##{build.id}
 
           %td.status
-            = ci_status_with_icon(build.status)
+            = render 'ci/status/badge', status: build.detailed_status(current_user)
 
           %td.status
             - if project
diff --git a/app/views/ci/status/_badge.html.haml b/app/views/ci/status/_badge.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..f2135af268679d52629575635a9e91bb306ac5d4
--- /dev/null
+++ b/app/views/ci/status/_badge.html.haml
@@ -0,0 +1,10 @@
+- status = local_assigns.fetch(:status)
+
+- if status.has_details?
+  = link_to status.details_path, class: "ci-status ci-#{status}" do
+    = custom_icon(status.icon)
+    = status.text
+- else
+  %span{ class: "ci-status ci-#{status}" }
+    = custom_icon(status.icon)
+    = status.text
diff --git a/app/views/projects/builds/_header.html.haml b/app/views/projects/builds/_header.html.haml
index f6aa20c4579cb154b87ac4573d8d3ade684eff21..057a720a54a7cef7bde405682945da0a7b60cc1d 100644
--- a/app/views/projects/builds/_header.html.haml
+++ b/app/views/projects/builds/_header.html.haml
@@ -1,6 +1,6 @@
 .content-block.build-header
   .header-content
-    = ci_status_with_icon(@build.status)
+    = render 'ci/status/badge', status: @build.detailed_status(current_user)
     Build
     %strong ##{@build.id}
     in pipeline
diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml
index 18b3b04154f4936ec7c99350572a775fb19d4a6d..f1cb020103224557f38af0a57a4f69904a8295b2 100644
--- a/app/views/projects/ci/builds/_build.html.haml
+++ b/app/views/projects/ci/builds/_build.html.haml
@@ -9,10 +9,7 @@
 
 %tr.build.commit{class: ('retried' if retried)}
   %td.status
-    - if can?(current_user, :read_build, build)
-      = ci_status_with_icon(build.status, namespace_project_build_url(build.project.namespace, build.project, build))
-    - else
-      = ci_status_with_icon(build.status)
+    = render "ci/status/badge", status: build.detailed_status(current_user)
 
   %td.branch-commit
     - if can?(current_user, :read_build, build)
diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml
index b58dceb58c9cdb3edae8c67599bb96144fe66d14..3f05a21990f20efd11a6953cd39a454201d91436 100644
--- a/app/views/projects/ci/pipelines/_pipeline.html.haml
+++ b/app/views/projects/ci/pipelines/_pipeline.html.haml
@@ -1,13 +1,10 @@
 - status = pipeline.status
-- detailed_status = pipeline.detailed_status
 - show_commit = local_assigns.fetch(:show_commit, true)
 - show_branch = local_assigns.fetch(:show_branch, true)
 
 %tr.commit
   %td.commit-link
-    = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: "ci-status ci-#{detailed_status}" do
-      = ci_icon_for_status(detailed_status)
-      = ci_text_for_status(detailed_status)
+    = render 'ci/status/badge', status: pipeline.detailed_status(current_user)
 
   %td
     = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id) do
diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
index 7f751d9ae2e87c032cee2abdb2e2d928098b541b..9f444f076c05427cd406725b5a693b9f85269268 100644
--- a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
+++ b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
@@ -8,10 +8,7 @@
 
 %tr.generic_commit_status{class: ('retried' if retried)}
   %td.status
-    - if can?(current_user, :read_commit_status, generic_commit_status) && generic_commit_status.target_url
-      = ci_status_with_icon(generic_commit_status.status, generic_commit_status.target_url)
-    - else
-      = ci_status_with_icon(generic_commit_status.status)
+    = render 'ci/status/badge', status: generic_commit_status.detailed_status(current_user)
 
   %td.generic_commit_status-link
     - if can?(current_user, :read_commit_status, generic_commit_status) && generic_commit_status.target_url
diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml
index 229bdfb0e8d983c9f996d92837ec9253cdefac45..b00ba2d5307c60c55e0ea280cdd5d9bb14c33778 100644
--- a/app/views/projects/pipelines/_info.html.haml
+++ b/app/views/projects/pipelines/_info.html.haml
@@ -1,6 +1,6 @@
 .page-content-header
   .header-main-content
-    = ci_status_with_icon(@pipeline.detailed_status)
+    = render 'ci/status/badge', status: @pipeline.detailed_status(current_user)
     %strong Pipeline ##{@commit.pipelines.last.id}
     triggered #{time_ago_with_tooltip(@commit.authored_date)} by
     = author_avatar(@commit, size: 24)
diff --git a/app/views/projects/stage/_graph.html.haml b/app/views/projects/stage/_graph.html.haml
index 1d8fa10db0c886096c23030a392e054eb6397370..bf8c75b6e5c6e240e2846406224667eeadf91863 100644
--- a/app/views/projects/stage/_graph.html.haml
+++ b/app/views/projects/stage/_graph.html.haml
@@ -19,4 +19,4 @@
           %li.build
             .curve
             .dropdown.inline.build-content
-              = render "projects/stage/in_stage_group", name: group_name, subject: grouped_statuses
+              = render 'projects/stage/in_stage_group', name: group_name, subject: grouped_statuses
diff --git a/app/views/shared/icons/_icon_status_manual.svg b/app/views/shared/icons/_icon_status_manual.svg
new file mode 100755
index 0000000000000000000000000000000000000000..c98839f51a933196085aef6dc364858ca8dc0825
--- /dev/null
+++ b/app/views/shared/icons/_icon_status_manual.svg
@@ -0,0 +1 @@
+<svg width="14" height="14" viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd"><path d="M0 7a7 7 0 1 1 14 0A7 7 0 0 1 0 7z"/><path d="M13 7A6 6 0 1 0 1 7a6 6 0 0 0 12 0z" fill="#FFF"/><path d="M10.5 7.63V6.37l-.787-.13c-.044-.175-.132-.349-.263-.61l.481-.652-.918-.913-.657.478a2.346 2.346 0 0 0-.612-.26L7.656 3.5H6.388l-.132.783c-.219.043-.394.13-.612.26l-.657-.478-.918.913.437.652c-.131.218-.175.392-.262.61l-.744.086v1.261l.787.13c.044.218.132.392.263.61l-.438.651.92.913.655-.434c.175.086.394.173.613.26l.131.783h1.313l.131-.783c.219-.043.394-.13.613-.26l.656.478.918-.913-.48-.652c.13-.218.218-.435.262-.61l.656-.13zM7 8.283a1.285 1.285 0 0 1-1.313-1.305c0-.739.57-1.304 1.313-1.304.744 0 1.313.565 1.313 1.304 0 .74-.57 1.305-1.313 1.305z"/></g></svg>
diff --git a/lib/gitlab/allowable.rb b/lib/gitlab/allowable.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f48abcc86d53ac932898619b76c35a361dcffaa6
--- /dev/null
+++ b/lib/gitlab/allowable.rb
@@ -0,0 +1,7 @@
+module Gitlab
+  module Allowable
+    def can?(user, action, subject)
+      Ability.allowed?(user, action, subject)
+    end
+  end
+end
diff --git a/lib/gitlab/ci/status/build/cancelable.rb b/lib/gitlab/ci/status/build/cancelable.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a979fe7d573939eee5fcba65c199ea295b40276e
--- /dev/null
+++ b/lib/gitlab/ci/status/build/cancelable.rb
@@ -0,0 +1,37 @@
+module Gitlab
+  module Ci
+    module Status
+      module Build
+        class Cancelable < SimpleDelegator
+          include Status::Extended
+
+          def has_action?
+            can?(user, :update_build, subject)
+          end
+
+          def action_icon
+            'ban'
+          end
+
+          def action_path
+            cancel_namespace_project_build_path(subject.project.namespace,
+                                                subject.project,
+                                                subject)
+          end
+
+          def action_method
+            :post
+          end
+
+          def action_title
+            'Cancel'
+          end
+
+          def self.matches?(build, user)
+            build.cancelable?
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/status/build/common.rb b/lib/gitlab/ci/status/build/common.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3fec2c5d4dbeada7cc9967eb3d387075b8837648
--- /dev/null
+++ b/lib/gitlab/ci/status/build/common.rb
@@ -0,0 +1,19 @@
+module Gitlab
+  module Ci
+    module Status
+      module Build
+        module Common
+          def has_details?
+            can?(user, :read_build, subject)
+          end
+
+          def details_path
+            namespace_project_build_path(subject.project.namespace,
+                                         subject.project,
+                                         subject)
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/status/build/factory.rb b/lib/gitlab/ci/status/build/factory.rb
new file mode 100644
index 0000000000000000000000000000000000000000..eee9a64120b287ad29724d425d1432717e75074b
--- /dev/null
+++ b/lib/gitlab/ci/status/build/factory.rb
@@ -0,0 +1,18 @@
+module Gitlab
+  module Ci
+    module Status
+      module Build
+        class Factory < Status::Factory
+          def self.extended_statuses
+            [Status::Build::Stop, Status::Build::Play,
+             Status::Build::Cancelable, Status::Build::Retryable]
+          end
+
+          def self.common_helpers
+            Status::Build::Common
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/status/build/play.rb b/lib/gitlab/ci/status/build/play.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5c506e6d59f3a9b36bae18e96588af3f22be66e5
--- /dev/null
+++ b/lib/gitlab/ci/status/build/play.rb
@@ -0,0 +1,53 @@
+module Gitlab
+  module Ci
+    module Status
+      module Build
+        class Play < SimpleDelegator
+          include Status::Extended
+
+          def text
+            'manual'
+          end
+
+          def label
+            'manual play action'
+          end
+
+          def icon
+            'icon_status_manual'
+          end
+
+          def has_action?
+            can?(user, :update_build, subject)
+          end
+
+          def action_icon
+            'play'
+          end
+
+          def action_title
+            'Play'
+          end
+
+          def action_class
+            'ci-play-icon'
+          end
+
+          def action_path
+            play_namespace_project_build_path(subject.project.namespace,
+                                              subject.project,
+                                              subject)
+          end
+
+          def action_method
+            :post
+          end
+
+          def self.matches?(build, user)
+            build.playable? && !build.stops_environment?
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/status/build/retryable.rb b/lib/gitlab/ci/status/build/retryable.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8e38d6a8523c19e506fea4537f3db2d011cde202
--- /dev/null
+++ b/lib/gitlab/ci/status/build/retryable.rb
@@ -0,0 +1,37 @@
+module Gitlab
+  module Ci
+    module Status
+      module Build
+        class Retryable < SimpleDelegator
+          include Status::Extended
+
+          def has_action?
+            can?(user, :update_build, subject)
+          end
+
+          def action_icon
+            'refresh'
+          end
+
+          def action_title
+            'Retry'
+          end
+
+          def action_path
+            retry_namespace_project_build_path(subject.project.namespace,
+                                               subject.project,
+                                               subject)
+          end
+
+          def action_method
+            :post
+          end
+
+          def self.matches?(build, user)
+            build.retryable?
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/status/build/stop.rb b/lib/gitlab/ci/status/build/stop.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f8ffa95cde47e7884b6a3b8a411018b6b6794183
--- /dev/null
+++ b/lib/gitlab/ci/status/build/stop.rb
@@ -0,0 +1,49 @@
+module Gitlab
+  module Ci
+    module Status
+      module Build
+        class Stop < SimpleDelegator
+          include Status::Extended
+
+          def text
+            'manual'
+          end
+
+          def label
+            'manual stop action'
+          end
+
+          def icon
+            'icon_status_manual'
+          end
+
+          def has_action?
+            can?(user, :update_build, subject)
+          end
+
+          def action_icon
+            'stop'
+          end
+
+          def action_title
+            'Stop'
+          end
+
+          def action_path
+            play_namespace_project_build_path(subject.project.namespace,
+                                              subject.project,
+                                              subject)
+          end
+
+          def action_method
+            :post
+          end
+
+          def self.matches?(build, user)
+            build.playable? && build.stops_environment?
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/status/core.rb b/lib/gitlab/ci/status/core.rb
index ce4108fdcf2ff8debdcc01e7b98c81d967d5b0b6..46fef8262c19b3819b8b81158388b9493e7e8f72 100644
--- a/lib/gitlab/ci/status/core.rb
+++ b/lib/gitlab/ci/status/core.rb
@@ -4,10 +4,14 @@ module Gitlab
       # Base abstract class fore core status
       #
       class Core
-        include Gitlab::Routing.url_helpers
+        include Gitlab::Routing
+        include Gitlab::Allowable
 
-        def initialize(subject)
+        attr_reader :subject, :user
+
+        def initialize(subject, user)
           @subject = subject
+          @user = user
         end
 
         def icon
@@ -18,10 +22,6 @@ module Gitlab
           raise NotImplementedError
         end
 
-        def title
-          "#{@subject.class.name.demodulize}: #{label}"
-        end
-
         # Deprecation warning: this method is here because we need to maintain
         # backwards compatibility with legacy statuses. We often do something
         # like "ci-status ci-status-#{status}" to set CSS class.
@@ -34,7 +34,7 @@ module Gitlab
         end
 
         def has_details?
-          raise NotImplementedError
+          false
         end
 
         def details_path
@@ -42,16 +42,27 @@ module Gitlab
         end
 
         def has_action?
-          raise NotImplementedError
+          false
         end
 
         def action_icon
           raise NotImplementedError
         end
 
+        def action_class
+        end
+
         def action_path
           raise NotImplementedError
         end
+
+        def action_method
+          raise NotImplementedError
+        end
+
+        def action_title
+          raise NotImplementedError
+        end
       end
     end
   end
diff --git a/lib/gitlab/ci/status/extended.rb b/lib/gitlab/ci/status/extended.rb
index 6bfb5d38c1f1c1bdfffe322384e6f2da2b9d8b20..d367c9bda69ff2d71f6e6cef91c3ef66fbc7b2b2 100644
--- a/lib/gitlab/ci/status/extended.rb
+++ b/lib/gitlab/ci/status/extended.rb
@@ -2,8 +2,12 @@ module Gitlab
   module Ci
     module Status
       module Extended
-        def matches?(_subject)
-          raise NotImplementedError
+        extend ActiveSupport::Concern
+
+        class_methods do
+          def matches?(_subject, _user)
+            raise NotImplementedError
+          end
         end
       end
     end
diff --git a/lib/gitlab/ci/status/factory.rb b/lib/gitlab/ci/status/factory.rb
index b2f896f2211fc8a597d9edc470c793e806cdd3e8..ae9ef895df49d27e3bb9c81ac02df667fee58e1d 100644
--- a/lib/gitlab/ci/status/factory.rb
+++ b/lib/gitlab/ci/status/factory.rb
@@ -2,10 +2,9 @@ module Gitlab
   module Ci
     module Status
       class Factory
-        attr_reader :subject
-
-        def initialize(subject)
+        def initialize(subject, user)
           @subject = subject
+          @user = user
         end
 
         def fabricate!
@@ -16,27 +15,32 @@ module Gitlab
           end
         end
 
+        def self.extended_statuses
+          []
+        end
+
+        def self.common_helpers
+          Module.new
+        end
+
         private
 
-        def subject_status
-          @subject_status ||= subject.status
+        def simple_status
+          @simple_status ||= @subject.status || :created
         end
 
         def core_status
           Gitlab::Ci::Status
-            .const_get(subject_status.capitalize)
-            .new(subject)
+            .const_get(simple_status.capitalize)
+            .new(@subject, @user)
+            .extend(self.class.common_helpers)
         end
 
         def extended_status
-          @extended ||= extended_statuses.find do |status|
-            status.matches?(subject)
+          @extended ||= self.class.extended_statuses.find do |status|
+            status.matches?(@subject, @user)
           end
         end
-
-        def extended_statuses
-          []
-        end
       end
     end
   end
diff --git a/lib/gitlab/ci/status/pipeline/common.rb b/lib/gitlab/ci/status/pipeline/common.rb
index 25e52bec3da1db99432dd097a4def7d30931d324..76bfd18bf4040f369e53a751d22d8f66d92eb337 100644
--- a/lib/gitlab/ci/status/pipeline/common.rb
+++ b/lib/gitlab/ci/status/pipeline/common.rb
@@ -4,13 +4,13 @@ module Gitlab
       module Pipeline
         module Common
           def has_details?
-            true
+            can?(user, :read_pipeline, subject)
           end
 
           def details_path
-            namespace_project_pipeline_path(@subject.project.namespace,
-                                            @subject.project,
-                                            @subject)
+            namespace_project_pipeline_path(subject.project.namespace,
+                                            subject.project,
+                                            subject)
           end
 
           def has_action?
diff --git a/lib/gitlab/ci/status/pipeline/factory.rb b/lib/gitlab/ci/status/pipeline/factory.rb
index 4ac4ec671d0eb7fdcc84322e2417148c3ff83046..16dcb326be9c46503d0fcd6a19ee6fc1057159b0 100644
--- a/lib/gitlab/ci/status/pipeline/factory.rb
+++ b/lib/gitlab/ci/status/pipeline/factory.rb
@@ -3,14 +3,12 @@ module Gitlab
     module Status
       module Pipeline
         class Factory < Status::Factory
-          private
-
-          def extended_statuses
+          def self.extended_statuses
             [Pipeline::SuccessWithWarnings]
           end
 
-          def core_status
-            super.extend(Status::Pipeline::Common)
+          def self.common_helpers
+            Status::Pipeline::Common
           end
         end
       end
diff --git a/lib/gitlab/ci/status/pipeline/success_with_warnings.rb b/lib/gitlab/ci/status/pipeline/success_with_warnings.rb
index 4b040d60df83320a8e427b04e4736e16cba38821..a7c98f9e909e285dd77dfee4a7358e080797ad92 100644
--- a/lib/gitlab/ci/status/pipeline/success_with_warnings.rb
+++ b/lib/gitlab/ci/status/pipeline/success_with_warnings.rb
@@ -3,7 +3,7 @@ module Gitlab
     module Status
       module Pipeline
         class SuccessWithWarnings < SimpleDelegator
-          extend Status::Extended
+          include Status::Extended
 
           def text
             'passed'
@@ -21,7 +21,7 @@ module Gitlab
             'success_with_warnings'
           end
 
-          def self.matches?(pipeline)
+          def self.matches?(pipeline, user)
             pipeline.success? && pipeline.has_warnings?
           end
         end
diff --git a/lib/gitlab/ci/status/stage/common.rb b/lib/gitlab/ci/status/stage/common.rb
index 14c437d2b98db80214f48dc123acee8ebb41614e..7852f492e1d0738b8f2e33af2e7412dd0c2e8356 100644
--- a/lib/gitlab/ci/status/stage/common.rb
+++ b/lib/gitlab/ci/status/stage/common.rb
@@ -4,14 +4,14 @@ module Gitlab
       module Stage
         module Common
           def has_details?
-            true
+            can?(user, :read_pipeline, subject.pipeline)
           end
 
           def details_path
-            namespace_project_pipeline_path(@subject.project.namespace,
-                                            @subject.project,
-                                            @subject.pipeline,
-                                            anchor: @subject.name)
+            namespace_project_pipeline_path(subject.project.namespace,
+                                            subject.project,
+                                            subject.pipeline,
+                                            anchor: subject.name)
           end
 
           def has_action?
diff --git a/lib/gitlab/ci/status/stage/factory.rb b/lib/gitlab/ci/status/stage/factory.rb
index c6522d5ada154485fab858027db84ece8d453a9c..689a5dd45bc3911e9d323c0fde6c02ec60a4afce 100644
--- a/lib/gitlab/ci/status/stage/factory.rb
+++ b/lib/gitlab/ci/status/stage/factory.rb
@@ -3,10 +3,8 @@ module Gitlab
     module Status
       module Stage
         class Factory < Status::Factory
-          private
-
-          def core_status
-            super.extend(Status::Stage::Common)
+          def self.common_helpers
+            Status::Stage::Common
           end
         end
       end
diff --git a/lib/gitlab/routing.rb b/lib/gitlab/routing.rb
index 5132177de516ee6b9eab38a57adfd87e161169b3..632e2d875000c9fccb8284d49edadd4d8bdbbb2f 100644
--- a/lib/gitlab/routing.rb
+++ b/lib/gitlab/routing.rb
@@ -1,5 +1,11 @@
 module Gitlab
   module Routing
+    extend ActiveSupport::Concern
+
+    included do
+      include Gitlab::Routing.url_helpers
+    end
+
     # Returns the URL helpers Module.
     #
     # This method caches the output as Rails' "url_helpers" method creates an
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index c443af09075536330e401acce0ebfe9368fdad55..62466c061941a4df5a06a32fb57c567656b7d2a1 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -12,12 +12,14 @@ FactoryGirl.define do
     started_at 'Di 29. Okt 09:51:28 CET 2013'
     finished_at 'Di 29. Okt 09:53:28 CET 2013'
     commands 'ls -a'
+
     options do
       {
         image: "ruby:2.1",
         services: ["postgres"]
       }
     end
+
     yaml_variables do
       [
         { key: :DB_NAME, value: 'postgres', public: true }
@@ -60,15 +62,20 @@ FactoryGirl.define do
     end
 
     trait :teardown_environment do
-      options do
-        { environment: { action: 'stop' } }
-      end
+      environment 'staging'
+      options environment: { name: 'staging',
+                             action: 'stop' }
     end
 
     trait :allowed_to_fail do
       allow_failure true
     end
 
+    trait :playable do
+      skipped
+      manual
+    end
+
     after(:build) do |build, evaluator|
       build.project = build.pipeline.project
     end
diff --git a/spec/lib/gitlab/allowable_spec.rb b/spec/lib/gitlab/allowable_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..87733d53e92b7f45953d2c2a2f3cacc13a86209c
--- /dev/null
+++ b/spec/lib/gitlab/allowable_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+
+describe Gitlab::Allowable do
+  subject do
+    Class.new.include(described_class).new
+  end
+
+  describe '#can?' do
+    let(:user) { create(:user) }
+
+    context 'when user is allowed to do something' do
+      let(:project) { create(:empty_project, :public) }
+
+      it 'reports correct ability to perform action' do
+        expect(subject.can?(user, :read_project, project)).to be true
+      end
+    end
+
+    context 'when user is not allowed to do something' do
+      let(:project) { create(:empty_project, :private) }
+
+      it 'reports correct ability to perform action' do
+        expect(subject.can?(user, :read_project, project)).to be false
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/status/build/cancelable_spec.rb b/spec/lib/gitlab/ci/status/build/cancelable_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9376bce17a13f0a5355bdb67f0a87745f705b4ad
--- /dev/null
+++ b/spec/lib/gitlab/ci/status/build/cancelable_spec.rb
@@ -0,0 +1,86 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Status::Build::Cancelable do
+  let(:status) { double('core status') }
+  let(:user) { double('user') }
+
+  subject do
+    described_class.new(status)
+  end
+
+  describe '#text' do
+    it 'does not override status text' do
+      expect(status).to receive(:text)
+
+      subject.text
+    end
+  end
+
+  describe '#icon' do
+    it 'does not override status icon' do
+      expect(status).to receive(:icon)
+
+      subject.icon
+    end
+  end
+
+  describe '#label' do
+    it 'does not override status label' do
+      expect(status).to receive(:label)
+
+      subject.label
+    end
+  end
+
+  describe 'action details' do
+    let(:user) { create(:user) }
+    let(:build) { create(:ci_build) }
+    let(:status) { Gitlab::Ci::Status::Core.new(build, user) }
+
+    describe '#has_action?' do
+      context 'when user is allowed to update build' do
+        before { build.project.team << [user, :developer] }
+
+        it { is_expected.to have_action }
+      end
+
+      context 'when user is not allowed to update build' do
+        it { is_expected.not_to have_action }
+      end
+    end
+
+    describe '#action_path' do
+      it { expect(subject.action_path).to include "#{build.id}/cancel" }
+    end
+
+    describe '#action_icon' do
+      it { expect(subject.action_icon).to eq 'ban' }
+    end
+
+    describe '#action_title' do
+      it { expect(subject.action_title).to eq 'Cancel' }
+    end
+  end
+
+  describe '.matches?' do
+    subject { described_class.matches?(build, user) }
+
+    context 'when build is cancelable' do
+      let(:build) do
+        create(:ci_build, :running)
+      end
+
+      it 'is a correct match' do
+        expect(subject).to be true
+      end
+    end
+
+    context 'when build is not cancelable' do
+      let(:build) { create(:ci_build, :success) }
+
+      it 'does not match' do
+        expect(subject).to be false
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/status/build/common_spec.rb b/spec/lib/gitlab/ci/status/build/common_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..40b96b1807b096a553dd5028ba30da93d11d9e35
--- /dev/null
+++ b/spec/lib/gitlab/ci/status/build/common_spec.rb
@@ -0,0 +1,37 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Status::Build::Common do
+  let(:user) { create(:user) }
+  let(:build) { create(:ci_build) }
+  let(:project) { build.project }
+
+  subject do
+    Gitlab::Ci::Status::Core
+      .new(build, user)
+      .extend(described_class)
+  end
+
+  describe '#has_action?' do
+    it { is_expected.not_to have_action }
+  end
+
+  describe '#has_details?' do
+    context 'when user has access to read build' do
+      before { project.team << [user, :developer] }
+
+      it { is_expected.to have_details }
+    end
+
+    context 'when user does not have access to read build' do
+      before { project.update(public_builds: false) }
+
+      it { is_expected.not_to have_details }
+    end
+  end
+
+  describe '#details_path' do
+    it 'links to the build details page' do
+      expect(subject.details_path).to include "builds/#{build.id}"
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/status/build/factory_spec.rb b/spec/lib/gitlab/ci/status/build/factory_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..dccb29b5ef606b380137e2b9090bf616bbfe4afe
--- /dev/null
+++ b/spec/lib/gitlab/ci/status/build/factory_spec.rb
@@ -0,0 +1,141 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Status::Build::Factory do
+  let(:user) { create(:user) }
+  let(:project) { build.project }
+
+  subject { described_class.new(build, user) }
+  let(:status) { subject.fabricate! }
+
+  before { project.team << [user, :developer] }
+
+  context 'when build is successful' do
+    let(:build) { create(:ci_build, :success) }
+
+    it 'fabricates a retryable build status' do
+      expect(status).to be_a Gitlab::Ci::Status::Build::Retryable
+    end
+
+    it 'fabricates status with correct details' do
+      expect(status.text).to eq 'passed'
+      expect(status.icon).to eq 'icon_status_success'
+      expect(status.label).to eq 'passed'
+      expect(status).to have_details
+      expect(status).to have_action
+    end
+  end
+
+  context 'when build is failed' do
+    let(:build) { create(:ci_build, :failed) }
+
+    it 'fabricates a retryable build status' do
+      expect(status).to be_a Gitlab::Ci::Status::Build::Retryable
+    end
+
+    it 'fabricates status with correct details' do
+      expect(status.text).to eq 'failed'
+      expect(status.icon).to eq 'icon_status_failed'
+      expect(status.label).to eq 'failed'
+      expect(status).to have_details
+      expect(status).to have_action
+    end
+  end
+
+  context 'when build is a canceled' do
+    let(:build) { create(:ci_build, :canceled) }
+
+    it 'fabricates a retryable build status' do
+      expect(status).to be_a Gitlab::Ci::Status::Build::Retryable
+    end
+
+    it 'fabricates status with correct details' do
+      expect(status.text).to eq 'canceled'
+      expect(status.icon).to eq 'icon_status_canceled'
+      expect(status.label).to eq 'canceled'
+      expect(status).to have_details
+      expect(status).to have_action
+    end
+  end
+
+  context 'when build is running' do
+    let(:build) { create(:ci_build, :running) }
+
+    it 'fabricates a canceable build status' do
+      expect(status).to be_a Gitlab::Ci::Status::Build::Cancelable
+    end
+
+    it 'fabricates status with correct details' do
+      expect(status.text).to eq 'running'
+      expect(status.icon).to eq 'icon_status_running'
+      expect(status.label).to eq 'running'
+      expect(status).to have_details
+      expect(status).to have_action
+    end
+  end
+
+  context 'when build is pending' do
+    let(:build) { create(:ci_build, :pending) }
+
+    it 'fabricates a cancelable build status' do
+      expect(status).to be_a Gitlab::Ci::Status::Build::Cancelable
+    end
+
+    it 'fabricates status with correct details' do
+      expect(status.text).to eq 'pending'
+      expect(status.icon).to eq 'icon_status_pending'
+      expect(status.label).to eq 'pending'
+      expect(status).to have_details
+      expect(status).to have_action
+    end
+  end
+
+  context 'when build is skipped' do
+    let(:build) { create(:ci_build, :skipped) }
+
+    it 'fabricates a core skipped status' do
+      expect(status).to be_a Gitlab::Ci::Status::Skipped
+    end
+
+    it 'fabricates status with correct details' do
+      expect(status.text).to eq 'skipped'
+      expect(status.icon).to eq 'icon_status_skipped'
+      expect(status.label).to eq 'skipped'
+      expect(status).to have_details
+      expect(status).not_to have_action
+    end
+  end
+
+  context 'when build is a manual action' do
+    context 'when build is a play action' do
+      let(:build) { create(:ci_build, :playable) }
+
+      it 'fabricates a core skipped status' do
+        expect(status).to be_a Gitlab::Ci::Status::Build::Play
+      end
+
+      it 'fabricates status with correct details' do
+        expect(status.text).to eq 'manual'
+        expect(status.icon).to eq 'icon_status_manual'
+        expect(status.label).to eq 'manual play action'
+        expect(status).to have_details
+        expect(status).to have_action
+      end
+    end
+
+    context 'when build is an environment stop action' do
+      let(:build) { create(:ci_build, :playable, :teardown_environment) }
+
+      it 'fabricates a core skipped status' do
+        expect(status).to be_a Gitlab::Ci::Status::Build::Stop
+      end
+
+      it 'fabricates status with correct details' do
+        expect(status.text).to eq 'manual'
+        expect(status.icon).to eq 'icon_status_manual'
+        expect(status.label).to eq 'manual stop action'
+        expect(status).to have_details
+        expect(status).to have_action
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/status/build/play_spec.rb b/spec/lib/gitlab/ci/status/build/play_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4ddf04a8e11b21cc0c1dd0e5fef71220fca87926
--- /dev/null
+++ b/spec/lib/gitlab/ci/status/build/play_spec.rb
@@ -0,0 +1,82 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Status::Build::Play do
+  let(:status) { double('core') }
+  let(:user) { double('user') }
+
+  subject { described_class.new(status) }
+
+  describe '#text' do
+    it { expect(subject.text).to eq 'manual' }
+  end
+
+  describe '#label' do
+    it { expect(subject.label).to eq 'manual play action' }
+  end
+
+  describe '#icon' do
+    it { expect(subject.icon).to eq 'icon_status_manual' }
+  end
+
+  describe 'action details' do
+    let(:user) { create(:user) }
+    let(:build) { create(:ci_build) }
+    let(:status) { Gitlab::Ci::Status::Core.new(build, user) }
+
+    describe '#has_action?' do
+      context 'when user is allowed to update build' do
+        before { build.project.team << [user, :developer] }
+
+        it { is_expected.to have_action }
+      end
+
+      context 'when user is not allowed to update build' do
+        it { is_expected.not_to have_action }
+      end
+    end
+
+    describe '#action_path' do
+      it { expect(subject.action_path).to include "#{build.id}/play" }
+    end
+
+    describe '#action_icon' do
+      it { expect(subject.action_icon).to eq 'play' }
+    end
+
+    describe '#action_title' do
+      it { expect(subject.action_title).to eq 'Play' }
+    end
+  end
+
+  describe '.matches?' do
+    subject { described_class.matches?(build, user) }
+
+    context 'when build is playable' do
+      context 'when build stops an environment' do
+        let(:build) do
+          create(:ci_build, :playable, :teardown_environment)
+        end
+
+        it 'does not match' do
+          expect(subject).to be false
+        end
+      end
+
+      context 'when build does not stop an environment' do
+        let(:build) { create(:ci_build, :playable) }
+
+        it 'is a correct match' do
+          expect(subject).to be true
+        end
+      end
+    end
+
+    context 'when build is not playable' do
+      let(:build) { create(:ci_build) }
+
+      it 'does not match' do
+        expect(subject).to be false
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/status/build/retryable_spec.rb b/spec/lib/gitlab/ci/status/build/retryable_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d61e5bbaa6bddfe2bc53c95a8eff2bd4a857bfcd
--- /dev/null
+++ b/spec/lib/gitlab/ci/status/build/retryable_spec.rb
@@ -0,0 +1,86 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Status::Build::Retryable do
+  let(:status) { double('core status') }
+  let(:user) { double('user') }
+
+  subject do
+    described_class.new(status)
+  end
+
+  describe '#text' do
+    it 'does not override status text' do
+      expect(status).to receive(:text)
+
+      subject.text
+    end
+  end
+
+  describe '#icon' do
+    it 'does not override status icon' do
+      expect(status).to receive(:icon)
+
+      subject.icon
+    end
+  end
+
+  describe '#label' do
+    it 'does not override status label' do
+      expect(status).to receive(:label)
+
+      subject.label
+    end
+  end
+
+  describe 'action details' do
+    let(:user) { create(:user) }
+    let(:build) { create(:ci_build) }
+    let(:status) { Gitlab::Ci::Status::Core.new(build, user) }
+
+    describe '#has_action?' do
+      context 'when user is allowed to update build' do
+        before { build.project.team << [user, :developer] }
+
+        it { is_expected.to have_action }
+      end
+
+      context 'when user is not allowed to update build' do
+        it { is_expected.not_to have_action }
+      end
+    end
+
+    describe '#action_path' do
+      it { expect(subject.action_path).to include "#{build.id}/retry" }
+    end
+
+    describe '#action_icon' do
+      it { expect(subject.action_icon).to eq 'refresh' }
+    end
+
+    describe '#action_title' do
+      it { expect(subject.action_title).to eq 'Retry' }
+    end
+  end
+
+  describe '.matches?' do
+    subject { described_class.matches?(build, user) }
+
+    context 'when build is retryable' do
+      let(:build) do
+        create(:ci_build, :success)
+      end
+
+      it 'is a correct match' do
+        expect(subject).to be true
+      end
+    end
+
+    context 'when build is not retryable' do
+      let(:build) { create(:ci_build, :running) }
+
+      it 'does not match' do
+        expect(subject).to be false
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/status/build/stop_spec.rb b/spec/lib/gitlab/ci/status/build/stop_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..59a85b55f906379eea7574148080b7b51c105d54
--- /dev/null
+++ b/spec/lib/gitlab/ci/status/build/stop_spec.rb
@@ -0,0 +1,84 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Status::Build::Stop do
+  let(:status) { double('core status') }
+  let(:user) { double('user') }
+
+  subject do
+    described_class.new(status)
+  end
+
+  describe '#text' do
+    it { expect(subject.text).to eq 'manual' }
+  end
+
+  describe '#label' do
+    it { expect(subject.label).to eq 'manual stop action' }
+  end
+
+  describe '#icon' do
+    it { expect(subject.icon).to eq 'icon_status_manual' }
+  end
+
+  describe 'action details' do
+    let(:user) { create(:user) }
+    let(:build) { create(:ci_build) }
+    let(:status) { Gitlab::Ci::Status::Core.new(build, user) }
+
+    describe '#has_action?' do
+      context 'when user is allowed to update build' do
+        before { build.project.team << [user, :developer] }
+
+        it { is_expected.to have_action }
+      end
+
+      context 'when user is not allowed to update build' do
+        it { is_expected.not_to have_action }
+      end
+    end
+
+    describe '#action_path' do
+      it { expect(subject.action_path).to include "#{build.id}/play" }
+    end
+
+    describe '#action_icon' do
+      it { expect(subject.action_icon).to eq 'stop' }
+    end
+
+    describe '#action_title' do
+      it { expect(subject.action_title).to eq 'Stop' }
+    end
+  end
+
+  describe '.matches?' do
+    subject { described_class.matches?(build, user) }
+
+    context 'when build is playable' do
+      context 'when build stops an environment' do
+        let(:build) do
+          create(:ci_build, :playable, :teardown_environment)
+        end
+
+        it 'is a correct match' do
+          expect(subject).to be true
+        end
+      end
+
+      context 'when build does not stop an environment' do
+        let(:build) { create(:ci_build, :playable) }
+
+        it 'does not match' do
+          expect(subject).to be false
+        end
+      end
+    end
+
+    context 'when build is not playable' do
+      let(:build) { create(:ci_build) }
+
+      it 'does not match' do
+        expect(subject).to be false
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/status/canceled_spec.rb b/spec/lib/gitlab/ci/status/canceled_spec.rb
index 619ecbcba6737475dd5336859ee4c515baf1bf9f..4639278ad450f8d08bb4797e2b29cc1ace421a34 100644
--- a/spec/lib/gitlab/ci/status/canceled_spec.rb
+++ b/spec/lib/gitlab/ci/status/canceled_spec.rb
@@ -1,7 +1,9 @@
 require 'spec_helper'
 
 describe Gitlab::Ci::Status::Canceled do
-  subject { described_class.new(double('subject')) }
+  subject do
+    described_class.new(double('subject'), double('user'))
+  end
 
   describe '#text' do
     it { expect(subject.label).to eq 'canceled' }
@@ -14,8 +16,4 @@ describe Gitlab::Ci::Status::Canceled do
   describe '#icon' do
     it { expect(subject.icon).to eq 'icon_status_canceled' }
   end
-
-  describe '#title' do
-    it { expect(subject.title).to eq 'Double: canceled' }
-  end
 end
diff --git a/spec/lib/gitlab/ci/status/created_spec.rb b/spec/lib/gitlab/ci/status/created_spec.rb
index 157302c65a845c248fa593038050cfcdf113100f..2ce176a29d634cf5df60379045e801b272a17fa1 100644
--- a/spec/lib/gitlab/ci/status/created_spec.rb
+++ b/spec/lib/gitlab/ci/status/created_spec.rb
@@ -1,7 +1,9 @@
 require 'spec_helper'
 
 describe Gitlab::Ci::Status::Created do
-  subject { described_class.new(double('subject')) }
+  subject do
+    described_class.new(double('subject'), double('user'))
+  end
 
   describe '#text' do
     it { expect(subject.label).to eq 'created' }
@@ -14,8 +16,4 @@ describe Gitlab::Ci::Status::Created do
   describe '#icon' do
     it { expect(subject.icon).to eq 'icon_status_created' }
   end
-
-  describe '#title' do
-    it { expect(subject.title).to eq 'Double: created' }
-  end
 end
diff --git a/spec/lib/gitlab/ci/status/extended_spec.rb b/spec/lib/gitlab/ci/status/extended_spec.rb
index 120e121aae5f31849f1b92e1e52992a04ab3b502..c2d74ca5cde0cbad5026e10fd590829d02b33c9d 100644
--- a/spec/lib/gitlab/ci/status/extended_spec.rb
+++ b/spec/lib/gitlab/ci/status/extended_spec.rb
@@ -2,11 +2,11 @@ require 'spec_helper'
 
 describe Gitlab::Ci::Status::Extended do
   subject do
-    Class.new.extend(described_class)
+    Class.new.include(described_class)
   end
 
   it 'requires subclass to implement matcher' do
-    expect { subject.matches?(double) }
+    expect { subject.matches?(double, double) }
       .to raise_error(NotImplementedError)
   end
 end
diff --git a/spec/lib/gitlab/ci/status/factory_spec.rb b/spec/lib/gitlab/ci/status/factory_spec.rb
index d5bd7f7102ba959fb9506572a2caf660650c2031..f92a1c149bf1c604c47fe2f81b917b57d649c395 100644
--- a/spec/lib/gitlab/ci/status/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/factory_spec.rb
@@ -2,15 +2,17 @@ require 'spec_helper'
 
 describe Gitlab::Ci::Status::Factory do
   subject do
-    described_class.new(object)
+    described_class.new(resource, user)
   end
 
+  let(:user) { create(:user) }
+
   let(:status) { subject.fabricate! }
 
   context 'when object has a core status' do
     HasStatus::AVAILABLE_STATUSES.each do |core_status|
       context "when core status is #{core_status}" do
-        let(:object) { double(status: core_status) }
+        let(:resource) { double(status: core_status) }
 
         it "fabricates a core status #{core_status}" do
           expect(status).to be_a(
diff --git a/spec/lib/gitlab/ci/status/failed_spec.rb b/spec/lib/gitlab/ci/status/failed_spec.rb
index 0b3cb8168e6eb1c49d83dc481b62bb73268f0c22..9d527e6a7efe50e1e5db8819c381fe6493c72adb 100644
--- a/spec/lib/gitlab/ci/status/failed_spec.rb
+++ b/spec/lib/gitlab/ci/status/failed_spec.rb
@@ -1,7 +1,9 @@
 require 'spec_helper'
 
 describe Gitlab::Ci::Status::Failed do
-  subject { described_class.new(double('subject')) }
+  subject do
+    described_class.new(double('subject'), double('user'))
+  end
 
   describe '#text' do
     it { expect(subject.label).to eq 'failed' }
@@ -14,8 +16,4 @@ describe Gitlab::Ci::Status::Failed do
   describe '#icon' do
     it { expect(subject.icon).to eq 'icon_status_failed' }
   end
-
-  describe '#title' do
-    it { expect(subject.title).to eq 'Double: failed' }
-  end
 end
diff --git a/spec/lib/gitlab/ci/status/pending_spec.rb b/spec/lib/gitlab/ci/status/pending_spec.rb
index 57c901c1202fa3bfb506633813ec654b86c8d148..d03f595d3c75c37792efa0e8e7116293c82082dc 100644
--- a/spec/lib/gitlab/ci/status/pending_spec.rb
+++ b/spec/lib/gitlab/ci/status/pending_spec.rb
@@ -1,7 +1,9 @@
 require 'spec_helper'
 
 describe Gitlab::Ci::Status::Pending do
-  subject { described_class.new(double('subject')) }
+  subject do
+    described_class.new(double('subject'), double('user'))
+  end
 
   describe '#text' do
     it { expect(subject.label).to eq 'pending' }
@@ -14,8 +16,4 @@ describe Gitlab::Ci::Status::Pending do
   describe '#icon' do
     it { expect(subject.icon).to eq 'icon_status_pending' }
   end
-
-  describe '#title' do
-    it { expect(subject.title).to eq 'Double: pending' }
-  end
 end
diff --git a/spec/lib/gitlab/ci/status/pipeline/common_spec.rb b/spec/lib/gitlab/ci/status/pipeline/common_spec.rb
index 21adee3f8e7d462a5d579ed628239bf4d010c380..d665674bf70095e8ff8ae2726c3b33a198dee2ab 100644
--- a/spec/lib/gitlab/ci/status/pipeline/common_spec.rb
+++ b/spec/lib/gitlab/ci/status/pipeline/common_spec.rb
@@ -1,23 +1,36 @@
 require 'spec_helper'
 
 describe Gitlab::Ci::Status::Pipeline::Common do
-  let(:pipeline) { create(:ci_pipeline) }
+  let(:user) { create(:user) }
+  let(:project) { create(:empty_project, :private) }
+  let(:pipeline) { create(:ci_pipeline, project: project) }
 
   subject do
-    Class.new(Gitlab::Ci::Status::Core)
-      .new(pipeline).extend(described_class)
+    Gitlab::Ci::Status::Core
+      .new(pipeline, user)
+      .extend(described_class)
   end
 
-  it 'does not have action' do
-    expect(subject).not_to have_action
+  describe '#has_action?' do
+    it { is_expected.not_to have_action }
   end
 
-  it 'has details' do
-    expect(subject).to have_details
+  describe '#has_details?' do
+    context 'when user has access to read pipeline' do
+      before { project.team << [user, :developer] }
+
+      it { is_expected.to have_details }
+    end
+
+    context 'when user does not have access to read pipeline' do
+      it { is_expected.not_to have_details }
+    end
   end
 
-  it 'links to the pipeline details page' do
-    expect(subject.details_path)
-      .to include "pipelines/#{pipeline.id}"
+  describe '#details_path' do
+    it 'links to the pipeline details page' do
+      expect(subject.details_path)
+        .to include "pipelines/#{pipeline.id}"
+    end
   end
 end
diff --git a/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb b/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb
index d6243940f2e5eaf37bc389c268d3b6aba3db811d..d4a2dc7fcc1e1bb0d5213ffb099dc12cd8a61f44 100644
--- a/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb
@@ -1,14 +1,21 @@
 require 'spec_helper'
 
 describe Gitlab::Ci::Status::Pipeline::Factory do
+  let(:user) { create(:user) }
+  let(:project) { pipeline.project }
+
   subject do
-    described_class.new(pipeline)
+    described_class.new(pipeline, user)
   end
 
   let(:status) do
     subject.fabricate!
   end
 
+  before do
+    project.team << [user, :developer]
+  end
+
   context 'when pipeline has a core status' do
     HasStatus::AVAILABLE_STATUSES.each do |core_status|
       context "when core status is #{core_status}" do
diff --git a/spec/lib/gitlab/ci/status/pipeline/success_with_warnings_spec.rb b/spec/lib/gitlab/ci/status/pipeline/success_with_warnings_spec.rb
index 02e526e3de2001aa6dccb43454dc2eda24b6e892..7e3383c307f1f8057e3edd6db7fb2939ffae8ea5 100644
--- a/spec/lib/gitlab/ci/status/pipeline/success_with_warnings_spec.rb
+++ b/spec/lib/gitlab/ci/status/pipeline/success_with_warnings_spec.rb
@@ -29,13 +29,13 @@ describe Gitlab::Ci::Status::Pipeline::SuccessWithWarnings do
         end
 
         it 'is a correct match' do
-          expect(described_class.matches?(pipeline)).to eq true
+          expect(described_class.matches?(pipeline, double)).to eq true
         end
       end
 
       context 'when pipeline does not have warnings' do
         it 'does not match' do
-          expect(described_class.matches?(pipeline)).to eq false
+          expect(described_class.matches?(pipeline, double)).to eq false
         end
       end
     end
@@ -51,13 +51,13 @@ describe Gitlab::Ci::Status::Pipeline::SuccessWithWarnings do
         end
 
         it 'does not match' do
-          expect(described_class.matches?(pipeline)).to eq false
+          expect(described_class.matches?(pipeline, double)).to eq false
         end
       end
 
       context 'when pipeline does not have warnings' do
         it 'does not match' do
-          expect(described_class.matches?(pipeline)).to eq false
+          expect(described_class.matches?(pipeline, double)).to eq false
         end
       end
     end
diff --git a/spec/lib/gitlab/ci/status/running_spec.rb b/spec/lib/gitlab/ci/status/running_spec.rb
index c023f1872cca14b647dd30017649d1a175466b30..9f47090d396af1afb84a8a2b0e029ede6cc5ae6a 100644
--- a/spec/lib/gitlab/ci/status/running_spec.rb
+++ b/spec/lib/gitlab/ci/status/running_spec.rb
@@ -1,7 +1,9 @@
 require 'spec_helper'
 
 describe Gitlab::Ci::Status::Running do
-  subject { described_class.new(double('subject')) }
+  subject do
+    described_class.new(double('subject'), double('user'))
+  end
 
   describe '#text' do
     it { expect(subject.label).to eq 'running' }
@@ -14,8 +16,4 @@ describe Gitlab::Ci::Status::Running do
   describe '#icon' do
     it { expect(subject.icon).to eq 'icon_status_running' }
   end
-
-  describe '#title' do
-    it { expect(subject.title).to eq 'Double: running' }
-  end
 end
diff --git a/spec/lib/gitlab/ci/status/skipped_spec.rb b/spec/lib/gitlab/ci/status/skipped_spec.rb
index d4f7f4b3b707fec1f0bbf9fe9957199b0adf60b8..94601648a8de25b762c7cf8e53c6410d273e40b8 100644
--- a/spec/lib/gitlab/ci/status/skipped_spec.rb
+++ b/spec/lib/gitlab/ci/status/skipped_spec.rb
@@ -1,7 +1,9 @@
 require 'spec_helper'
 
 describe Gitlab::Ci::Status::Skipped do
-  subject { described_class.new(double('subject')) }
+  subject do
+    described_class.new(double('subject'), double('user'))
+  end
 
   describe '#text' do
     it { expect(subject.label).to eq 'skipped' }
@@ -14,8 +16,4 @@ describe Gitlab::Ci::Status::Skipped do
   describe '#icon' do
     it { expect(subject.icon).to eq 'icon_status_skipped' }
   end
-
-  describe '#title' do
-    it { expect(subject.title).to eq 'Double: skipped' }
-  end
 end
diff --git a/spec/lib/gitlab/ci/status/stage/common_spec.rb b/spec/lib/gitlab/ci/status/stage/common_spec.rb
index f3259c6f23e0738cbba6c14f70a179065b1f0dd5..8814a7614a0715ff95da507f92cc38fee506f0fb 100644
--- a/spec/lib/gitlab/ci/status/stage/common_spec.rb
+++ b/spec/lib/gitlab/ci/status/stage/common_spec.rb
@@ -1,26 +1,43 @@
 require 'spec_helper'
 
 describe Gitlab::Ci::Status::Stage::Common do
-  let(:pipeline) { create(:ci_empty_pipeline) }
-  let(:stage) { build(:ci_stage, pipeline: pipeline, name: 'test') }
+  let(:user) { create(:user) }
+  let(:project) { create(:empty_project) }
+  let(:pipeline) { create(:ci_empty_pipeline, project: project) }
+
+  let(:stage) do
+    build(:ci_stage, pipeline: pipeline, name: 'test')
+  end
 
   subject do
     Class.new(Gitlab::Ci::Status::Core)
-      .new(stage).extend(described_class)
+      .new(stage, user).extend(described_class)
   end
 
   it 'does not have action' do
     expect(subject).not_to have_action
   end
 
-  it 'has details' do
-    expect(subject).to have_details
-  end
-
   it 'links to the pipeline details page' do
     expect(subject.details_path)
       .to include "pipelines/#{pipeline.id}"
     expect(subject.details_path)
       .to include "##{stage.name}"
   end
+
+  context 'when user has permission to read pipeline' do
+    before do
+      project.team << [user, :master]
+    end
+
+    it 'has details' do
+      expect(subject).to have_details
+    end
+  end
+
+  context 'when user does not have permission to read pipeline' do
+    it 'does not have details' do
+      expect(subject).not_to have_details
+    end
+  end
 end
diff --git a/spec/lib/gitlab/ci/status/stage/factory_spec.rb b/spec/lib/gitlab/ci/status/stage/factory_spec.rb
index 17929665c836f11f924ba4dc906df0d04a1697c0..6f8721d30c22c14de01d88ea1095397a6643de73 100644
--- a/spec/lib/gitlab/ci/status/stage/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/stage/factory_spec.rb
@@ -1,17 +1,26 @@
 require 'spec_helper'
 
 describe Gitlab::Ci::Status::Stage::Factory do
-  let(:pipeline) { create(:ci_empty_pipeline) }
-  let(:stage) { build(:ci_stage, pipeline: pipeline, name: 'test') }
+  let(:user) { create(:user) }
+  let(:project) { create(:empty_project) }
+  let(:pipeline) { create(:ci_empty_pipeline, project: project) }
+
+  let(:stage) do
+    build(:ci_stage, pipeline: pipeline, name: 'test')
+  end
 
   subject do
-    described_class.new(stage)
+    described_class.new(stage, user)
   end
 
   let(:status) do
     subject.fabricate!
   end
 
+  before do
+    project.team << [user, :developer]
+  end
+
   context 'when stage has a core status' do
     HasStatus::AVAILABLE_STATUSES.each do |core_status|
       context "when core status is #{core_status}" do
diff --git a/spec/lib/gitlab/ci/status/success_spec.rb b/spec/lib/gitlab/ci/status/success_spec.rb
index 9e261a3aa5f207ac63acb708610093449f991524..90f9f615e0d52afe10d51df48c4b8d5c691ad37d 100644
--- a/spec/lib/gitlab/ci/status/success_spec.rb
+++ b/spec/lib/gitlab/ci/status/success_spec.rb
@@ -1,7 +1,9 @@
 require 'spec_helper'
 
 describe Gitlab::Ci::Status::Success do
-  subject { described_class.new(double('subject')) }
+  subject do
+    described_class.new(double('subject'), double('user'))
+  end
 
   describe '#text' do
     it { expect(subject.label).to eq 'passed' }
@@ -14,8 +16,4 @@ describe Gitlab::Ci::Status::Success do
   describe '#icon' do
     it { expect(subject.icon).to eq 'icon_status_success' }
   end
-
-  describe '#title' do
-    it { expect(subject.title).to eq 'Double: passed' }
-  end
 end
diff --git a/spec/lib/gitlab/routing_spec.rb b/spec/lib/gitlab/routing_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..01d5acfc15b7ef244be147ba51fab4bfc92e2045
--- /dev/null
+++ b/spec/lib/gitlab/routing_spec.rb
@@ -0,0 +1,23 @@
+require 'spec_helper'
+
+describe Gitlab::Routing do
+  context 'when module is included' do
+    subject do
+      Class.new.include(described_class).new
+    end
+
+    it 'makes it possible to access url helpers' do
+      expect(subject).to respond_to(:namespace_project_path)
+    end
+  end
+
+  context 'when module is not included' do
+    subject do
+      Class.new.include(described_class.url_helpers).new
+    end
+
+    it 'exposes url helpers module through a method' do
+      expect(subject).to respond_to(:namespace_project_path)
+    end
+  end
+end
diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb
index d4970e38f7ca1ce6ddff1ae5d5dc17d22935734a..7f39aff7639ec9bdd30df93b8e858f161588d344 100644
--- a/spec/models/build_spec.rb
+++ b/spec/models/build_spec.rb
@@ -899,21 +899,87 @@ describe Ci::Build, models: true do
     end
   end
 
+  describe '#cancelable?' do
+    subject { build }
+
+    context 'when build is cancelable' do
+      context 'when build is pending' do
+        it { is_expected.to be_cancelable }
+      end
+
+      context 'when build is running' do
+        before do
+          build.run!
+        end
+
+        it { is_expected.to be_cancelable }
+      end
+    end
+
+    context 'when build is not cancelable' do
+      context 'when build is successful' do
+        before do
+          build.success!
+        end
+
+        it { is_expected.not_to be_cancelable }
+      end
+
+      context 'when build is failed' do
+        before do
+          build.drop!
+        end
+
+        it { is_expected.not_to be_cancelable }
+      end
+    end
+  end
+
   describe '#retryable?' do
-    context 'when build is running' do
-      before do
-        build.run!
+    subject { build }
+
+    context 'when build is retryable' do
+      context 'when build is successful' do
+        before do
+          build.success!
+        end
+
+        it { is_expected.to be_retryable }
+      end
+
+      context 'when build is failed' do
+        before do
+          build.drop!
+        end
+
+        it { is_expected.to be_retryable }
       end
 
-      it { expect(build).not_to be_retryable }
+      context 'when build is canceled' do
+        before do
+          build.cancel!
+        end
+
+        it { is_expected.to be_retryable }
+      end
     end
 
-    context 'when build is finished' do
-      before do
-        build.success!
+    context 'when build is not retryable' do
+      context 'when build is running' do
+        before do
+          build.run!
+        end
+
+        it { is_expected.not_to be_retryable }
       end
 
-      it { expect(build).to be_retryable }
+      context 'when build is skipped' do
+        before do
+          build.skip!
+        end
+
+        it { is_expected.not_to be_retryable }
+      end
     end
   end
 
@@ -1180,4 +1246,13 @@ describe Ci::Build, models: true do
       it { is_expected.to eq('review/master') }
     end
   end
+
+  describe '#detailed_status' do
+    let(:user) { create(:user) }
+
+    it 'returns a detailed status' do
+      expect(build.detailed_status(user))
+        .to be_a Gitlab::Ci::Status::Build::Cancelable
+    end
+  end
 end
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 8158e71dd55aa92572efbafc75f0e77fe53701fa..e78ae14b7377497795d8f01aeb6def1cec2424d4 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -442,11 +442,15 @@ describe Ci::Pipeline, models: true do
   end
 
   describe '#detailed_status' do
+    let(:user) { create(:user) }
+
+    subject { pipeline.detailed_status(user) }
+
     context 'when pipeline is created' do
       let(:pipeline) { create(:ci_pipeline, status: :created) }
 
       it 'returns detailed status for created pipeline' do
-        expect(pipeline.detailed_status.text).to eq 'created'
+        expect(subject.text).to eq 'created'
       end
     end
 
@@ -454,7 +458,7 @@ describe Ci::Pipeline, models: true do
       let(:pipeline) { create(:ci_pipeline, status: :pending) }
 
       it 'returns detailed status for pending pipeline' do
-        expect(pipeline.detailed_status.text).to eq 'pending'
+        expect(subject.text).to eq 'pending'
       end
     end
 
@@ -462,7 +466,7 @@ describe Ci::Pipeline, models: true do
       let(:pipeline) { create(:ci_pipeline, status: :running) }
 
       it 'returns detailed status for running pipeline' do
-        expect(pipeline.detailed_status.text).to eq 'running'
+        expect(subject.text).to eq 'running'
       end
     end
 
@@ -470,7 +474,7 @@ describe Ci::Pipeline, models: true do
       let(:pipeline) { create(:ci_pipeline, status: :success) }
 
       it 'returns detailed status for successful pipeline' do
-        expect(pipeline.detailed_status.text).to eq 'passed'
+        expect(subject.text).to eq 'passed'
       end
     end
 
@@ -478,7 +482,7 @@ describe Ci::Pipeline, models: true do
       let(:pipeline) { create(:ci_pipeline, status: :failed) }
 
       it 'returns detailed status for failed pipeline' do
-        expect(pipeline.detailed_status.text).to eq 'failed'
+        expect(subject.text).to eq 'failed'
       end
     end
 
@@ -486,7 +490,7 @@ describe Ci::Pipeline, models: true do
       let(:pipeline) { create(:ci_pipeline, status: :canceled) }
 
       it 'returns detailed status for canceled pipeline' do
-        expect(pipeline.detailed_status.text).to eq 'canceled'
+        expect(subject.text).to eq 'canceled'
       end
     end
 
@@ -494,7 +498,7 @@ describe Ci::Pipeline, models: true do
       let(:pipeline) { create(:ci_pipeline, status: :skipped) }
 
       it 'returns detailed status for skipped pipeline' do
-        expect(pipeline.detailed_status.text).to eq 'skipped'
+        expect(subject.text).to eq 'skipped'
       end
     end
 
@@ -506,7 +510,7 @@ describe Ci::Pipeline, models: true do
       end
 
       it 'retruns detailed status for successful pipeline with warnings' do
-        expect(pipeline.detailed_status.label).to eq 'passed with warnings'
+        expect(subject.label).to eq 'passed with warnings'
       end
     end
   end
diff --git a/spec/models/ci/stage_spec.rb b/spec/models/ci/stage_spec.rb
index f232761dba282b636ff4f8c75d900a9c02cb00f7..8fff38f7cda1b6ac5d994d5b56ba0b35657851d6 100644
--- a/spec/models/ci/stage_spec.rb
+++ b/spec/models/ci/stage_spec.rb
@@ -68,7 +68,9 @@ describe Ci::Stage, models: true do
   end
 
   describe '#detailed_status' do
-    subject { stage.detailed_status }
+    let(:user) { create(:user) }
+
+    subject { stage.detailed_status(user) }
 
     context 'when build is created' do
       let!(:stage_build) { create_job(:ci_build, status: :created) }
diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb
index 1ec08c2a9d0ef72b3a01bec979ebb512945dffdf..701f3323c0f5bc4468d8d7def7039d9a69fa2787 100644
--- a/spec/models/commit_status_spec.rb
+++ b/spec/models/commit_status_spec.rb
@@ -234,4 +234,13 @@ describe CommitStatus, models: true do
       end
     end
   end
+
+  describe '#detailed_status' do
+    let(:user) { create(:user) }
+
+    it 'returns a detailed status' do
+      expect(commit_status.detailed_status(user))
+        .to be_a Gitlab::Ci::Status::Success
+    end
+  end
 end
diff --git a/spec/models/generic_commit_status_spec.rb b/spec/models/generic_commit_status_spec.rb
index 615cfe3142b1b78dc8adc9c178d635a03cc61b09..6004bfdb7b747960da08681e0705fce223bb2282 100644
--- a/spec/models/generic_commit_status_spec.rb
+++ b/spec/models/generic_commit_status_spec.rb
@@ -1,8 +1,11 @@
 require 'spec_helper'
 
 describe GenericCommitStatus, models: true do
-  let(:pipeline) { FactoryGirl.create :ci_pipeline }
-  let(:generic_commit_status) { FactoryGirl.create :generic_commit_status, pipeline: pipeline }
+  let(:pipeline) { create(:ci_pipeline) }
+
+  let(:generic_commit_status) do
+    create(:generic_commit_status, pipeline: pipeline)
+  end
 
   describe '#context' do
     subject { generic_commit_status.context }
@@ -17,6 +20,15 @@ describe GenericCommitStatus, models: true do
     it { is_expected.to eq([:external]) }
   end
 
+  describe '#detailed_status' do
+    let(:user) { create(:user) }
+
+    it 'returns detailed status object' do
+      expect(generic_commit_status.detailed_status(user))
+        .to be_a Gitlab::Ci::Status::Success
+    end
+  end
+
   describe 'set_default_values' do
     before do
       generic_commit_status.context = nil