diff --git a/app/assets/javascripts/pipelines/components/pipeline_details_header.vue b/app/assets/javascripts/pipelines/components/pipeline_details_header.vue
index e7cc55f95fac88ec70d6a384bf76ea19111fb814..7d4395dd579c13804e481c959f9ecb42587a7645 100644
--- a/app/assets/javascripts/pipelines/components/pipeline_details_header.vue
+++ b/app/assets/javascripts/pipelines/components/pipeline_details_header.vue
@@ -1,9 +1,12 @@
 <script>
-import { GlLoadingIcon } from '@gitlab/ui';
-import { __ } from '~/locale';
+import { GlBadge, GlIcon, GlLink, GlLoadingIcon, GlSprintf, GlTooltipDirective } from '@gitlab/ui';
+import { __, s__, sprintf } from '~/locale';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
 import CiBadgeLink from '~/vue_shared/components/ci_badge_link.vue';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import { LOAD_FAILURE, POST_FAILURE, DELETE_FAILURE, DEFAULT } from '../constants';
 import getPipelineQuery from '../graphql/queries/get_pipeline_header_data.query.graphql';
+import TimeAgo from './pipelines_list/time_ago.vue';
 import { getQueryHeaders } from './graph/utils';
 
 const POLL_INTERVAL = 10000;
@@ -13,7 +16,41 @@ export default {
   finishedStatuses: ['FAILED', 'SUCCESS', 'CANCELED'],
   components: {
     CiBadgeLink,
+    ClipboardButton,
+    GlBadge,
+    GlIcon,
+    GlLink,
     GlLoadingIcon,
+    GlSprintf,
+    TimeAgo,
+  },
+  directives: {
+    GlTooltip: GlTooltipDirective,
+    SafeHtml,
+  },
+  i18n: {
+    scheduleBadgeText: s__('Pipelines|Scheduled'),
+    scheduleBadgeTooltip: __('This pipeline was triggered by a schedule'),
+    childBadgeText: s__('Pipelines|Child pipeline (%{linkStart}parent%{linkEnd})'),
+    childBadgeTooltip: __('This is a child pipeline within the parent pipeline'),
+    latestBadgeText: s__('Pipelines|latest'),
+    latestBadgeTooltip: __('Latest pipeline for the most recent commit on this branch'),
+    mergeTrainBadgeText: s__('Pipelines|merge train'),
+    mergeTrainBadgeTooltip: s__(
+      'Pipelines|This pipeline ran on the contents of this merge request combined with the contents of all other merge requests queued for merging into the target branch.',
+    ),
+    invalidBadgeText: s__('Pipelines|yaml invalid'),
+    failedBadgeText: s__('Pipelines|error'),
+    autoDevopsBadgeText: s__('Pipelines|Auto DevOps'),
+    autoDevopsBadgeTooltip: __(
+      'This pipeline makes use of a predefined CI/CD configuration enabled by Auto DevOps.',
+    ),
+    detachedBadgeText: s__('Pipelines|merge request'),
+    detachedBadgeTooltip: s__(
+      "Pipelines|This pipeline ran on the contents of this merge request's source branch, not the target branch.",
+    ),
+    stuckBadgeText: s__('Pipelines|stuck'),
+    stuckBadgeTooltip: s__('Pipelines|This pipeline is stuck'),
   },
   errorTexts: {
     [LOAD_FAILURE]: __('We are currently unable to fetch data for the pipeline header.'),
@@ -58,6 +95,11 @@ export default {
       required: false,
       default: '',
     },
+    refText: {
+      type: String,
+      required: false,
+      default: '',
+    },
     badges: {
       type: Object,
       required: false,
@@ -76,7 +118,9 @@ export default {
           iid: this.pipelineIid,
         };
       },
-      update: (data) => data.project.pipeline,
+      update(data) {
+        return data.project.pipeline;
+      },
       error() {
         this.reportFailure(LOAD_FAILURE);
       },
@@ -150,6 +194,36 @@ export default {
           };
       }
     },
