diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 8bc3a81d7716c3d2fd31b6b78963a405dd3cedcb..f87337b67aa8a472d6acbde49a6ce4cdfca0be62 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -198,43 +198,11 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
   end
 
   def ci_environments_status
-    environments =
-      begin
-        @merge_request.environments_for(current_user).map do |environment|
-          project    = environment.project
-          deployment = environment.first_deployment_for(@merge_request.diff_head_sha)
-
-          stop_url =
-            if can?(current_user, :stop_environment, environment)
-              stop_project_environment_path(project, environment)
-            end
-
-          metrics_url =
-            if can?(current_user, :read_environment, environment) && environment.has_metrics?
-              metrics_project_environment_deployment_path(project, environment, deployment)
-            end
-
-          metrics_monitoring_url =
-            if can?(current_user, :read_environment, environment)
-              environment_metrics_path(environment)
-            end
-
-          {
-            id: environment.id,
-            name: environment.name,
-            url: project_environment_path(project, environment),
-            metrics_url: metrics_url,
-            metrics_monitoring_url: metrics_monitoring_url,
-            stop_url: stop_url,
-            external_url: environment.external_url,
-            external_url_formatted: environment.formatted_external_url,
-            deployed_at: deployment.try(:created_at),
-            deployed_at_formatted: deployment.try(:formatted_deployment_time)
-          }
-        end.compact
-      end
+    environments = @merge_request.environments_for(current_user).map do |environment|
+      EnvironmentStatus.new(environment, @merge_request)
+    end
 
-    render json: environments
+    render json: EnvironmentStatusSerializer.new(current_user: current_user).represent(environments)
   end
 
   def rebase
diff --git a/app/models/environment_status.rb b/app/models/environment_status.rb
new file mode 100644
index 0000000000000000000000000000000000000000..cae0d396089a0013eb2be7ff03857d865bb87ed9
--- /dev/null
+++ b/app/models/environment_status.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+class EnvironmentStatus
+  include Gitlab::Utils::StrongMemoize
+
+  attr_reader :environment, :merge_request
+
+  delegate :id, to: :environment
+  delegate :name, to: :environment
+  delegate :project, to: :environment
+  delegate :deployed_at, to: :deployment, allow_nil: true
+
+  def initialize(environment, merge_request)
+    @environment = environment
+    @merge_request = merge_request
+  end
+
+  def deployment
+    strong_memoize(:deployment) do
+      environment.first_deployment_for(merge_request.diff_head_sha)
+    end
+  end
+
+  def deployed_at
+    deployment&.created_at
+  end
+end
diff --git a/app/serializers/environment_status_entity.rb b/app/serializers/environment_status_entity.rb
new file mode 100644
index 0000000000000000000000000000000000000000..62152dd1d40812aea93eb3c3ee18c7527392f480
--- /dev/null
+++ b/app/serializers/environment_status_entity.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+class EnvironmentStatusEntity < Grape::Entity
+  include RequestAwareEntity
+
+  expose :id
+  expose :name
+
+  expose :url do |es|
+    project_environment_path(es.project, es.environment)
+  end
+
+  expose :metrics_url, if: ->(*) { can_read_environment? && environment.has_metrics? } do |es|
+    metrics_project_environment_deployment_path(es.project, es.environment, es.deployment)
+  end
+
+  expose :metrics_monitoring_url, if: ->(*) { can_read_environment? } do |es|
+    environment_metrics_path(es.environment)
+  end
+
+  expose :stop_url, if: ->(*) { can_stop_environment? } do |es|
+    stop_project_environment_path(es.project, es.environment)
+  end
+
+  expose :external_url do |es|
+    es.environment.external_url
+  end
+
+  expose :external_url_formatted do |es|
+    es.environment.formatted_external_url
+  end
+
+  expose :deployed_at
+
+  expose :deployed_at_formatted do |es|
+    es.deployment.try(:formatted_deployment_time)
+  end
+
+  private
+
+  def environment
+    object.environment
+  end
+
+  def project
+    object.environment.project
+  end
+
+  def current_user
+    request.current_user
+  end
+
+  def can_read_environment?
+    can?(current_user, :read_environment, environment)
+  end
+
+  def can_stop_environment?
+    can?(current_user, :stop_environment, environment)
+  end
+end
diff --git a/app/serializers/environment_status_serializer.rb b/app/serializers/environment_status_serializer.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f8d37934763dbb73c93b429962cece50323ccaf6
--- /dev/null
+++ b/app/serializers/environment_status_serializer.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+class EnvironmentStatusSerializer < BaseSerializer
+  entity EnvironmentStatusEntity
+end
diff --git a/spec/factories/deployments.rb b/spec/factories/deployments.rb
index cac566953190618ed84e1454eee90cdb94ac6970..90d6a33847994a46cfbdfdb44e2f45d0403d855f 100644
--- a/spec/factories/deployments.rb
+++ b/spec/factories/deployments.rb
@@ -16,5 +16,10 @@ FactoryBot.define do
         allow(deployment.project.repository).to receive(:create_ref)
       end
     end
