diff --git a/app/assets/javascripts/ci/pipeline_details/graph/constants.js b/app/assets/javascripts/ci/pipeline_details/graph/constants.js
index 6d23c553f69adbbcb9fff98e207cdf880ce8a28f..f1498600aaa37d224212f1927abb8b02a05a8e4e 100644
--- a/app/assets/javascripts/ci/pipeline_details/graph/constants.js
+++ b/app/assets/javascripts/ci/pipeline_details/graph/constants.js
@@ -25,3 +25,10 @@ export const IID_FAILURE = 'missing_iid';
 
 export const RETRY_ACTION_TITLE = 'Retry';
 export const MANUAL_ACTION_TITLE = 'Run';
+
+/*
+  this poll interval is shared between the graph,
+  pipeline header, jobs tab and failed jobs tab to
+  keep all the data relatively in sync
+*/
+export const POLL_INTERVAL = 10000;
diff --git a/app/assets/javascripts/ci/pipeline_details/graph/graph_component_wrapper.vue b/app/assets/javascripts/ci/pipeline_details/graph/graph_component_wrapper.vue
index e5a25073d2454f26fc69920c8c8f04d5856d5ff8..e09c3da9c2cbd944a47d69ec7b782965505ae3f2 100644
--- a/app/assets/javascripts/ci/pipeline_details/graph/graph_component_wrapper.vue
+++ b/app/assets/javascripts/ci/pipeline_details/graph/graph_component_wrapper.vue
@@ -15,6 +15,7 @@ import {
   SKIP_RETRY_MODAL_KEY,
   STAGE_VIEW,
   VIEW_TYPE_KEY,
+  POLL_INTERVAL,
 } from './constants';
 import PipelineGraph from './components/graph_component.vue';
 import GraphViewSelector from './components/graph_view_selector.vue';
@@ -129,7 +130,7 @@ export default {
         return getQueryHeaders(this.graphqlResourceEtag);
       },
       query: getPipelineDetails,