+    usersName() {
+      return this.pipeline?.user?.name || '';
+    },
+    userPath() {
+      return this.pipeline?.user?.webPath || '';
+    },
+    shortId() {
+      return this.pipeline?.commit?.shortId || '';
+    },
+    commitPath() {
+      return this.pipeline?.commit?.webPath || '';
+    },
+    totalJobsText() {
+      return sprintf(__('%{jobs} Jobs'), {
+        jobs: this.totalJobs,
+      });
+    },
+    triggeredText() {
+      return sprintf(__('%{linkStart}%{name}%{linkEnd} triggered pipeline for commit'), {
+        name: this.usersName,
+      });
+    },
+    inProgress() {
+      return this.status === 'RUNNING';
+    },
+    inProgressText() {
+      return sprintf(__('In progress, queued for %{queuedDuration} seconds'), {
+        queuedDuration: this.pipeline?.queuedDuration || 0,
+      });
+    },
   },
   methods: {
     reportFailure(errorType, errorMessages = []) {
@@ -164,8 +238,126 @@ export default {
   <div class="gl-mt-3">
     <gl-loading-icon v-if="loading" class="gl-text-left" size="lg" />
     <template v-else>
-      <div class="gl-mb-2">
+      <h3 v-if="name" class="gl-mt-0 gl-mb-2" data-testid="pipeline-name">{{ name }}</h3>
+      <div>
         <ci-badge-link :status="detailedStatus" />
+        <div class="gl-ml-2 gl-mb-2 gl-display-inline-block gl-h-6">
+          <gl-sprintf :message="triggeredText">
+            <template #link="{ content }">
+              <gl-link
+                :href="userPath"
+                class="gl-text-gray-900 gl-font-weight-bold"
+                target="_blank"
+              >
+                {{ content }}
+              </gl-link>
+            </template>
+          </gl-sprintf>
+          <gl-link
+            :href="commitPath"
+            class="gl-bg-blue-50 gl-rounded-base gl-px-2 gl-mx-2"
+            data-testid="commit-link"
+          >
+            {{ shortId }}
+          </gl-link>
+          <clipboard-button
+            :text="shortId"
+            category="tertiary"
+            :title="__('Copy commit SHA')"
+            size="small"
+          />
+          <time-ago
+            v-if="isFinished"
+            :pipeline="pipeline"
+            class="gl-display-inline gl-mb-0"
+            :display-calendar-icon="false"
+            font-size="gl-font-md"
+          />
+        </div>
+      </div>
+      <div v-safe-html="refText" class="gl-mb-2" data-testid="pipeline-ref-text"></div>
+      <div>
+        <gl-badge
+          v-if="badges.schedule"
+          v-gl-tooltip
+          :title="$options.i18n.scheduleBadgeTooltip"
+          variant="info"
+        >
+          {{ $options.i18n.scheduleBadgeText }}
+        </gl-badge>
+        <gl-badge
+          v-if="badges.child"
+          v-gl-tooltip
+          :title="$options.i18n.childBadgeTooltip"
+          variant="info"
+        >
+          <gl-sprintf :message="$options.i18n.childBadgeText">
+            <template #link="{ content }">
+              <gl-link :href="paths.triggeredByPath" target="_blank">
+                {{ content }}
+              </gl-link>
+            </template>
+          </gl-sprintf>
+        </gl-badge>
+        <gl-badge
+          v-if="badges.latest"
+          v-gl-tooltip
+          :title="$options.i18n.latestBadgeTooltip"
+          variant="success"
+        >
+          {{ $options.i18n.latestBadgeText }}
+        </gl-badge>
+        <gl-badge
+          v-if="badges.mergeTrainPipeline"
+          v-gl-tooltip
+          :title="$options.i18n.mergeTrainBadgeTooltip"
+          variant="info"
+        >
+          {{ $options.i18n.mergeTrainBadgeText }}
+        </gl-badge>
+        <gl-badge v-if="badges.invalid" v-gl-tooltip :title="yamlErrors" variant="danger">
+          {{ $options.i18n.invalidBadgeText }}
+        </gl-badge>
+        <gl-badge v-if="badges.failed" v-gl-tooltip :title="failureReason" variant="danger">
+          {{ $options.i18n.failedBadgeText }}
+        </gl-badge>
+        <gl-badge
+          v-if="badges.autoDevops"
+          v-gl-tooltip
+          :title="$options.i18n.autoDevopsBadgeTooltip"
+          variant="info"
+        >
+          {{ $options.i18n.autoDevopsBadgeText }}
+        </gl-badge>
+        <gl-badge
+          v-if="badges.detached"
+          v-gl-tooltip
+          :title="$options.i18n.detachedBadgeTooltip"
+          variant="info"
+          data-qa-selector="merge_request_badge_tag"
+        >
+          {{ $options.i18n.detachedBadgeText }}
+        </gl-badge>
+        <gl-badge
+          v-if="badges.stuck"
+          v-gl-tooltip
+          :title="$options.i18n.stuckBadgeTooltip"
+          variant="warning"
+        >
+          {{ $options.i18n.stuckBadgeText }}
+        </gl-badge>
+        <span class="gl-ml-2" data-testid="total-jobs">
+          <gl-icon name="pipeline" />
+          {{ totalJobsText }}
+        </span>
+        <span v-if="isFinished" class="gl-ml-2" data-testid="compute-credits">
+          <gl-icon name="quota" />
+          {{ computeCredits }}
+        </span>
+        <span v-if="inProgress" class="gl-ml-2" data-testid="pipeline-running-text">
+          <gl-icon name="timer" />
+          {{ inProgressText }}
+        </span>
       </div>
     </template>
   </div>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue b/app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue
index d068eb16ed45a49fe441a46001ce60dd0544177f..b7c812162b18669fe171dd02eb952b8e22bc1ba2 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue
@@ -14,6 +14,17 @@ export default {
       type: Object,
       required: true,
     },
+    displayCalendarIcon: {
+      type: Boolean,
+      required: false,
+      default: true,
+    },
+    fontSize: {
+      type: String,
+      required: false,
+      default: 'gl-font-sm',
+      validator: (fontSize) => ['gl-font-sm', 'gl-font-md'].includes(fontSize),
+    },
   },
   computed: {
     duration() {
@@ -23,7 +34,7 @@ export default {
       return formatTime(this.duration * 1000);
     },
     finishedTime() {
-      return this.pipeline?.details?.finished_at;
+      return this.pipeline?.details?.finished_at || this.pipeline?.finishedAt;
     },
     showInProgress() {
       return !this.duration && !this.finishedTime && !this.skipped;
@@ -35,13 +46,13 @@ export default {
       return this.pipeline?.details?.status?.label === 'skipped';
     },
     stuck() {
-      return this.pipeline.flags.stuck;
+      return this.pipeline?.flags?.stuck;
     },
   },
 };
 </script>
 <template>
-  <div class="gl-display-flex gl-flex-direction-column gl-font-sm time-ago">
+  <div class="gl-display-flex gl-flex-direction-column time-ago" :class="fontSize">
     <span
       v-if="showInProgress"
       class="gl-display-inline-flex gl-align-items-center"
@@ -63,7 +74,13 @@ export default {
     </p>
 
     <p v-if="finishedTime" class="finished-at gl-display-inline-flex gl-align-items-center">
-      <gl-icon name="calendar" class="gl-mr-2" :size="12" />
+      <gl-icon
+        v-if="displayCalendarIcon"
+        name="calendar"
+        class="gl-mr-2"
+        :size="12"
+        data-testid="calendar-icon"
+      />
 
       <time
         v-gl-tooltip
diff --git a/app/assets/javascripts/pipelines/graphql/queries/get_pipeline_header_data.query.graphql b/app/assets/javascripts/pipelines/graphql/queries/get_pipeline_header_data.query.graphql
index 47bc167ca525c87eccff77c265c2c2483c48ba5e..a47df2c0197ddea5f666f099d80133e41034dade 100644
--- a/app/assets/javascripts/pipelines/graphql/queries/get_pipeline_header_data.query.graphql
+++ b/app/assets/javascripts/pipelines/graphql/queries/get_pipeline_header_data.query.graphql
@@ -32,6 +32,13 @@ query getPipelineHeaderData($fullPath: ID!, $iid: ID!) {
           emoji
         }
       }
+      commit {
+        id
+        shortId
+        webPath
+      }
+      finishedAt
+      queuedDuration
     }
   }
 }
