diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index aa23b5cf97c142be111110b8ff974cb4aac09378..d14825c082ac8ba33ddd57fb02ac56e6e3a9c6b1 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -104,6 +104,10 @@ module Ci
       statuses.select(:stage).distinct.count
     end
 
+    def stages_name
+      statuses.order(:stage_idx).distinct.pluck(:stage)
+    end
+
     def stages
       status_sql = statuses.latest.where('stage=sg.stage').status_sql
 
diff --git a/lib/gitlab/data_builder/pipeline.rb b/lib/gitlab/data_builder/pipeline.rb
index e0284d55da8d9ff05a68f0fb496ea2b7e95b3c6c..e50e54b6e99f89154ea1e880326d7db49cd8373b 100644
--- a/lib/gitlab/data_builder/pipeline.rb
+++ b/lib/gitlab/data_builder/pipeline.rb
@@ -22,7 +22,7 @@ module Gitlab
           sha: pipeline.sha,
           before_sha: pipeline.before_sha,
           status: pipeline.status,
-          stages: pipeline.stages.map(&:name),
+          stages: pipeline.stages_name,
           created_at: pipeline.created_at,
           finished_at: pipeline.finished_at,
           duration: pipeline.duration
diff --git a/spec/factories/ci/stages.rb b/spec/factories/ci/stages.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ee3b17b8bf1fe0b485b215b4a79b5ae41d5977e2
--- /dev/null
+++ b/spec/factories/ci/stages.rb
@@ -0,0 +1,13 @@
+FactoryGirl.define do
+  factory :ci_stage, class: Ci::Stage do
+    transient do
+      name 'test'
+      status nil
+      pipeline factory: :ci_empty_pipeline
+    end
+
+    initialize_with do
+      Ci::Stage.new(pipeline, name: name, status: status)
+    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..939ad2edf04d9527b057a30a9f934380952bd5b0 100644
--- a/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb
@@ -21,7 +21,7 @@ describe Gitlab::Ci::Status::Pipeline::Factory do
             Gitlab::Ci::Status.const_get(core_status.capitalize))
         end
 
-        it 'extends core status with common pipeline methods' do
+        it 'extends core status with common stage methods' do
           expect(status).to have_details
           expect(status).not_to have_action
           expect(status.details_path)
@@ -45,7 +45,7 @@ describe Gitlab::Ci::Status::Pipeline::Factory do
         .to be_a Gitlab::Ci::Status::Pipeline::SuccessWithWarnings
     end
 
-    it 'extends core status with common pipeline methods' do
+    it 'extends core status with common stage methods' do
       expect(status).to have_details
     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 f15d604787882e8270ecdd7440a5a7ff3cc34dd7..f3259c6f23e0738cbba6c14f70a179065b1f0dd5 100644
--- a/spec/lib/gitlab/ci/status/stage/common_spec.rb
+++ b/spec/lib/gitlab/ci/status/stage/common_spec.rb
@@ -1,8 +1,8 @@
 require 'spec_helper'
 
 describe Gitlab::Ci::Status::Stage::Common do
-  let(:pipeline) { create(:ci_pipeline) }
-  let(:stage) { Ci::Stage.new(pipeline, name: 'test') }
+  let(:pipeline) { create(:ci_empty_pipeline) }
+  let(:stage) { build(:ci_stage, pipeline: pipeline, name: 'test') }
 
   subject do
     Class.new(Gitlab::Ci::Status::Core)
diff --git a/spec/lib/gitlab/ci/status/stage/factory_spec.rb b/spec/lib/gitlab/ci/status/stage/factory_spec.rb
index 2d22bd1e2a05626dc9821721f7f77df458417925..17929665c836f11f924ba4dc906df0d04a1697c0 100644
--- a/spec/lib/gitlab/ci/status/stage/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/stage/factory_spec.rb
@@ -1,8 +1,8 @@
 require 'spec_helper'
 
 describe Gitlab::Ci::Status::Stage::Factory do
-  let(:pipeline) { create(:ci_pipeline) }
-  let(:stage) { Ci::Stage.new(pipeline, name: 'test') }
+  let(:pipeline) { create(:ci_empty_pipeline) }
+  let(:stage) { build(:ci_stage, pipeline: pipeline, name: 'test') }
 
   subject do
     described_class.new(stage)
@@ -15,8 +15,10 @@ describe Gitlab::Ci::Status::Stage::Factory do
   context 'when stage has a core status' do
     HasStatus::AVAILABLE_STATUSES.each do |core_status|
       context "when core status is #{core_status}" do
-        let!(:build) do
+        before do
           create(:ci_build, pipeline: pipeline, stage: 'test', status: core_status)
+          create(:commit_status, pipeline: pipeline, stage: 'test', status: core_status)
+          create(:ci_build, pipeline: pipeline, stage: 'build', status: :failed)
         end
 
         it "fabricates a core status #{core_status}" do