-      pollInterval: 10000,
+      pollInterval: POLL_INTERVAL,
       variables() {
         return {
           projectPath: this.pipelineProjectPath,
diff --git a/app/assets/javascripts/ci/pipeline_details/header/constants.js b/app/assets/javascripts/ci/pipeline_details/header/constants.js
index a4aed7b8f46e7df8007f90453317fc347a283eff..2bc110d9dc21cdd8e6b8693da1d9bc07b449c856 100644
--- a/app/assets/javascripts/ci/pipeline_details/header/constants.js
+++ b/app/assets/javascripts/ci/pipeline_details/header/constants.js
@@ -1,7 +1,5 @@
 export const DELETE_MODAL_ID = 'pipeline-delete-modal';
 
-export const POLL_INTERVAL = 10000;
-
 export const SCHEDULE_SOURCE = 'schedule';
 export const AUTO_DEVOPS_SOURCE = 'AUTO_DEVOPS_SOURCE';
 export const DETACHED_EVENT_TYPE = 'DETACHED';
diff --git a/app/assets/javascripts/ci/pipeline_details/header/pipeline_header.vue b/app/assets/javascripts/ci/pipeline_details/header/pipeline_header.vue
index 9a9f971dedfa47c05afbcbabb4db2641eeb4ceda..6564588486f16c5ac859e91617ae7a2c8713b485 100644
--- a/app/assets/javascripts/ci/pipeline_details/header/pipeline_header.vue
+++ b/app/assets/javascripts/ci/pipeline_details/header/pipeline_header.vue
@@ -14,10 +14,10 @@ import cancelPipelineMutation from '../graphql/mutations/cancel_pipeline.mutatio
 import deletePipelineMutation from '../graphql/mutations/delete_pipeline.mutation.graphql';
 import retryPipelineMutation from '../graphql/mutations/retry_pipeline.mutation.graphql';
 import { getQueryHeaders } from '../graph/utils';
+import { POLL_INTERVAL } from '../graph/constants';
 import HeaderActions from './components/header_actions.vue';
 import HeaderBadges from './components/header_badges.vue';
 import getPipelineQuery from './graphql/queries/get_pipeline_header_data.query.graphql';
-import { POLL_INTERVAL } from './constants';
 
 export default {
   name: 'PipelineHeader',
diff --git a/app/assets/javascripts/ci/pipeline_details/jobs/failed_jobs_app.vue b/app/assets/javascripts/ci/pipeline_details/jobs/failed_jobs_app.vue
index b946a40e590102048411c863346edb0ed3d6f9ae..dc11af69bb4c23a40fbe252d0ba83a9f8836e19f 100644
--- a/app/assets/javascripts/ci/pipeline_details/jobs/failed_jobs_app.vue
+++ b/app/assets/javascripts/ci/pipeline_details/jobs/failed_jobs_app.vue
@@ -2,6 +2,8 @@
 import { GlLoadingIcon } from '@gitlab/ui';
 import { s__ } from '~/locale';
 import { createAlert } from '~/alert';
+import { getQueryHeaders } from '../graph/utils';
+import { POLL_INTERVAL } from '../graph/constants';
 import GetFailedJobsQuery from './graphql/queries/get_failed_jobs.query.graphql';
 import FailedJobsTable from './components/failed_jobs_table.vue';
 
@@ -17,10 +19,17 @@ export default {
     pipelineIid: {
       default: '',
     },
+    graphqlResourceEtag: {
+      default: '',
+    },
   },
   apollo: {
     failedJobs: {
+      context() {
+        return getQueryHeaders(this.graphqlResourceEtag);
+      },
       query: GetFailedJobsQuery,
+      pollInterval: POLL_INTERVAL,
       variables() {
         return {
           fullPath: this.projectPath,
diff --git a/app/assets/javascripts/ci/pipeline_details/jobs/jobs_app.vue b/app/assets/javascripts/ci/pipeline_details/jobs/jobs_app.vue
index 81b6152347dcd5f8b4510c3d25f02ab890f040fe..0c1a8e995ee21bdadeefe5bac1eb9f809d2bd98c 100644
--- a/app/assets/javascripts/ci/pipeline_details/jobs/jobs_app.vue
+++ b/app/assets/javascripts/ci/pipeline_details/jobs/jobs_app.vue
@@ -6,6 +6,8 @@ import { __ } from '~/locale';
 import eventHub from '~/ci/jobs_page/event_hub';
 import JobsTable from '~/ci/jobs_page/components/jobs_table.vue';
 import { JOBS_TAB_FIELDS } from '~/ci/jobs_page/constants';
+import { getQueryHeaders } from '../graph/utils';
+import { POLL_INTERVAL } from '../graph/constants';
 import getPipelineJobs from './graphql/queries/get_pipeline_jobs.query.graphql';
 
 export default {
@@ -23,10 +25,17 @@ export default {
     pipelineIid: {
       default: '',
     },
+    graphqlResourceEtag: {
+      default: '',
+    },
   },
   apollo: {
     jobs: {
+      context() {
+        return getQueryHeaders(this.graphqlResourceEtag);
+      },
       query: getPipelineJobs,
+      pollInterval: POLL_INTERVAL,
       variables() {
         return {
           ...this.queryVariables,
diff --git a/spec/frontend/ci/pipeline_details/jobs/failed_jobs_app_spec.js b/spec/frontend/ci/pipeline_details/jobs/failed_jobs_app_spec.js
index 17b43aa422b3399fd74855811dd219c0cdcf7e76..a0806cabadb08dcfb259b4336d24a7acad025295 100644
--- a/spec/frontend/ci/pipeline_details/jobs/failed_jobs_app_spec.js
+++ b/spec/frontend/ci/pipeline_details/jobs/failed_jobs_app_spec.js
@@ -8,6 +8,7 @@ import { createAlert } from '~/alert';
 import FailedJobsApp from '~/ci/pipeline_details/jobs/failed_jobs_app.vue';
 import FailedJobsTable from '~/ci/pipeline_details/jobs/components/failed_jobs_table.vue';
 import GetFailedJobsQuery from '~/ci/pipeline_details/jobs/graphql/queries/get_failed_jobs.query.graphql';
+import { POLL_INTERVAL } from '~/ci/pipeline_details/graph/constants';
 import { mockFailedJobsQueryResponse } from 'jest/ci/pipeline_details/mock_data';
 
 Vue.use(VueApollo);
@@ -27,11 +28,14 @@ describe('Failed Jobs App', () => {
     return createMockApollo(requestHandlers);
   };
 
+  const graphqlResourceEtag = '/api/graphql:pipelines/id/1';
+
   const createComponent = (resolver) => {
     wrapper = shallowMount(FailedJobsApp, {
       provide: {
         fullPath: 'root/ci-project',
         pipelineIid: 1,
+        graphqlResourceEtag,
       },
       apolloProvider: createMockApolloProvider(resolver),
     });
@@ -77,4 +81,18 @@ describe('Failed Jobs App', () => {
       message: 'There was a problem fetching the failed jobs.',
     });
   });
+
+  describe('polling', () => {
+    beforeEach(() => {
+      createComponent(resolverSpy);
+    });
+
+    it('polls for query data', () => {
+      expect(resolverSpy).toHaveBeenCalledTimes(1);
+
+      jest.advanceTimersByTime(POLL_INTERVAL);
+
+      expect(resolverSpy).toHaveBeenCalledTimes(2);
+    });
+  });
 });
diff --git a/spec/frontend/ci/pipeline_details/jobs/jobs_app_spec.js b/spec/frontend/ci/pipeline_details/jobs/jobs_app_spec.js
index 4a3a901502eccc60e66c9ae0a4efacb487d01608..ff02b8542b9e2e45432bc7818f18aa8e160a8dea 100644
--- a/spec/frontend/ci/pipeline_details/jobs/jobs_app_spec.js
+++ b/spec/frontend/ci/pipeline_details/jobs/jobs_app_spec.js
@@ -8,6 +8,7 @@ import { createAlert } from '~/alert';
 import JobsApp from '~/ci/pipeline_details/jobs/jobs_app.vue';
 import JobsTable from '~/ci/jobs_page/components/jobs_table.vue';
 import getPipelineJobsQuery from '~/ci/pipeline_details/jobs/graphql/queries/get_pipeline_jobs.query.graphql';
+import { POLL_INTERVAL } from '~/ci/pipeline_details/graph/constants';
 import { mockPipelineJobsQueryResponse } from '../mock_data';
 
 Vue.use(VueApollo);
@@ -31,11 +32,14 @@ describe('Jobs app', () => {
     return createMockApollo(requestHandlers);
   };
 
+  const graphqlResourceEtag = '/api/graphql:pipelines/id/1';
+
   const createComponent = (resolver) => {
     wrapper = shallowMount(JobsApp, {
       provide: {
         projectPath: 'root/ci-project',
         pipelineIid: 1,
+        graphqlResourceEtag,
       },
       apolloProvider: createMockApolloProvider(resolver),
     });
@@ -124,4 +128,18 @@ describe('Jobs app', () => {
 
     expect(findSkeletonLoader().exists()).toBe(false);
   });
+
+  describe('polling', () => {
+    beforeEach(() => {
+      createComponent(resolverSpy);
+    });
+
+    it('polls for query data', () => {
+      expect(resolverSpy).toHaveBeenCalledTimes(1);
+
+      jest.advanceTimersByTime(POLL_INTERVAL);
+
+      expect(resolverSpy).toHaveBeenCalledTimes(2);
+    });
+  });
 });