diff --git a/app/assets/javascripts/commit/pipelines/pipelines_bundle.js b/app/assets/javascripts/commit/pipelines/pipelines_bundle.js
index b5a988df8975eeceda684c48c992573fc2b3fe3d..a9f2d462c31092c612df50193c44c1a62f64becc 100644
--- a/app/assets/javascripts/commit/pipelines/pipelines_bundle.js
+++ b/app/assets/javascripts/commit/pipelines/pipelines_bundle.js
@@ -1,8 +1,9 @@
-/* eslint-disable no-new, no-param-reassign */
-/* global Vue, CommitsPipelineStore, PipelinesService, Flash */
+/* eslint-disable no-param-reassign */
+import CommitPipelinesTable from './pipelines_table';
 
 window.Vue = require('vue');
-require('./pipelines_table');
+window.Vue.use(require('vue-resource'));
+
 /**
  * Commits View > Pipelines Tab > Pipelines Table.
  * Merge Request View > Pipelines Tab > Pipelines Table.
@@ -21,7 +22,7 @@ $(() => {
   }
 
   const pipelineTableViewEl = document.querySelector('#commit-pipeline-table-view');
-  gl.commits.pipelines.PipelinesTableBundle = new gl.commits.pipelines.PipelinesTableView();
+  gl.commits.pipelines.PipelinesTableBundle = new CommitPipelinesTable();
 
   if (pipelineTableViewEl && pipelineTableViewEl.dataset.disableInitialization === undefined) {
     gl.commits.pipelines.PipelinesTableBundle.$mount(pipelineTableViewEl);
diff --git a/app/assets/javascripts/commit/pipelines/pipelines_service.js b/app/assets/javascripts/commit/pipelines/pipelines_service.js
deleted file mode 100644
index 8ae98f9bf978fd95d197109f4c80e054227c2561..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/commit/pipelines/pipelines_service.js
+++ /dev/null
@@ -1,44 +0,0 @@
-/* globals Vue */
-/* eslint-disable no-unused-vars, no-param-reassign */
-
-/**
- * Pipelines service.
- *
- * Used to fetch the data used to render the pipelines table.
- * Uses Vue.Resource
- */
-class PipelinesService {
-
-  /**
-   * FIXME: The url provided to request the pipelines in the new merge request
-   * page already has `.json`.
-   * This should be fixed when the endpoint is improved.
-   *
-   * @param  {String} root
-   */
-  constructor(root) {
-    let endpoint;
-
-    if (root.indexOf('.json') === -1) {
-      endpoint = `${root}.json`;
-    } else {
-      endpoint = root;
-    }
-    this.pipelines = Vue.resource(endpoint);
-  }
-
-  /**
-   * Given the root param provided when the class is initialized, will
-   * make a GET request.
-   *
-   * @return {Promise}
-   */
-  all() {
-    return this.pipelines.get();
-  }
-}
-
-window.gl = window.gl || {};
-gl.commits = gl.commits || {};
-gl.commits.pipelines = gl.commits.pipelines || {};
-gl.commits.pipelines.PipelinesService = PipelinesService;
diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.js b/app/assets/javascripts/commit/pipelines/pipelines_table.js
index 631ed34851c9318fc99264f5b3d4c4ee1a4e7c1b..832c4b1bd2ad1e6b16201766743d557ed228bc4d 100644
--- a/app/assets/javascripts/commit/pipelines/pipelines_table.js
+++ b/app/assets/javascripts/commit/pipelines/pipelines_table.js
@@ -1,13 +1,12 @@
-/* eslint-disable no-new, no-param-reassign */
-/* global Vue, CommitsPipelineStore, PipelinesService, Flash */
-
-window.Vue = require('vue');
-window.Vue.use(require('vue-resource'));
-require('../../lib/utils/common_utils');
-require('../../vue_shared/vue_resource_interceptor');
-require('../../vue_shared/components/pipelines_table');
-require('./pipelines_service');
-const PipelineStore = require('./pipelines_store');
+/* eslint-disable no-new*/
+/* global Flash */
+import Vue from 'vue';
+import PipelinesTableComponent from '../../vue_shared/components/pipelines_table';
+import PipelinesService from '../../vue_pipelines_index/services/pipelines_service';
+import PipelineStore from '../../vue_pipelines_index/stores/pipelines_store';
+import eventHub from '../../vue_pipelines_index/event_hub';
+import '../../lib/utils/common_utils';
+import '../../vue_shared/vue_resource_interceptor';
 
 /**
  *
@@ -20,48 +19,59 @@ const PipelineStore = require('./pipelines_store');
  * as soon as we have Webpack and can load them directly into JS files.
  */
 
