diff --git a/app/assets/javascripts/pipeline_editor/components/header/pipeline_editor_mini_graph.vue b/app/assets/javascripts/pipeline_editor/components/header/pipeline_editor_mini_graph.vue
index 7beabcfe4035dfc63351f9795c37fd15b1436f77..3662100d5266c5ce8c3c932105ae02ace15788ca 100644
--- a/app/assets/javascripts/pipeline_editor/components/header/pipeline_editor_mini_graph.vue
+++ b/app/assets/javascripts/pipeline_editor/components/header/pipeline_editor_mini_graph.vue
@@ -10,8 +10,6 @@ export default {
   },
   components: {
     PipelineMiniGraph,
-    LinkedPipelinesMiniList: () =>
-      import('ee_component/vue_shared/components/linked_pipelines_mini_list.vue'),
   },
   inject: ['projectFullPath'],
   props: {
@@ -47,9 +45,6 @@ export default {
     downstreamPipelines() {
       return this.linkedPipelines?.downstream?.nodes || [];
     },
-    hasDownstreamPipelines() {
-      return this.downstreamPipelines.length > 0;
-    },
     hasPipelineStages() {
       return this.pipelineStages.length > 0;
     },
@@ -87,23 +82,11 @@ export default {
 </script>
 
 <template>
-  <div
+  <pipeline-mini-graph
     v-if="hasPipelineStages"
-    class="gl-align-items-center gl-display-inline-flex gl-flex-wrap stage-cell gl-mr-5"
-  >
-    <linked-pipelines-mini-list
-      v-if="upstreamPipeline"
-      :triggered-by="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ [
-        upstreamPipeline,
-      ] /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
-      data-testid="pipeline-editor-mini-graph-upstream"
-    />
-    <pipeline-mini-graph :stages="pipelineStages" />
-    <linked-pipelines-mini-list
-      v-if="hasDownstreamPipelines"
-      :triggered="downstreamPipelines"
-      :pipeline-path="pipelinePath"
-      data-testid="pipeline-editor-mini-graph-downstream"
-    />
-  </div>
+    :downstream-pipelines="downstreamPipelines"
+    :pipeline-path="pipelinePath"
+    :stages="pipelineStages"
+    :upstream-pipeline="upstreamPipeline"
+  />
 </template>
diff --git a/app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue b/app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue
index 4b9c98135ec60030d09a112b15a3be39c2887ef9..137dfca68d6d0873b87c6ee7314f3be46d20f79a 100644
--- a/app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue
+++ b/app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue
@@ -174,7 +174,7 @@ export default {
       <div class="gl-display-flex gl-flex-wrap">
         <pipeline-editor-mini-graph :pipeline="pipeline" v-on="$listeners" />
         <gl-button
-          class="gl-mt-2 gl-md-mt-0"
+          class="gl-ml-3"
           category="secondary"
           variant="confirm"
           :href="status.detailsPath"
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_mini_graph.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_mini_graph.vue
index 05cb2ebb769e6a0f8f0d62653f9ced09d4207229..f1edc3d4be8d73e382c6a51163ce079688906f2e 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_mini_graph.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_mini_graph.vue
@@ -1,32 +1,60 @@
 <script>
-import PipelineStage from '~/pipelines/components/pipelines_list/pipeline_stage.vue';
+import { GlIcon } from '@gitlab/ui';
+import PipelineStages from '~/pipelines/components/pipelines_list/pipeline_stages.vue';
 /**
  * Renders the pipeline mini graph.
  */
 export default {
   components: {
-    PipelineStage,
+    GlIcon,
+    PipelineStages,
+    LinkedPipelinesMiniList: () =>
+      import('ee_component/vue_shared/components/linked_pipelines_mini_list.vue'),
   },
+  arrowStyles: [
+    'arrow-icon gl-display-inline-block gl-mx-1 gl-text-gray-500 gl-vertical-align-middle!',
+  ],
   props: {
-    stages: {
+    downstreamPipelines: {
       type: Array,
-      required: true,
+      required: false,
+      default: () => [],
     },
-    updateDropdown: {
+    isMergeTrain: {
       type: Boolean,
       required: false,
       default: false,
     },
+    pipelinePath: {
+      type: String,
+      required: false,
+      default: '',
+    },
+    stages: {
+      type: Array,
+      required: true,
+      default: () => [],
+    },
     stagesClass: {
       type: [Array, Object, String],
       required: false,
       default: '',
     },
-    isMergeTrain: {
+    updateDropdown: {
       type: Boolean,
       required: false,
       default: false,
     },
+    upstreamPipeline: {
+      type: Object,
+      required: false,
+      default: () => {},
+    },
+  },
+  computed: {
+    hasDownstreamPipelines() {
+      return Boolean(this.downstreamPipelines.length);
+    },
   },
   methods: {
     onPipelineActionRequestComplete() {
@@ -36,19 +64,39 @@ export default {
 };
 </script>
 <template>
-  <div data-testid="pipeline-mini-graph" class="gl-display-inline gl-vertical-align-middle">
-    <div
-      v-for="stage in stages"
-      :key="stage.name"
-      :class="stagesClass"
-      class="dropdown gl-display-inline-block gl-mr-2 gl-my-2 gl-vertical-align-middle stage-container"
-    >
-      <pipeline-stage
-        :stage="stage"
-        :update-dropdown="updateDropdown"
-        :is-merge-train="isMergeTrain"
-        @pipelineActionRequestComplete="onPipelineActionRequestComplete"
-      />
-    </div>
+  <div class="stage-cell" data-testid="pipeline-mini-graph">
+    <linked-pipelines-mini-list
+      v-if="upstreamPipeline"
+      :triggered-by="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ [
+        upstreamPipeline,
+      ] /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
+      data-testid="pipeline-mini-graph-upstream"
+    />
+    <gl-icon
+      v-if="upstreamPipeline"
+      :class="$options.arrowStyles"
+      name="long-arrow"
+      data-testid="upstream-arrow-icon"
+    />
+    <pipeline-stages
+      :is-merge-train="isMergeTrain"
+      :stages="stages"
+      :update-dropdown="updateDropdown"
+      :stages-class="stagesClass"
+      data-testid="pipeline-stages"
+      @pipelineActionRequestComplete="onPipelineActionRequestComplete"
+    />
+    <gl-icon
+      v-if="hasDownstreamPipelines"
+      :class="$options.arrowStyles"
+      name="long-arrow"
+      data-testid="downstream-arrow-icon"
+    />
+    <linked-pipelines-mini-list
+      v-if="hasDownstreamPipelines"
+      :triggered="downstreamPipelines"
+      :pipeline-path="pipelinePath"
+      data-testid="pipeline-mini-graph-downstream"
+    />
   </div>
 </template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stages.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stages.vue
new file mode 100644
index 0000000000000000000000000000000000000000..f1923e94a477a3d282d159a8665ac7594988dde4
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stages.vue
@@ -0,0 +1,54 @@
+<script>
+import PipelineStage from '~/pipelines/components/pipelines_list/pipeline_stage.vue';
+/**
+ * Renders the pipeline stages portion of the pipeline mini graph.
+ */
+export default {
+  components: {
+    PipelineStage,
+  },
+  props: {
+    stages: {
+      type: Array,
+      required: true,
+    },
+    updateDropdown: {
+      type: Boolean,
+      required: false,
+      default: false,
+    },
+    stagesClass: {
+      type: [Array, Object, String],
+      required: false,
+      default: '',
+    },
+    isMergeTrain: {
+      type: Boolean,
+      required: false,
+      default: false,
+    },
+  },
+  methods: {
+    onPipelineActionRequestComplete() {
+      this.$emit('pipelineActionRequestComplete');
+    },
+  },
+};
+</script>
+<template>
+  <div data-testid="pipeline-stages" class="gl-display-inline gl-vertical-align-middle">
+    <div
+      v-for="stage in stages"
+      :key="stage.name"
+      :class="stagesClass"
+      class="dropdown gl-display-inline-block gl-mr-2 gl-my-2 gl-vertical-align-middle stage-container"
+    >
+      <pipeline-stage
+        :stage="stage"
+        :update-dropdown="updateDropdown"
+        :is-merge-train="isMergeTrain"
+        @pipelineActionRequestComplete="onPipelineActionRequestComplete"
+      />
+    </div>
+  </div>
+</template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue
index 53da98434b0d8c2a0bb7efbf3bf2188a059889c0..4046ee69428dec3f553080abfc9d67d1202ee6d7 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue
@@ -17,8 +17,6 @@ const DEFAULT_TH_CLASSES =
 export default {
   components: {
     GlTableLite,
-    LinkedPipelinesMiniList: () =>
-      import('ee_component/vue_shared/components/linked_pipelines_mini_list.vue'),
     PipelineMiniGraph,
     PipelineOperations,
     PipelinesStatusBadge,
@@ -169,29 +167,14 @@ export default {
       </template>
 
       <template #cell(stages)="{ item }">
-        <div class="stage-cell">
-          <!-- This empty div should be removed, see https://gitlab.com/gitlab-org/gitlab/-/issues/323488 -->
-          <div></div>
-          <linked-pipelines-mini-list
-            v-if="item.triggered_by"
-            :triggered-by="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ [
-              item.triggered_by,
-            ] /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
-            data-testid="mini-graph-upstream"
-          />
-          <pipeline-mini-graph
-            v-if="item.details && item.details.stages && item.details.stages.length > 0"
-            :stages="item.details.stages"
-            :update-dropdown="updateGraphDropdown"
-            @pipelineActionRequestComplete="onPipelineActionRequestComplete"
-          />
-          <linked-pipelines-mini-list
-            v-if="item.triggered.length"
-            :triggered="item.triggered"
-            :pipeline-path="item.path"
-            data-testid="mini-graph-downstream"
-          />
-        </div>
+        <pipeline-mini-graph
+          :downstream-pipelines="item.triggered"
+          :pipeline-path="item.path"
+          :stages="item.details.stages"
+          :update-dropdown="updateGraphDropdown"
+          :upstream-pipeline="item.triggered_by"
+          @pipelineActionRequestComplete="onPipelineActionRequestComplete"
+        />
       </template>
 
       <template #cell(actions)="{ item }">
diff --git a/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue b/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue
index 1cdf26b76b752e9f88ee7efdc4cea1f6b3e69b67..a4044106a531b2d85cee27c0dd982f69b8b18ed5 100644
--- a/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue
+++ b/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue
@@ -2,11 +2,11 @@
 import { GlLoadingIcon } from '@gitlab/ui';
 import createFlash from '~/flash';
 import { __ } from '~/locale';
-import PipelineMiniGraph from '~/pipelines/components/pipelines_list/pipeline_mini_graph.vue';
 import {
   getQueryHeaders,
   toggleQueryPollingByVisibility,
 } from '~/pipelines/components/graph/utils';
+import PipelineMiniGraph from '~/pipelines/components/pipelines_list/pipeline_mini_graph.vue';
 import { formatStages } from '../utils';
 import getLinkedPipelinesQuery from '../graphql/queries/get_linked_pipelines.query.graphql';
 import getPipelineStagesQuery from '../graphql/queries/get_pipeline_stages.query.graphql';
@@ -21,8 +21,6 @@ export default {
   components: {
     GlLoadingIcon,
     PipelineMiniGraph,
-    LinkedPipelinesMiniList: () =>
-      import('ee_component/vue_shared/components/linked_pipelines_mini_list.vue'),
   },
   inject: {
     fullPath: {
@@ -92,12 +90,12 @@ export default {
     };
   },
   computed: {
-    hasDownstream() {
-      return this.pipeline?.downstream?.nodes.length > 0;
-    },
     downstreamPipelines() {
       return this.pipeline?.downstream?.nodes;
     },
+    pipelinePath() {
+      return this.pipeline?.path ?? '';
+    },
     upstreamPipeline() {
       return this.pipeline?.upstream;
     },
@@ -128,23 +126,13 @@ export default {
 <template>
   <div class="gl-pt-2">
     <gl-loading-icon v-if="$apollo.queries.pipeline.loading" />
-    <div v-else class="gl-align-items-center gl-display-flex">
-      <linked-pipelines-mini-list
-        v-if="upstreamPipeline"
-        :triggered-by="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ [
-          upstreamPipeline,
-        ] /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
-        data-testid="commit-box-mini-graph-upstream"
-      />
-
-      <pipeline-mini-graph :stages="formattedStages" data-testid="commit-box-mini-graph" />
-
-      <linked-pipelines-mini-list
-        v-if="hasDownstream"
-        :triggered="downstreamPipelines"
-        :pipeline-path="pipeline.path"
-        data-testid="commit-box-mini-graph-downstream"
-      />
-    </div>
+    <pipeline-mini-graph
+      v-else
+      data-testid="commit-box-pipeline-mini-graph"
+      :downstream-pipelines="downstreamPipelines"
+      :pipeline-path="pipelinePath"
+      :stages="formattedStages"
+      :upstream-pipeline="upstreamPipeline"
+    />
   </div>
 </template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
index 1e1a20494145715440559ba2e9c5815b47ac52b5..5ecf49b51be8112b85ccd5637d5927a0f279819b 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
@@ -11,8 +11,8 @@ import {
 } from '@gitlab/ui';
 import mrWidgetPipelineMixin from 'ee_else_ce/vue_merge_request_widget/mixins/mr_widget_pipeline';
 import { s__, n__ } from '~/locale';
-import PipelineMiniGraph from '~/pipelines/components/pipelines_list/pipeline_mini_graph.vue';
 import PipelineArtifacts from '~/pipelines/components/pipelines_list/pipelines_artifacts.vue';
+import PipelineMiniGraph from '~/pipelines/components/pipelines_list/pipeline_mini_graph.vue';
 import CiIcon from '~/vue_shared/components/ci_icon.vue';
 import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
 import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
@@ -31,8 +31,6 @@ export default {
     PipelineMiniGraph,
     TimeAgoTooltip,
     TooltipOnTruncate,
-    LinkedPipelinesMiniList: () =>
-      import('ee_component/vue_shared/components/linked_pipelines_mini_list.vue'),
   },
   directives: {
     GlTooltip: GlTooltipDirective,
@@ -276,17 +274,15 @@ export default {
           </div>
         </div>
         <div>
-          <span class="gl-align-items-center gl-display-inline-flex mr-widget-pipeline-graph">
-            <span class="gl-align-items-center gl-display-inline-flex gl-flex-wrap stage-cell">
-              <linked-pipelines-mini-list v-if="triggeredBy.length" :triggered-by="triggeredBy" />
-              <pipeline-mini-graph
-                v-if="hasStages"
-                stages-class="mr-widget-pipeline-stages"
-                :stages="pipeline.details.stages"
-                :is-merge-train="isMergeTrain"
-              />
-            </span>
-            <linked-pipelines-mini-list v-if="triggered.length" :triggered="triggered" />
+          <span class="gl-align-items-center gl-display-inline-flex">
+            <pipeline-mini-graph
+              v-if="pipeline.details.stages"
+              :downstream-pipelines="triggered"
+              :is-merge-train="isMergeTrain"
+              :stages="pipeline.details.stages"
+              :upstream-pipeline="triggeredBy[0]"
+              stages-class="mr-widget-pipeline-stages"
+            />
             <pipeline-artifacts :pipeline-id="pipeline.id" :artifacts="artifacts" class="gl-ml-3" />
           </span>
         </div>
diff --git a/app/assets/stylesheets/page_bundles/merge_requests.scss b/app/assets/stylesheets/page_bundles/merge_requests.scss
index 14873c54cd77d8f6cddbf0ed82b76811ef1c737c..269afd01615e32e5a7f6f178b9cbeaecb8b776e9 100644
--- a/app/assets/stylesheets/page_bundles/merge_requests.scss
+++ b/app/assets/stylesheets/page_bundles/merge_requests.scss
@@ -400,12 +400,6 @@ $tabs-holder-z-index: 250;
     display: block;
   }
 
-  .mr-widget-pipeline-graph {
-    .dropdown-menu {
-      z-index: $zindex-dropdown-menu;
-    }
-  }
-
   .normal {
     flex: 1;
     flex-basis: auto;
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index c96d8ecc782ee805b286ed6c1c325846f8f47688..19318d8773127c63d7052430852adda1b6beab41 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -33,12 +33,6 @@
       height: 22px;
     }
   }
-
-  .mr-widget-pipeline-graph {
-    .dropdown-menu {
-      margin-top: 11px;
-    }
-  }
 }
 
 .branch-info .commit-icon {
diff --git a/ee/app/assets/javascripts/vue_shared/components/linked_pipelines_mini_list.vue b/ee/app/assets/javascripts/vue_shared/components/linked_pipelines_mini_list.vue
index 4ebe9539411cb469eae60a8908ef311b9f16729e..4d180dfcd824905ca818d51914257c944e64134a 100644
--- a/ee/app/assets/javascripts/vue_shared/components/linked_pipelines_mini_list.vue
+++ b/ee/app/assets/javascripts/vue_shared/components/linked_pipelines_mini_list.vue
@@ -1,20 +1,18 @@
 <script>
-import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
+import { GlTooltipDirective } from '@gitlab/ui';
 import { sprintf, s__ } from '~/locale';
 import CiIcon from '~/vue_shared/components/ci_icon.vue';
 import { accessValue } from '../accessors/linked_pipelines_accessors';
-
+/**
+ * Renders the upstream/downstream portions of the pipeline mini graph.
+ */
 export default {
   directives: {
     GlTooltip: GlTooltipDirective,
   },
   components: {
     CiIcon,
-    GlIcon,
   },
-  arrowStyles: [
-    'arrow-icon gl-display-inline-block gl-mx-1 gl-text-gray-500 gl-vertical-align-middle!',
-  ],
   inject: {
     dataMethod: {
       default: 'rest',
@@ -101,8 +99,6 @@ export default {
     }"
     class="linked-pipeline-mini-list gl-display-inline gl-vertical-align-middle"
   >
-    <gl-icon v-if="isDownstream" :class="$options.arrowStyles" name="long-arrow" />
-
     <a
       v-for="pipeline in linkedPipelinesTrimmed"
       :key="pipeline.id"
@@ -132,7 +128,5 @@ export default {
     >
       {{ counterLabel }}
     </a>
-
-    <gl-icon v-if="isUpstream" :class="$options.arrowStyles" name="long-arrow" />
   </span>
 </template>
diff --git a/ee/spec/frontend/commit_box/components/commit_box_pipeline_mini_graph_spec.js b/ee/spec/frontend/commit_box/components/commit_box_pipeline_mini_graph_spec.js
index ceb43768657a0bbe134c0e71c932d7093d524d2d..4dd6e9e54867aa553ddfd668dc71f05424f4a384 100644
--- a/ee/spec/frontend/commit_box/components/commit_box_pipeline_mini_graph_spec.js
+++ b/ee/spec/frontend/commit_box/components/commit_box_pipeline_mini_graph_spec.js
@@ -7,6 +7,7 @@ import { extendedWrapper } from 'helpers/vue_test_utils_helper';
 import waitForPromises from 'helpers/wait_for_promises';
 import createFlash from '~/flash';
 import CommitBoxPipelineMiniGraph from '~/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue';
+import PipelineMiniGraph from '~/pipelines/components/pipelines_list/pipeline_mini_graph.vue';
 import { COMMIT_BOX_POLL_INTERVAL } from '~/projects/commit_box/info/constants';
 import getLinkedPipelinesQuery from '~/projects/commit_box/info/graphql/queries/get_linked_pipelines.query.graphql';
 import getPipelineStagesQuery from '~/projects/commit_box/info/graphql/queries/get_pipeline_stages.query.graphql';
@@ -37,9 +38,7 @@ describe('Commit box pipeline mini graph', () => {
   const stagesHandler = jest.fn().mockResolvedValue(mockPipelineStagesQueryResponse);
 
   const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
-  const findMiniGraph = () => wrapper.findByTestId('commit-box-mini-graph');
-  const findUpstream = () => wrapper.findByTestId('commit-box-mini-graph-upstream');
-  const findDownstream = () => wrapper.findByTestId('commit-box-mini-graph-downstream');
+  const findPipelineMiniGraph = () => wrapper.findComponent(PipelineMiniGraph);
 
   const advanceToNextFetch = () => {
     jest.advanceTimersByTime(COMMIT_BOX_POLL_INTERVAL);
@@ -80,18 +79,25 @@ describe('Commit box pipeline mini graph', () => {
       createComponent();
 
       expect(findLoadingIcon().exists()).toBe(true);
-      expect(findMiniGraph().exists()).toBe(false);
+      expect(findPipelineMiniGraph().exists()).toBe(false);
     });
   });
 
   describe('loaded state', () => {
+    const samplePipeline = {
+      id: expect.any(String),
+      path: expect.any(String),
+      project: expect.any(Object),
+      detailedStatus: expect.any(Object),
+    };
+
     it('should not display loading state after the query is resolved', async () => {
       createComponent();
 
       await waitForPromises();
 
       expect(findLoadingIcon().exists()).toBe(false);
-      expect(findMiniGraph().exists()).toBe(true);
+      expect(findPipelineMiniGraph().exists()).toBe(true);
     });
 
     it('should pass the pipeline path prop for the counter badge', async () => {
@@ -100,24 +106,45 @@ describe('Commit box pipeline mini graph', () => {
       await waitForPromises();
 
       const expectedPath = mockDownstreamQueryResponse.data.project.pipeline.path;
+      const pipelinePath = findPipelineMiniGraph().props('pipelinePath');
 
-      expect(findDownstream().props('pipelinePath')).toBe(expectedPath);
+      expect(pipelinePath).toBe(expectedPath);
     });
 
-    describe.each`
-      handler                      | downstreamRenders | upstreamRenders
-      ${downstreamHandler}         | ${true}           | ${false}
-      ${upstreamHandler}           | ${false}          | ${true}
-      ${upstreamDownstreamHandler} | ${true}           | ${true}
-    `('given a linked pipeline', ({ handler, downstreamRenders, upstreamRenders }) => {
-      it('should render the correct linked pipelines', async () => {
-        createComponent(handler);
+    it('should render a downstream pipeline only', async () => {
+      createComponent(downstreamHandler);
+
+      await waitForPromises();
 
-        await waitForPromises();
+      const downstreamPipelines = findPipelineMiniGraph().props('downstreamPipelines');
+      const upstreamPipeline = findPipelineMiniGraph().props('upstreamPipeline');
 
-        expect(findDownstream().exists()).toBe(downstreamRenders);
-        expect(findUpstream().exists()).toBe(upstreamRenders);
-      });
+      expect(downstreamPipelines).toEqual(expect.any(Array));
+      expect(upstreamPipeline).toEqual(null);
+    });
+
+    it('should render an upstream pipeline only', async () => {
+      createComponent(upstreamHandler);
+
+      await waitForPromises();
+
+      const downstreamPipelines = findPipelineMiniGraph().props('downstreamPipelines');
+      const upstreamPipeline = findPipelineMiniGraph().props('upstreamPipeline');
+
+      expect(upstreamPipeline).toEqual(samplePipeline);
+      expect(downstreamPipelines).toHaveLength(0);
+    });
+
+    it('should render downstream and upstream pipelines', async () => {
+      createComponent(upstreamDownstreamHandler);
+
+      await waitForPromises();
+
+      const downstreamPipelines = findPipelineMiniGraph().props('downstreamPipelines');
+      const upstreamPipeline = findPipelineMiniGraph().props('upstreamPipeline');
+
+      expect(upstreamPipeline).toEqual(samplePipeline);
+      expect(downstreamPipelines).toEqual(expect.arrayContaining([samplePipeline]));
     });
 
     it('formatted stages should be passed to the pipeline mini graph', async () => {
@@ -139,7 +166,7 @@ describe('Commit box pipeline mini graph', () => {
 
       await waitForPromises();
 
-      expect(findMiniGraph().props('stages')).toEqual(expectedStages);
+      expect(findPipelineMiniGraph().props('stages')).toEqual(expectedStages);
     });
   });
 
diff --git a/ee/spec/frontend/pipeline_editor/components/header/pipeline_editor_mini_graph_spec.js b/ee/spec/frontend/pipeline_editor/components/header/pipeline_editor_mini_graph_spec.js
index cd4910e77411ebea0bb74b1f9931e0475bf12b13..c6700a74e34fa164c76527de9ad4ac3d493aab5f 100644
--- a/ee/spec/frontend/pipeline_editor/components/header/pipeline_editor_mini_graph_spec.js
+++ b/ee/spec/frontend/pipeline_editor/components/header/pipeline_editor_mini_graph_spec.js
@@ -1,9 +1,9 @@
 import { shallowMount } from '@vue/test-utils';
 import Vue from 'vue';
 import VueApollo from 'vue-apollo';
-import waitForPromises from 'helpers/wait_for_promises';
 import createMockApollo from 'helpers/mock_apollo_helper';
 import PipelineEditorMiniGraph from '~/pipeline_editor/components/header/pipeline_editor_mini_graph.vue';
+import PipelineMiniGraph from '~/pipelines/components/pipelines_list/pipeline_mini_graph.vue';
 import getLinkedPipelinesQuery from '~/projects/commit_box/info/graphql/queries/get_linked_pipelines.query.graphql';
 import { mockLinkedPipelines, mockProjectFullPath, mockProjectPipeline } from '../../mock_data';
 
@@ -39,9 +39,7 @@ describe('Pipeline Status', () => {
     });
   };
 
-  const findUpstream = () => wrapper.find('[data-testid="pipeline-editor-mini-graph-upstream"]');
-  const findDownstream = () =>
-    wrapper.find('[data-testid="pipeline-editor-mini-graph-downstream"]');
+  const findPipelineMiniGraph = () => wrapper.findComponent(PipelineMiniGraph);
 
   beforeEach(() => {
     mockLinkedPipelinesQuery = jest.fn();
@@ -52,33 +50,31 @@ describe('Pipeline Status', () => {
     wrapper.destroy();
   });
 
-  describe('when querying upstream and downstream pipelines', () => {
-    describe('when query succeeds', () => {
+  describe('when querying pipeline stages', () => {
+    describe('when query returns data', () => {
       beforeEach(() => {
         mockLinkedPipelinesQuery.mockResolvedValue(mockLinkedPipelines());
         createComponentWithApollo();
       });
 
-      describe('linked pipeline rendering based on given data', () => {
-        it.each`
-          hasDownstream | hasUpstream | downstreamRenderAction | upstreamRenderAction
-          ${true}       | ${true}     | ${'renders'}           | ${'renders'}
-          ${true}       | ${false}    | ${'renders'}           | ${'hides'}
-          ${false}      | ${true}     | ${'hides'}             | ${'renders'}
-          ${false}      | ${false}    | ${'hides'}             | ${'hides'}
-        `(
-          '$downstreamRenderAction downstream and $upstreamRenderAction upstream',
-          async ({ hasDownstream, hasUpstream }) => {
-            mockLinkedPipelinesQuery.mockResolvedValue(
-              mockLinkedPipelines({ hasDownstream, hasUpstream }),
-            );
-            createComponentWithApollo();
-            await waitForPromises();
+      describe('pipeline mini graph rendering based on given data', () => {
+        it('renders pipeline mini graph', () => {
+          expect(findPipelineMiniGraph().exists()).toBe(true);
+        });
+      });
+    });
+
+    describe('when query returns no data', () => {
+      beforeEach(() => {
+        mockLinkedPipelinesQuery.mockResolvedValue(mockLinkedPipelines());
+        const hasStages = false;
+        createComponentWithApollo(hasStages);
+      });
 
-            expect(findUpstream().exists()).toBe(hasUpstream);
-            expect(findDownstream().exists()).toBe(hasDownstream);
-          },
-        );
+      describe('pipeline mini graph rendering based on given data', () => {
+        it('does not render pipeline mini graph', () => {
+          expect(findPipelineMiniGraph().exists()).toBe(false);
+        });
       });
     });
   });
diff --git a/ee/spec/frontend/pipelines/pipelines_table_spec.js b/ee/spec/frontend/pipelines/pipelines_table_spec.js
index 5ce7f28dee1939be8e255ba0e6045f421c4d5562..b17ab954c568be52bbda044c91f0ce8f7ab6cc5a 100644
--- a/ee/spec/frontend/pipelines/pipelines_table_spec.js
+++ b/ee/spec/frontend/pipelines/pipelines_table_spec.js
@@ -2,6 +2,7 @@ import { mount } from '@vue/test-utils';
 import fixture from 'test_fixtures/pipelines/pipelines.json';
 import { extendedWrapper } from 'helpers/vue_test_utils_helper';
 import PipelinesTable from '~/pipelines/components/pipelines_list/pipelines_table.vue';
+import PipelineMiniGraph from '~/pipelines/components/pipelines_list/pipeline_mini_graph.vue';
 import { PipelineKeyOptions } from '~/pipelines/constants';
 
 import { triggeredBy, triggered } from './mock_data';
@@ -35,8 +36,7 @@ describe('Pipelines Table', () => {
     );
   };
 
-  const findUpstream = () => wrapper.findByTestId('mini-graph-upstream');
-  const findDownstream = () => wrapper.findByTestId('mini-graph-downstream');
+  const findPipelineMiniGraph = () => wrapper.findComponent(PipelineMiniGraph);
 
   beforeEach(() => {
     pipeline = createMockPipeline();
@@ -47,6 +47,19 @@ describe('Pipelines Table', () => {
   });
 
   describe('Pipelines Table', () => {
+    describe('pipeline mini graph', () => {
+      beforeEach(() => {
+        pipeline = createMockPipeline();
+        pipeline.triggered_by = triggeredBy;
+
+        createComponent({ pipelines: [pipeline] });
+      });
+
+      it('should render a pipeline mini graph', () => {
+        expect(findPipelineMiniGraph().exists()).toBe(true);
+      });
+    });
+
     describe('upstream linked pipelines', () => {
       beforeEach(() => {
         pipeline = createMockPipeline();
@@ -56,16 +69,17 @@ describe('Pipelines Table', () => {
       });
 
       it('should render only a upstream pipeline', () => {
-        expect(findUpstream().exists()).toBe(true);
-        expect(findDownstream().exists()).toBe(false);
+        const upstreamPipeline = findPipelineMiniGraph().props('upstreamPipeline');
+        const downstreamPipelines = findPipelineMiniGraph().props('downstreamPipelines');
+
+        expect(upstreamPipeline).toEqual(expect.any(Object));
+        expect(downstreamPipelines).toHaveLength(0);
       });
 
-      it('should pass an array of the correct data to the linked pipeline component', () => {
-        const triggeredByProps = findUpstream().props('triggeredBy');
+      it('should pass an object of the correct data to the linked pipeline component', () => {
+        const upstreamPipeline = findPipelineMiniGraph().props('upstreamPipeline');
 
-        expect(triggeredByProps).toEqual(expect.any(Array));
-        expect(triggeredByProps).toHaveLength(1);
-        expect(triggeredByProps[0]).toBe(triggeredBy);
+        expect(upstreamPipeline).toBe(triggeredBy);
       });
     });
 
@@ -78,12 +92,22 @@ describe('Pipelines Table', () => {
       });
 
       it('should pass the pipeline path prop for the counter badge', () => {
-        expect(findDownstream().props('pipelinePath')).toBe(pipeline.path);
+        const pipelinePath = findPipelineMiniGraph().props('pipelinePath');
+        expect(pipelinePath).toBe(pipeline.path);
       });
 
       it('should render only a downstream pipeline', () => {
-        expect(findDownstream().exists()).toBe(true);
-        expect(findUpstream().exists()).toBe(false);
+        const upstreamPipeline = findPipelineMiniGraph().props('upstreamPipeline');
+        const downstreamPipelines = findPipelineMiniGraph().props('downstreamPipelines');
+
+        expect(downstreamPipelines).toEqual(expect.any(Array));
+        expect(upstreamPipeline).toEqual(null);
+      });
+
+      it('should pass an array of the correct data to the linked pipeline component', () => {
+        const downstreamPipelines = findPipelineMiniGraph().props('downstreamPipelines');
+
+        expect(downstreamPipelines).toEqual(triggered);
       });
     });
 
@@ -97,8 +121,11 @@ describe('Pipelines Table', () => {
       });
 
       it('should render both downstream and upstream pipelines', () => {
-        expect(findDownstream().exists()).toBe(true);
-        expect(findUpstream().exists()).toBe(true);
+        const upstreamPipeline = findPipelineMiniGraph().props('upstreamPipeline');
+        const downstreamPipelines = findPipelineMiniGraph().props('downstreamPipelines');
+
+        expect(downstreamPipelines).toEqual(triggered);
+        expect(upstreamPipeline).toEqual(triggeredBy);
       });
     });
   });
diff --git a/ee/spec/frontend/vue_mr_widget/mr_widget_pipeline_spec.js b/ee/spec/frontend/vue_mr_widget/mr_widget_pipeline_spec.js
index 8c35938fcb9c875363572cfa4ec64b53dbc6c925..7b87df4d5cf3a55b7564e51f90697ecf44d9e5aa 100644
--- a/ee/spec/frontend/vue_mr_widget/mr_widget_pipeline_spec.js
+++ b/ee/spec/frontend/vue_mr_widget/mr_widget_pipeline_spec.js
@@ -1,14 +1,14 @@
 import { shallowMount } from '@vue/test-utils';
-import LinkedPipelinesMiniList from 'ee/vue_shared/components/linked_pipelines_mini_list.vue';
 import mockData from 'ee_jest/vue_mr_widget/mock_data';
 import MrWidgetPipeline from '~/vue_merge_request_widget/components/mr_widget_pipeline.vue';
+import PipelineMiniGraph from '~/pipelines/components/pipelines_list/pipeline_mini_graph.vue';
 import mockLinkedPipelines from '../vue_shared/components/linked_pipelines_mock_data';
 
 describe('MRWidgetPipeline', () => {
   let wrapper;
 
   const findPipelineInfoContainer = () => wrapper.find('[data-testid="pipeline-info-container"');
-  const findPipelinesMiniList = () => wrapper.findComponent(LinkedPipelinesMiniList);
+  const findPipelineMiniGraph = () => wrapper.findComponent(PipelineMiniGraph);
 
   const createWrapper = (props) => {
     wrapper = shallowMount(MrWidgetPipeline, {
@@ -75,20 +75,14 @@ describe('MRWidgetPipeline', () => {
         createWrapper({ pipeline });
       });
 
-      it('should render the linked pipelines mini list', () => {
-        expect(findPipelinesMiniList().exists()).toBe(true);
+      it('should render the pipeline mini graph', () => {
+        expect(findPipelineMiniGraph().exists()).toBe(true);
       });
 
-      it('should render the linked pipelines mini list as an upstream list', () => {
-        expect(findPipelinesMiniList().classes('is-upstream')).toBe(true);
-      });
-
-      it('should add a single triggeredBy into an array', () => {
-        const triggeredBy = findPipelinesMiniList().props('triggeredBy');
+      it('should send upstream pipeline', () => {
+        const upstreamPipeline = findPipelineMiniGraph().props('upstreamPipeline');
 
-        expect(triggeredBy).toEqual(expect.any(Array));
-        expect(triggeredBy).toHaveLength(1);
-        expect(triggeredBy[0]).toBe(mockLinkedPipelines.triggered_by);
+        expect(upstreamPipeline).toBe(mockLinkedPipelines.triggered_by);
       });
     });
 
@@ -99,18 +93,14 @@ describe('MRWidgetPipeline', () => {
         createWrapper({ pipeline });
       });
 
-      it('should render the linked pipelines mini list', () => {
-        expect(findPipelinesMiniList().exists()).toBe(true);
+      it('should render the pipeline mini graph', () => {
+        expect(findPipelineMiniGraph().exists()).toBe(true);
       });
 
       it('should render the linked pipelines mini list as a downstream list', () => {
-        expect(findPipelinesMiniList().classes('is-downstream')).toBe(true);
-      });
-
-      it('should pass the triggered pipelines', () => {
-        const triggered = findPipelinesMiniList().props('triggered');
+        const downstreamPipelines = findPipelineMiniGraph().props('downstreamPipelines');
 
-        expect(triggered).toBe(mockLinkedPipelines.triggered);
+        expect(downstreamPipelines).toBe(mockLinkedPipelines.triggered);
       });
     });
   });
diff --git a/ee/spec/frontend/vue_shared/components/linked_pipelines_mini_list_spec.js b/ee/spec/frontend/vue_shared/components/linked_pipelines_mini_list_spec.js
index 3cebe451ceb5224a281742cd2a5c88bfe897b920..8896faf5c34b28c5340da867cd378949d47262e4 100644
--- a/ee/spec/frontend/vue_shared/components/linked_pipelines_mini_list_spec.js
+++ b/ee/spec/frontend/vue_shared/components/linked_pipelines_mini_list_spec.js
@@ -7,7 +7,6 @@ import mockData from './linked_pipelines_mock_data';
 describe('Linked pipeline mini list', () => {
   let wrapper;
 
-  const findArrowIcon = () => wrapper.find('[data-testid="long-arrow-icon"]');
   const findCiIcon = () => wrapper.findComponent(CiIcon);
   const findCiIcons = () => wrapper.findAllComponents(CiIcon);
   const findLinkedPipelineCounter = () => wrapper.find('[data-testid="linked-pipeline-counter"]');
@@ -73,13 +72,6 @@ describe('Linked pipeline mini list', () => {
       expect(findCiIcon().classes('ci-status-icon-running')).toBe(true);
     });
 
-    it('should render an arrow icon', () => {
-      expect(findArrowIcon().exists()).toBe(true);
-
-      expect(findArrowIcon().props('name')).toBe('long-arrow');
-      expect(findArrowIcon().classes('arrow-icon')).toBe(true);
-    });
-
     it('should have an activated tooltip', () => {
       expect(findLinkedPipelineMiniItem().exists()).toBe(true);
       const tooltip = getBinding(findLinkedPipelineMiniItem().element, 'gl-tooltip');
@@ -129,13 +121,6 @@ describe('Linked pipeline mini list', () => {
       expect(findCiIcon().classes('ci-status-icon-running')).toBe(true);
     });
 
-    it('should render an arrow icon', () => {
-      expect(findArrowIcon().exists()).toBe(true);
-
-      expect(findArrowIcon().props('name')).toBe('long-arrow');
-      expect(findArrowIcon().classes('arrow-icon')).toBe(true);
-    });
-
     it('should have an activated tooltip', () => {
       expect(findLinkedPipelineMiniItem().exists()).toBe(true);
       const tooltip = getBinding(findLinkedPipelineMiniItem().element, 'gl-tooltip');
diff --git a/spec/features/projects/commit/mini_pipeline_graph_spec.rb b/spec/features/projects/commit/mini_pipeline_graph_spec.rb
index e472cff38cefb34918d88b1793d1ca2995b9626f..4740f6e19fe57248dab609c1367ba85fc58b055e 100644
--- a/spec/features/projects/commit/mini_pipeline_graph_spec.rb
+++ b/spec/features/projects/commit/mini_pipeline_graph_spec.rb
@@ -27,7 +27,7 @@
     end
 
     it 'displays a mini pipeline graph' do
-      expect(page).to have_selector('[data-testid="commit-box-mini-graph"]')
+      expect(page).to have_selector('[data-testid="commit-box-pipeline-mini-graph"]')
 
       first('[data-testid="mini-pipeline-graph-dropdown"]').click
 
diff --git a/spec/frontend/commit/commit_box_pipeline_mini_graph_spec.js b/spec/frontend/commit/commit_box_pipeline_mini_graph_spec.js
index b1c8ba48475f7358a18a6227f5e40e194d6993cd..ab5055de5e3fd1c02d7f16f58501ee623bbbb0ff 100644
--- a/spec/frontend/commit/commit_box_pipeline_mini_graph_spec.js
+++ b/spec/frontend/commit/commit_box_pipeline_mini_graph_spec.js
@@ -6,6 +6,7 @@ import { extendedWrapper } from 'helpers/vue_test_utils_helper';
 import waitForPromises from 'helpers/wait_for_promises';
 import createFlash from '~/flash';
 import CommitBoxPipelineMiniGraph from '~/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue';
+import PipelineMiniGraph from '~/pipelines/components/pipelines_list/pipeline_mini_graph.vue';
 import getLinkedPipelinesQuery from '~/projects/commit_box/info/graphql/queries/get_linked_pipelines.query.graphql';
 import getPipelineStagesQuery from '~/projects/commit_box/info/graphql/queries/get_pipeline_stages.query.graphql';
 import { mockPipelineStagesQueryResponse, mockStages } from './mock_data';
@@ -17,9 +18,7 @@ Vue.use(VueApollo);
 describe('Commit box pipeline mini graph', () => {
   let wrapper;
 
-  const findMiniGraph = () => wrapper.findByTestId('commit-box-mini-graph');
-  const findUpstream = () => wrapper.findByTestId('commit-box-mini-graph-upstream');
-  const findDownstream = () => wrapper.findByTestId('commit-box-mini-graph-downstream');
+  const findPipelineMiniGraph = () => wrapper.findComponent(PipelineMiniGraph);
 
   const stagesHandler = jest.fn().mockResolvedValue(mockPipelineStagesQueryResponse);
 
@@ -51,13 +50,16 @@ describe('Commit box pipeline mini graph', () => {
       await createComponent();
     });
 
-    it('should display the mini pipeine graph', () => {
-      expect(findMiniGraph().exists()).toBe(true);
+    it('should display the pipeline mini graph', () => {
+      expect(findPipelineMiniGraph().exists()).toBe(true);
     });
 
     it('should not display linked pipelines', () => {
-      expect(findUpstream().exists()).toBe(false);
-      expect(findDownstream().exists()).toBe(false);
+      const downstreamPipelines = findPipelineMiniGraph().props('downstreamPipelines');
+      const upstreamPipeline = findPipelineMiniGraph().props('upstreamPipeline');
+
+      expect(downstreamPipelines).toHaveLength(0);
+      expect(upstreamPipeline).toEqual(undefined);
     });
   });
 
diff --git a/spec/frontend/pipeline_editor/components/header/pipeline_editor_mini_graph_spec.js b/spec/frontend/pipeline_editor/components/header/pipeline_editor_mini_graph_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..93eb18c90cf16b31852cc411200c14b01347c829
--- /dev/null
+++ b/spec/frontend/pipeline_editor/components/header/pipeline_editor_mini_graph_spec.js
@@ -0,0 +1,109 @@
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import PipelineEditorMiniGraph from '~/pipeline_editor/components/header/pipeline_editor_mini_graph.vue';
+import PipelineMiniGraph from '~/pipelines/components/pipelines_list/pipeline_mini_graph.vue';
+import getLinkedPipelinesQuery from '~/projects/commit_box/info/graphql/queries/get_linked_pipelines.query.graphql';
+import { PIPELINE_FAILURE } from '~/pipeline_editor/constants';
+import { mockLinkedPipelines, mockProjectFullPath, mockProjectPipeline } from '../../mock_data';
+
+Vue.use(VueApollo);
+
+describe('Pipeline Status', () => {
+  let wrapper;
+  let mockApollo;
+  let mockLinkedPipelinesQuery;
+
+  const createComponent = ({ hasStages = true, options } = {}) => {
+    wrapper = shallowMount(PipelineEditorMiniGraph, {
+      provide: {
+        dataMethod: 'graphql',
+        projectFullPath: mockProjectFullPath,
+      },
+      propsData: {
+        pipeline: mockProjectPipeline({ hasStages }).pipeline,
+      },
+      ...options,
+    });
+  };
+
+  const createComponentWithApollo = (hasStages = true) => {
+    const handlers = [[getLinkedPipelinesQuery, mockLinkedPipelinesQuery]];
+    mockApollo = createMockApollo(handlers);
+
+    createComponent({
+      hasStages,
+      options: {
+        apolloProvider: mockApollo,
+      },
+    });
+  };
+
+  const findPipelineMiniGraph = () => wrapper.findComponent(PipelineMiniGraph);
+
+  beforeEach(() => {
+    mockLinkedPipelinesQuery = jest.fn();
+  });
+
+  afterEach(() => {
+    mockLinkedPipelinesQuery.mockReset();
+    wrapper.destroy();
+  });
+
+  describe('when there are stages', () => {
+    beforeEach(() => {
+      createComponent();
+    });
+
+    it('renders pipeline mini graph', () => {
+      expect(findPipelineMiniGraph().exists()).toBe(true);
+    });
+  });
+
+  describe('when there are no stages', () => {
+    beforeEach(() => {
+      createComponent({ hasStages: false });
+    });
+
+    it('does not render pipeline mini graph', () => {
+      expect(findPipelineMiniGraph().exists()).toBe(false);
+    });
+  });
+
+  describe('when querying upstream and downstream pipelines', () => {
+    describe('when query succeeds', () => {
+      beforeEach(() => {
+        mockLinkedPipelinesQuery.mockResolvedValue(mockLinkedPipelines());
+        createComponentWithApollo();
+      });
+
+      it('should call the query with the correct variables', () => {
+        expect(mockLinkedPipelinesQuery).toHaveBeenCalledTimes(1);
+        expect(mockLinkedPipelinesQuery).toHaveBeenCalledWith({
+          fullPath: mockProjectFullPath,
+          iid: mockProjectPipeline().pipeline.iid,
+        });
+      });
+    });
+
+    describe('when query fails', () => {
+      beforeEach(async () => {
+        mockLinkedPipelinesQuery.mockRejectedValue(new Error());
+        createComponentWithApollo();
+        await waitForPromises();
+      });
+
+      it('should emit an error event when query fails', async () => {
+        expect(wrapper.emitted('showError')).toHaveLength(1);
+        expect(wrapper.emitted('showError')[0]).toEqual([
+          {
+            type: PIPELINE_FAILURE,
+            reasons: [wrapper.vm.$options.i18n.linkedPipelinesFetchError],
+          },
+        ]);
+      });
+    });
+  });
+});
diff --git a/spec/frontend/pipelines/components/pipelines_list/pipeline_mini_graph_spec.js b/spec/frontend/pipelines/components/pipelines_list/pipeline_stages_spec.js
similarity index 92%
rename from spec/frontend/pipelines/components/pipelines_list/pipeline_mini_graph_spec.js
rename to spec/frontend/pipelines/components/pipelines_list/pipeline_stages_spec.js
index 1cb43c199aa5f70e461dc53ecef07dc4f462fbb0..1e31d8a62ffd997941a3d22efc96a66550b8b68e 100644
--- a/spec/frontend/pipelines/components/pipelines_list/pipeline_mini_graph_spec.js
+++ b/spec/frontend/pipelines/components/pipelines_list/pipeline_stages_spec.js
@@ -1,18 +1,18 @@
 import { shallowMount } from '@vue/test-utils';
 import { pipelines } from 'test_fixtures/pipelines/pipelines.json';
-import PipelineMiniGraph from '~/pipelines/components/pipelines_list/pipeline_mini_graph.vue';
 import PipelineStage from '~/pipelines/components/pipelines_list/pipeline_stage.vue';
+import PipelineStages from '~/pipelines/components/pipelines_list/pipeline_stages.vue';
 
 const mockStages = pipelines[0].details.stages;
 
-describe('Pipeline Mini Graph', () => {
+describe('Pipeline Stages', () => {
   let wrapper;
 
   const findPipelineStages = () => wrapper.findAll(PipelineStage);
   const findPipelineStagesAt = (i) => findPipelineStages().at(i);
 
   const createComponent = (props = {}) => {
-    wrapper = shallowMount(PipelineMiniGraph, {
+    wrapper = shallowMount(PipelineStages, {
       propsData: {
         stages: mockStages,
         ...props,
diff --git a/spec/frontend/pipelines/linked_pipelines_mock_data.js b/spec/frontend/pipelines/linked_pipelines_mock_data.js
new file mode 100644
index 0000000000000000000000000000000000000000..117c7f2ae529fc75849d75e27361e40ac9f3a712
--- /dev/null
+++ b/spec/frontend/pipelines/linked_pipelines_mock_data.js
@@ -0,0 +1,407 @@
+export default {
+  triggered_by: {
+    id: 129,
+    active: true,
+    path: '/gitlab-org/gitlab-foss/-/pipelines/129',
+    project: {
+      name: 'GitLabCE',
+    },
+    details: {
+      status: {
+        icon: 'status_running',
+        text: 'running',
+        label: 'running',
+        group: 'running',
+        has_details: true,
+        details_path: '/gitlab-org/gitlab-foss/-/pipelines/129',
+        favicon:
+          '/assets/ci_favicons/dev/favicon_status_running-c3ad2fc53ea6079c174e5b6c1351ff349e99ec3af5a5622fb77b0fe53ea279c1.ico',
+      },
+    },
+    flags: {
+      latest: false,
+      triggered: false,
+      stuck: false,
+      yaml_errors: false,
+      retryable: true,
+      cancelable: true,
+    },
+    ref: {
+      name: '7-5-stable',
+      path: '/gitlab-org/gitlab-foss/commits/7-5-stable',
+      tag: false,
+      branch: true,
+    },
+    commit: {
+      id: '23433d4d8b20d7e45c103d0b6048faad38a130ab',
+      short_id: '23433d4d',
+      title: 'Version 7.5.0.rc1',
+      created_at: '2014-11-17T15:44:14.000+01:00',
+      parent_ids: ['30ac909f30f58d319b42ed1537664483894b18cd'],
+      message: 'Version 7.5.0.rc1\n',
+      author_name: 'Jacob Vosmaer',
+      author_email: 'contact@jacobvosmaer.nl',
+      authored_date: '2014-11-17T15:44:14.000+01:00',
+      committer_name: 'Jacob Vosmaer',
+      committer_email: 'contact@jacobvosmaer.nl',
+      committed_date: '2014-11-17T15:44:14.000+01:00',
+      author_gravatar_url:
+        'http://www.gravatar.com/avatar/e66d11c0eedf8c07b3b18fca46599807?s=80&d=identicon',
+      commit_url:
+        'http://localhost:3000/gitlab-org/gitlab-foss/commit/23433d4d8b20d7e45c103d0b6048faad38a130ab',
+      commit_path: '/gitlab-org/gitlab-foss/commit/23433d4d8b20d7e45c103d0b6048faad38a130ab',
+    },
+    retry_path: '/gitlab-org/gitlab-foss/-/pipelines/129/retry',
+    cancel_path: '/gitlab-org/gitlab-foss/-/pipelines/129/cancel',
+    created_at: '2017-05-24T14:46:20.090Z',
+    updated_at: '2017-05-24T14:46:29.906Z',
+  },
+  triggered: [
+    {
+      id: 132,
+      active: true,
+      path: '/gitlab-org/gitlab-foss/-/pipelines/132',
+      project: {
+        name: 'GitLabCE',
+      },
+      details: {
+        status: {
+          icon: 'status_running',
+          text: 'running',
+          label: 'running',
+          group: 'running',
+          has_details: true,
+          details_path: '/gitlab-org/gitlab-foss/-/pipelines/132',
+          favicon:
+            '/assets/ci_favicons/dev/favicon_status_running-c3ad2fc53ea6079c174e5b6c1351ff349e99ec3af5a5622fb77b0fe53ea279c1.ico',
+        },
+      },
+      flags: {
+        latest: false,
+        triggered: false,
+        stuck: false,
+        yaml_errors: false,
+        retryable: true,
+        cancelable: true,
+      },
+      ref: {
+        name: 'crowd',
+        path: '/gitlab-org/gitlab-foss/commits/crowd',
+        tag: false,
+        branch: true,
+      },
+      commit: {
+        id: 'b9d58c4cecd06be74c3cc32ccfb522b31544ab2e',
+        short_id: 'b9d58c4c',
+        title: 'getting user keys publically through http without any authentication, the github…',
+        created_at: '2013-10-03T12:50:33.000+05:30',
+        parent_ids: ['e219cf7246c6a0495e4507deaffeba11e79f13b8'],
+        message:
+          'getting user keys publically through http without any authentication, the github way. E.g: http://github.com/devaroop.keys\n\nchangelog updated to include ssh key retrieval feature update\n',
+        author_name: 'devaroop',
+        author_email: 'devaroop123@yahoo.co.in',
+        authored_date: '2013-10-02T20:39:29.000+05:30',
+        committer_name: 'devaroop',
+        committer_email: 'devaroop123@yahoo.co.in',
+        committed_date: '2013-10-03T12:50:33.000+05:30',
+        author_gravatar_url:
+          'http://www.gravatar.com/avatar/35df4b155ec66a3127d53459941cf8a2?s=80&d=identicon',
+        commit_url:
+          'http://localhost:3000/gitlab-org/gitlab-foss/commit/b9d58c4cecd06be74c3cc32ccfb522b31544ab2e',
+        commit_path: '/gitlab-org/gitlab-foss/commit/b9d58c4cecd06be74c3cc32ccfb522b31544ab2e',
+      },
+      retry_path: '/gitlab-org/gitlab-foss/-/pipelines/132/retry',
+      cancel_path: '/gitlab-org/gitlab-foss/-/pipelines/132/cancel',
+      created_at: '2017-05-24T14:46:24.644Z',
+      updated_at: '2017-05-24T14:48:55.226Z',
+    },
+    {
+      id: 133,
+      active: true,
+      path: '/gitlab-org/gitlab-foss/-/pipelines/133',
+      project: {
+        name: 'GitLabCE',
+      },
+      details: {
+        status: {
+          icon: 'status_running',
+          text: 'running',
+          label: 'running',
+          group: 'running',
+          has_details: true,
+          details_path: '/gitlab-org/gitlab-foss/-/pipelines/133',
+          favicon:
+            '/assets/ci_favicons/dev/favicon_status_running-c3ad2fc53ea6079c174e5b6c1351ff349e99ec3af5a5622fb77b0fe53ea279c1.ico',
+        },
+      },
+      flags: {
+        latest: false,
+        triggered: false,
+        stuck: false,
+        yaml_errors: false,
+        retryable: true,
+        cancelable: true,
+      },
+      ref: {
+        name: 'crowd',
+        path: '/gitlab-org/gitlab-foss/commits/crowd',
+        tag: false,
+        branch: true,
+      },
+      commit: {
+        id: 'b6bd4856a33df3d144be66c4ed1f1396009bb08b',
+        short_id: 'b6bd4856',
+        title: 'getting user keys publically through http without any authentication, the github…',
+        created_at: '2013-10-02T20:39:29.000+05:30',
+        parent_ids: ['e219cf7246c6a0495e4507deaffeba11e79f13b8'],
+        message:
+          'getting user keys publically through http without any authentication, the github way. E.g: http://github.com/devaroop.keys\n',
+        author_name: 'devaroop',
+        author_email: 'devaroop123@yahoo.co.in',
+        authored_date: '2013-10-02T20:39:29.000+05:30',
+        committer_name: 'devaroop',
+        committer_email: 'devaroop123@yahoo.co.in',
+        committed_date: '2013-10-02T20:39:29.000+05:30',
+        author_gravatar_url:
+          'http://www.gravatar.com/avatar/35df4b155ec66a3127d53459941cf8a2?s=80&d=identicon',
+        commit_url:
+          'http://localhost:3000/gitlab-org/gitlab-foss/commit/b6bd4856a33df3d144be66c4ed1f1396009bb08b',
+        commit_path: '/gitlab-org/gitlab-foss/commit/b6bd4856a33df3d144be66c4ed1f1396009bb08b',
+      },
+      retry_path: '/gitlab-org/gitlab-foss/-/pipelines/133/retry',
+      cancel_path: '/gitlab-org/gitlab-foss/-/pipelines/133/cancel',
+      created_at: '2017-05-24T14:46:24.648Z',
+      updated_at: '2017-05-24T14:48:59.673Z',
+    },
+    {
+      id: 130,
+      active: true,
+      path: '/gitlab-org/gitlab-foss/-/pipelines/130',
+      project: {
+        name: 'GitLabCE',
+      },
+      details: {
+        status: {
+          icon: 'status_running',
+          text: 'running',
+          label: 'running',
+          group: 'running',
+          has_details: true,
+          details_path: '/gitlab-org/gitlab-foss/-/pipelines/130',
+          favicon:
+            '/assets/ci_favicons/dev/favicon_status_running-c3ad2fc53ea6079c174e5b6c1351ff349e99ec3af5a5622fb77b0fe53ea279c1.ico',
+        },
+      },
+      flags: {
+        latest: false,
+        triggered: false,
+        stuck: false,
+        yaml_errors: false,
+        retryable: true,
+        cancelable: true,
+      },
+      ref: {
+        name: 'crowd',
+        path: '/gitlab-org/gitlab-foss/commits/crowd',
+        tag: false,
+        branch: true,
+      },
+      commit: {
+        id: '6d7ced4a2311eeff037c5575cca1868a6d3f586f',
+        short_id: '6d7ced4a',
+        title: 'Whitespace fixes to patch',
+        created_at: '2013-10-08T13:53:22.000-05:00',
+        parent_ids: ['1875141a963a4238bda29011d8f7105839485253'],
+        message: 'Whitespace fixes to patch\n',
+        author_name: 'Dale Hamel',
+        author_email: 'dale.hamel@srvthe.net',
+        authored_date: '2013-10-08T13:53:22.000-05:00',
+        committer_name: 'Dale Hamel',
+        committer_email: 'dale.hamel@invenia.ca',
+        committed_date: '2013-10-08T13:53:22.000-05:00',
+        author_gravatar_url:
+          'http://www.gravatar.com/avatar/cd08930e69fa5ad1a669206e7bafe476?s=80&d=identicon',
+        commit_url:
+          'http://localhost:3000/gitlab-org/gitlab-foss/commit/6d7ced4a2311eeff037c5575cca1868a6d3f586f',
+        commit_path: '/gitlab-org/gitlab-foss/commit/6d7ced4a2311eeff037c5575cca1868a6d3f586f',
+      },
+      retry_path: '/gitlab-org/gitlab-foss/-/pipelines/130/retry',
+      cancel_path: '/gitlab-org/gitlab-foss/-/pipelines/130/cancel',
+      created_at: '2017-05-24T14:46:24.630Z',
+      updated_at: '2017-05-24T14:49:45.091Z',
+    },
+    {
+      id: 131,
+      active: true,
+      path: '/gitlab-org/gitlab-foss/-/pipelines/132',
+      project: {
+        name: 'GitLabCE',
+      },
+      details: {
+        status: {
+          icon: 'status_running',
+          text: 'running',
+          label: 'running',
+          group: 'running',
+          has_details: true,
+          details_path: '/gitlab-org/gitlab-foss/-/pipelines/132',
+          favicon:
+            '/assets/ci_favicons/dev/favicon_status_running-c3ad2fc53ea6079c174e5b6c1351ff349e99ec3af5a5622fb77b0fe53ea279c1.ico',
+        },
+      },
+      flags: {
+        latest: false,
+        triggered: false,
+        stuck: false,
+        yaml_errors: false,
+        retryable: true,
+        cancelable: true,
+      },
+      ref: {
+        name: 'crowd',
+        path: '/gitlab-org/gitlab-foss/commits/crowd',
+        tag: false,
+        branch: true,
+      },
+      commit: {
+        id: 'b9d58c4cecd06be74c3cc32ccfb522b31544ab2e',
+        short_id: 'b9d58c4c',
+        title: 'getting user keys publically through http without any authentication, the github…',
+        created_at: '2013-10-03T12:50:33.000+05:30',
+        parent_ids: ['e219cf7246c6a0495e4507deaffeba11e79f13b8'],
+        message:
+          'getting user keys publically through http without any authentication, the github way. E.g: http://github.com/devaroop.keys\n\nchangelog updated to include ssh key retrieval feature update\n',
+        author_name: 'devaroop',
+        author_email: 'devaroop123@yahoo.co.in',
+        authored_date: '2013-10-02T20:39:29.000+05:30',
+        committer_name: 'devaroop',
+        committer_email: 'devaroop123@yahoo.co.in',
+        committed_date: '2013-10-03T12:50:33.000+05:30',
+        author_gravatar_url:
+          'http://www.gravatar.com/avatar/35df4b155ec66a3127d53459941cf8a2?s=80&d=identicon',
+        commit_url:
+          'http://localhost:3000/gitlab-org/gitlab-foss/commit/b9d58c4cecd06be74c3cc32ccfb522b31544ab2e',
+        commit_path: '/gitlab-org/gitlab-foss/commit/b9d58c4cecd06be74c3cc32ccfb522b31544ab2e',
+      },
+      retry_path: '/gitlab-org/gitlab-foss/-/pipelines/132/retry',
+      cancel_path: '/gitlab-org/gitlab-foss/-/pipelines/132/cancel',
+      created_at: '2017-05-24T14:46:24.644Z',
+      updated_at: '2017-05-24T14:48:55.226Z',
+    },
+    {
+      id: 134,
+      active: true,
+      path: '/gitlab-org/gitlab-foss/-/pipelines/133',
+      project: {
+        name: 'GitLabCE',
+      },
+      details: {
+        status: {
+          icon: 'status_running',
+          text: 'running',
+          label: 'running',
+          group: 'running',
+          has_details: true,
+          details_path: '/gitlab-org/gitlab-foss/-/pipelines/133',
+          favicon:
+            '/assets/ci_favicons/dev/favicon_status_running-c3ad2fc53ea6079c174e5b6c1351ff349e99ec3af5a5622fb77b0fe53ea279c1.ico',
+        },
+      },
+      flags: {
+        latest: false,
+        triggered: false,
+        stuck: false,
+        yaml_errors: false,
+        retryable: true,
+        cancelable: true,
+      },
+      ref: {
+        name: 'crowd',
+        path: '/gitlab-org/gitlab-foss/commits/crowd',
+        tag: false,
+        branch: true,
+      },
+      commit: {
+        id: 'b6bd4856a33df3d144be66c4ed1f1396009bb08b',
+        short_id: 'b6bd4856',
+        title: 'getting user keys publically through http without any authentication, the github…',
+        created_at: '2013-10-02T20:39:29.000+05:30',
+        parent_ids: ['e219cf7246c6a0495e4507deaffeba11e79f13b8'],
+        message:
+          'getting user keys publically through http without any authentication, the github way. E.g: http://github.com/devaroop.keys\n',
+        author_name: 'devaroop',
+        author_email: 'devaroop123@yahoo.co.in',
+        authored_date: '2013-10-02T20:39:29.000+05:30',
+        committer_name: 'devaroop',
+        committer_email: 'devaroop123@yahoo.co.in',
+        committed_date: '2013-10-02T20:39:29.000+05:30',
+        author_gravatar_url:
+          'http://www.gravatar.com/avatar/35df4b155ec66a3127d53459941cf8a2?s=80&d=identicon',
+        commit_url:
+          'http://localhost:3000/gitlab-org/gitlab-foss/commit/b6bd4856a33df3d144be66c4ed1f1396009bb08b',
+        commit_path: '/gitlab-org/gitlab-foss/commit/b6bd4856a33df3d144be66c4ed1f1396009bb08b',
+      },
+      retry_path: '/gitlab-org/gitlab-foss/-/pipelines/133/retry',
+      cancel_path: '/gitlab-org/gitlab-foss/-/pipelines/133/cancel',
+      created_at: '2017-05-24T14:46:24.648Z',
+      updated_at: '2017-05-24T14:48:59.673Z',
+    },
+    {
+      id: 135,
+      active: true,
+      path: '/gitlab-org/gitlab-foss/-/pipelines/130',
+      project: {
+        name: 'GitLabCE',
+      },
+      details: {
+        status: {
+          icon: 'status_running',
+          text: 'running',
+          label: 'running',
+          group: 'running',
+          has_details: true,
+          details_path: '/gitlab-org/gitlab-foss/-/pipelines/130',
+          favicon:
+            '/assets/ci_favicons/dev/favicon_status_running-c3ad2fc53ea6079c174e5b6c1351ff349e99ec3af5a5622fb77b0fe53ea279c1.ico',
+        },
+      },
+      flags: {
+        latest: false,
+        triggered: false,
+        stuck: false,
+        yaml_errors: false,
+        retryable: true,
+        cancelable: true,
+      },
+      ref: {
+        name: 'crowd',
+        path: '/gitlab-org/gitlab-foss/commits/crowd',
+        tag: false,
+        branch: true,
+      },
+      commit: {
+        id: '6d7ced4a2311eeff037c5575cca1868a6d3f586f',
+        short_id: '6d7ced4a',
+        title: 'Whitespace fixes to patch',
+        created_at: '2013-10-08T13:53:22.000-05:00',
+        parent_ids: ['1875141a963a4238bda29011d8f7105839485253'],
+        message: 'Whitespace fixes to patch\n',
+        author_name: 'Dale Hamel',
+        author_email: 'dale.hamel@srvthe.net',
+        authored_date: '2013-10-08T13:53:22.000-05:00',
+        committer_name: 'Dale Hamel',
+        committer_email: 'dale.hamel@invenia.ca',
+        committed_date: '2013-10-08T13:53:22.000-05:00',
+        author_gravatar_url:
+          'http://www.gravatar.com/avatar/cd08930e69fa5ad1a669206e7bafe476?s=80&d=identicon',
+        commit_url:
+          'http://localhost:3000/gitlab-org/gitlab-foss/commit/6d7ced4a2311eeff037c5575cca1868a6d3f586f',
+        commit_path: '/gitlab-org/gitlab-foss/commit/6d7ced4a2311eeff037c5575cca1868a6d3f586f',
+      },
+      retry_path: '/gitlab-org/gitlab-foss/-/pipelines/130/retry',
+      cancel_path: '/gitlab-org/gitlab-foss/-/pipelines/130/cancel',
+      created_at: '2017-05-24T14:46:24.630Z',
+      updated_at: '2017-05-24T14:49:45.091Z',
+    },
+  ],
+};
diff --git a/spec/frontend/pipelines/pipeline_mini_graph_spec.js b/spec/frontend/pipelines/pipeline_mini_graph_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..81aa97ce13fe3db1fee771e1fe52a141c7db2e23
--- /dev/null
+++ b/spec/frontend/pipelines/pipeline_mini_graph_spec.js
@@ -0,0 +1,149 @@
+import { mount } from '@vue/test-utils';
+import { pipelines } from 'test_fixtures/pipelines/pipelines.json';
+import PipelineMiniGraph from '~/pipelines/components/pipelines_list/pipeline_mini_graph.vue';
+import PipelineStages from '~/pipelines/components/pipelines_list/pipeline_stages.vue';
+import mockLinkedPipelines from './linked_pipelines_mock_data';
+
+const mockStages = pipelines[0].details.stages;
+
+describe('Pipeline Mini Graph', () => {
+  let wrapper;
+
+  const findPipelineMiniGraph = () => wrapper.findComponent(PipelineMiniGraph);
+  const findPipelineStages = () => wrapper.findComponent(PipelineStages);
+
+  const findLinkedPipelineUpstream = () =>
+    wrapper.findComponent('[data-testid="pipeline-mini-graph-upstream"]');
+  const findLinkedPipelineDownstream = () =>
+    wrapper.findComponent('[data-testid="pipeline-mini-graph-downstream"]');
+  const findDownstreamArrowIcon = () => wrapper.find('[data-testid="downstream-arrow-icon"]');
+  const findUpstreamArrowIcon = () => wrapper.find('[data-testid="upstream-arrow-icon"]');
+
+  const createComponent = (props = {}) => {
+    wrapper = mount(PipelineMiniGraph, {
+      propsData: {
+        stages: mockStages,
+        ...props,
+      },
+    });
+  };
+
+  describe('rendered state without upstream or downstream pipelines', () => {
+    beforeEach(() => {
+      createComponent();
+    });
+
+    afterEach(() => {
+      wrapper.destroy();
+      wrapper = null;
+    });
+
+    it('should render the pipeline stages', () => {
+      expect(findPipelineStages().exists()).toBe(true);
+    });
+
+    it('should have the correct props', () => {
+      expect(findPipelineMiniGraph().props()).toMatchObject({
+        downstreamPipelines: [],
+        isMergeTrain: false,
+        pipelinePath: '',
+        stages: expect.any(Array),
+        stagesClass: '',
+        updateDropdown: false,
+        upstreamPipeline: undefined,
+      });
+    });
+
+    it('should have no linked pipelines', () => {
+      expect(findLinkedPipelineDownstream().exists()).toBe(false);
+      expect(findLinkedPipelineUpstream().exists()).toBe(false);
+    });
+
+    it('should not render arrow icons', () => {
+      expect(findUpstreamArrowIcon().exists()).toBe(false);
+      expect(findDownstreamArrowIcon().exists()).toBe(false);
+    });
+
+    it('triggers events in "action request complete"', () => {
+      createComponent();
+
+      findPipelineMiniGraph(0).vm.$emit('pipelineActionRequestComplete');
+      findPipelineMiniGraph(1).vm.$emit('pipelineActionRequestComplete');
+
+      expect(wrapper.emitted('pipelineActionRequestComplete')).toHaveLength(2);
+    });
+  });
+
+  describe('rendered state with upstream pipeline', () => {
+    beforeEach(() => {
+      createComponent({
+        upstreamPipeline: mockLinkedPipelines.triggered_by,
+      });
+    });
+
+    afterEach(() => {
+      wrapper.destroy();
+      wrapper = null;
+    });
+
+    it('should have the correct props', () => {
+      expect(findPipelineMiniGraph().props()).toMatchObject({
+        downstreamPipelines: [],
+        isMergeTrain: false,
+        pipelinePath: '',
+        stages: expect.any(Array),
+        stagesClass: '',
+        updateDropdown: false,
+        upstreamPipeline: expect.any(Object),
+      });
+    });
+
+    it('should render the upstream linked pipelines mini list only', () => {
+      expect(findLinkedPipelineUpstream().exists()).toBe(true);
+      expect(findLinkedPipelineDownstream().exists()).toBe(false);
+    });
+
+    it('should render an upstream arrow icon only', () => {
+      expect(findDownstreamArrowIcon().exists()).toBe(false);
+      expect(findUpstreamArrowIcon().exists()).toBe(true);
+      expect(findUpstreamArrowIcon().props('name')).toBe('long-arrow');
+    });
+  });
+
+  describe('rendered state with downstream pipelines', () => {
+    beforeEach(() => {
+      createComponent({
+        downstreamPipelines: mockLinkedPipelines.triggered,
+        pipelinePath: 'my/pipeline/path',
+      });
+    });
+
+    it('should have the correct props', () => {
+      expect(findPipelineMiniGraph().props()).toMatchObject({
+        downstreamPipelines: expect.any(Array),
+        isMergeTrain: false,
+        pipelinePath: 'my/pipeline/path',
+        stages: expect.any(Array),
+        stagesClass: '',
+        updateDropdown: false,
+        upstreamPipeline: undefined,
+      });
+    });
+
+    afterEach(() => {
+      wrapper.destroy();
+      wrapper = null;
+    });
+
+    it('should render the downstream linked pipelines mini list only', () => {
+      expect(findLinkedPipelineDownstream().exists()).toBe(true);
+      expect(findLinkedPipelineUpstream().exists()).toBe(false);
+    });
+
+    it('should render a downstream arrow icon only', () => {
+      expect(findUpstreamArrowIcon().exists()).toBe(false);
+      expect(findDownstreamArrowIcon().exists()).toBe(true);
+      expect(findDownstreamArrowIcon().props('name')).toBe('long-arrow');
+    });
+  });
+});
diff --git a/spec/frontend/pipelines/pipelines_table_spec.js b/spec/frontend/pipelines/pipelines_table_spec.js
index 7b49baa5a204586f10ed97b826ca7f9e4159782a..07818b9dadbe9129ca667de3ecfbb9c7afecac53 100644
--- a/spec/frontend/pipelines/pipelines_table_spec.js
+++ b/spec/frontend/pipelines/pipelines_table_spec.js
@@ -113,40 +113,28 @@ describe('Pipelines Table', () => {
     });
 
     describe('stages cell', () => {
-      it('should render a pipeline mini graph', () => {
+      it('should render pipeline mini graph', () => {
         expect(findPipelineMiniGraph().exists()).toBe(true);
       });
 
       it('should render the right number of stages', () => {
         const stagesLength = pipeline.details.stages.length;
-        expect(
-          findPipelineMiniGraph().findAll('[data-testid="mini-pipeline-graph-dropdown"]'),
-        ).toHaveLength(stagesLength);
+        expect(findPipelineMiniGraph().props('stages').length).toBe(stagesLength);
       });
 
       describe('when pipeline does not have stages', () => {
         beforeEach(() => {
           pipeline = createMockPipeline();
-          pipeline.details.stages = null;
+          pipeline.details.stages = [];
 
           createComponent({ pipelines: [pipeline] });
         });
 
         it('stages are not rendered', () => {
-          expect(findPipelineMiniGraph().exists()).toBe(false);
+          expect(findPipelineMiniGraph().props('stages')).toHaveLength(0);
         });
       });
 
-      it('should not update dropdown', () => {
-        expect(findPipelineMiniGraph().props('updateDropdown')).toBe(false);
-      });
-
-      it('when update graph dropdown is set, should update graph dropdown', () => {
-        createComponent({ pipelines: [pipeline], updateGraphDropdown: true });
-
-        expect(findPipelineMiniGraph().props('updateDropdown')).toBe(true);
-      });
-
       it('when action request is complete, should refresh table', () => {
         findPipelineMiniGraph().vm.$emit('pipelineActionRequestComplete');
 
diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_pipeline_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_pipeline_spec.js
index 6347e3c3be34cc706c3001069a2b1a0a87c2580d..a32f61c4567bf6cd2bb7a18627afde00615fbd83 100644
--- a/spec/frontend/vue_mr_widget/components/mr_widget_pipeline_spec.js
+++ b/spec/frontend/vue_mr_widget/components/mr_widget_pipeline_spec.js
@@ -4,9 +4,8 @@ import axios from 'axios';
 import MockAdapter from 'axios-mock-adapter';
 import { trimText } from 'helpers/text_helper';
 import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import MRWidgetPipelineComponent from '~/vue_merge_request_widget/components/mr_widget_pipeline.vue';
 import PipelineMiniGraph from '~/pipelines/components/pipelines_list/pipeline_mini_graph.vue';
-import PipelineStage from '~/pipelines/components/pipelines_list/pipeline_stage.vue';
-import PipelineComponent from '~/vue_merge_request_widget/components/mr_widget_pipeline.vue';
 import { SUCCESS } from '~/vue_merge_request_widget/constants';
 import mockData from '../mock_data';
 
@@ -30,14 +29,13 @@ describe('MRWidgetPipeline', () => {
   const findPipelineInfoContainer = () => wrapper.findByTestId('pipeline-info-container');
   const findCommitLink = () => wrapper.findByTestId('commit-link');
   const findPipelineFinishedAt = () => wrapper.findByTestId('finished-at');
-  const findPipelineMiniGraph = () => wrapper.findComponent(PipelineMiniGraph);
-  const findAllPipelineStages = () => wrapper.findAllComponents(PipelineStage);
   const findPipelineCoverage = () => wrapper.findByTestId('pipeline-coverage');
   const findPipelineCoverageDelta = () => wrapper.findByTestId('pipeline-coverage-delta');
   const findPipelineCoverageTooltipText = () =>
     wrapper.findByTestId('pipeline-coverage-tooltip').text();
   const findPipelineCoverageDeltaTooltipText = () =>
     wrapper.findByTestId('pipeline-coverage-delta-tooltip').text();
+  const findPipelineMiniGraph = () => wrapper.findComponent(PipelineMiniGraph);
   const findMonitoringPipelineMessage = () => wrapper.findByTestId('monitoring-pipeline-message');
   const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
 
@@ -45,7 +43,7 @@ describe('MRWidgetPipeline', () => {
 
   const createWrapper = (props = {}, mountFn = shallowMount) => {
     wrapper = extendedWrapper(
-      mountFn(PipelineComponent, {
+      mountFn(MRWidgetPipelineComponent, {
         propsData: {
           ...defaultProps,
           ...props,
@@ -106,8 +104,10 @@ describe('MRWidgetPipeline', () => {
     });
 
     it('should render pipeline graph', () => {
+      const stagesCount = mockData.pipeline.details.stages.length;
+
       expect(findPipelineMiniGraph().exists()).toBe(true);
-      expect(findAllPipelineStages()).toHaveLength(mockData.pipeline.details.stages.length);
+      expect(findPipelineMiniGraph().props('stages')).toHaveLength(stagesCount);
     });
 
     describe('should render pipeline coverage information', () => {
@@ -176,15 +176,11 @@ describe('MRWidgetPipeline', () => {
       expect(findPipelineInfoContainer().text()).toMatch(mockData.pipeline.details.status.label);
     });
 
-    it('should render pipeline graph with correct styles', () => {
+    it('should render pipeline graph', () => {
       const stagesCount = mockData.pipeline.details.stages.length;
 
       expect(findPipelineMiniGraph().exists()).toBe(true);
-      expect(findPipelineMiniGraph().findAll('.mr-widget-pipeline-stages')).toHaveLength(
-        stagesCount,
-      );
-
-      expect(findAllPipelineStages()).toHaveLength(stagesCount);
+      expect(findPipelineMiniGraph().props('stages')).toHaveLength(stagesCount);
     });
 
     it('should render coverage information', () => {