+
+    trait :review_app do
+      sha { TestEnv::BRANCH_SHA['pages-deploy'] }
+      ref 'pages-deploy'
+    end
   end
 end
diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb
index 8094c43b0650cc3acb3c5ad98ff1342a4ba85114..2392bfc4a53cc6c784cce5b13c823a03bc292308 100644
--- a/spec/factories/merge_requests.rb
+++ b/spec/factories/merge_requests.rb
@@ -101,6 +101,20 @@ FactoryBot.define do
       end
     end
 
+    trait :deployed_review_app do
+      target_branch 'pages-deploy-target'
+
+      transient do
+        deployment { create(:deployment, :review_app) }
+      end
+
+      after(:build) do |merge_request, evaluator|
+        merge_request.source_branch = evaluator.deployment.ref
+        merge_request.source_project = evaluator.deployment.project
+        merge_request.target_project = evaluator.deployment.project
+      end
+    end
+
     after(:build) do |merge_request|
       target_project = merge_request.target_project
       source_project = merge_request.source_project
diff --git a/spec/models/environment_status_spec.rb b/spec/models/environment_status_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0fbf949d38e647618397fdc23f954e39117cddb3
--- /dev/null
+++ b/spec/models/environment_status_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+
+describe EnvironmentStatus do
+  let(:deployment)    { create(:deployment, :review_app) }
+  let(:environment)   { deployment.environment}
+  let(:project)       { deployment.project }
+  let(:merge_request) { create(:merge_request, :deployed_review_app, deployment: deployment) }
+
+  subject(:environment_status) { described_class.new(environment, merge_request) }
+
+  it { is_expected.to delegate_method(:id).to(:environment) }
+  it { is_expected.to delegate_method(:name).to(:environment) }
+  it { is_expected.to delegate_method(:project).to(:environment) }
+  it { is_expected.to delegate_method(:deployed_at).to(:deployment).as(:created_at) }
+
+  describe '#project' do
+    subject { environment_status.project }
+
+    it { is_expected.to eq(project) }
+  end
+
+  describe '#merge_request' do
+    subject { environment_status.merge_request }
+
+    it { is_expected.to eq(merge_request) }
+  end
+
+  describe '#deployment' do
+    subject { environment_status.deployment }
+
+    it { is_expected.to eq(deployment) }
+  end
+end
diff --git a/spec/serializers/environment_status_entity_spec.rb b/spec/serializers/environment_status_entity_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..867ebecc77dcbfe76cf11ebf7aecd09809eda8cd
--- /dev/null
+++ b/spec/serializers/environment_status_entity_spec.rb
@@ -0,0 +1,40 @@
+require 'spec_helper'
+
+describe EnvironmentStatusEntity do
+  let(:user)    { create(:user) }
+  let(:request) { double('request') }
+
+  let(:deployment)    { create(:deployment, :review_app) }
+  let(:environment)   { deployment.environment}
+  let(:project)       { deployment.project }
+  let(:merge_request) { create(:merge_request, :deployed_review_app, deployment: deployment) }
+
+  let(:environment_status) { EnvironmentStatus.new(environment, merge_request) }
+  let(:entity)             { described_class.new(environment_status, request: request) }
+
+  subject { entity.as_json }
+
+  before do
+    allow(request).to receive(:current_user).and_return(user)
+  end
+
+  it { is_expected.to include(:id) }
+  it { is_expected.to include(:name) }
+  it { is_expected.to include(:url) }
+  it { is_expected.to include(:external_url) }
+  it { is_expected.to include(:external_url_formatted) }
+  it { is_expected.to include(:deployed_at) }
+  it { is_expected.to include(:deployed_at_formatted) }
+
+  it { is_expected.not_to include(:stop_url) }
+  it { is_expected.not_to include(:metrics_url) }
+  it { is_expected.not_to include(:metrics_monitoring_url) }
+
+  context 'when the user is project maintainer' do
+    before do
+      project.add_maintainer(user)
+    end
+
+    it { is_expected.to include(:stop_url) }
+  end
+end
diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb
index 97875669d0e966f5ec0316fef56e637ebfb58573..1a9aa2525116f7fa55ac0546c85b16a75a60827d 100644
--- a/spec/support/helpers/test_env.rb
+++ b/spec/support/helpers/test_env.rb
@@ -31,6 +31,8 @@ module TestEnv
     'symlink-expand-diff'                => '81e6355',
     'expand-collapse-files'              => '025db92',
     'expand-collapse-lines'              => '238e82d',
+    'pages-deploy'                       => '7897d5b',
+    'pages-deploy-target'                => '7975be0',
     'video'                              => '8879059',
     'add-balsamiq-file'                  => 'b89b56d',
     'crlf-diff'                          => '5938907',