-(() => {
-  window.gl = window.gl || {};
-  gl.commits = gl.commits || {};
-  gl.commits.pipelines = gl.commits.pipelines || {};
+export default Vue.component('pipelines-table', {
+  components: {
+    'pipelines-table-component': PipelinesTableComponent,
+  },
 
-  gl.commits.pipelines.PipelinesTableView = Vue.component('pipelines-table', {
+  /**
+   * Accesses the DOM to provide the needed data.
+   * Returns the necessary props to render `pipelines-table-component` component.
+   *
+   * @return {Object}
+   */
+  data() {
+    const pipelinesTableData = document.querySelector('#commit-pipeline-table-view').dataset;
+    const store = new PipelineStore();
 
-    components: {
-      'pipelines-table-component': gl.pipelines.PipelinesTableComponent,
-    },
+    return {
+      endpoint: pipelinesTableData.endpoint,
+      store,
+      state: store.state,
+      isLoading: false,
+    };
+  },
 
-    /**
-     * Accesses the DOM to provide the needed data.
-     * Returns the necessary props to render `pipelines-table-component` component.
-     *
-     * @return {Object}
-     */
-    data() {
-      const pipelinesTableData = document.querySelector('#commit-pipeline-table-view').dataset;
-      const store = new PipelineStore();
+  /**
+   * When the component is about to be mounted, tell the service to fetch the data
+   *
+   * A request to fetch the pipelines will be made.
+   * In case of a successfull response we will store the data in the provided
+   * store, in case of a failed response we need to warn the user.
+   *
+   */
+  beforeMount() {
+    this.service = new PipelinesService(this.endpoint);
 
-      return {
-        endpoint: pipelinesTableData.endpoint,
-        store,
-        state: store.state,
-        isLoading: false,
-      };
-    },
+    this.fetchPipelines();
+
+    eventHub.$on('refreshPipelines', this.fetchPipelines);
+  },
+
+  beforeUpdate() {
+    if (this.state.pipelines.length && this.$children) {
+      this.store.startTimeAgoLoops.call(this, Vue);
+    }
+  },
 
-    /**
-     * When the component is about to be mounted, tell the service to fetch the data
-     *
-     * A request to fetch the pipelines will be made.
-     * In case of a successfull response we will store the data in the provided
-     * store, in case of a failed response we need to warn the user.
-     *
-     */
-    beforeMount() {
-      const pipelinesService = new gl.commits.pipelines.PipelinesService(this.endpoint);
+  beforeDestroyed() {
+    eventHub.$off('refreshPipelines');
+  },
 
+  methods: {
+    fetchPipelines() {
       this.isLoading = true;
-      return pipelinesService.all()
+      return this.service.getPipelines()
         .then(response => response.json())
         .then((json) => {
           // depending of the endpoint the response can either bring a `pipelines` key or not.
@@ -71,34 +81,30 @@ const PipelineStore = require('./pipelines_store');
         })
         .catch(() => {
           this.isLoading = false;
-          new Flash('An error occurred while fetching the pipelines, please reload the page again.', 'alert');
+          new Flash('An error occurred while fetching the pipelines, please reload the page again.');
         });
     },
+  },
 
-    beforeUpdate() {
-      if (this.state.pipelines.length && this.$children) {
-        PipelineStore.startTimeAgoLoops.call(this, Vue);
-      }
-    },
-
-    template: `
-      <div class="pipelines">
-        <div class="realtime-loading" v-if="isLoading">
-          <i class="fa fa-spinner fa-spin"></i>
-        </div>
+  template: `
+    <div class="pipelines">
+      <div class="realtime-loading" v-if="isLoading">
+        <i class="fa fa-spinner fa-spin"></i>
+      </div>
 
-        <div class="blank-state blank-state-no-icon"
-          v-if="!isLoading && state.pipelines.length === 0">
-          <h2 class="blank-state-title js-blank-state-title">
-            No pipelines to show
-          </h2>
-        </div>
+      <div class="blank-state blank-state-no-icon"
+        v-if="!isLoading && state.pipelines.length === 0">
+        <h2 class="blank-state-title js-blank-state-title">
+          No pipelines to show
+        </h2>
+      </div>
 
-        <div class="table-holder pipelines"
-          v-if="!isLoading && state.pipelines.length > 0">
-          <pipelines-table-component :pipelines="state.pipelines"/>
-        </div>
+      <div class="table-holder pipelines"
+        v-if="!isLoading && state.pipelines.length > 0">
+        <pipelines-table-component
+          :pipelines="state.pipelines"
+          :service="service" />
       </div>
-    `,
-  });
-})();
+    </div>
+  `,
+});
diff --git a/app/assets/javascripts/environments/components/environment.js b/app/assets/javascripts/environments/components/environment.js
index 0923ce6b550fac7d618ebee93590b66650386505..51aab8460f6e16dd6a470bb3c1620e6088446564 100644
--- a/app/assets/javascripts/environments/components/environment.js
+++ b/app/assets/javascripts/environments/components/environment.js
@@ -1,21 +1,18 @@
-/* eslint-disable no-param-reassign, no-new */
+/* eslint-disable no-new */
 /* global Flash */
+import Vue from 'vue';
 import EnvironmentsService from '../services/environments_service';
 import EnvironmentTable from './environments_table';
 import EnvironmentsStore from '../stores/environments_store';
+import TablePaginationComponent from '../../vue_shared/components/table_pagination';
+import '../../lib/utils/common_utils';
 import eventHub from '../event_hub';
 
-const Vue = window.Vue = require('vue');
-window.Vue.use(require('vue-resource'));
-require('../../vue_shared/components/table_pagination');
-require('../../lib/utils/common_utils');
-require('../../vue_shared/vue_resource_interceptor');
-
 export default Vue.component('environment-component', {
 
   components: {
     'environment-table': EnvironmentTable,
-    'table-pagination': gl.VueGlPagination,
+    'table-pagination': TablePaginationComponent,
   },
 
   data() {
@@ -59,7 +56,6 @@ export default Vue.component('environment-component', {
     canCreateEnvironmentParsed() {
       return gl.utils.convertPermissionToBoolean(this.canCreateEnvironment);
     },
-
   },
 
   /**
diff --git a/app/assets/javascripts/environments/components/environment_item.js b/app/assets/javascripts/environments/components/environment_item.js
index 93919d41c6009cc00792e5f5249b6a2635356182..66ed10e19d12f9c80b50a17b70323f5e92bc47cf 100644
--- a/app/assets/javascripts/environments/components/environment_item.js
+++ b/app/assets/javascripts/environments/components/environment_item.js
@@ -1,24 +1,22 @@
 import Timeago from 'timeago.js';
+import '../../lib/utils/text_utility';
 import ActionsComponent from './environment_actions';
 import ExternalUrlComponent from './environment_external_url';
 import StopComponent from './environment_stop';
 import RollbackComponent from './environment_rollback';
 import TerminalButtonComponent from './environment_terminal_button';
-import '../../lib/utils/text_utility';
-import '../../vue_shared/components/commit';
+import CommitComponent from '../../vue_shared/components/commit';
 
 /**
  * Envrionment Item Component
  *
  * Renders a table row for each environment.
  */
-
 const timeagoInstance = new Timeago();
 
 export default {
-
   components: {
-    'commit-component': gl.CommitComponent,
+    'commit-component': CommitComponent,
     'actions-component': ActionsComponent,
     'external-url-component': ExternalUrlComponent,
     'stop-component': StopComponent,
diff --git a/app/assets/javascripts/environments/components/environments_table.js b/app/assets/javascripts/environments/components/environments_table.js
index 5f07b612b915f87c590c8c5863a1d5c6008526d0..338dff40bc986982e5604d53435ac37bbf4e409d 100644
--- a/app/assets/javascripts/environments/components/environments_table.js
+++ b/app/assets/javascripts/environments/components/environments_table.js
@@ -1,11 +1,11 @@
 /**
  * Render environments table.
  */
-import EnvironmentItem from './environment_item';
+import EnvironmentTableRowComponent from './environment_item';
 
 export default {
   components: {
-    'environment-item': EnvironmentItem,
+    'environment-item': EnvironmentTableRowComponent,
   },
 
   props: {
diff --git a/app/assets/javascripts/environments/folder/environments_folder_view.js b/app/assets/javascripts/environments/folder/environments_folder_view.js
index 7abcf6dbbea79fe62e3297cac3a3deafd6837fd3..8abbcf0c227f20ecf4b1b2929bda18ddcee705ea 100644
--- a/app/assets/javascripts/environments/folder/environments_folder_view.js
+++ b/app/assets/javascripts/environments/folder/environments_folder_view.js
@@ -1,20 +1,17 @@
-/* eslint-disable no-param-reassign, no-new */
+/* eslint-disable no-new */
 /* global Flash */
+import Vue from 'vue';
 import EnvironmentsService from '../services/environments_service';
 import EnvironmentTable from '../components/environments_table';
 import EnvironmentsStore from '../stores/environments_store';
-
-const Vue = window.Vue = require('vue');
-window.Vue.use(require('vue-resource'));
-require('../../vue_shared/components/table_pagination');
-require('../../lib/utils/common_utils');
-require('../../vue_shared/vue_resource_interceptor');
+import TablePaginationComponent from '../../vue_shared/components/table_pagination';
+import '../../lib/utils/common_utils';
+import '../../vue_shared/vue_resource_interceptor';
 
 export default Vue.component('environment-folder-view', {
-
   components: {
     'environment-table': EnvironmentTable,
-    'table-pagination': gl.VueGlPagination,
+    'table-pagination': TablePaginationComponent,
   },
 
   data() {
diff --git a/app/assets/javascripts/environments/services/environments_service.js b/app/assets/javascripts/environments/services/environments_service.js
index 76296c83d113001a420de34def120f461b512465..07040bf0d7374db826fa6e4fe15b969853745250 100644
--- a/app/assets/javascripts/environments/services/environments_service.js
+++ b/app/assets/javascripts/environments/services/environments_service.js
@@ -1,5 +1,8 @@
 /* eslint-disable class-methods-use-this */
 import Vue from 'vue';
+import VueResource from 'vue-resource';
+
+Vue.use(VueResource);
 
 export default class EnvironmentsService {
   constructor(endpoint) {
diff --git a/app/assets/javascripts/environments/stores/environments_store.js b/app/assets/javascripts/environments/stores/environments_store.js
index d3fe3872c56348521c5d579ed6518223d033590c..3c3084f3b78961d85056b23b7cece66fd3cc9193 100644
--- a/app/assets/javascripts/environments/stores/environments_store.js
+++ b/app/assets/javascripts/environments/stores/environments_store.js
@@ -1,5 +1,4 @@
 import '~/lib/utils/common_utils';
-
 /**
  * Environments Store.
  *
diff --git a/app/assets/javascripts/vue_pipelines_index/components/async_button.js b/app/assets/javascripts/vue_pipelines_index/components/async_button.js
new file mode 100644
index 0000000000000000000000000000000000000000..aaebf29d8ae3cbfe463a346f88ddf5e3e51dec80
--- /dev/null
+++ b/app/assets/javascripts/vue_pipelines_index/components/async_button.js
@@ -0,0 +1,92 @@
+/* eslint-disable no-new, no-alert */
+/* global Flash */
+import '~/flash';
+import eventHub from '../event_hub';
+
+export default {
+  props: {
+    endpoint: {
+      type: String,
+      required: true,
+    },
+
+    service: {
+      type: Object,
+      required: true,
+    },
+
+    title: {
+      type: String,
+      required: true,
+    },
+
+    icon: {
+      type: String,
+      required: true,
+    },
+
+    cssClass: {
+      type: String,
+      required: true,
+    },
+
+    confirmActionMessage: {
+      type: String,
+      required: false,
+    },
+  },
+
+  data() {
+    return {
+      isLoading: false,
+    };
+  },
+
+  computed: {
+    iconClass() {
+      return `fa fa-${this.icon}`;
+    },
+
+    buttonClass() {
+      return `btn has-tooltip ${this.cssClass}`;
+    },
+  },
+
+  methods: {
+    onClick() {
+      if (this.confirmActionMessage && confirm(this.confirmActionMessage)) {
+        this.makeRequest();
+      } else if (!this.confirmActionMessage) {
+        this.makeRequest();
+      }
+    },
+
+    makeRequest() {
+      this.isLoading = true;
+
+      this.service.postAction(this.endpoint)
+      .then(() => {
+        this.isLoading = false;
+        eventHub.$emit('refreshPipelines');
+      })
+      .catch(() => {
+        this.isLoading = false;
+        new Flash('An error occured while making the request.');
+      });
+    },
+  },
+
+  template: `
+    <button
+      type="button"
+      @click="onClick"
+      :class="buttonClass"
+      :title="title"
+      :aria-label="title"
+      data-placement="top"
+      :disabled="isLoading">
+      <i :class="iconClass" aria-hidden="true"/>
+      <i class="fa fa-spinner fa-spin" aria-hidden="true" v-if="isLoading" />
+    </button>
+  `,
+};
diff --git a/app/assets/javascripts/vue_pipelines_index/components/pipeline_url.js b/app/assets/javascripts/vue_pipelines_index/components/pipeline_url.js
new file mode 100644
index 0000000000000000000000000000000000000000..4e183d5c8eca1400f87745c7a43ecb6296c90694
--- /dev/null
+++ b/app/assets/javascripts/vue_pipelines_index/components/pipeline_url.js
@@ -0,0 +1,56 @@
+export default {
+  props: [
+    'pipeline',
+  ],
+  computed: {
+    user() {
+      return !!this.pipeline.user;
+    },
+  },
+  template: `
+    <td>
+      <a
+        :href="pipeline.path"
+        class="js-pipeline-url-link">
+        <span class="pipeline-id">#{{pipeline.id}}</span>
+      </a>
+      <span>by</span>
+      <a
+        class="js-pipeline-url-user"
+        v-if="user"
+        :href="pipeline.user.web_url">
+        <img
+          v-if="user"
+          class="avatar has-tooltip s20 "
+          :title="pipeline.user.name"
+          data-container="body"
+          :src="pipeline.user.avatar_url"
+        >
+      </a>
+      <span
+        v-if="!user"
+        class="js-pipeline-url-api api monospace">
+        API
+      </span>
+      <span
+        v-if="pipeline.flags.latest"
+        class="js-pipeline-url-lastest label label-success has-tooltip"
+        title="Latest pipeline for this branch"
+        data-original-title="Latest pipeline for this branch">
+        latest
+      </span>
+      <span
+        v-if="pipeline.flags.yaml_errors"
+        class="js-pipeline-url-yaml label label-danger has-tooltip"
+        :title="pipeline.yaml_errors"
+        :data-original-title="pipeline.yaml_errors">
+        yaml invalid
+      </span>
+      <span
+        v-if="pipeline.flags.stuck"
+        class="js-pipeline-url-stuck label label-warning">
+        stuck
+      </span>
+    </td>
+  `,
+};
diff --git a/app/assets/javascripts/vue_pipelines_index/components/pipelines_actions.js b/app/assets/javascripts/vue_pipelines_index/components/pipelines_actions.js
new file mode 100644
index 0000000000000000000000000000000000000000..4bb2b04888460d24e8d3b74a95580c9e5c9cdf8a
--- /dev/null
+++ b/app/assets/javascripts/vue_pipelines_index/components/pipelines_actions.js
@@ -0,0 +1,71 @@
+/* eslint-disable no-new */
+/* global Flash */
+import '~/flash';
+import playIconSvg from 'icons/_icon_play.svg';
+import eventHub from '../event_hub';
+
+export default {
+  props: {
+    actions: {
+      type: Array,
+      required: true,
+    },
+
+    service: {
+      type: Object,
+      required: true,
+    },
+  },
+
+  data() {
+    return {
+      playIconSvg,
+      isLoading: false,
+    };
+  },
+
+  methods: {
+    onClickAction(endpoint) {
+      this.isLoading = true;
+
+      this.service.postAction(endpoint)
+      .then(() => {
+        this.isLoading = false;
+        eventHub.$emit('refreshPipelines');
+      })
+      .catch(() => {
+        this.isLoading = false;
+        new Flash('An error occured while making the request.');
+      });
+    },
+  },
+
+  template: `
+    <div class="btn-group" v-if="actions">
+      <button
+        type="button"
+        class="dropdown-toggle btn btn-default has-tooltip js-pipeline-dropdown-manual-actions"
+        title="Manual job"
+        data-toggle="dropdown"
+        data-placement="top"
+        aria-label="Manual job"
+        :disabled="isLoading">
+        ${playIconSvg}
+        <i class="fa fa-caret-down" aria-hidden="true"></i>
+        <i v-if="isLoading" class="fa fa-spinner fa-spin" aria-hidden="true"></i>
+      </button>
+
+      <ul class="dropdown-menu dropdown-menu-align-right">
+        <li v-for="action in actions">
+          <button
+            type="button"
+            class="js-pipeline-action-link no-btn"
+            @click="onClickAction(action.path)">
+            ${playIconSvg}
+            <span>{{action.name}}</span>
+          </button>
+        </li>
+      </ul>
+    </div>
+  `,
+};
diff --git a/app/assets/javascripts/vue_pipelines_index/components/pipelines_artifacts.js b/app/assets/javascripts/vue_pipelines_index/components/pipelines_artifacts.js
new file mode 100644
index 0000000000000000000000000000000000000000..3555040d60f754b561a49508e6b77a722529e64f
--- /dev/null
+++ b/app/assets/javascripts/vue_pipelines_index/components/pipelines_artifacts.js
@@ -0,0 +1,32 @@
+export default {
+  props: {
+    artifacts: {
+      type: Array,
+      required: true,
+    },
+  },
+
+  template: `
+    <div class="btn-group" role="group">
+      <button
+        class="dropdown-toggle btn btn-default build-artifacts has-tooltip js-pipeline-dropdown-download"
+        title="Artifacts"
+        data-placement="top"
+        data-toggle="dropdown"
+        aria-label="Artifacts">
+        <i class="fa fa-download" aria-hidden="true"></i>
+        <i class="fa fa-caret-down" aria-hidden="true"></i>
+      </button>
+      <ul class="dropdown-menu dropdown-menu-align-right">
+        <li v-for="artifact in artifacts">
+          <a
+            rel="nofollow"
+            :href="artifact.path">
+            <i class="fa fa-download" aria-hidden="true"></i>
+            <span>Download {{artifact.name}} artifacts</span>
+          </a>
+        </li>
+      </ul>
+    </div>
+  `,
+};
diff --git a/app/assets/javascripts/vue_pipelines_index/components/stage.js b/app/assets/javascripts/vue_pipelines_index/components/stage.js
new file mode 100644
index 0000000000000000000000000000000000000000..a2c29002707005a2fd35c5c5f1767e02645c9767
--- /dev/null
+++ b/app/assets/javascripts/vue_pipelines_index/components/stage.js
@@ -0,0 +1,116 @@
+/* global Flash */
+import canceledSvg from 'icons/_icon_status_canceled_borderless.svg';
+import createdSvg from 'icons/_icon_status_created_borderless.svg';
+import failedSvg from 'icons/_icon_status_failed_borderless.svg';
+import manualSvg from 'icons/_icon_status_manual_borderless.svg';
+import pendingSvg from 'icons/_icon_status_pending_borderless.svg';
+import runningSvg from 'icons/_icon_status_running_borderless.svg';
+import skippedSvg from 'icons/_icon_status_skipped_borderless.svg';
+import successSvg from 'icons/_icon_status_success_borderless.svg';
+import warningSvg from 'icons/_icon_status_warning_borderless.svg';
+
+export default {
+  data() {
+    const svgsDictionary = {
+      icon_status_canceled: canceledSvg,
+      icon_status_created: createdSvg,
+      icon_status_failed: failedSvg,
+      icon_status_manual: manualSvg,
+      icon_status_pending: pendingSvg,
+      icon_status_running: runningSvg,
+      icon_status_skipped: skippedSvg,
+      icon_status_success: successSvg,
+      icon_status_warning: warningSvg,
+    };
+
+    return {
+      builds: '',
+      spinner: '<span class="fa fa-spinner fa-spin"></span>',
+      svg: svgsDictionary[this.stage.status.icon],
+    };
+  },
+
+  props: {
+    stage: {
+      type: Object,
+      required: true,
+    },
+  },
+
+  updated() {
+    if (this.builds) {
+      this.stopDropdownClickPropagation();
+    }
+  },
+
+  methods: {
+    fetchBuilds(e) {
+      const ariaExpanded = e.currentTarget.attributes['aria-expanded'];
+
+      if (ariaExpanded && (ariaExpanded.textContent === 'true')) return null;
+
+      return this.$http.get(this.stage.dropdown_path)
+        .then((response) => {
+          this.builds = JSON.parse(response.body).html;
+        }, () => {
+          const flash = new Flash('Something went wrong on our end.');
+          return flash;
+        });
+    },
+
+    /**
+     * When the user right clicks or cmd/ctrl + click in the job name
+     * the dropdown should not be closed and the link should open in another tab,
+     * so we stop propagation of the click event inside the dropdown.
+     *
+     * Since this component is rendered multiple times per page we need to guarantee we only
+     * target the click event of this component.
+     */
+    stopDropdownClickPropagation() {
+      $(this.$el.querySelectorAll('.js-builds-dropdown-list a.mini-pipeline-graph-dropdown-item')).on('click', (e) => {
+        e.stopPropagation();
+      });
+    },
+  },
+  computed: {
+    buildsOrSpinner() {
+      return this.builds ? this.builds : this.spinner;
+    },
+    dropdownClass() {
+      if (this.builds) return 'js-builds-dropdown-container';
+      return 'js-builds-dropdown-loading builds-dropdown-loading';
+    },
+    buildStatus() {
+      return `Build: ${this.stage.status.label}`;
+    },
+    tooltip() {
+      return `has-tooltip ci-status-icon ci-status-icon-${this.stage.status.group}`;
+    },
+    triggerButtonClass() {
+      return `mini-pipeline-graph-dropdown-toggle has-tooltip js-builds-dropdown-button ci-status-icon-${this.stage.status.group}`;
+    },
+  },
+  template: `
+    <div>
+      <button
+        @click="fetchBuilds($event)"
+        :class="triggerButtonClass"
+        :title="stage.title"
+        data-placement="top"
+        data-toggle="dropdown"
+        type="button"
+        :aria-label="stage.title">
+        <span v-html="svg" aria-hidden="true"></span>
+        <i class="fa fa-caret-down" aria-hidden="true"></i>
+      </button>
+      <ul class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container">
+        <div class="arrow-up" aria-hidden="true"></div>
+        <div
+          :class="dropdownClass"
+          class="js-builds-dropdown-list scrollable-menu"
+          v-html="buildsOrSpinner">
+        </div>
+      </ul>
+    </div>
+  `,
+};
diff --git a/app/assets/javascripts/vue_pipelines_index/components/status.js b/app/assets/javascripts/vue_pipelines_index/components/status.js
new file mode 100644
index 0000000000000000000000000000000000000000..21a281af438ddaeafd95cdbebd2bfc5156a45d48
--- /dev/null
+++ b/app/assets/javascripts/vue_pipelines_index/components/status.js
@@ -0,0 +1,60 @@
+import canceledSvg from 'icons/_icon_status_canceled.svg';
+import createdSvg from 'icons/_icon_status_created.svg';
+import failedSvg from 'icons/_icon_status_failed.svg';
+import manualSvg from 'icons/_icon_status_manual.svg';
+import pendingSvg from 'icons/_icon_status_pending.svg';
+import runningSvg from 'icons/_icon_status_running.svg';
+import skippedSvg from 'icons/_icon_status_skipped.svg';
+import successSvg from 'icons/_icon_status_success.svg';
+import warningSvg from 'icons/_icon_status_warning.svg';
+
+export default {
+  props: {
+    pipeline: {
+      type: Object,
+      required: true,
+    },
+  },
+
+  data() {
+    const svgsDictionary = {
+      icon_status_canceled: canceledSvg,
+      icon_status_created: createdSvg,
+      icon_status_failed: failedSvg,
+      icon_status_manual: manualSvg,
+      icon_status_pending: pendingSvg,
+      icon_status_running: runningSvg,
+      icon_status_skipped: skippedSvg,
+      icon_status_success: successSvg,
+      icon_status_warning: warningSvg,
+    };
+
+    return {
+      svg: svgsDictionary[this.pipeline.details.status.icon],
+    };
+  },
+
+  computed: {
+    cssClasses() {
+      return `ci-status ci-${this.pipeline.details.status.group}`;
+    },
+
+    detailsPath() {
+      const { status } = this.pipeline.details;
+      return status.has_details ? status.details_path : false;
+    },
+
+    content() {
+      return `${this.svg} ${this.pipeline.details.status.text}`;
+    },
+  },
+  template: `
+    <td class="commit-link">
+      <a
+        :class="cssClasses"
+        :href="detailsPath"
+        v-html="content">
+      </a>
+    </td>
+  `,
+};
diff --git a/app/assets/javascripts/vue_pipelines_index/components/time_ago.js b/app/assets/javascripts/vue_pipelines_index/components/time_ago.js
new file mode 100644
index 0000000000000000000000000000000000000000..498d0715f54657320ce8e6c7705c218c679b6324
--- /dev/null
+++ b/app/assets/javascripts/vue_pipelines_index/components/time_ago.js
@@ -0,0 +1,71 @@
+import iconTimerSvg from 'icons/_icon_timer.svg';
+import '../../lib/utils/datetime_utility';
+
+export default {
+  data() {
+    return {
+      currentTime: new Date(),
+      iconTimerSvg,
+    };
+  },
+  props: ['pipeline'],
+  computed: {
+    timeAgo() {
+      return gl.utils.getTimeago();
+    },
+    localTimeFinished() {
+      return gl.utils.formatDate(this.pipeline.details.finished_at);
+    },
+    timeStopped() {
+      const changeTime = this.currentTime;
+      const options = {
+        weekday: 'long',
+        year: 'numeric',
+        month: 'short',
+        day: 'numeric',
+      };
+      options.timeZoneName = 'short';
+      const finished = this.pipeline.details.finished_at;
+      if (!finished && changeTime) return false;
+      return ({ words: this.timeAgo.format(finished) });
+    },
+    duration() {
+      const { duration } = this.pipeline.details;
+      const date = new Date(duration * 1000);
+
+      let hh = date.getUTCHours();
+      let mm = date.getUTCMinutes();
+      let ss = date.getSeconds();
+
+      if (hh < 10) hh = `0${hh}`;
+      if (mm < 10) mm = `0${mm}`;
+      if (ss < 10) ss = `0${ss}`;
+
+      if (duration !== null) return `${hh}:${mm}:${ss}`;
+      return false;
+    },
+  },
+  methods: {
+    changeTime() {
+      this.currentTime = new Date();
+    },
+  },
+  template: `
+    <td class="pipelines-time-ago">
+      <p class="duration" v-if='duration'>
+        <span v-html="iconTimerSvg"></span>
+        {{duration}}
+      </p>
+      <p class="finished-at" v-if='timeStopped'>
+        <i class="fa fa-calendar"></i>
+        <time
+          data-toggle="tooltip"
+          data-placement="top"
+          data-container="body"
+          :data-original-title='localTimeFinished'>
+          {{timeStopped.words}}
+        </time>
+      </p>
+    </td>
+  `,
+};
diff --git a/app/assets/javascripts/vue_pipelines_index/event_hub.js b/app/assets/javascripts/vue_pipelines_index/event_hub.js
new file mode 100644
index 0000000000000000000000000000000000000000..0948c2e53524a736a55c060600868ce89ee7687a
--- /dev/null
+++ b/app/assets/javascripts/vue_pipelines_index/event_hub.js
@@ -0,0 +1,3 @@
+import Vue from 'vue';
+
+export default new Vue();
diff --git a/app/assets/javascripts/vue_pipelines_index/index.js b/app/assets/javascripts/vue_pipelines_index/index.js
index a90bd1518e9697acb70698f715290f1893d78018..b4e2d3a1143dc35d567dc8a0e3e721a7c5663ccc 100644
--- a/app/assets/javascripts/vue_pipelines_index/index.js
+++ b/app/assets/javascripts/vue_pipelines_index/index.js
@@ -1,29 +1,28 @@
-/* eslint-disable no-param-reassign */
-/* global Vue, VueResource, gl */
-window.Vue = require('vue');
+import PipelinesStore from './stores/pipelines_store';
+import PipelinesComponent from './pipelines';
+import '../vue_shared/vue_resource_interceptor';
+
+const Vue = window.Vue = require('vue');
 window.Vue.use(require('vue-resource'));
-require('../lib/utils/common_utils');
-require('../vue_shared/vue_resource_interceptor');
-require('./pipelines');
 
 $(() => new Vue({
   el: document.querySelector('.vue-pipelines-index'),
 
   data() {
     const project = document.querySelector('.pipelines');
+    const store = new PipelinesStore();
 
     return {
-      scope: project.dataset.url,
-      store: new gl.PipelineStore(),
+      store,
+      endpoint: project.dataset.url,
     };
   },
   components: {
-    'vue-pipelines': gl.VuePipelines,
+    'vue-pipelines': PipelinesComponent,
   },
   template: `
     <vue-pipelines
-      :scope="scope"
-      :store="store">
-    </vue-pipelines>
+      :endpoint="endpoint"
+      :store="store" />
   `,
 }));
diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js
deleted file mode 100644
index 583d6915a8530c2151b61b82313666d594d3def2..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js
+++ /dev/null
@@ -1,123 +0,0 @@
-/* global Vue, Flash, gl */
-/* eslint-disable no-param-reassign,  no-alert */
-const playIconSvg = require('icons/_icon_play.svg');
-
-((gl) => {
-  gl.VuePipelineActions = Vue.extend({
-    props: ['pipeline'],
-    computed: {
-      actions() {
-        return this.pipeline.details.manual_actions.length > 0;
-      },
-      artifacts() {
-        return this.pipeline.details.artifacts.length > 0;
-      },
-    },
-    methods: {
-      download(name) {
-        return `Download ${name} artifacts`;
-      },
-
-      /**
-       * Shows a dialog when the user clicks in the cancel button.
-       * We need to prevent the default behavior and stop propagation because the
-       * link relies on UJS.
-       *
-       * @param  {Event} event
-       */
-      confirmAction(event) {
-        if (!confirm('Are you sure you want to cancel this pipeline?')) {
-          event.preventDefault();
-          event.stopPropagation();
-        }
-      },
-    },
-
-    data() {
-      return { playIconSvg };
-    },
-
-    template: `
-      <td class="pipeline-actions">
-        <div class="pull-right">
-          <div class="btn-group">
-            <div class="btn-group" v-if="actions">
-              <button
-                class="dropdown-toggle btn btn-default has-tooltip js-pipeline-dropdown-manual-actions"
-                data-toggle="dropdown"
-                title="Manual job"
-                data-placement="top"
-                data-container="body"
-                aria-label="Manual job">
-                <span v-html="playIconSvg" aria-hidden="true"></span>
-                <i class="fa fa-caret-down" aria-hidden="true"></i>
-              </button>
-              <ul class="dropdown-menu dropdown-menu-align-right">
-                <li v-for='action in pipeline.details.manual_actions'>
-                  <a
-                    rel="nofollow"
-                    data-method="post"
-                    :href="action.path" >
-                    <span v-html="playIconSvg" aria-hidden="true"></span>
-                    <span>{{action.name}}</span>
-                  </a>
-                </li>
-              </ul>
-            </div>
-
-            <div class="btn-group" v-if="artifacts">
-              <button
-                class="dropdown-toggle btn btn-default build-artifacts has-tooltip js-pipeline-dropdown-download"
-                title="Artifacts"
-                data-placement="top"
-                data-container="body"
-                data-toggle="dropdown"
-                aria-label="Artifacts">
-                <i class="fa fa-download" aria-hidden="true"></i>
-                <i class="fa fa-caret-down" aria-hidden="true"></i>
-              </button>
-              <ul class="dropdown-menu dropdown-menu-align-right">
-                <li v-for='artifact in pipeline.details.artifacts'>
-                  <a
-                    rel="nofollow"
-                    :href="artifact.path">
-                    <i class="fa fa-download" aria-hidden="true"></i>
-                    <span>{{download(artifact.name)}}</span>
-                  </a>
-                </li>
-              </ul>
-            </div>
-            <div class="btn-group" v-if="pipeline.flags.retryable">
-              <a
-                class="btn btn-default btn-retry has-tooltip"
-                title="Retry"
-                rel="nofollow"
-                data-method="post"
-                data-placement="top"
-                data-container="body"
-                data-toggle="dropdown"
-                :href='pipeline.retry_path'
-                aria-label="Retry">
-                <i class="fa fa-repeat" aria-hidden="true"></i>
-              </a>
-            </div>
-            <div class="btn-group" v-if="pipeline.flags.cancelable">
-              <a
-                class="btn btn-remove has-tooltip"
-                title="Cancel"
-                rel="nofollow"
-                data-method="post"
-                data-placement="top"
-                data-container="body"
-                data-toggle="dropdown"
-                :href='pipeline.cancel_path'
-                aria-label="Cancel">
-                <i class="fa fa-remove" aria-hidden="true"></i>
-              </a>
-            </div>
-          </div>
-        </div>
-      </td>
-    `,
-  });
-})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_url.js b/app/assets/javascripts/vue_pipelines_index/pipeline_url.js
deleted file mode 100644
index ae5649f0519d67e477c2e08633093bf200ddfb8b..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/vue_pipelines_index/pipeline_url.js
+++ /dev/null
@@ -1,63 +0,0 @@
-/* global Vue, gl */
-/* eslint-disable no-param-reassign */
-
-((gl) => {
-  gl.VuePipelineUrl = Vue.extend({
-    props: [
-      'pipeline',
-    ],
-    computed: {
-      user() {
-        return !!this.pipeline.user;
-      },
-    },
-    template: `
-      <td>
-        <a :href='pipeline.path'>
-          <span class="pipeline-id">#{{pipeline.id}}</span>
-        </a>
-        <span>by</span>
-        <a
-          v-if='user'
-          :href='pipeline.user.web_url'
-        >
-          <img
-            v-if='user'
-            class="avatar has-tooltip s20 "
-            :title='pipeline.user.name'
-            data-container="body"
-            :src='pipeline.user.avatar_url'
-          >
-        </a>
-        <span
-          v-if='!user'
-          class="api monospace"
-        >
-          API
-        </span>
-        <span
-          v-if='pipeline.flags.latest'
-          class="label label-success has-tooltip"
-          title="Latest pipeline for this branch"
-          data-original-title="Latest pipeline for this branch"
-        >
-          latest
-        </span>
-        <span
-          v-if='pipeline.flags.yaml_errors'
-          class="label label-danger has-tooltip"
-          :title='pipeline.yaml_errors'
-          :data-original-title='pipeline.yaml_errors'
-        >
-          yaml invalid
-        </span>
-        <span
-          v-if='pipeline.flags.stuck'
-          class="label label-warning"
-        >
-          stuck
-        </span>
-      </td>
-    `,
-  });
-})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js b/app/assets/javascripts/vue_pipelines_index/pipelines.js
index 601ef41e9171d34955954352f9e4119f19fad760..f389e5e495041a69f704768576e4fe14f52b2588 100644
--- a/app/assets/javascripts/vue_pipelines_index/pipelines.js
+++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js
@@ -1,87 +1,121 @@
-/* global Vue, gl */
-/* eslint-disable no-param-reassign */
+/* global Flash */
+/* eslint-disable no-new */
+import '~/flash';
+import Vue from 'vue';
+import PipelinesService from './services/pipelines_service';
+import eventHub from './event_hub';
+import PipelinesTableComponent from '../vue_shared/components/pipelines_table';
+import TablePaginationComponent from '../vue_shared/components/table_pagination';
 
-window.Vue = require('vue');
-require('../vue_shared/components/table_pagination');
-require('./store');
-require('../vue_shared/components/pipelines_table');
-const CommitPipelinesStoreWithTimeAgo = require('../commit/pipelines/pipelines_store');
-
-((gl) => {
-  gl.VuePipelines = Vue.extend({
-
-    components: {
-      'gl-pagination': gl.VueGlPagination,
-      'pipelines-table-component': gl.pipelines.PipelinesTableComponent,
+export default {
+  props: {
+    endpoint: {
+      type: String,
+      required: true,
     },
 
-    data() {
-      return {
-        pipelines: [],
-        timeLoopInterval: '',
-        intervalId: '',
-        apiScope: 'all',
-        pageInfo: {},
-        pagenum: 1,
-        count: {},
-        pageRequest: false,
-      };
-    },
-    props: ['scope', 'store'],
-    created() {
-      const pagenum = gl.utils.getParameterByName('page');
-      const scope = gl.utils.getParameterByName('scope');
-      if (pagenum) this.pagenum = pagenum;
-      if (scope) this.apiScope = scope;
-
-      this.store.fetchDataLoop.call(this, Vue, this.pagenum, this.scope, this.apiScope);
+    store: {
+      type: Object,
+      required: true,
     },
+  },
+
+  components: {
+    'gl-pagination': TablePaginationComponent,
+    'pipelines-table-component': PipelinesTableComponent,
+  },
+
+  data() {
+    return {
+      state: this.store.state,
+      apiScope: 'all',
+      pagenum: 1,
+      pageRequest: false,
+    };
+  },
+
+  created() {
+    this.service = new PipelinesService(this.endpoint);
+
+    this.fetchPipelines();
+
+    eventHub.$on('refreshPipelines', this.fetchPipelines);
+  },
+
+  beforeUpdate() {
+    if (this.state.pipelines.length && this.$children) {
+      this.store.startTimeAgoLoops.call(this, Vue);
+    }
+  },
 
-    beforeUpdate() {
-      if (this.pipelines.length && this.$children) {
-        CommitPipelinesStoreWithTimeAgo.startTimeAgoLoops.call(this, Vue);
-      }
+  beforeDestroyed() {
+    eventHub.$off('refreshPipelines');
+  },
+
+  methods: {
+    /**
+     * Will change the page number and update the URL.
+     *
+     * @param  {Number} pageNumber desired page to go to.
+     */
+    change(pageNumber) {
+      const param = gl.utils.setParamInURL('page', pageNumber);
+
+      gl.utils.visitUrl(param);
+      return param;
     },
 
-    methods: {
-      /**
-       * Will change the page number and update the URL.
-       *
-       * @param  {Number} pageNumber desired page to go to.
-       */
-      change(pageNumber) {
-        const param = gl.utils.setParamInURL('page', pageNumber);
-
-        gl.utils.visitUrl(param);
-        return param;
-      },
+    fetchPipelines() {
+      const pageNumber = gl.utils.getParameterByName('page') || this.pagenum;
+      const scope = gl.utils.getParameterByName('scope') || this.apiScope;
+
+      this.pageRequest = true;
+      return this.service.getPipelines(scope, pageNumber)
+        .then(resp => ({
+          headers: resp.headers,
+          body: resp.json(),
+        }))
+        .then((response) => {
+          this.store.storeCount(response.body.count);
+          this.store.storePipelines(response.body.pipelines);
+          this.store.storePagination(response.headers);
+        })
+        .then(() => {
+          this.pageRequest = false;
+        })
+        .catch(() => {
+          this.pageRequest = false;
+          new Flash('An error occurred while fetching the pipelines, please reload the page again.');
+        });
     },
-    template: `
-      <div>
-        <div class="pipelines realtime-loading" v-if='pageRequest'>
-          <i class="fa fa-spinner fa-spin"></i>
-        </div>
-
-        <div class="blank-state blank-state-no-icon"
-          v-if="!pageRequest && pipelines.length === 0">
-          <h2 class="blank-state-title js-blank-state-title">
-            No pipelines to show
-          </h2>
-        </div>
-
-        <div class="table-holder" v-if='!pageRequest && pipelines.length'>
-          <pipelines-table-component :pipelines='pipelines'/>
-        </div>
-
-        <gl-pagination
-          v-if='!pageRequest && pipelines.length && pageInfo.total > pageInfo.perPage'
-          :pagenum='pagenum'
-          :change='change'
-          :count='count.all'
-          :pageInfo='pageInfo'
-        >
-        </gl-pagination>
+  },
+  template: `
+    <div>
+      <div class="pipelines realtime-loading" v-if="pageRequest">
+        <i class="fa fa-spinner fa-spin" aria-hidden="true"></i>
+      </div>
+
+      <div class="blank-state blank-state-no-icon"
+        v-if="!pageRequest && state.pipelines.length === 0">
+        <h2 class="blank-state-title js-blank-state-title">
+          No pipelines to show
+        </h2>
+      </div>
+
+      <div class="table-holder" v-if="!pageRequest && state.pipelines.length">
+        <pipelines-table-component
+          :pipelines="state.pipelines"
+          :service="service"/>
       </div>
-    `,
-  });
-})(window.gl || (window.gl = {}));
+
+      <gl-pagination
+        v-if="!pageRequest && state.pipelines.length && state.pageInfo.total > state.pageInfo.perPage"
+        :pagenum="pagenum"
+        :change="change"
+        :count="state.count.all"
+        :pageInfo="state.pageInfo"
+      >
+      </gl-pagination>
+    </div>
+  `,
+};
diff --git a/app/assets/javascripts/vue_pipelines_index/services/pipelines_service.js b/app/assets/javascripts/vue_pipelines_index/services/pipelines_service.js
new file mode 100644
index 0000000000000000000000000000000000000000..708f5068dd30ad2b10660d070589c5eaca84b644
--- /dev/null
+++ b/app/assets/javascripts/vue_pipelines_index/services/pipelines_service.js
@@ -0,0 +1,44 @@
+/* eslint-disable class-methods-use-this */
+import Vue from 'vue';
+import VueResource from 'vue-resource';
+
+Vue.use(VueResource);
+
+export default class PipelinesService {
+
+  /**
+  * Commits and merge request endpoints need to be requested with `.json`.
+  *
+  * The url provided to request the pipelines in the new merge request
+  * page already has `.json`.
+  *
+  * @param  {String} root
+  */
+  constructor(root) {
+    let endpoint;
+
+    if (root.indexOf('.json') === -1) {
+      endpoint = `${root}.json`;
+    } else {
+      endpoint = root;
+    }
+
+    this.pipelines = Vue.resource(endpoint);
+  }
+
+  getPipelines(scope, page) {
+    return this.pipelines.get({ scope, page });
+  }
+
+  /**
+   * Post request for all pipelines actions.
+   * Endpoint content type needs to be:
+   * `Content-Type:application/x-www-form-urlencoded`
+   *
+   * @param  {String} endpoint
+   * @return {Promise}
+   */
+  postAction(endpoint) {
+    return Vue.http.post(endpoint, {}, { emulateJSON: true });
+  }
+}
diff --git a/app/assets/javascripts/vue_pipelines_index/stage.js b/app/assets/javascripts/vue_pipelines_index/stage.js
deleted file mode 100644
index ae4f0b4a53b4e89105c8a414d0cde4391abb28f8..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/vue_pipelines_index/stage.js
+++ /dev/null
@@ -1,119 +0,0 @@
-/* global Vue, Flash, gl */
-/* eslint-disable no-param-reassign */
-import canceledSvg from 'icons/_icon_status_canceled_borderless.svg';
-import createdSvg from 'icons/_icon_status_created_borderless.svg';
-import failedSvg from 'icons/_icon_status_failed_borderless.svg';
-import manualSvg from 'icons/_icon_status_manual_borderless.svg';
-import pendingSvg from 'icons/_icon_status_pending_borderless.svg';
-import runningSvg from 'icons/_icon_status_running_borderless.svg';
-import skippedSvg from 'icons/_icon_status_skipped_borderless.svg';
-import successSvg from 'icons/_icon_status_success_borderless.svg';
-import warningSvg from 'icons/_icon_status_warning_borderless.svg';
-
-((gl) => {
-  gl.VueStage = Vue.extend({
-    data() {
-      const svgsDictionary = {
-        icon_status_canceled: canceledSvg,
-        icon_status_created: createdSvg,
-        icon_status_failed: failedSvg,
-        icon_status_manual: manualSvg,
-        icon_status_pending: pendingSvg,
-        icon_status_running: runningSvg,
-        icon_status_skipped: skippedSvg,
-        icon_status_success: successSvg,
-        icon_status_warning: warningSvg,
-      };
-
-      return {
-        builds: '',
-        spinner: '<span class="fa fa-spinner fa-spin"></span>',
-        svg: svgsDictionary[this.stage.status.icon],
-      };
-    },
-
-    props: {
-      stage: {
-        type: Object,
-        required: true,
-      },
-    },
-
-    updated() {
-      if (this.builds) {
-        this.stopDropdownClickPropagation();
-      }
-    },
-
-    methods: {
-      fetchBuilds(e) {
-        const areaExpanded = e.currentTarget.attributes['aria-expanded'];
-
-        if (areaExpanded && (areaExpanded.textContent === 'true')) return null;
-
-        return this.$http.get(this.stage.dropdown_path)
-          .then((response) => {
-            this.builds = JSON.parse(response.body).html;
-          }, () => {
-            const flash = new Flash('Something went wrong on our end.');
-            return flash;
-          });
-      },
-
-      /**
-       * When the user right clicks or cmd/ctrl + click in the job name
-       * the dropdown should not be closed and the link should open in another tab,
-       * so we stop propagation of the click event inside the dropdown.
-       *
-       * Since this component is rendered multiple times per page we need to guarantee we only
-       * target the click event of this component.
-       */
-      stopDropdownClickPropagation() {
-        $(this.$el).on('click', '.js-builds-dropdown-list a.mini-pipeline-graph-dropdown-item', (e) => {
-          e.stopPropagation();
-        });
-      },
-    },
-    computed: {
-      buildsOrSpinner() {
-        return this.builds ? this.builds : this.spinner;
-      },
-      dropdownClass() {
-        if (this.builds) return 'js-builds-dropdown-container';
-        return 'js-builds-dropdown-loading builds-dropdown-loading';
-      },
-      buildStatus() {
-        return `Build: ${this.stage.status.label}`;
-      },
-      tooltip() {
-        return `has-tooltip ci-status-icon ci-status-icon-${this.stage.status.group}`;
-      },
-      triggerButtonClass() {
-        return `mini-pipeline-graph-dropdown-toggle has-tooltip js-builds-dropdown-button ci-status-icon-${this.stage.status.group}`;
-      },
-    },
-    template: `
-      <div>
-        <button
-          @click="fetchBuilds($event)"
-          :class="triggerButtonClass"
-          :title="stage.title"
-          data-placement="top"
-          data-toggle="dropdown"
-          type="button"
-          :aria-label="stage.title">
-          <span v-html="svg" aria-hidden="true"></span>
-          <i class="fa fa-caret-down" aria-hidden="true"></i>
-        </button>
-        <ul class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container">
-          <div class="arrow-up" aria-hidden="true"></div>
-          <div
-            :class="dropdownClass"
-            class="js-builds-dropdown-list scrollable-menu"
-            v-html="buildsOrSpinner">
-          </div>
-        </ul>
-      </div>
-    `,
-  });
-})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/vue_pipelines_index/status.js b/app/assets/javascripts/vue_pipelines_index/status.js
deleted file mode 100644
index 8d9f83ac1130fb7b1b7928447da5bfc5440c87cb..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/vue_pipelines_index/status.js
+++ /dev/null
@@ -1,64 +0,0 @@
-/* global Vue, gl */
-/* eslint-disable no-param-reassign */
-
-import canceledSvg from 'icons/_icon_status_canceled.svg';
-import createdSvg from 'icons/_icon_status_created.svg';
-import failedSvg from 'icons/_icon_status_failed.svg';
-import manualSvg from 'icons/_icon_status_manual.svg';
-import pendingSvg from 'icons/_icon_status_pending.svg';
-import runningSvg from 'icons/_icon_status_running.svg';
-import skippedSvg from 'icons/_icon_status_skipped.svg';
-import successSvg from 'icons/_icon_status_success.svg';
-import warningSvg from 'icons/_icon_status_warning.svg';
-
-((gl) => {
-  gl.VueStatusScope = Vue.extend({
-    props: [
-      'pipeline',
-    ],
-
-    data() {
-      const svgsDictionary = {
-        icon_status_canceled: canceledSvg,
-        icon_status_created: createdSvg,
-        icon_status_failed: failedSvg,
-        icon_status_manual: manualSvg,
-        icon_status_pending: pendingSvg,
-        icon_status_running: runningSvg,
-        icon_status_skipped: skippedSvg,
-        icon_status_success: successSvg,
-        icon_status_warning: warningSvg,
-      };
-
-      return {
-        svg: svgsDictionary[this.pipeline.details.status.icon],
-      };
-    },
-
-    computed: {
-      cssClasses() {
-        const cssObject = { 'ci-status': true };
-        cssObject[`ci-${this.pipeline.details.status.group}`] = true;
-        return cssObject;
-      },
-
-      detailsPath() {
-        const { status } = this.pipeline.details;
-        return status.has_details ? status.details_path : false;
-      },
-
-      content() {
-        return `${this.svg} ${this.pipeline.details.status.text}`;
-      },
-    },
-    template: `
-      <td class="commit-link">
-        <a
-          :class="cssClasses"
-          :href="detailsPath"
-          v-html="content">
-        </a>
-      </td>
-    `,
-  });
-})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/vue_pipelines_index/store.js b/app/assets/javascripts/vue_pipelines_index/store.js
deleted file mode 100644
index 909007267b952ee3fbf7cf18273bee8df7d43c3f..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/vue_pipelines_index/store.js
+++ /dev/null
@@ -1,31 +0,0 @@
-/* global gl, Flash */
-/* eslint-disable no-param-reassign */
-
-((gl) => {
-  const pageValues = (headers) => {
-    const normalized = gl.utils.normalizeHeaders(headers);
-    const paginationInfo = gl.utils.parseIntPagination(normalized);
-    return paginationInfo;
-  };
-
-  gl.PipelineStore = class {
-    fetchDataLoop(Vue, pageNum, url, apiScope) {
-      this.pageRequest = true;
-
-      return this.$http.get(`${url}?scope=${apiScope}&page=${pageNum}`)
-      .then((response) => {
-        const pageInfo = pageValues(response.headers);
-        this.pageInfo = Object.assign({}, this.pageInfo, pageInfo);
-
-        const res = JSON.parse(response.body);
-        this.count = Object.assign({}, this.count, res.count);
-        this.pipelines = Object.assign([], this.pipelines, res.pipelines);
-
-        this.pageRequest = false;
-      }, () => {
-        this.pageRequest = false;
-        return new Flash('An error occurred while fetching the pipelines, please reload the page again.');
-      });
-    }
-  };
-})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/commit/pipelines/pipelines_store.js b/app/assets/javascripts/vue_pipelines_index/stores/pipelines_store.js
similarity index 62%
rename from app/assets/javascripts/commit/pipelines/pipelines_store.js
rename to app/assets/javascripts/vue_pipelines_index/stores/pipelines_store.js
index f1b80e45444568616f02b8c805dd3d1b3e7b11e0..7ac10086a55cac7b35ff13fda90993c2d649c1c5 100644
--- a/app/assets/javascripts/commit/pipelines/pipelines_store.js
+++ b/app/assets/javascripts/vue_pipelines_index/stores/pipelines_store.js
@@ -1,31 +1,46 @@
 /* eslint-disable no-underscore-dangle*/
-/**
- * Pipelines' Store for commits view.
- *
- * Used to store the Pipelines rendered in the commit view in the pipelines table.
- */
-require('../../vue_realtime_listener');
-
-class PipelinesStore {
+import '../../vue_realtime_listener';
+
+export default class PipelinesStore {
   constructor() {
     this.state = {};
+
     this.state.pipelines = [];
+    this.state.count = {};
+    this.state.pageInfo = {};
   }
 
   storePipelines(pipelines = []) {
     this.state.pipelines = pipelines;
+  }
 
-    return pipelines;
+  storeCount(count = {}) {
+    this.state.count = count;
+  }
+
+  storePagination(pagination = {}) {
+    let paginationInfo;
+
+    if (Object.keys(pagination).length) {
+      const normalizedHeaders = gl.utils.normalizeHeaders(pagination);
+      paginationInfo = gl.utils.parseIntPagination(normalizedHeaders);
+    } else {
+      paginationInfo = pagination;
+    }
+
+    this.state.pageInfo = paginationInfo;
   }
 
   /**
+   * FIXME: Move this inside the component.
+   *
    * Once the data is received we will start the time ago loops.
    *
    * Everytime a request is made like retry or cancel a pipeline, every 10 seconds we
    * update the time to show how long as passed.
    *
    */
-  static startTimeAgoLoops() {
+  startTimeAgoLoops() {
     const startTimeLoops = () => {
       this.timeLoopInterval = setInterval(() => {
         this.$children[0].$children.reduce((acc, component) => {
@@ -44,5 +59,3 @@ class PipelinesStore {
     gl.VueRealtimeListener(removeIntervals, startIntervals);
   }
 }
-
-module.exports = PipelinesStore;
diff --git a/app/assets/javascripts/vue_pipelines_index/time_ago.js b/app/assets/javascripts/vue_pipelines_index/time_ago.js
deleted file mode 100644
index a383570857d756608dbf454b8bc9720944d25438..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/vue_pipelines_index/time_ago.js
+++ /dev/null
@@ -1,78 +0,0 @@
-/* global Vue, gl */
-/* eslint-disable no-param-reassign */
-
-window.Vue = require('vue');
-require('../lib/utils/datetime_utility');
-
-const iconTimerSvg = require('../../../views/shared/icons/_icon_timer.svg');
-
-((gl) => {
-  gl.VueTimeAgo = Vue.extend({
-    data() {
-      return {
-        currentTime: new Date(),
-        iconTimerSvg,
-      };
-    },
-    props: ['pipeline'],
-    computed: {
-      timeAgo() {
-        return gl.utils.getTimeago();
-      },
-      localTimeFinished() {
-        return gl.utils.formatDate(this.pipeline.details.finished_at);
-      },
-      timeStopped() {
-        const changeTime = this.currentTime;
-        const options = {
-          weekday: 'long',
-          year: 'numeric',
-          month: 'short',
-          day: 'numeric',
-        };
-        options.timeZoneName = 'short';
-        const finished = this.pipeline.details.finished_at;
-        if (!finished && changeTime) return false;
-        return ({ words: this.timeAgo.format(finished) });
-      },
-      duration() {
-        const { duration } = this.pipeline.details;
-        const date = new Date(duration * 1000);
-
-        let hh = date.getUTCHours();
-        let mm = date.getUTCMinutes();
-        let ss = date.getSeconds();
-
-        if (hh < 10) hh = `0${hh}`;
-        if (mm < 10) mm = `0${mm}`;
-        if (ss < 10) ss = `0${ss}`;
-
-        if (duration !== null) return `${hh}:${mm}:${ss}`;
-        return false;
-      },
-    },
-    methods: {
-      changeTime() {
-        this.currentTime = new Date();
-      },
-    },
-    template: `
-      <td class="pipelines-time-ago">
-        <p class="duration" v-if='duration'>
-          <span v-html="iconTimerSvg"></span>
-          {{duration}}
-        </p>
-        <p class="finished-at" v-if='timeStopped'>
-          <i class="fa fa-calendar"></i>
-          <time
-            data-toggle="tooltip"
-            data-placement="top"
-            data-container="body"
-            :data-original-title='localTimeFinished'>
-            {{timeStopped.words}}
-          </time>
-        </p>
-      </td>
-    `,
-  });
-})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/vue_shared/components/commit.js b/app/assets/javascripts/vue_shared/components/commit.js
index 4381487b79eb08c7a0b9f27302c6ff6e38603635..fb68abd95a21825e32e8242de4c0ee419e6b26ca 100644
--- a/app/assets/javascripts/vue_shared/components/commit.js
+++ b/app/assets/javascripts/vue_shared/components/commit.js
@@ -1,164 +1,157 @@
-/* global Vue */
-window.Vue = require('vue');
-const commitIconSvg = require('icons/_icon_commit.svg');
-
-(() => {
-  window.gl = window.gl || {};
-
-  window.gl.CommitComponent = Vue.component('commit-component', {
-
-    props: {
-      /**
-       * Indicates the existance of a tag.
-       * Used to render the correct icon, if true will render `fa-tag` icon,
-       * if false will render `fa-code-fork` icon.
-       */
-      tag: {
-        type: Boolean,
-        required: false,
-        default: false,
-      },
-
-      /**
-       * If provided is used to render the branch name and url.
-       * Should contain the following properties:
-       * name
-       * ref_url
-       */
-      commitRef: {
-        type: Object,
-        required: false,
-        default: () => ({}),
-      },
-
-      /**
-       * Used to link to the commit sha.
-       */
-      commitUrl: {
-        type: String,
-        required: false,
-        default: '',
-      },
-
-      /**
-       * Used to show the commit short sha that links to the commit url.
-       */
-      shortSha: {
-        type: String,
-        required: false,
-        default: '',
-      },
-
-      /**
-       * If provided shows the commit tile.
-       */
-      title: {
-        type: String,
-        required: false,
-        default: '',
-      },
-
-      /**
-       * If provided renders information about the author of the commit.
-       * When provided should include:
-       * `avatar_url` to render the avatar icon
-       * `web_url` to link to user profile
-       * `username` to render alt and title tags
-       */
-      author: {
-        type: Object,
-        required: false,
-        default: () => ({}),
-      },
+import commitIconSvg from 'icons/_icon_commit.svg';
+
+export default {
+  props: {
+    /**
+     * Indicates the existance of a tag.
+     * Used to render the correct icon, if true will render `fa-tag` icon,
+     * if false will render `fa-code-fork` icon.
+     */
+    tag: {
+      type: Boolean,
+      required: false,
+      default: false,
     },
 
-    computed: {
-      /**
-       * Used to verify if all the properties needed to render the commit
-       * ref section were provided.
-       *
-       * TODO: Improve this! Use lodash _.has when we have it.
-       *
-       * @returns {Boolean}
-       */
-      hasCommitRef() {
-        return this.commitRef && this.commitRef.name && this.commitRef.ref_url;
-      },
-
-      /**
-       * Used to verify if all the properties needed to render the commit
-       * author section were provided.
-       *
-       * TODO: Improve this! Use lodash _.has when we have it.
-       *
-       * @returns {Boolean}
-       */
-      hasAuthor() {
-        return this.author &&
-          this.author.avatar_url &&
-          this.author.web_url &&
-          this.author.username;
-      },
-
-      /**
-       * If information about the author is provided will return a string
-       * to be rendered as the alt attribute of the img tag.
-       *
-       * @returns {String}
-       */
-      userImageAltDescription() {
-        return this.author &&
-          this.author.username ? `${this.author.username}'s avatar` : null;
-      },
+    /**
+     * If provided is used to render the branch name and url.
+     * Should contain the following properties:
+     * name
+     * ref_url
+     */
+    commitRef: {
+      type: Object,
+      required: false,
+      default: () => ({}),
     },
 
-    data() {
-      return { commitIconSvg };
+    /**
+     * Used to link to the commit sha.
+     */
+    commitUrl: {
+      type: String,
+      required: false,
+      default: '',
     },
 
-    template: `
-      <div class="branch-commit">
-
-        <div v-if="hasCommitRef" class="icon-container">
-          <i v-if="tag" class="fa fa-tag"></i>
-          <i v-if="!tag" class="fa fa-code-fork"></i>
-        </div>
-
-        <a v-if="hasCommitRef"
-          class="monospace branch-name"
-          :href="commitRef.ref_url">
-          {{commitRef.name}}
-        </a>
-
-        <div v-html="commitIconSvg" class="commit-icon js-commit-icon"></div>
-
-        <a class="commit-id monospace"
-          :href="commitUrl">
-          {{shortSha}}
-        </a>
-
-        <p class="commit-title">
-          <span v-if="title">
-            <a v-if="hasAuthor"
-              class="avatar-image-container"
-              :href="author.web_url">
-              <img
-                class="avatar has-tooltip s20"
-                :src="author.avatar_url"
-                :alt="userImageAltDescription"
-                :title="author.username" />
-            </a>
-
-            <a class="commit-row-message"
-              :href="commitUrl">
-              {{title}}
-            </a>
-          </span>
-          <span v-else>
-            Cant find HEAD commit for this branch
-          </span>
-        </p>
+    /**
+     * Used to show the commit short sha that links to the commit url.
+     */
+    shortSha: {
+      type: String,
+      required: false,
+      default: '',
+    },
+
+    /**
+     * If provided shows the commit tile.
+     */
+    title: {
+      type: String,
+      required: false,
+      default: '',
+    },
+
+    /**
+     * If provided renders information about the author of the commit.
+     * When provided should include:
+     * `avatar_url` to render the avatar icon
+     * `web_url` to link to user profile
+     * `username` to render alt and title tags
+     */
+    author: {
+      type: Object,
+      required: false,
+      default: () => ({}),
+    },
+  },
+
+  computed: {
+    /**
+     * Used to verify if all the properties needed to render the commit
+     * ref section were provided.
+     *
+     * TODO: Improve this! Use lodash _.has when we have it.
+     *
+     * @returns {Boolean}
+     */
+    hasCommitRef() {
+      return this.commitRef && this.commitRef.name && this.commitRef.ref_url;
+    },
+
+    /**
+     * Used to verify if all the properties needed to render the commit
+     * author section were provided.
+     *
+     * TODO: Improve this! Use lodash _.has when we have it.
+     *
+     * @returns {Boolean}
+     */
+    hasAuthor() {
+      return this.author &&
+        this.author.avatar_url &&
+        this.author.web_url &&
+        this.author.username;
+    },
+
+    /**
+     * If information about the author is provided will return a string
+     * to be rendered as the alt attribute of the img tag.
+     *
+     * @returns {String}
+     */
+    userImageAltDescription() {
+      return this.author &&
+        this.author.username ? `${this.author.username}'s avatar` : null;
+    },
+  },
+
+  data() {
+    return { commitIconSvg };
+  },
+
+  template: `
+    <div class="branch-commit">
+
+      <div v-if="hasCommitRef" class="icon-container">
+        <i v-if="tag" class="fa fa-tag"></i>
+        <i v-if="!tag" class="fa fa-code-fork"></i>
       </div>
-    `,
-  });
-})();
+
+      <a v-if="hasCommitRef"
+        class="monospace branch-name"
+        :href="commitRef.ref_url">
+        {{commitRef.name}}
+      </a>
+
+      <div v-html="commitIconSvg" class="commit-icon js-commit-icon"></div>
+
+      <a class="commit-id monospace"
+        :href="commitUrl">
+        {{shortSha}}
+      </a>
+
+      <p class="commit-title">
+        <span v-if="title">
+          <a v-if="hasAuthor"
+            class="avatar-image-container"
+            :href="author.web_url">
+            <img
+              class="avatar has-tooltip s20"
+              :src="author.avatar_url"
+              :alt="userImageAltDescription"
+              :title="author.username" />
+          </a>
+
+          <a class="commit-row-message"
+            :href="commitUrl">
+            {{title}}
+          </a>
+        </span>
+        <span v-else>
+          Cant find HEAD commit for this branch
+        </span>
+      </p>
+    </div>
+  `,
+};
diff --git a/app/assets/javascripts/vue_shared/components/pipelines_table.js b/app/assets/javascripts/vue_shared/components/pipelines_table.js
index 0d8f85db965dac4dbe2ac80402073754ebbbd985..afd8d7acf6bddc59723b696bc4b0eb46a44cd9fb 100644
--- a/app/assets/javascripts/vue_shared/components/pipelines_table.js
+++ b/app/assets/javascripts/vue_shared/components/pipelines_table.js
@@ -1,52 +1,48 @@
-/* eslint-disable no-param-reassign */
-/* global Vue */
+import PipelinesTableRowComponent from './pipelines_table_row';
 
-require('./pipelines_table_row');
 /**
  * Pipelines Table Component.
  *
  * Given an array of objects, renders a table.
  */
-
-(() => {
-  window.gl = window.gl || {};
-  gl.pipelines = gl.pipelines || {};
-
-  gl.pipelines.PipelinesTableComponent = Vue.component('pipelines-table-component', {
-
-    props: {
-      pipelines: {
-        type: Array,
-        required: true,
-        default: () => ([]),
-      },
-
+export default {
+  props: {
+    pipelines: {
+      type: Array,
+      required: true,
+      default: () => ([]),
     },
 
-    components: {
-      'pipelines-table-row-component': gl.pipelines.PipelinesTableRowComponent,
+    service: {
+      type: Object,
+      required: true,
     },
+  },
+
+  components: {
+    'pipelines-table-row-component': PipelinesTableRowComponent,
+  },
 
-    template: `
-      <table class="table ci-table">
-        <thead>
-          <tr>
-            <th class="js-pipeline-status pipeline-status">Status</th>
-            <th class="js-pipeline-info pipeline-info">Pipeline</th>
-            <th class="js-pipeline-commit pipeline-commit">Commit</th>
-            <th class="js-pipeline-stages pipeline-stages">Stages</th>
-            <th class="js-pipeline-date pipeline-date"></th>
-            <th class="js-pipeline-actions pipeline-actions"></th>
-          </tr>
-        </thead>
-        <tbody>
-          <template v-for="model in pipelines"
-            v-bind:model="model">
-            <tr is="pipelines-table-row-component"
-              :pipeline="model"></tr>
-          </template>
-        </tbody>
-      </table>
-    `,
-  });
-})();
+  template: `
+    <table class="table ci-table">
+      <thead>
+        <tr>
+          <th class="js-pipeline-status pipeline-status">Status</th>
+          <th class="js-pipeline-info pipeline-info">Pipeline</th>
+          <th class="js-pipeline-commit pipeline-commit">Commit</th>
+          <th class="js-pipeline-stages pipeline-stages">Stages</th>
+          <th class="js-pipeline-date pipeline-date"></th>
+          <th class="js-pipeline-actions pipeline-actions"></th>
+        </tr>
+      </thead>
+      <tbody>
+        <template v-for="model in pipelines"
+          v-bind:model="model">
+          <tr is="pipelines-table-row-component"
+            :pipeline="model"
+            :service="service"></tr>
+        </template>
+      </tbody>
+    </table>
+  `,
+};
diff --git a/app/assets/javascripts/vue_shared/components/pipelines_table_row.js b/app/assets/javascripts/vue_shared/components/pipelines_table_row.js
index e5e88186a850b7cbc0b119bfa4f9d70358ca7c0f..f5b3cb9214e8b0554b2182625496bcff9b6a9e48 100644
--- a/app/assets/javascripts/vue_shared/components/pipelines_table_row.js
+++ b/app/assets/javascripts/vue_shared/components/pipelines_table_row.js
@@ -1,199 +1,228 @@
 /* eslint-disable no-param-reassign */
-/* global Vue */
-
-require('../../vue_pipelines_index/status');
-require('../../vue_pipelines_index/pipeline_url');
-require('../../vue_pipelines_index/stage');
-require('../../vue_pipelines_index/pipeline_actions');
-require('../../vue_pipelines_index/time_ago');
-require('./commit');
+
+import AsyncButtonComponent from '../../vue_pipelines_index/components/async_button';
+import PipelinesActionsComponent from '../../vue_pipelines_index/components/pipelines_actions';
+import PipelinesArtifactsComponent from '../../vue_pipelines_index/components/pipelines_artifacts';
+import PipelinesStatusComponent from '../../vue_pipelines_index/components/status';
+import PipelinesStageComponent from '../../vue_pipelines_index/components/stage';
+import PipelinesUrlComponent from '../../vue_pipelines_index/components/pipeline_url';
+import PipelinesTimeagoComponent from '../../vue_pipelines_index/components/time_ago';
+import CommitComponent from './commit';
+
 /**
  * Pipeline table row.
  *
  * Given the received object renders a table row in the pipelines' table.
  */
-(() => {
-  window.gl = window.gl || {};
-  gl.pipelines = gl.pipelines || {};
-
-  gl.pipelines.PipelinesTableRowComponent = Vue.component('pipelines-table-row-component', {
-
-    props: {
-      pipeline: {
-        type: Object,
-        required: true,
-        default: () => ({}),
-      },
+export default {
+  props: {
+    pipeline: {
+      type: Object,
+      required: true,
+    },
 
+    service: {
+      type: Object,
+      required: true,
+    },
+  },
+
+  components: {
+    'async-button-component': AsyncButtonComponent,
+    'pipelines-actions-component': PipelinesActionsComponent,
+    'pipelines-artifacts-component': PipelinesArtifactsComponent,
+    'commit-component': CommitComponent,
+    'dropdown-stage': PipelinesStageComponent,
+    'pipeline-url': PipelinesUrlComponent,
+    'status-scope': PipelinesStatusComponent,
+    'time-ago': PipelinesTimeagoComponent,
+  },
+
+  computed: {
+    /**
+     * If provided, returns the commit tag.
+     * Needed to render the commit component column.
+     *
+     * This field needs a lot of verification, because of different possible cases:
+     *
+     * 1. person who is an author of a commit might be a GitLab user
+     * 2. if person who is an author of a commit is a GitLab user he/she can have a GitLab avatar
+     * 3. If GitLab user does not have avatar he/she might have a Gravatar
+     * 4. If committer is not a GitLab User he/she can have a Gravatar
+     * 5. We do not have consistent API object in this case
+     * 6. We should improve API and the code
+     *
+     * @returns {Object|Undefined}
+     */
+    commitAuthor() {
+      let commitAuthorInformation;
+
+      // 1. person who is an author of a commit might be a GitLab user
+      if (this.pipeline &&
+        this.pipeline.commit &&
+        this.pipeline.commit.author) {
+        // 2. if person who is an author of a commit is a GitLab user
+        // he/she can have a GitLab avatar
+        if (this.pipeline.commit.author.avatar_url) {
+          commitAuthorInformation = this.pipeline.commit.author;
+
+          // 3. If GitLab user does not have avatar he/she might have a Gravatar
+        } else if (this.pipeline.commit.author_gravatar_url) {
+          commitAuthorInformation = Object.assign({}, this.pipeline.commit.author, {
+            avatar_url: this.pipeline.commit.author_gravatar_url,
+          });
+        }
+      }
+
+      // 4. If committer is not a GitLab User he/she can have a Gravatar
+      if (this.pipeline &&
+        this.pipeline.commit) {
+        commitAuthorInformation = {
+          avatar_url: this.pipeline.commit.author_gravatar_url,
+          web_url: `mailto:${this.pipeline.commit.author_email}`,
+          username: this.pipeline.commit.author_name,
+        };
+      }
+
+      return commitAuthorInformation;
     },
 
-    components: {
-      'commit-component': gl.CommitComponent,
-      'pipeline-actions': gl.VuePipelineActions,
-      'dropdown-stage': gl.VueStage,
-      'pipeline-url': gl.VuePipelineUrl,
-      'status-scope': gl.VueStatusScope,
-      'time-ago': gl.VueTimeAgo,
+    /**
+     * If provided, returns the commit tag.
+     * Needed to render the commit component column.
+     *
+     * @returns {String|Undefined}
+     */
+    commitTag() {
+      if (this.pipeline.ref &&
+        this.pipeline.ref.tag) {
+        return this.pipeline.ref.tag;
+      }
+      return undefined;
     },
 
-    computed: {
-      /**
-       * If provided, returns the commit tag.
-       * Needed to render the commit component column.
-       *
-       * This field needs a lot of verification, because of different possible cases:
-       *
-       * 1. person who is an author of a commit might be a GitLab user
-       * 2. if person who is an author of a commit is a GitLab user he/she can have a GitLab avatar
-       * 3. If GitLab user does not have avatar he/she might have a Gravatar
-       * 4. If committer is not a GitLab User he/she can have a Gravatar
-       * 5. We do not have consistent API object in this case
-       * 6. We should improve API and the code
-       *
-       * @returns {Object|Undefined}
-       */
-      commitAuthor() {
-        let commitAuthorInformation;
-
-        // 1. person who is an author of a commit might be a GitLab user
-        if (this.pipeline &&
-          this.pipeline.commit &&
-          this.pipeline.commit.author) {
-          // 2. if person who is an author of a commit is a GitLab user
-          // he/she can have a GitLab avatar
-          if (this.pipeline.commit.author.avatar_url) {
-            commitAuthorInformation = this.pipeline.commit.author;
-
-            // 3. If GitLab user does not have avatar he/she might have a Gravatar
-          } else if (this.pipeline.commit.author_gravatar_url) {
-            commitAuthorInformation = Object.assign({}, this.pipeline.commit.author, {
-              avatar_url: this.pipeline.commit.author_gravatar_url,
-            });
+    /**
+     * If provided, returns the commit ref.
+     * Needed to render the commit component column.
+     *
+     * Matches `path` prop sent in the API to `ref_url` prop needed
+     * in the commit component.
+     *
+     * @returns {Object|Undefined}
+     */
+    commitRef() {
+      if (this.pipeline.ref) {
+        return Object.keys(this.pipeline.ref).reduce((accumulator, prop) => {
+          if (prop === 'path') {
+            accumulator.ref_url = this.pipeline.ref[prop];
+          } else {
+            accumulator[prop] = this.pipeline.ref[prop];
           }
-        }
+          return accumulator;
+        }, {});
+      }
 
-        // 4. If committer is not a GitLab User he/she can have a Gravatar
-        if (this.pipeline &&
-          this.pipeline.commit) {
-          commitAuthorInformation = {
-            avatar_url: this.pipeline.commit.author_gravatar_url,
-            web_url: `mailto:${this.pipeline.commit.author_email}`,
-            username: this.pipeline.commit.author_name,
-          };
-        }
+      return undefined;
+    },
 
-        return commitAuthorInformation;
-      },
-
-      /**
-       * If provided, returns the commit tag.
-       * Needed to render the commit component column.
-       *
-       * @returns {String|Undefined}
-       */
-      commitTag() {
-        if (this.pipeline.ref &&
-          this.pipeline.ref.tag) {
-          return this.pipeline.ref.tag;
-        }
-        return undefined;
-      },
-
-      /**
-       * If provided, returns the commit ref.
-       * Needed to render the commit component column.
-       *
-       * Matches `path` prop sent in the API to `ref_url` prop needed
-       * in the commit component.
-       *
-       * @returns {Object|Undefined}
-       */
-      commitRef() {
-        if (this.pipeline.ref) {
-          return Object.keys(this.pipeline.ref).reduce((accumulator, prop) => {
-            if (prop === 'path') {
-              accumulator.ref_url = this.pipeline.ref[prop];
-            } else {
-              accumulator[prop] = this.pipeline.ref[prop];
-            }
-            return accumulator;
-          }, {});
-        }
+    /**
+     * If provided, returns the commit url.
+     * Needed to render the commit component column.
+     *
+     * @returns {String|Undefined}
+     */
+    commitUrl() {
+      if (this.pipeline.commit &&
+        this.pipeline.commit.commit_path) {
+        return this.pipeline.commit.commit_path;
+      }
+      return undefined;
+    },
 
-        return undefined;
-      },
-
-      /**
-       * If provided, returns the commit url.
-       * Needed to render the commit component column.
-       *
-       * @returns {String|Undefined}
-       */
-      commitUrl() {
-        if (this.pipeline.commit &&
-          this.pipeline.commit.commit_path) {
-          return this.pipeline.commit.commit_path;
-        }
-        return undefined;
-      },
-
-      /**
-       * If provided, returns the commit short sha.
-       * Needed to render the commit component column.
-       *
-       * @returns {String|Undefined}
-       */
-      commitShortSha() {
-        if (this.pipeline.commit &&
-          this.pipeline.commit.short_id) {
-          return this.pipeline.commit.short_id;
-        }
-        return undefined;
-      },
-
-      /**
-       * If provided, returns the commit title.
-       * Needed to render the commit component column.
-       *
-       * @returns {String|Undefined}
-       */
-      commitTitle() {
-        if (this.pipeline.commit &&
-          this.pipeline.commit.title) {
-          return this.pipeline.commit.title;
-        }
-        return undefined;
-      },
+    /**
+     * If provided, returns the commit short sha.
+     * Needed to render the commit component column.
+     *
+     * @returns {String|Undefined}
+     */
+    commitShortSha() {
+      if (this.pipeline.commit &&
+        this.pipeline.commit.short_id) {
+        return this.pipeline.commit.short_id;
+      }
+      return undefined;
     },
 
-    template: `
-      <tr class="commit">
-        <status-scope :pipeline="pipeline"/>
-
-        <pipeline-url :pipeline="pipeline"></pipeline-url>
-
-        <td>
-          <commit-component
-            :tag="commitTag"
-            :commit-ref="commitRef"
-            :commit-url="commitUrl"
-            :short-sha="commitShortSha"
-            :title="commitTitle"
-            :author="commitAuthor"/>
-        </td>
-
-        <td class="stage-cell">
-          <div class="stage-container dropdown js-mini-pipeline-graph"
-            v-if="pipeline.details.stages.length > 0"
-            v-for="stage in pipeline.details.stages">
-            <dropdown-stage :stage="stage"/>
-          </div>
-        </td>
-
-        <time-ago :pipeline="pipeline"/>
-
-        <pipeline-actions :pipeline="pipeline" />
-      </tr>
-    `,
-  });
-})();
+    /**
+     * If provided, returns the commit title.
+     * Needed to render the commit component column.
+     *
+     * @returns {String|Undefined}
+     */
+    commitTitle() {
+      if (this.pipeline.commit &&
+        this.pipeline.commit.title) {
+        return this.pipeline.commit.title;
+      }
+      return undefined;
+    },
+  },
+
+  template: `
+    <tr class="commit">
+      <status-scope :pipeline="pipeline"/>
+
+      <pipeline-url :pipeline="pipeline"></pipeline-url>
+
+      <td>
+        <commit-component
+          :tag="commitTag"
+          :commit-ref="commitRef"
+          :commit-url="commitUrl"
+          :short-sha="commitShortSha"
+          :title="commitTitle"
+          :author="commitAuthor"/>
+      </td>
+
+      <td class="stage-cell">
+        <div class="stage-container dropdown js-mini-pipeline-graph"
+          v-if="pipeline.details.stages.length > 0"
+          v-for="stage in pipeline.details.stages">
+          <dropdown-stage :stage="stage"/>
+        </div>
+      </td>
+
+      <time-ago :pipeline="pipeline"/>
+
+      <td class="pipeline-actions">
+        <div class="pull-right btn-group">
+          <pipelines-actions-component
+            v-if="pipeline.details.manual_actions.length"
+            :actions="pipeline.details.manual_actions"
+            :service="service" />
+
+          <pipelines-artifacts-component
+            v-if="pipeline.details.artifacts.length"
+            :artifacts="pipeline.details.artifacts" />
+
+          <async-button-component
+            v-if="pipeline.flags.retryable"
+            :service="service"
+            :endpoint="pipeline.retry_path"
+            css-class="js-pipelines-retry-button btn-default btn-retry"
+            title="Retry"
+            icon="repeat" />
+
+          <async-button-component
+            v-if="pipeline.flags.cancelable"
+            :service="service"
+            :endpoint="pipeline.cancel_path"
+            css-class="js-pipelines-cancel-button btn-remove"
+            title="Cancel"
+            icon="remove"
+            confirm-action-message="Are you sure you want to cancel this pipeline?" />
+        </div>
+      </td>
+    </tr>
+  `,
+};
diff --git a/app/assets/javascripts/vue_shared/components/table_pagination.js b/app/assets/javascripts/vue_shared/components/table_pagination.js
index 8943b850a72b4b6cf895a4bd05c4143dfb8559d3..b9cd28f62493584bbe3c4df1b3f6f5802b561b66 100644
--- a/app/assets/javascripts/vue_shared/components/table_pagination.js
+++ b/app/assets/javascripts/vue_shared/components/table_pagination.js
@@ -1,147 +1,135 @@
-/* global Vue, gl */
-/* eslint-disable no-param-reassign, no-plusplus */
-
-window.Vue = require('vue');
-
-((gl) => {
-  const PAGINATION_UI_BUTTON_LIMIT = 4;
-  const UI_LIMIT = 6;
-  const SPREAD = '...';
-  const PREV = 'Prev';
-  const NEXT = 'Next';
-  const FIRST = '<< First';
-  const LAST = 'Last >>';
-
-  gl.VueGlPagination = Vue.extend({
-    props: {
-
-      // TODO: Consider refactoring in light of turbolinks removal.
-
-      /**
-        This function will take the information given by the pagination component
-
-        Here is an example `change` method:
-
-        change(pagenum) {
-          gl.utils.visitUrl(`?page=${pagenum}`);
-        },
-      */
-
-      change: {
-        type: Function,
-        required: true,
+const PAGINATION_UI_BUTTON_LIMIT = 4;
+const UI_LIMIT = 6;
+const SPREAD = '...';
+const PREV = 'Prev';
+const NEXT = 'Next';
+const FIRST = '<< First';
+const LAST = 'Last >>';
+
+export default {
+  props: {
+    /**
+      This function will take the information given by the pagination component
+
+      Here is an example `change` method:
+
+      change(pagenum) {
+        gl.utils.visitUrl(`?page=${pagenum}`);
       },
+    */
+    change: {
+      type: Function,
+      required: true,
+    },
 
-      /**
-        pageInfo will come from the headers of the API call
-        in the `.then` clause of the VueResource API call
-        there should be a function that contructs the pageInfo for this component
-
-        This is an example:
-
-        const pageInfo = headers => ({
-          perPage: +headers['X-Per-Page'],
-          page: +headers['X-Page'],
-          total: +headers['X-Total'],
-          totalPages: +headers['X-Total-Pages'],
-          nextPage: +headers['X-Next-Page'],
-          previousPage: +headers['X-Prev-Page'],
-        });
-      */
-
-      pageInfo: {
-        type: Object,
-        required: true,
-      },
+    /**
+      pageInfo will come from the headers of the API call
+      in the `.then` clause of the VueResource API call
+      there should be a function that contructs the pageInfo for this component
+
+      This is an example:
+
+      const pageInfo = headers => ({
+        perPage: +headers['X-Per-Page'],
+        page: +headers['X-Page'],
+        total: +headers['X-Total'],
+        totalPages: +headers['X-Total-Pages'],
+        nextPage: +headers['X-Next-Page'],
+        previousPage: +headers['X-Prev-Page'],
+      });
+    */
+    pageInfo: {
+      type: Object,
+      required: true,
     },
-    methods: {
-      changePage(e) {
-        const text = e.target.innerText;
-        const { totalPages, nextPage, previousPage } = this.pageInfo;
-
-        switch (text) {
-          case SPREAD:
-            break;
-          case LAST:
-            this.change(totalPages);
-            break;
-          case NEXT:
-            this.change(nextPage);
-            break;
-          case PREV:
-            this.change(previousPage);
-            break;
-          case FIRST:
-            this.change(1);
-            break;
-          default:
-            this.change(+text);
-            break;
-        }
-      },
+  },
+  methods: {
+    changePage(e) {
+      const text = e.target.innerText;
+      const { totalPages, nextPage, previousPage } = this.pageInfo;
+
+      switch (text) {
+        case SPREAD:
+          break;
+        case LAST:
+          this.change(totalPages);
+          break;
+        case NEXT:
+          this.change(nextPage);
+          break;
+        case PREV:
+          this.change(previousPage);
+          break;
+        case FIRST:
+          this.change(1);
+          break;
+        default:
+          this.change(+text);
+          break;
+      }
     },
-    computed: {
-      prev() {
-        return this.pageInfo.previousPage;
-      },
-      next() {
-        return this.pageInfo.nextPage;
-      },
-      getItems() {
-        const total = this.pageInfo.totalPages;
-        const page = this.pageInfo.page;
-        const items = [];
+  },
+  computed: {
+    prev() {
+      return this.pageInfo.previousPage;
+    },
+    next() {
+      return this.pageInfo.nextPage;
+    },
+    getItems() {
+      const total = this.pageInfo.totalPages;
+      const page = this.pageInfo.page;
+      const items = [];
 
-        if (page > 1) items.push({ title: FIRST });
+      if (page > 1) items.push({ title: FIRST });
 
-        if (page > 1) {
-          items.push({ title: PREV, prev: true });
-        } else {
-          items.push({ title: PREV, disabled: true, prev: true });
-        }
+      if (page > 1) {
+        items.push({ title: PREV, prev: true });
+      } else {
+        items.push({ title: PREV, disabled: true, prev: true });
+      }
 
-        if (page > UI_LIMIT) items.push({ title: SPREAD, separator: true });
+      if (page > UI_LIMIT) items.push({ title: SPREAD, separator: true });
 
-        const start = Math.max(page - PAGINATION_UI_BUTTON_LIMIT, 1);
-        const end = Math.min(page + PAGINATION_UI_BUTTON_LIMIT, total);
+      const start = Math.max(page - PAGINATION_UI_BUTTON_LIMIT, 1);
+      const end = Math.min(page + PAGINATION_UI_BUTTON_LIMIT, total);
 
-        for (let i = start; i <= end; i++) {
-          const isActive = i === page;
-          items.push({ title: i, active: isActive, page: true });
-        }
+      for (let i = start; i <= end; i += 1) {
+        const isActive = i === page;
+        items.push({ title: i, active: isActive, page: true });
+      }
 
-        if (total - page > PAGINATION_UI_BUTTON_LIMIT) {
-          items.push({ title: SPREAD, separator: true, page: true });
-        }
+      if (total - page > PAGINATION_UI_BUTTON_LIMIT) {
+        items.push({ title: SPREAD, separator: true, page: true });
+      }
 
-        if (page === total) {
-          items.push({ title: NEXT, disabled: true, next: true });
-        } else if (total - page >= 1) {
-          items.push({ title: NEXT, next: true });
-        }
+      if (page === total) {
+        items.push({ title: NEXT, disabled: true, next: true });
+      } else if (total - page >= 1) {
+        items.push({ title: NEXT, next: true });
+      }
 
-        if (total - page >= 1) items.push({ title: LAST, last: true });
+      if (total - page >= 1) items.push({ title: LAST, last: true });
 
-        return items;
-      },
+      return items;
     },
-    template: `
-      <div class="gl-pagination">
-        <ul class="pagination clearfix">
-          <li v-for='item in getItems'
-            :class='{
-              page: item.page,
-              prev: item.prev,
-              next: item.next,
-              separator: item.separator,
-              active: item.active,
-              disabled: item.disabled
-            }'
-          >
-            <a @click="changePage($event)">{{item.title}}</a>
-          </li>
-        </ul>
-      </div>
-    `,
-  });
-})(window.gl || (window.gl = {}));
+  },
+  template: `
+    <div class="gl-pagination">
+      <ul class="pagination clearfix">
+        <li v-for='item in getItems'
+          :class='{
+            page: item.page,
+            prev: item.prev,
+            next: item.next,
+            separator: item.separator,
+            active: item.active,
+            disabled: item.disabled
+          }'
+        >
+          <a @click="changePage($event)">{{item.title}}</a>
+        </li>
+      </ul>
+    </div>
+  `,
+};
diff --git a/app/assets/javascripts/vue_shared/vue_resource_interceptor.js b/app/assets/javascripts/vue_shared/vue_resource_interceptor.js
index 4157fefddc9d99d3d60da969bd9d40174dc6b3c9..f1c1e553b1669108d151ae716bda0e313f6bdc5d 100644
--- a/app/assets/javascripts/vue_shared/vue_resource_interceptor.js
+++ b/app/assets/javascripts/vue_shared/vue_resource_interceptor.js
@@ -1,11 +1,13 @@
-/* eslint-disable func-names, prefer-arrow-callback, no-unused-vars,
-no-param-reassign, no-plusplus */
-/* global Vue */
+/* eslint-disable no-param-reassign, no-plusplus */
+import Vue from 'vue';
+import VueResource from 'vue-resource';
+
+Vue.use(VueResource);
 
 Vue.http.interceptors.push((request, next) => {
   Vue.activeResources = Vue.activeResources ? Vue.activeResources + 1 : 1;
 
-  next((response) => {
+  next(() => {
     Vue.activeResources--;
   });
 });
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index 20eabc83142e64882a1488952d24abed39cd3c80..33b38ca692339d23d32aedffbf698ec5c6144212 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -72,11 +72,6 @@
           color: $gl-text-color-secondary;
           font-size: 14px;
         }
-
-        svg,
-        .fa {
-          margin-right: 0;
-        }
       }
 
       .btn-group {
@@ -921,3 +916,22 @@
     }
   }
 }
+
+/**
+ * Play button with icon in dropdowns
+ */
+.ci-table .no-btn {
+  border: none;
+  background: none;
+  outline: none;
+  width: 100%;
+  text-align: left;
+
+  .icon-play {
+    position: relative;
+    top: 2px;
+    margin-right: 5px;
+    height: 13px;
+    width: 12px;
+  }
+}
diff --git a/changelogs/unreleased/fl-remove-ujs-pipelines.yml b/changelogs/unreleased/fl-remove-ujs-pipelines.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f353400753a5d949f1933b97241b179d64f5c32d
--- /dev/null
+++ b/changelogs/unreleased/fl-remove-ujs-pipelines.yml
@@ -0,0 +1,4 @@
+---
+title: 'Removes UJS from pipelines tables'
+merge_request: 9929
+author:
diff --git a/spec/features/merge_requests/created_from_fork_spec.rb b/spec/features/merge_requests/created_from_fork_spec.rb
index 73c5ef31edc5513d65d40522459efcc258259918..18833ba72668d30a155f27a50ea4f908d338ab76 100644
--- a/spec/features/merge_requests/created_from_fork_spec.rb
+++ b/spec/features/merge_requests/created_from_fork_spec.rb
@@ -60,9 +60,6 @@
         expect(page).to have_content pipeline.status
         expect(page).to have_content pipeline.id
       end
-
-      expect(page.find('a.btn-remove')[:href])
-        .to include fork_project.path_with_namespace
     end
   end
 
diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb
index 22bf1bfbdf09e2584a60b3350dbd97076bdff759..162056671e08aa43f47971343e257d440062280b 100644
--- a/spec/features/projects/pipelines/pipelines_spec.rb
+++ b/spec/features/projects/pipelines/pipelines_spec.rb
@@ -99,15 +99,18 @@
         end
 
         it 'indicates that pipeline can be canceled' do
-          expect(page).to have_link('Cancel')
+          expect(page).to have_selector('.js-pipelines-cancel-button')
           expect(page).to have_selector('.ci-running')
         end
 
         context 'when canceling' do
-          before { click_link('Cancel') }
+          before do
+            find('.js-pipelines-cancel-button').click
+            wait_for_vue_resource
+          end
 
           it 'indicated that pipelines was canceled' do
-            expect(page).not_to have_link('Cancel')
+            expect(page).not_to have_selector('.js-pipelines-cancel-button')
             expect(page).to have_selector('.ci-canceled')
           end
         end
@@ -126,15 +129,18 @@
         end
 
         it 'indicates that pipeline can be retried' do
-          expect(page).to have_link('Retry')
+          expect(page).to have_selector('.js-pipelines-retry-button')
           expect(page).to have_selector('.ci-failed')
         end
 
         context 'when retrying' do
-          before { click_link('Retry') }
+          before do
+            find('.js-pipelines-retry-button').click
+            wait_for_vue_resource
+          end
 
           it 'shows running pipeline that is not retryable' do
-            expect(page).not_to have_link('Retry')
+            expect(page).not_to have_selector('.js-pipelines-retry-button')
             expect(page).to have_selector('.ci-running')
           end
         end
@@ -176,17 +182,17 @@
         it 'has link to the manual action' do
           find('.js-pipeline-dropdown-manual-actions').click
 
-          expect(page).to have_link('manual build')
+          expect(page).to have_button('manual build')
         end
 
         context 'when manual action was played' do
           before do
             find('.js-pipeline-dropdown-manual-actions').click
-            click_link('manual build')
+            click_button('manual build')
           end
 
           it 'enqueues manual action job' do
-            expect(manual.reload).to be_pending
+            expect(page).to have_selector('.js-pipeline-dropdown-manual-actions:disabled')
           end
         end
       end
@@ -203,7 +209,7 @@
           before { visit_project_pipelines }
 
           it 'is cancelable' do
-            expect(page).to have_link('Cancel')
+            expect(page).to have_selector('.js-pipelines-cancel-button')
           end
 
           it 'has pipeline running' do
@@ -211,10 +217,10 @@
           end
 
           context 'when canceling' do
-            before { click_link('Cancel') }
+            before { find('.js-pipelines-cancel-button').trigger('click') }
 
             it 'indicates that pipeline was canceled' do
-              expect(page).not_to have_link('Cancel')
+              expect(page).not_to have_selector('.js-pipelines-cancel-button')
               expect(page).to have_selector('.ci-canceled')
             end
           end
@@ -233,7 +239,7 @@
           end
 
           it 'is not retryable' do
-            expect(page).not_to have_link('Retry')
+            expect(page).not_to have_selector('.js-pipelines-retry-button')
           end
 
           it 'has failed pipeline' do
diff --git a/spec/javascripts/commit/pipelines/mock_data.js b/spec/javascripts/commit/pipelines/mock_data.js
index 188908d66bdc5212a372275b79c4352e800f426a..82b00b4c1ec614c9a4fe8abeefb82abd3275bba5 100644
--- a/spec/javascripts/commit/pipelines/mock_data.js
+++ b/spec/javascripts/commit/pipelines/mock_data.js
@@ -1,5 +1,4 @@
-/* eslint-disable no-unused-vars */
-const pipeline = {
+export default {
   id: 73,
   user: {
     name: 'Administrator',
@@ -88,5 +87,3 @@ const pipeline = {
   created_at: '2017-01-16T17:13:59.800Z',
   updated_at: '2017-01-25T00:00:17.132Z',
 };
-
-module.exports = pipeline;
diff --git a/spec/javascripts/commit/pipelines/pipelines_spec.js b/spec/javascripts/commit/pipelines/pipelines_spec.js
index f09c57978a10d6329496d6709024fc6251147837..75efcc065853a5ae3b909cd3aafb89d535d11411 100644
--- a/spec/javascripts/commit/pipelines/pipelines_spec.js
+++ b/spec/javascripts/commit/pipelines/pipelines_spec.js
@@ -1,11 +1,6 @@
-/* global pipeline, Vue */
-
-require('~/flash');
-require('~/commit/pipelines/pipelines_store');
-require('~/commit/pipelines/pipelines_service');
-require('~/commit/pipelines/pipelines_table');
-require('~/vue_shared/vue_resource_interceptor');
-const pipeline = require('./mock_data');
+import Vue from 'vue';
+import PipelinesTable from '~/commit/pipelines/pipelines_table';
+import pipeline from './mock_data';
 
 describe('Pipelines table in Commits and Merge requests', () => {
   preloadFixtures('static/pipelines_table.html.raw');
@@ -33,7 +28,7 @@ describe('Pipelines table in Commits and Merge requests', () => {
       });
 
       it('should render the empty state', (done) => {
-        const component = new gl.commits.pipelines.PipelinesTableView({
+        const component = new PipelinesTable({
           el: document.querySelector('#commit-pipeline-table-view'),
         });
 
@@ -62,7 +57,7 @@ describe('Pipelines table in Commits and Merge requests', () => {
       });
 
       it('should render a table with the received pipelines', (done) => {
-        const component = new gl.commits.pipelines.PipelinesTableView({
+        const component = new PipelinesTable({
           el: document.querySelector('#commit-pipeline-table-view'),
         });
 
@@ -92,7 +87,7 @@ describe('Pipelines table in Commits and Merge requests', () => {
     });
 
     it('should render empty state', (done) => {
-      const component = new gl.commits.pipelines.PipelinesTableView({
+      const component = new PipelinesTable({
         el: document.querySelector('#commit-pipeline-table-view'),
       });
 
diff --git a/spec/javascripts/commit/pipelines/pipelines_store_spec.js b/spec/javascripts/commit/pipelines/pipelines_store_spec.js
deleted file mode 100644
index 949734199790bd4c331cce8288de99f29d7640db..0000000000000000000000000000000000000000
--- a/spec/javascripts/commit/pipelines/pipelines_store_spec.js
+++ /dev/null
@@ -1,33 +0,0 @@
-const PipelinesStore = require('~/commit/pipelines/pipelines_store');
-
-describe('Store', () => {
-  let store;
-
-  beforeEach(() => {
-    store = new PipelinesStore();
-  });
-
-  // unregister intervals and event handlers
-  afterEach(() => gl.VueRealtimeListener.reset());
-
-  it('should start with a blank state', () => {
-    expect(store.state.pipelines.length).toBe(0);
-  });
-
-  it('should store an array of pipelines', () => {
-    const pipelines = [
-      {
-        id: '1',
-        name: 'pipeline',
-      },
-      {
-        id: '2',
-        name: 'pipeline_2',
-      },
-    ];
-
-    store.storePipelines(pipelines);
-
-    expect(store.state.pipelines.length).toBe(pipelines.length);
-  });
-});
diff --git a/spec/javascripts/vue_pipelines_index/async_button_spec.js b/spec/javascripts/vue_pipelines_index/async_button_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..bc8e504c4137399f5868708613d21629e3257bd3
--- /dev/null
+++ b/spec/javascripts/vue_pipelines_index/async_button_spec.js
@@ -0,0 +1,93 @@
+import Vue from 'vue';
+import asyncButtonComp from '~/vue_pipelines_index/components/async_button';
+
+describe('Pipelines Async Button', () => {
+  let component;
+  let spy;
+  let AsyncButtonComponent;
+
+  beforeEach(() => {
+    AsyncButtonComponent = Vue.extend(asyncButtonComp);
+
+    spy = jasmine.createSpy('spy').and.returnValue(Promise.resolve());
+
+    component = new AsyncButtonComponent({
+      propsData: {
+        endpoint: '/foo',
+        title: 'Foo',
+        icon: 'fa fa-foo',
+        cssClass: 'bar',
+        service: {
+          postAction: spy,
+        },
+      },
+    }).$mount();
+  });
+
+  it('should render a button', () => {
+    expect(component.$el.tagName).toEqual('BUTTON');
+  });
+
+  it('should render the provided icon', () => {
+    expect(component.$el.querySelector('i').getAttribute('class')).toContain('fa fa-foo');
+  });
+
+  it('should render the provided title', () => {
+    expect(component.$el.getAttribute('title')).toContain('Foo');
+    expect(component.$el.getAttribute('aria-label')).toContain('Foo');
+  });
+
+  it('should render the provided cssClass', () => {
+    expect(component.$el.getAttribute('class')).toContain('bar');
+  });
+
+  it('should call the service when it is clicked with the provided endpoint', () => {
+    component.$el.click();
+    expect(spy).toHaveBeenCalledWith('/foo');
+  });
+
+  it('should hide loading if request fails', () => {
+    spy = jasmine.createSpy('spy').and.returnValue(Promise.reject());
+
+    component = new AsyncButtonComponent({
+      propsData: {
+        endpoint: '/foo',
+        title: 'Foo',
+        icon: 'fa fa-foo',
+        cssClass: 'bar',
+        dataAttributes: {
+          'data-foo': 'foo',
+        },
+        service: {
+          postAction: spy,
+        },
+      },
+    }).$mount();
+
+    component.$el.click();
+    expect(component.$el.querySelector('.fa-spinner')).toBe(null);
+  });
+
+  describe('With confirm dialog', () => {
+    it('should call the service when confimation is positive', () => {
+      spyOn(window, 'confirm').and.returnValue(true);
+      spy = jasmine.createSpy('spy').and.returnValue(Promise.resolve());
+
+      component = new AsyncButtonComponent({
+        propsData: {
+          endpoint: '/foo',
+          title: 'Foo',
+          icon: 'fa fa-foo',
+          cssClass: 'bar',
+          service: {
+            postAction: spy,
+          },
+          confirmActionMessage: 'bar',
+        },
+      }).$mount();
+
+      component.$el.click();
+      expect(spy).toHaveBeenCalledWith('/foo');
+    });
+  });
+});
diff --git a/spec/javascripts/vue_pipelines_index/pipeline_url_spec.js b/spec/javascripts/vue_pipelines_index/pipeline_url_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..96a2a37b5f78aec05e4f34749022159397d61a0b
--- /dev/null
+++ b/spec/javascripts/vue_pipelines_index/pipeline_url_spec.js
@@ -0,0 +1,100 @@
+import Vue from 'vue';
+import pipelineUrlComp from '~/vue_pipelines_index/components/pipeline_url';
+
+describe('Pipeline Url Component', () => {
+  let PipelineUrlComponent;
+
+  beforeEach(() => {
+    PipelineUrlComponent = Vue.extend(pipelineUrlComp);
+  });
+
+  it('should render a table cell', () => {
+    const component = new PipelineUrlComponent({
+      propsData: {
+        pipeline: {
+          id: 1,
+          path: 'foo',
+          flags: {},
+        },
+      },
+    }).$mount();
+
+    expect(component.$el.tagName).toEqual('TD');
+  });
+
+  it('should render a link the provided path and id', () => {
+    const component = new PipelineUrlComponent({
+      propsData: {
+        pipeline: {
+          id: 1,
+          path: 'foo',
+          flags: {},
+        },
+      },
+    }).$mount();
+
+    expect(component.$el.querySelector('.js-pipeline-url-link').getAttribute('href')).toEqual('foo');
+    expect(component.$el.querySelector('.js-pipeline-url-link span').textContent).toEqual('#1');
+  });
+
+  it('should render user information when a user is provided', () => {
+    const mockData = {
+      pipeline: {
+        id: 1,
+        path: 'foo',
+        flags: {},
+        user: {
+          web_url: '/',
+          name: 'foo',
+          avatar_url: '/',
+        },
+      },
+    };
+
+    const component = new PipelineUrlComponent({
+      propsData: mockData,
+    }).$mount();
+
+    const image = component.$el.querySelector('.js-pipeline-url-user img');
+
+    expect(
+      component.$el.querySelector('.js-pipeline-url-user').getAttribute('href'),
+    ).toEqual(mockData.pipeline.user.web_url);
+    expect(image.getAttribute('title')).toEqual(mockData.pipeline.user.name);
+    expect(image.getAttribute('src')).toEqual(mockData.pipeline.user.avatar_url);
+  });
+
+  it('should render "API" when no user is provided', () => {
+    const component = new PipelineUrlComponent({
+      propsData: {
+        pipeline: {
+          id: 1,
+          path: 'foo',
+          flags: {},
+        },
+      },
+    }).$mount();
+
+    expect(component.$el.querySelector('.js-pipeline-url-api').textContent).toContain('API');
+  });
+
+  it('should render latest, yaml invalid and stuck flags when provided', () => {
+    const component = new PipelineUrlComponent({
+      propsData: {
+        pipeline: {
+          id: 1,
+          path: 'foo',
+          flags: {
+            latest: true,
+            yaml_errors: true,
+            stuck: true,
+          },
+        },
+      },
+    }).$mount();
+
+    expect(component.$el.querySelector('.js-pipeline-url-lastest').textContent).toContain('latest');
+    expect(component.$el.querySelector('.js-pipeline-url-yaml').textContent).toContain('yaml invalid');
+    expect(component.$el.querySelector('.js-pipeline-url-stuck').textContent).toContain('stuck');
+  });
+});
diff --git a/spec/javascripts/vue_pipelines_index/pipelines_actions_spec.js b/spec/javascripts/vue_pipelines_index/pipelines_actions_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..dba998c7688d356357aa57d627abc9e9ebc7f62e
--- /dev/null
+++ b/spec/javascripts/vue_pipelines_index/pipelines_actions_spec.js
@@ -0,0 +1,62 @@
+import Vue from 'vue';
+import pipelinesActionsComp from '~/vue_pipelines_index/components/pipelines_actions';
+
+describe('Pipelines Actions dropdown', () => {
+  let component;
+  let spy;
+  let actions;
+  let ActionsComponent;
+
+  beforeEach(() => {
+    ActionsComponent = Vue.extend(pipelinesActionsComp);
+
+    actions = [
+      {
+        name: 'stop_review',
+        path: '/root/review-app/builds/1893/play',
+      },
+    ];
+
+    spy = jasmine.createSpy('spy').and.returnValue(Promise.resolve());
+
+    component = new ActionsComponent({
+      propsData: {
+        actions,
+        service: {
+          postAction: spy,
+        },
+      },
+    }).$mount();
+  });
+
+  it('should render a dropdown with the provided actions', () => {
+    expect(
+      component.$el.querySelectorAll('.dropdown-menu li').length,
+    ).toEqual(actions.length);
+  });
+
+  it('should call the service when an action is clicked', () => {
+    component.$el.querySelector('.js-pipeline-dropdown-manual-actions').click();
+    component.$el.querySelector('.js-pipeline-action-link').click();
+
+    expect(spy).toHaveBeenCalledWith(actions[0].path);
+  });
+
+  it('should hide loading if request fails', () => {
+    spy = jasmine.createSpy('spy').and.returnValue(Promise.reject());
+
+    component = new ActionsComponent({
+      propsData: {
+        actions,
+        service: {
+          postAction: spy,
+        },
+      },
+    }).$mount();
+
+    component.$el.querySelector('.js-pipeline-dropdown-manual-actions').click();
+    component.$el.querySelector('.js-pipeline-action-link').click();
+
+    expect(component.$el.querySelector('.fa-spinner')).toEqual(null);
+  });
+});
diff --git a/spec/javascripts/vue_pipelines_index/pipelines_artifacts_spec.js b/spec/javascripts/vue_pipelines_index/pipelines_artifacts_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..f7f49649c1c6b52525093416f6c94a713e50a687
--- /dev/null
+++ b/spec/javascripts/vue_pipelines_index/pipelines_artifacts_spec.js
@@ -0,0 +1,40 @@
+import Vue from 'vue';
+import artifactsComp from '~/vue_pipelines_index/components/pipelines_artifacts';
+
+describe('Pipelines Artifacts dropdown', () => {
+  let component;
+  let artifacts;
+
+  beforeEach(() => {
+    const ArtifactsComponent = Vue.extend(artifactsComp);
+
+    artifacts = [
+      {
+        name: 'artifact',
+        path: '/download/path',
+      },
+    ];
+
+    component = new ArtifactsComponent({
+      propsData: {
+        artifacts,
+      },
+    }).$mount();
+  });
+
+  it('should render a dropdown with the provided artifacts', () => {
+    expect(
+      component.$el.querySelectorAll('.dropdown-menu li').length,
+    ).toEqual(artifacts.length);
+  });
+
+  it('should render a link with the provided path', () => {
+    expect(
+      component.$el.querySelector('.dropdown-menu li a').getAttribute('href'),
+    ).toEqual(artifacts[0].path);
+
+    expect(
+      component.$el.querySelector('.dropdown-menu li a span').textContent,
+    ).toContain(artifacts[0].name);
+  });
+});
diff --git a/spec/javascripts/vue_pipelines_index/pipelines_store_spec.js b/spec/javascripts/vue_pipelines_index/pipelines_store_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..5c0934404bb5deaec9f91bfcc81c08c81d2b42d9
--- /dev/null
+++ b/spec/javascripts/vue_pipelines_index/pipelines_store_spec.js
@@ -0,0 +1,72 @@
+import PipelineStore from '~/vue_pipelines_index/stores/pipelines_store';
+
+describe('Pipelines Store', () => {
+  let store;
+
+  beforeEach(() => {
+    store = new PipelineStore();
+  });
+
+  it('should be initialized with an empty state', () => {
+    expect(store.state.pipelines).toEqual([]);
+    expect(store.state.count).toEqual({});
+    expect(store.state.pageInfo).toEqual({});
+  });
+
+  describe('storePipelines', () => {
+    it('should use the default parameter if none is provided', () => {
+      store.storePipelines();
+      expect(store.state.pipelines).toEqual([]);
+    });
+
+    it('should store the provided array', () => {
+      const array = [{ id: 1, status: 'running' }, { id: 2, status: 'success' }];
+      store.storePipelines(array);
+      expect(store.state.pipelines).toEqual(array);
+    });
+  });
+
+  describe('storeCount', () => {
+    it('should use the default parameter if none is provided', () => {
+      store.storeCount();
+      expect(store.state.count).toEqual({});
+    });
+
+    it('should store the provided count', () => {
+      const count = { all: 20, finished: 10 };
+      store.storeCount(count);
+
+      expect(store.state.count).toEqual(count);
+    });
+  });
+
+  describe('storePagination', () => {
+    it('should use the default parameter if none is provided', () => {
+      store.storePagination();
+      expect(store.state.pageInfo).toEqual({});
+    });
+
+    it('should store pagination information normalized and parsed', () => {
+      const pagination = {
+        'X-nExt-pAge': '2',
+        'X-page': '1',
+        'X-Per-Page': '1',
+        'X-Prev-Page': '2',
+        'X-TOTAL': '37',
+        'X-Total-Pages': '2',
+      };
+
+      const expectedResult = {
+        perPage: 1,
+        page: 1,
+        total: 37,
+        totalPages: 2,
+        nextPage: 2,
+        previousPage: 2,
+      };
+
+      store.storePagination(pagination);
+      expect(store.state.pageInfo).toEqual(expectedResult);
+    });
+  });
+});
diff --git a/spec/javascripts/vue_shared/components/commit_spec.js b/spec/javascripts/vue_shared/components/commit_spec.js
index 15ab10b9b6952bdb2874443f495cd7e52da1e468..df547299d75cbd8bc71a55bf5de55dcedff5b928 100644
--- a/spec/javascripts/vue_shared/components/commit_spec.js
+++ b/spec/javascripts/vue_shared/components/commit_spec.js
@@ -1,13 +1,17 @@
-require('~/vue_shared/components/commit');
+import Vue from 'vue';
+import commitComp from '~/vue_shared/components/commit';
 
 describe('Commit component', () => {
   let props;
   let component;
+  let CommitComponent;
+
+  beforeEach(() => {
+    CommitComponent = Vue.extend(commitComp);
+  });
 
   it('should render a code-fork icon if it does not represent a tag', () => {
-    setFixtures('<div class="test-commit-container"></div>');
-    component = new window.gl.CommitComponent({
-      el: document.querySelector('.test-commit-container'),
+    component = new CommitComponent({
       propsData: {
         tag: false,
         commitRef: {
@@ -23,15 +27,13 @@ describe('Commit component', () => {
           username: 'jschatz1',
         },
       },
-    });
+    }).$mount();
 
     expect(component.$el.querySelector('.icon-container i').classList).toContain('fa-code-fork');
   });
 
   describe('Given all the props', () => {
     beforeEach(() => {
-      setFixtures('<div class="test-commit-container"></div>');
-
       props = {
         tag: true,
         commitRef: {
@@ -49,10 +51,9 @@ describe('Commit component', () => {
         commitIconSvg: '<svg></svg>',
       };
 
-      component = new window.gl.CommitComponent({
-        el: document.querySelector('.test-commit-container'),
+      component = new CommitComponent({
         propsData: props,
-      });
+      }).$mount();
     });
 
     it('should render a tag icon if it represents a tag', () => {
@@ -105,7 +106,6 @@ describe('Commit component', () => {
 
   describe('When commit title is not provided', () => {
     it('should render default message', () => {
-      setFixtures('<div class="test-commit-container"></div>');
       props = {
         tag: false,
         commitRef: {
@@ -118,10 +118,9 @@ describe('Commit component', () => {
         author: {},
       };
 
-      component = new window.gl.CommitComponent({
-        el: document.querySelector('.test-commit-container'),
+      component = new CommitComponent({
         propsData: props,
-      });
+      }).$mount();
 
       expect(
         component.$el.querySelector('.commit-title span').textContent,
diff --git a/spec/javascripts/vue_shared/components/pipelines_table_row_spec.js b/spec/javascripts/vue_shared/components/pipelines_table_row_spec.js
index 412abfd5e4174622a98d55794df32dcd4a669dd7..699625cdbb7e1ac3bdc1a14efff7988d136890db 100644
--- a/spec/javascripts/vue_shared/components/pipelines_table_row_spec.js
+++ b/spec/javascripts/vue_shared/components/pipelines_table_row_spec.js
@@ -1,20 +1,20 @@
-require('~/vue_shared/components/pipelines_table_row');
-const pipeline = require('../../commit/pipelines/mock_data');
+import Vue from 'vue';
+import tableRowComp from '~/vue_shared/components/pipelines_table_row';
+import pipeline from '../../commit/pipelines/mock_data';
 
 describe('Pipelines Table Row', () => {
   let component;
-  preloadFixtures('static/environments/element.html.raw');
 
   beforeEach(() => {
-    loadFixtures('static/environments/element.html.raw');
+    const PipelinesTableRowComponent = Vue.extend(tableRowComp);
 
-    component = new gl.pipelines.PipelinesTableRowComponent({
+    component = new PipelinesTableRowComponent({
       el: document.querySelector('.test-dom-element'),
       propsData: {
         pipeline,
-        svgs: {},
+        service: {},
       },
-    });
+    }).$mount();
   });
 
   it('should render a table row', () => {
diff --git a/spec/javascripts/vue_shared/components/pipelines_table_spec.js b/spec/javascripts/vue_shared/components/pipelines_table_spec.js
index 54d81e2ea7d63e9427c19a3b486f28f3d44778f5..b0b1df5a753d90116df9bec9d0a6440153de9bce 100644
--- a/spec/javascripts/vue_shared/components/pipelines_table_spec.js
+++ b/spec/javascripts/vue_shared/components/pipelines_table_spec.js
@@ -1,24 +1,24 @@
-require('~/vue_shared/components/pipelines_table');
-require('~/lib/utils/datetime_utility');
-const pipeline = require('../../commit/pipelines/mock_data');
+import Vue from 'vue';
+import pipelinesTableComp from '~/vue_shared/components/pipelines_table';
+import '~/lib/utils/datetime_utility';
+import pipeline from '../../commit/pipelines/mock_data';
 
 describe('Pipelines Table', () => {
-  preloadFixtures('static/environments/element.html.raw');
+  let PipelinesTableComponent;
 
   beforeEach(() => {
-    loadFixtures('static/environments/element.html.raw');
+    PipelinesTableComponent = Vue.extend(pipelinesTableComp);
   });
 
   describe('table', () => {
     let component;
     beforeEach(() => {
-      component = new gl.pipelines.PipelinesTableComponent({
-        el: document.querySelector('.test-dom-element'),
+      component = new PipelinesTableComponent({
         propsData: {
           pipelines: [],
-          svgs: {},
+          service: {},
         },
-      });
+      }).$mount();
     });
 
     it('should render a table', () => {
@@ -37,26 +37,25 @@ describe('Pipelines Table', () => {
 
   describe('without data', () => {
     it('should render an empty table', () => {
-      const component = new gl.pipelines.PipelinesTableComponent({
-        el: document.querySelector('.test-dom-element'),
+      const component = new PipelinesTableComponent({
         propsData: {
           pipelines: [],
-          svgs: {},
+          service: {},
         },
-      });
+      }).$mount();
       expect(component.$el.querySelectorAll('tbody tr').length).toEqual(0);
     });
   });
 
   describe('with data', () => {
     it('should render rows', () => {
-      const component = new gl.pipelines.PipelinesTableComponent({
+      const component = new PipelinesTableComponent({
         el: document.querySelector('.test-dom-element'),
         propsData: {
           pipelines: [pipeline],
-          svgs: {},
+          service: {},
         },
-      });
+      }).$mount();
 
       expect(component.$el.querySelectorAll('tbody tr').length).toEqual(1);
     });
diff --git a/spec/javascripts/vue_shared/components/table_pagination_spec.js b/spec/javascripts/vue_shared/components/table_pagination_spec.js
index 9cb067921a714b37c39093803afe9ad087c46a1f..a5c3870b3acaa10cb2081c5b47067e109d4db864 100644
--- a/spec/javascripts/vue_shared/components/table_pagination_spec.js
+++ b/spec/javascripts/vue_shared/components/table_pagination_spec.js
@@ -1,8 +1,10 @@
-require('~/lib/utils/common_utils');
-require('~/vue_shared/components/table_pagination');
+import Vue from 'vue';
+import paginationComp from '~/vue_shared/components/table_pagination';
+import '~/lib/utils/common_utils';
 
 describe('Pagination component', () => {
   let component;
+  let PaginationComponent;
 
   const changeChanges = {
     one: '',
@@ -12,11 +14,12 @@ describe('Pagination component', () => {
     changeChanges.one = one;
   };
 
-  it('should render and start at page 1', () => {
-    setFixtures('<div class="test-pagination-container"></div>');
+  beforeEach(() => {
+    PaginationComponent = Vue.extend(paginationComp);
+  });
 
-    component = new window.gl.VueGlPagination({
-      el: document.querySelector('.test-pagination-container'),
+  it('should render and start at page 1', () => {
+    component = new PaginationComponent({
       propsData: {
         pageInfo: {
           totalPages: 10,
@@ -25,7 +28,7 @@ describe('Pagination component', () => {
         },
         change,
       },
-    });
+    }).$mount();
 
     expect(component.$el.classList).toContain('gl-pagination');
 
@@ -35,10 +38,7 @@ describe('Pagination component', () => {
   });
 
   it('should go to the previous page', () => {
-    setFixtures('<div class="test-pagination-container"></div>');
-
-    component = new window.gl.VueGlPagination({
-      el: document.querySelector('.test-pagination-container'),
+    component = new PaginationComponent({
       propsData: {
         pageInfo: {
           totalPages: 10,
@@ -47,7 +47,7 @@ describe('Pagination component', () => {
         },
         change,
       },
-    });
+    }).$mount();
 
     component.changePage({ target: { innerText: 'Prev' } });
 
@@ -55,10 +55,7 @@ describe('Pagination component', () => {
   });
 
   it('should go to the next page', () => {
-    setFixtures('<div class="test-pagination-container"></div>');
-
-    component = new window.gl.VueGlPagination({
-      el: document.querySelector('.test-pagination-container'),
+    component = new PaginationComponent({
       propsData: {
         pageInfo: {
           totalPages: 10,
@@ -67,7 +64,7 @@ describe('Pagination component', () => {
         },
         change,
       },
-    });
+    }).$mount();
 
     component.changePage({ target: { innerText: 'Next' } });
 
@@ -75,10 +72,7 @@ describe('Pagination component', () => {
   });
 
   it('should go to the last page', () => {
-    setFixtures('<div class="test-pagination-container"></div>');
-
-    component = new window.gl.VueGlPagination({
-      el: document.querySelector('.test-pagination-container'),
+    component = new PaginationComponent({
       propsData: {
         pageInfo: {
           totalPages: 10,
@@ -87,7 +81,7 @@ describe('Pagination component', () => {
         },
         change,
       },
-    });
+    }).$mount();
 
     component.changePage({ target: { innerText: 'Last >>' } });
 
@@ -95,10 +89,7 @@ describe('Pagination component', () => {
   });
 
   it('should go to the first page', () => {
-    setFixtures('<div class="test-pagination-container"></div>');
-
-    component = new window.gl.VueGlPagination({
-      el: document.querySelector('.test-pagination-container'),
+    component = new PaginationComponent({
       propsData: {
         pageInfo: {
           totalPages: 10,
@@ -107,7 +98,7 @@ describe('Pagination component', () => {
         },
         change,
       },
-    });
+    }).$mount();
 
     component.changePage({ target: { innerText: '<< First' } });
 
@@ -115,10 +106,7 @@ describe('Pagination component', () => {
   });
 
   it('should do nothing', () => {
-    setFixtures('<div class="test-pagination-container"></div>');
-
-    component = new window.gl.VueGlPagination({
-      el: document.querySelector('.test-pagination-container'),
+    component = new PaginationComponent({
       propsData: {
         pageInfo: {
           totalPages: 10,
@@ -127,7 +115,7 @@ describe('Pagination component', () => {
         },
         change,
       },
-    });
+    }).$mount();
 
     component.changePage({ target: { innerText: '...' } });