@@ -24,7 +26,7 @@ describe Gitlab::Ci::Status::Stage::Factory do
             Gitlab::Ci::Status.const_get(core_status.capitalize))
         end
 
-        it 'extends core status with common pipeline methods' do
+        it 'extends core status with common stage methods' do
           expect(status).to have_details
           expect(status.details_path).to include "pipelines/#{pipeline.id}"
           expect(status.details_path).to include "##{stage.name}"
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index cdc858c13b4be84d759cf9f1bb28e059b008814e..8158e71dd55aa92572efbafc75f0e77fe53701fa 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -142,6 +142,10 @@ describe Ci::Pipeline, models: true do
       expect(pipeline.stages_count).to eq(3)
     end
 
+    it 'returns a valid names of stages' do
+      expect(pipeline.stages_name).to eq(['build', 'test', 'deploy'])
+    end
+
     context 'stages with statuses' do
       let(:statuses) do
         subject.map do |stage|
diff --git a/spec/models/ci/stage_spec.rb b/spec/models/ci/stage_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f232761dba282b636ff4f8c75d900a9c02cb00f7
--- /dev/null
+++ b/spec/models/ci/stage_spec.rb
@@ -0,0 +1,133 @@
+require 'spec_helper'
+
+describe Ci::Stage, models: true do
+  let(:stage) { build(:ci_stage) }
+  let(:pipeline) { stage.pipeline }
+  let(:stage_name) { stage.name }
+
+  describe '#expectations' do
+    subject { stage }
+
+    it { is_expected.to include_module(StaticModel) }
+
+    it { is_expected.to respond_to(:pipeline) }
+    it { is_expected.to respond_to(:name) }
+
+    it { is_expected.to delegate_method(:project).to(:pipeline) }
+  end
+
+  describe '#statuses' do
+    let!(:stage_build) { create_job(:ci_build) }
+    let!(:commit_status) { create_job(:commit_status) }
+    let!(:other_build) { create_job(:ci_build, stage: 'other stage') }
+
+    subject { stage.statuses }
+
+    it "returns only matching statuses" do
+      is_expected.to contain_exactly(stage_build, commit_status)
+    end
+  end
+
+  describe '#builds' do
+    let!(:stage_build) { create_job(:ci_build) }
+    let!(:commit_status) { create_job(:commit_status) }
+
+    subject { stage.builds }
+
+    it "returns only builds" do
+      is_expected.to contain_exactly(stage_build)
+    end
+  end
+
+  describe '#status' do
+    subject { stage.status }
+
+    context 'if status is already defined' do
+      let(:stage) { build(:ci_stage, status: 'success') }
+
+      it "returns defined status" do
+        is_expected.to eq('success')
+      end
+    end
+
+    context 'if status has to be calculated' do
+      let!(:stage_build) { create_job(:ci_build, status: :failed) }
+
+      it "returns status of a build" do
+        is_expected.to eq('failed')
+      end
+
+      context 'and builds are retried' do
+        let!(:new_build) { create_job(:ci_build, status: :success) }
+
+        it "returns status of latest build" do
+          is_expected.to eq('success')
+        end
+      end
+    end
+  end
+
+  describe '#detailed_status' do
+    subject { stage.detailed_status }
+
+    context 'when build is created' do
+      let!(:stage_build) { create_job(:ci_build, status: :created) }
+
+      it 'returns detailed status for created stage' do
+        expect(subject.text).to eq 'created'
+      end
+    end
+
+    context 'when build is pending' do
+      let!(:stage_build) { create_job(:ci_build, status: :pending) }
+
+      it 'returns detailed status for pending stage' do
+        expect(subject.text).to eq 'pending'
+      end
+    end
+
+    context 'when build is running' do
+      let!(:stage_build) { create_job(:ci_build, status: :running) }
+
+      it 'returns detailed status for running stage' do
+        expect(subject.text).to eq 'running'
+      end
+    end
+
+    context 'when build is successful' do
+      let!(:stage_build) { create_job(:ci_build, status: :success) }
+
+      it 'returns detailed status for successful stage' do
+        expect(subject.text).to eq 'passed'
+      end
+    end
+
+    context 'when build is failed' do
+      let!(:stage_build) { create_job(:ci_build, status: :failed) }
+
+      it 'returns detailed status for failed stage' do
+        expect(subject.text).to eq 'failed'
+      end
+    end
+
+    context 'when build is canceled' do
+      let!(:stage_build) { create_job(:ci_build, status: :canceled) }
+
+      it 'returns detailed status for canceled stage' do
+        expect(subject.text).to eq 'canceled'
+      end
+    end
+
+    context 'when build is skipped' do
+      let!(:stage_build) { create_job(:ci_build, status: :skipped) }
+
+      it 'returns detailed status for skipped stage' do
+        expect(subject.text).to eq 'skipped'
+      end
+    end
+  end
+
+  def create_job(type, status: 'success', stage: stage_name)
+    create(type, pipeline: pipeline, stage: stage, status: status)
+  end
+end