diff --git a/app/assets/javascripts/pipelines/pipeline_details_header.js b/app/assets/javascripts/pipelines/pipeline_details_header.js
index e3e95ba3ccce1eb82016c8b82a07007ef523dd99..807ef225eddc35d12c8f1e893cad57526274bab1 100644
--- a/app/assets/javascripts/pipelines/pipeline_details_header.js
+++ b/app/assets/javascripts/pipelines/pipeline_details_header.js
@@ -62,6 +62,7 @@ export const createPipelineDetailsHeaderApp = (elSelector, apolloProvider, graph
     autoDevops,
     detached,
     stuck,
+    refText,
   } = el.dataset;
 
   // eslint-disable-next-line no-new
@@ -86,6 +87,7 @@ export const createPipelineDetailsHeaderApp = (elSelector, apolloProvider, graph
           computeCredits,
           yamlErrors,
           failureReason,
+          refText,
           badges: {
             schedule: parseBoolean(schedule),
             child: parseBoolean(child),
diff --git a/app/helpers/projects/pipeline_helper.rb b/app/helpers/projects/pipeline_helper.rb
index 8a65340062e10b736593ade70b73b582f11f98b0..caebbd5250e5f7174eb764be675f6952107779b6 100644
--- a/app/helpers/projects/pipeline_helper.rb
+++ b/app/helpers/projects/pipeline_helper.rb
@@ -44,7 +44,8 @@ def js_pipeline_details_header_data(project, pipeline)
         failed: pipeline.failure_reason?.to_s,
         auto_devops: pipeline.auto_devops_source?.to_s,
         detached: pipeline.detached_merge_request_pipeline?.to_s,
-        stuck: pipeline.stuck?
+        stuck: pipeline.stuck?,
+        ref_text: pipeline.ref_text
       }
     end
   end
diff --git a/app/presenters/ci/pipeline_presenter.rb b/app/presenters/ci/pipeline_presenter.rb
index 8c9ff49b0e7b55806b32ff6a33710777e46aea42..4ad88188e45688aba3e3460add8b545985fec300 100644
--- a/app/presenters/ci/pipeline_presenter.rb
+++ b/app/presenters/ci/pipeline_presenter.rb
@@ -65,7 +65,7 @@ def coverage
       '%.2f' % pipeline.coverage
     end
 
-    def ref_text
+    def ref_text_legacy
       if pipeline.detached_merge_request_pipeline?
         _("for %{link_to_merge_request} with %{link_to_merge_request_source_branch}")
           .html_safe % {
@@ -87,6 +87,28 @@ def ref_text
       end
     end
 
+    def ref_text
+      if pipeline.detached_merge_request_pipeline?
+        _("For merge request %{link_to_merge_request} to merge %{link_to_merge_request_source_branch}")
+          .html_safe % {
+            link_to_merge_request: link_to_merge_request,
+            link_to_merge_request_source_branch: link_to_merge_request_source_branch
+          }
+      elsif pipeline.merged_result_pipeline?
+        _("For merge request %{link_to_merge_request} to merge %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}")
+          .html_safe % {
+            link_to_merge_request: link_to_merge_request,
+            link_to_merge_request_source_branch: link_to_merge_request_source_branch,
+            link_to_merge_request_target_branch: link_to_merge_request_target_branch
+          }
+      elsif pipeline.ref && pipeline.ref_exists?
+        _("For %{link_to_pipeline_ref}")
+        .html_safe % { link_to_pipeline_ref: link_to_pipeline_ref }
+      elsif pipeline.ref
+        _("For %{ref}").html_safe % { ref: plain_ref_name }
+      end
+    end
+
     def all_related_merge_request_text(limit: nil)
       if all_related_merge_requests.none?
         _("No related merge requests found.")
@@ -106,7 +128,7 @@ def has_many_merge_requests?
     def link_to_pipeline_ref
       ApplicationController.helpers.link_to(pipeline.ref,
         project_commits_path(pipeline.project, pipeline.ref),
-        class: "ref-name")
+        class: "ref-name gl-link gl-bg-blue-50 gl-rounded-base gl-px-2")
     end
 
     def link_to_merge_request
diff --git a/app/presenters/merge_request_presenter.rb b/app/presenters/merge_request_presenter.rb
index 12f4b0496e48fb0eafe2cd00b9acf33cb33b04fc..8d2baa6ee99b8ecb8b8540e1aa258adb3cb46bce 100644
--- a/app/presenters/merge_request_presenter.rb
+++ b/app/presenters/merge_request_presenter.rb
@@ -197,7 +197,7 @@ def subscribed?
 
   def source_branch_link
     if source_branch_exists?
-      link_to(source_branch, source_branch_commits_path, class: 'ref-name')
+      link_to(source_branch, source_branch_commits_path, class: 'ref-name gl-link gl-bg-blue-50 gl-rounded-base gl-px-2')
     else
       content_tag(:span, source_branch, class: 'ref-name')
     end
@@ -205,7 +205,7 @@ def source_branch_link
 
   def target_branch_link
     if target_branch_exists?
-      link_to(target_branch, target_branch_commits_path, class: 'ref-name')
+      link_to(target_branch, target_branch_commits_path, class: 'ref-name gl-link gl-bg-blue-50 gl-rounded-base gl-px-2')
     else
       content_tag(:span, target_branch, class: 'ref-name')
     end
diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml
index 3ff370dfaa4f9cd016c7aaa5927b5fbab2451612..753bb77e7552ae31dfb930c23240077d90d93dc1 100644
--- a/app/views/projects/pipelines/_info.html.haml
+++ b/app/views/projects/pipelines/_info.html.haml
@@ -16,7 +16,7 @@
       .icon-container
         = sprite_icon('clock', css_class: 'gl-top-0!')
       = n_('%d job', '%d jobs', @pipeline.total_size) % @pipeline.total_size
-      = @pipeline.ref_text
+      = @pipeline.ref_text_legacy
     - if @pipeline.finished_at
       - duration = time_interval_in_words(@pipeline.duration)
       - queued_duration = time_interval_in_words(@pipeline.queued_duration)
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index b5e6e8e2cdeaf94692cca4904d8648e3731e0065..0b33fa34a11a0c401bcad3afdeadfbdd105bd311 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -789,6 +789,9 @@ msgstr ""
 msgid "%{itemsCount} issues with a limit of %{maxIssueCount}"
 msgstr ""
 
+msgid "%{jobs} Jobs"
+msgstr ""
+
 msgid "%{key} is not a valid URL."
 msgstr ""
 
@@ -870,6 +873,9 @@ msgstr ""
 msgid "%{linkStart} Learn more%{linkEnd}."
 msgstr ""
 
+msgid "%{linkStart}%{name}%{linkEnd} triggered pipeline for commit"
+msgstr ""
+
 msgid "%{listToShow}, and %{awardsListLength} more"
 msgstr ""
 
@@ -19070,6 +19076,12 @@ msgstr ""
 msgid "Footer message"
 msgstr ""
 
+msgid "For %{link_to_pipeline_ref}"
+msgstr ""
+
+msgid "For %{ref}"
+msgstr ""
+
 msgid "For a faster browsing experience, only %{strongStart}%{visible} of %{total}%{strongEnd} files are shown. Download one of the files below to see all changes."
 msgstr ""
 
@@ -19106,6 +19118,12 @@ msgstr ""
 msgid "For investigating IT service disruptions or outages"
 msgstr ""
 
+msgid "For merge request %{link_to_merge_request} to merge %{link_to_merge_request_source_branch}"
+msgstr ""
+
+msgid "For merge request %{link_to_merge_request} to merge %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
+msgstr ""
+
 msgid "For more info, read the documentation."
 msgstr ""
 
@@ -22972,6 +22990,9 @@ msgstr ""
 msgid "In progress"
 msgstr ""
 
+msgid "In progress, queued for %{queuedDuration} seconds"
+msgstr ""
+
 msgid "In the background, we're attempting to connect you again."
 msgstr ""
 
@@ -33169,6 +33190,9 @@ msgstr ""
 msgid "Pipelines|CI/CD Catalog"
 msgstr ""
 
+msgid "Pipelines|Child pipeline (%{linkStart}parent%{linkEnd})"
+msgstr ""
+
 msgid "Pipelines|Child pipeline (%{link_start}parent%{link_end})"
 msgstr ""
 
@@ -33286,6 +33310,9 @@ msgstr ""
 msgid "Pipelines|Revoke trigger"
 msgstr ""
 
+msgid "Pipelines|Scheduled"
+msgstr ""
+
 msgid "Pipelines|Set up a runner"
 msgstr ""
 
@@ -33334,6 +33361,9 @@ msgstr ""
 msgid "Pipelines|This is a child pipeline within the parent pipeline"
 msgstr ""
 
+msgid "Pipelines|This pipeline is stuck"
+msgstr ""
+
 msgid "Pipelines|This pipeline ran on the contents of this merge request combined with the contents of all other merge requests queued for merging into the target branch."
 msgstr ""
 
@@ -46262,6 +46292,9 @@ msgstr ""
 msgid "This is a Jira user."
 msgstr ""
 
+msgid "This is a child pipeline within the parent pipeline"
+msgstr ""
+
 msgid "This is a confidential %{noteableTypeText}."
 msgstr ""
 
@@ -46466,6 +46499,12 @@ msgstr ""
 msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
 msgstr ""
 
+msgid "This pipeline makes use of a predefined CI/CD configuration enabled by Auto DevOps."
+msgstr ""
+
+msgid "This pipeline was triggered by a schedule"
+msgstr ""
+
 msgid "This pipeline was triggered by a schedule."
 msgstr ""
 
diff --git a/spec/frontend/fixtures/pipeline_header.rb b/spec/frontend/fixtures/pipeline_header.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a4fba7e8675b9a4f8ce707b1ba292f6c098a42be
--- /dev/null
+++ b/spec/frontend/fixtures/pipeline_header.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe "GraphQL Pipeline Header", '(JavaScript fixtures)', type: :request, feature_category: :pipeline_composition do
+  include ApiHelpers
+  include GraphqlHelpers
+  include JavaScriptFixturesHelpers
+
+  let_it_be(:namespace) { create(:namespace, name: 'frontend-fixtures') }
+  let_it_be(:project) { create(:project, :public, :repository) }
+  let_it_be(:user) { project.first_owner }
+  let_it_be(:commit) { create(:commit, project: project) }
+
+  let(:query_path) { 'pipelines/graphql/queries/get_pipeline_header_data.query.graphql' }
+
+  context 'with successful pipeline' do
+    let_it_be(:pipeline) do
+      create(
+        :ci_pipeline,
+        project: project,
+        sha: commit.id,
+        ref: 'master',
+        user: user,
+        status: :success,
+        started_at: 1.hour.ago,
+        finished_at: Time.current
+      )
+    end
+
+    it "graphql/pipelines/pipeline_header_success.json" do
+      query = get_graphql_query_as_string(query_path)
+
+      post_graphql(query, current_user: user, variables: { fullPath: project.full_path, iid: pipeline.iid })
+
+      expect_graphql_errors_to_be_empty
+    end
+  end
+
+  context 'with running pipeline' do
+    let_it_be(:pipeline) do
+      create(
+        :ci_pipeline,
+        project: project,
+        sha: commit.id,
+        ref: 'master',
+        user: user,
+        status: :running,
+        created_at: 2.hours.ago,
+        started_at: 1.hour.ago
+      )
+    end
+
+    it "graphql/pipelines/pipeline_header_running.json" do
+      query = get_graphql_query_as_string(query_path)
+
+      post_graphql(query, current_user: user, variables: { fullPath: project.full_path, iid: pipeline.iid })
+
+      expect_graphql_errors_to_be_empty
+    end
+  end
+end
diff --git a/spec/frontend/pipelines/mock_data.js b/spec/frontend/pipelines/mock_data.js
index a4b8d223a0c9d9c15ce883bae4732581fbd0e8b9..fd654eb6f106ee174eb7d53dfd4fe32efece8e0b 100644
--- a/spec/frontend/pipelines/mock_data.js
+++ b/spec/frontend/pipelines/mock_data.js
@@ -1,3 +1,6 @@
+import pipelineHeaderSuccess from 'test_fixtures/graphql/pipelines/pipeline_header_success.json';
+import pipelineHeaderRunning from 'test_fixtures/graphql/pipelines/pipeline_header_running.json';
+
 const PIPELINE_RUNNING = 'RUNNING';
 const PIPELINE_CANCELED = 'CANCELED';
 const PIPELINE_FAILED = 'FAILED';
@@ -5,6 +8,8 @@ const PIPELINE_FAILED = 'FAILED';
 const threeWeeksAgo = new Date();
 threeWeeksAgo.setDate(threeWeeksAgo.getDate() - 21);
 
+export { pipelineHeaderSuccess, pipelineHeaderRunning };
+
 export const mockPipelineHeader = {
   detailedStatus: {},
   id: 123,
diff --git a/spec/frontend/pipelines/pipeline_details_header_spec.js b/spec/frontend/pipelines/pipeline_details_header_spec.js
index c049dad9e539110c17c9268d799f15bab1d0323c..08ae35fe808ca926d00f1392cf651f0c7082f943 100644
--- a/spec/frontend/pipelines/pipeline_details_header_spec.js
+++ b/spec/frontend/pipelines/pipeline_details_header_spec.js
@@ -1,44 +1,78 @@
-import { GlLoadingIcon } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
+import { GlBadge, GlLoadingIcon } from '@gitlab/ui';
 import Vue from 'vue';
 import VueApollo from 'vue-apollo';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
 import createMockApollo from 'helpers/mock_apollo_helper';
 import waitForPromises from 'helpers/wait_for_promises';
 import PipelineDetailsHeader from '~/pipelines/components/pipeline_details_header.vue';
+import TimeAgo from '~/pipelines/components/pipelines_list/time_ago.vue';
 import CiBadgeLink from '~/vue_shared/components/ci_badge_link.vue';
 import getPipelineDetailsQuery from '~/pipelines/graphql/queries/get_pipeline_header_data.query.graphql';
-import { mockSuccessfulPipelineHeader } from './mock_data';
+import { pipelineHeaderSuccess, pipelineHeaderRunning } from './mock_data';
 
 Vue.use(VueApollo);
 
 describe('Pipeline details header', () => {
   let wrapper;
 
-  const successHandler = jest.fn().mockResolvedValue(mockSuccessfulPipelineHeader);
+  const successHandler = jest.fn().mockResolvedValue(pipelineHeaderSuccess);
+  const runningHandler = jest.fn().mockResolvedValue(pipelineHeaderRunning);
 
   const findStatus = () => wrapper.findComponent(CiBadgeLink);
   const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
+  const findTimeAgo = () => wrapper.findComponent(TimeAgo);
+  const findAllBadges = () => wrapper.findAllComponents(GlBadge);
+  const findPipelineName = () => wrapper.findByTestId('pipeline-name');
+  const findTotalJobs = () => wrapper.findByTestId('total-jobs');
+  const findComputeCredits = () => wrapper.findByTestId('compute-credits');
+  const findCommitLink = () => wrapper.findByTestId('commit-link');
+  const findPipelineRunningText = () => wrapper.findByTestId('pipeline-running-text').text();
+  const findPipelineRefText = () => wrapper.findByTestId('pipeline-ref-text').text();
 
   const defaultHandlers = [[getPipelineDetailsQuery, successHandler]];
 
   const defaultProvideOptions = {
-    pipelineId: '14',
     pipelineIid: 1,
     paths: {
       pipelinesPath: '/namespace/my-project/-/pipelines',
       fullProject: '/namespace/my-project',
+      triggeredByPath: '',
     },
   };
 
+  const defaultProps = {
+    name: 'Ruby 3.0 master branch pipeline',
+    totalJobs: '50',
+    computeCredits: '0.65',
+    yamlErrors: 'errors',
+    failureReason: 'pipeline failed',
+    badges: {
+      schedule: true,
+      child: false,
+      latest: true,
+      mergeTrainPipeline: false,
+      invalid: false,
+      failed: false,
+      autoDevops: false,
+      detached: false,
+      stuck: false,
+    },
+    refText:
+      'For merge request <a class="mr-iid" href="/root/ci-project/-/merge_requests/1">!1</a> to merge <a class="ref-name" href="/root/ci-project/-/commits/test">test</a>',
+  };
+
   const createMockApolloProvider = (handlers) => {
     return createMockApollo(handlers);
   };
 
-  const createComponent = (handlers = defaultHandlers) => {
-    wrapper = shallowMount(PipelineDetailsHeader, {
+  const createComponent = (handlers = defaultHandlers, props = defaultProps) => {
+    wrapper = shallowMountExtended(PipelineDetailsHeader, {
       provide: {
         ...defaultProvideOptions,
       },
+      propsData: {
+        ...props,
+      },
       apolloProvider: createMockApolloProvider(handlers),
     });
   };
@@ -51,21 +85,83 @@ describe('Pipeline details header', () => {
     });
   });
 
-  describe('loaded state', () => {
-    it('does not display loading icon', async () => {
+  describe('defaults', () => {
+    beforeEach(async () => {
       createComponent();
 
       await waitForPromises();
+    });
 
+    it('does not display loading icon', () => {
       expect(findLoadingIcon().exists()).toBe(false);
     });
 
-    it('displays pipeline status', async () => {
+    it('displays pipeline status', () => {
+      expect(findStatus().exists()).toBe(true);
+    });
+
+    it('displays pipeline name', () => {
+      expect(findPipelineName().text()).toBe(defaultProps.name);
+    });
+
+    it('displays total jobs', () => {
+      expect(findTotalJobs().text()).toBe('50 Jobs');
+    });
+
+    it('has link to commit', () => {
+      const {
+        data: {
+          project: { pipeline },
+        },
+      } = pipelineHeaderSuccess;
+
+      expect(findCommitLink().attributes('href')).toBe(pipeline.commit.webPath);
+    });
+
+    it('displays correct badges', () => {
+      expect(findAllBadges()).toHaveLength(2);
+      expect(wrapper.findByText('latest').exists()).toBe(true);
+      expect(wrapper.findByText('Scheduled').exists()).toBe(true);
+    });
+
+    it('displays ref text', () => {
+      expect(findPipelineRefText()).toBe('For merge request !1 to merge test');
+    });
+  });
+
+  describe('finished pipeline', () => {
+    beforeEach(async () => {
       createComponent();
 
       await waitForPromises();
+    });
 
-      expect(findStatus().exists()).toBe(true);
+    it('displays compute credits', () => {
+      expect(findComputeCredits().text()).toBe('0.65');
+    });
+
+    it('displays time ago', () => {
+      expect(findTimeAgo().exists()).toBe(true);
+    });
+  });
+
+  describe('running pipeline', () => {
+    beforeEach(async () => {
+      createComponent([[getPipelineDetailsQuery, runningHandler]]);
+
+      await waitForPromises();
+    });
+
+    it('does not display compute credits', () => {
+      expect(findComputeCredits().exists()).toBe(false);
+    });
+
+    it('does not display time ago', () => {
+      expect(findTimeAgo().exists()).toBe(false);
+    });
+
+    it('displays pipeline running text', () => {
+      expect(findPipelineRunningText()).toBe('In progress, queued for 3600 seconds');
     });
   });
 });
diff --git a/spec/frontend/pipelines/time_ago_spec.js b/spec/frontend/pipelines/time_ago_spec.js
index efb1bf09d2030038d701c3ac734d591b1555d0f2..ccc07d9b1a897728c789bc8cebe787e9d5a4294b 100644
--- a/spec/frontend/pipelines/time_ago_spec.js
+++ b/spec/frontend/pipelines/time_ago_spec.js
@@ -8,7 +8,7 @@ describe('Timeago component', () => {
 
   const defaultProps = { duration: 0, finished_at: '' };
 
-  const createComponent = (props = defaultProps, stuck = false) => {
+  const createComponent = (props = defaultProps, stuck = false, extraProps) => {
     wrapper = extendedWrapper(
       shallowMount(TimeAgo, {
         propsData: {
@@ -20,6 +20,7 @@ describe('Timeago component', () => {
               stuck,
             },
           },
+          ...extraProps,
         },
         data() {
           return {
@@ -36,6 +37,7 @@ describe('Timeago component', () => {
   const findSkipped = () => wrapper.findByTestId('pipeline-skipped');
   const findHourGlassIcon = () => wrapper.findByTestId('hourglass-icon');
   const findWarningIcon = () => wrapper.findByTestId('warning-icon');
+  const findCalendarIcon = () => wrapper.findByTestId('calendar-icon');
 
   describe('with duration', () => {
     beforeEach(() => {
@@ -61,18 +63,28 @@ describe('Timeago component', () => {
   });
 
   describe('with finishedTime', () => {
-    beforeEach(() => {
+    it('should render time', () => {
       createComponent({ duration: 0, finished_at: '2017-04-26T12:40:23.277Z' });
-    });
 
-    it('should render time and calendar icon', () => {
-      const icon = finishedAt().findComponent(GlIcon);
       const time = finishedAt().find('time');
 
       expect(finishedAt().exists()).toBe(true);
-      expect(icon.props('name')).toBe('calendar');
       expect(time.exists()).toBe(true);
     });
+
+    it('should display calendar icon by default', () => {
+      createComponent({ duration: 0, finished_at: '2017-04-26T12:40:23.277Z' });
+
+      expect(findCalendarIcon().exists()).toBe(true);
+    });
+
+    it('should hide calendar icon if correct prop is passed', () => {
+      createComponent({ duration: 0, finished_at: '2017-04-26T12:40:23.277Z' }, false, {
+        displayCalendarIcon: false,
+      });
+
+      expect(findCalendarIcon().exists()).toBe(false);
+    });
   });
 
   describe('without finishedTime', () => {
@@ -82,6 +94,7 @@ describe('Timeago component', () => {
 
     it('should not render time and calendar icon', () => {
       expect(finishedAt().exists()).toBe(false);
+      expect(findCalendarIcon().exists()).toBe(false);
     });
   });
 
diff --git a/spec/helpers/projects/pipeline_helper_spec.rb b/spec/helpers/projects/pipeline_helper_spec.rb
index e9b2738cfad5169d6b6a0f7b705c807677262762..a69da91599021d18af4b1d3b1ea66e9e09af4b92 100644
--- a/spec/helpers/projects/pipeline_helper_spec.rb
+++ b/spec/helpers/projects/pipeline_helper_spec.rb
@@ -61,7 +61,8 @@
         failed: pipeline.failure_reason?.to_s,
         auto_devops: pipeline.auto_devops_source?.to_s,
         detached: pipeline.detached_merge_request_pipeline?.to_s,
-        stuck: pipeline.stuck?
+        stuck: pipeline.stuck?,
+        ref_text: pipeline.ref_text
       })
     end
   end
diff --git a/spec/presenters/ci/pipeline_presenter_spec.rb b/spec/presenters/ci/pipeline_presenter_spec.rb
index 7f4c8120e1721c5cbb9f77b957ace80edf6a2019..86e4bb703dca2473b1de26cf8de08d188b4a0051 100644
--- a/spec/presenters/ci/pipeline_presenter_spec.rb
+++ b/spec/presenters/ci/pipeline_presenter_spec.rb
@@ -146,8 +146,8 @@
     end
   end
 
-  describe '#ref_text' do
-    subject { presenter.ref_text }
+  describe '#ref_text_legacy' do
+    subject { presenter.ref_text_legacy }
 
     context 'when pipeline is detached merge request pipeline' do
       let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) }
@@ -155,7 +155,7 @@
 
       it 'returns a correct ref text' do
         is_expected.to eq("for <a class=\"mr-iid\" href=\"#{project_merge_request_path(merge_request.project, merge_request)}\">#{merge_request.to_reference}</a> " \
-                          "with <a class=\"ref-name\" href=\"#{project_commits_path(merge_request.source_project, merge_request.source_branch)}\">#{merge_request.source_branch}</a>")
+                          "with <a class=\"ref-name gl-link gl-bg-blue-50 gl-rounded-base gl-px-2\" href=\"#{project_commits_path(merge_request.source_project, merge_request.source_branch)}\">#{merge_request.source_branch}</a>")
       end
     end
 
@@ -165,8 +165,8 @@
 
       it 'returns a correct ref text' do
         is_expected.to eq("for <a class=\"mr-iid\" href=\"#{project_merge_request_path(merge_request.project, merge_request)}\">#{merge_request.to_reference}</a> " \
-                          "with <a class=\"ref-name\" href=\"#{project_commits_path(merge_request.source_project, merge_request.source_branch)}\">#{merge_request.source_branch}</a> " \
-                          "into <a class=\"ref-name\" href=\"#{project_commits_path(merge_request.target_project, merge_request.target_branch)}\">#{merge_request.target_branch}</a>")
+                          "with <a class=\"ref-name gl-link gl-bg-blue-50 gl-rounded-base gl-px-2\" href=\"#{project_commits_path(merge_request.source_project, merge_request.source_branch)}\">#{merge_request.source_branch}</a> " \
+                          "into <a class=\"ref-name gl-link gl-bg-blue-50 gl-rounded-base gl-px-2\" href=\"#{project_commits_path(merge_request.target_project, merge_request.target_branch)}\">#{merge_request.target_branch}</a>")
       end
     end
 
@@ -177,7 +177,7 @@
         end
 
         it 'returns a correct ref text' do
-          is_expected.to eq("for <a class=\"ref-name\" href=\"#{project_commits_path(pipeline.project, pipeline.ref)}\">#{pipeline.ref}</a>")
+          is_expected.to eq("for <a class=\"ref-name gl-link gl-bg-blue-50 gl-rounded-base gl-px-2\" href=\"#{project_commits_path(pipeline.project, pipeline.ref)}\">#{pipeline.ref}</a>")
         end
 
         context 'when ref contains malicious script' do
@@ -209,6 +209,69 @@
     end
   end
 
+  describe '#ref_text' do
+    subject { presenter.ref_text }
+
+    context 'when pipeline is detached merge request pipeline' do
+      let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) }
+      let(:pipeline) { merge_request.all_pipelines.last }
+
+      it 'returns a correct ref text' do
+        is_expected.to eq("For merge request <a class=\"mr-iid\" href=\"#{project_merge_request_path(merge_request.project, merge_request)}\">#{merge_request.to_reference}</a> " \
+                          "to merge <a class=\"ref-name gl-link gl-bg-blue-50 gl-rounded-base gl-px-2\" href=\"#{project_commits_path(merge_request.source_project, merge_request.source_branch)}\">#{merge_request.source_branch}</a>")
+      end
+    end
+
+    context 'when pipeline is merge request pipeline' do
+      let(:merge_request) { create(:merge_request, :with_merge_request_pipeline) }
+      let(:pipeline) { merge_request.all_pipelines.last }
+
+      it 'returns a correct ref text' do
+        is_expected.to eq("For merge request <a class=\"mr-iid\" href=\"#{project_merge_request_path(merge_request.project, merge_request)}\">#{merge_request.to_reference}</a> " \
+                          "to merge <a class=\"ref-name gl-link gl-bg-blue-50 gl-rounded-base gl-px-2\" href=\"#{project_commits_path(merge_request.source_project, merge_request.source_branch)}\">#{merge_request.source_branch}</a> " \
+                          "into <a class=\"ref-name gl-link gl-bg-blue-50 gl-rounded-base gl-px-2\" href=\"#{project_commits_path(merge_request.target_project, merge_request.target_branch)}\">#{merge_request.target_branch}</a>")
+      end
+    end
+
+    context 'when pipeline is branch pipeline' do
+      context 'when ref exists in the repository' do
+        before do
+          allow(pipeline).to receive(:ref_exists?) { true }
+        end
+
+        it 'returns a correct ref text' do
+          is_expected.to eq("For <a class=\"ref-name gl-link gl-bg-blue-50 gl-rounded-base gl-px-2\" href=\"#{project_commits_path(pipeline.project, pipeline.ref)}\">#{pipeline.ref}</a>")
+        end
+
+        context 'when ref contains malicious script' do
+          let(:pipeline) { create(:ci_pipeline, ref: "<script>alter('1')</script>", project: project) }
+
+          it 'does not include the malicious script' do
+            is_expected.not_to include("<script>alter('1')</script>")
+          end
+        end
+      end
+
+      context 'when ref does not exist in the repository' do
+        before do
+          allow(pipeline).to receive(:ref_exists?) { false }
+        end
+
+        it 'returns a correct ref text' do
+          is_expected.to eq("For <span class=\"ref-name\">#{pipeline.ref}</span>")
+        end
+
+        context 'when ref contains malicious script' do
+          let(:pipeline) { create(:ci_pipeline, ref: "<script>alter('1')</script>", project: project) }
+
+          it 'does not include the malicious script' do
+            is_expected.not_to include("<script>alter('1')</script>")
+          end
+        end
+      end
+    end
+  end
+
   describe '#all_related_merge_request_text' do
     subject { presenter.all_related_merge_request_text }
 
diff --git a/spec/presenters/merge_request_presenter_spec.rb b/spec/presenters/merge_request_presenter_spec.rb
index 6f40d3f5b48a0a48f5c258af0d81cd53b580b825..d0febf640351cb0cdf149045e060132fb49e10d2 100644
--- a/spec/presenters/merge_request_presenter_spec.rb
+++ b/spec/presenters/merge_request_presenter_spec.rb
@@ -474,7 +474,7 @@
         allow(resource).to receive(:source_branch_exists?) { true }
 
         is_expected
-          .to eq("<a class=\"ref-name\" href=\"#{presenter.source_branch_commits_path}\">#{presenter.source_branch}</a>")
+          .to eq("<a class=\"ref-name gl-link gl-bg-blue-50 gl-rounded-base gl-px-2\" href=\"#{presenter.source_branch_commits_path}\">#{presenter.source_branch}</a>")
       end
     end
 
@@ -497,7 +497,7 @@
         allow(resource).to receive(:target_branch_exists?) { true }
 
         is_expected
-          .to eq("<a class=\"ref-name\" href=\"#{presenter.target_branch_commits_path}\">#{presenter.target_branch}</a>")
+          .to eq("<a class=\"ref-name gl-link gl-bg-blue-50 gl-rounded-base gl-px-2\" href=\"#{presenter.target_branch_commits_path}\">#{presenter.target_branch}</a>")
       end
     end