Commit 218b55de authored by Phil Hughes's avatar Phil Hughes

Merge branch '40487-stop-polling' into 'master'

Resolve "Pipeline view stages showing incorrectly"

Closes #40487

See merge request gitlab-org/gitlab-ce!18254
parents cee3df6c cac2ed25
<script> <script>
import $ from 'jquery';
/** /**
* Renders each stage of the pipeline mini graph. * Renders each stage of the pipeline mini graph.
...@@ -13,8 +12,11 @@ ...@@ -13,8 +12,11 @@
* 3. Merge request widget * 3. Merge request widget
* 4. Commit widget * 4. Commit widget
*/ */
import axios from '../../lib/utils/axios_utils';
import $ from 'jquery';
import Flash from '../../flash'; import Flash from '../../flash';
import axios from '../../lib/utils/axios_utils';
import eventHub from '../event_hub';
import Icon from '../../vue_shared/components/icon.vue'; import Icon from '../../vue_shared/components/icon.vue';
import LoadingIcon from '../../vue_shared/components/loading_icon.vue'; import LoadingIcon from '../../vue_shared/components/loading_icon.vue';
import tooltip from '../../vue_shared/directives/tooltip'; import tooltip from '../../vue_shared/directives/tooltip';
...@@ -82,6 +84,7 @@ ...@@ -82,6 +84,7 @@
methods: { methods: {
onClickStage() { onClickStage() {
if (!this.isDropdownOpen()) { if (!this.isDropdownOpen()) {
eventHub.$emit('clickedDropdown');
this.isLoading = true; this.isLoading = true;
this.fetchJobs(); this.fetchJobs();
} }
......
// eslint-disable-next-line import/prefer-default-export
export const CANCEL_REQUEST = 'CANCEL_REQUEST';
...@@ -7,6 +7,7 @@ import SvgBlankState from '../components/blank_state.vue'; ...@@ -7,6 +7,7 @@ import SvgBlankState from '../components/blank_state.vue';
import LoadingIcon from '../../vue_shared/components/loading_icon.vue'; import LoadingIcon from '../../vue_shared/components/loading_icon.vue';
import PipelinesTableComponent from '../components/pipelines_table.vue'; import PipelinesTableComponent from '../components/pipelines_table.vue';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import { CANCEL_REQUEST } from '../constants';
export default { export default {
components: { components: {
...@@ -52,34 +53,58 @@ export default { ...@@ -52,34 +53,58 @@ export default {
}); });
eventHub.$on('postAction', this.postAction); eventHub.$on('postAction', this.postAction);
eventHub.$on('clickedDropdown', this.updateTable);
}, },
beforeDestroy() { beforeDestroy() {
eventHub.$off('postAction', this.postAction); eventHub.$off('postAction', this.postAction);
eventHub.$off('clickedDropdown', this.updateTable);
}, },
destroyed() { destroyed() {
this.poll.stop(); this.poll.stop();
}, },
methods: { methods: {
updateTable() {
// Cancel ongoing request
if (this.isMakingRequest) {
this.service.cancelationSource.cancel(CANCEL_REQUEST);
}
// Stop polling
this.poll.stop();
// Update the table
return this.getPipelines()
.then(() => this.poll.restart());
},
fetchPipelines() { fetchPipelines() {
if (!this.isMakingRequest) { if (!this.isMakingRequest) {
this.isLoading = true; this.isLoading = true;
this.service.getPipelines(this.requestData) this.getPipelines();
.then(response => this.successCallback(response))
.catch(() => this.errorCallback());
} }
}, },
getPipelines() {
return this.service.getPipelines(this.requestData)
.then(response => this.successCallback(response))
.catch((error) => this.errorCallback(error));
},
setCommonData(pipelines) { setCommonData(pipelines) {
this.store.storePipelines(pipelines); this.store.storePipelines(pipelines);
this.isLoading = false; this.isLoading = false;
this.updateGraphDropdown = true; this.updateGraphDropdown = true;
this.hasMadeRequest = true; this.hasMadeRequest = true;
// In case the previous polling request returned an error, we need to reset it
if (this.hasError) {
this.hasError = false;
}
}, },
errorCallback() { errorCallback(error) {
this.hasError = true; this.hasMadeRequest = true;
this.isLoading = false; this.isLoading = false;
if (error && error.message && error.message !== CANCEL_REQUEST) {
this.hasError = true;
this.updateGraphDropdown = false; this.updateGraphDropdown = false;
this.hasMadeRequest = true; }
}, },
setIsMakingRequest(isMakingRequest) { setIsMakingRequest(isMakingRequest) {
this.isMakingRequest = isMakingRequest; this.isMakingRequest = isMakingRequest;
......
...@@ -19,8 +19,13 @@ export default class PipelinesService { ...@@ -19,8 +19,13 @@ export default class PipelinesService {
getPipelines(data = {}) { getPipelines(data = {}) {
const { scope, page } = data; const { scope, page } = data;
const CancelToken = axios.CancelToken;
this.cancelationSource = CancelToken.source();
return axios.get(this.endpoint, { return axios.get(this.endpoint, {
params: { scope, page }, params: { scope, page },
cancelToken: this.cancelationSource.token,
}); });
} }
......
This diff is collapsed.
...@@ -4,6 +4,7 @@ import axios from '~/lib/utils/axios_utils'; ...@@ -4,6 +4,7 @@ import axios from '~/lib/utils/axios_utils';
import pipelinesComp from '~/pipelines/components/pipelines.vue'; import pipelinesComp from '~/pipelines/components/pipelines.vue';
import Store from '~/pipelines/stores/pipelines_store'; import Store from '~/pipelines/stores/pipelines_store';
import mountComponent from 'spec/helpers/vue_mount_component_helper'; import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { pipelineWithStages, stageReply } from './mock_data';
describe('Pipelines', () => { describe('Pipelines', () => {
const jsonFixtureName = 'pipelines/pipelines.json'; const jsonFixtureName = 'pipelines/pipelines.json';
...@@ -668,4 +669,79 @@ describe('Pipelines', () => { ...@@ -668,4 +669,79 @@ describe('Pipelines', () => {
}); });
}); });
}); });
describe('updates results when a staged is clicked', () => {
beforeEach(() => {
const copyPipeline = Object.assign({}, pipelineWithStages);
copyPipeline.id += 1;
mock
.onGet('twitter/flight/pipelines.json').reply(200, {
pipelines: [pipelineWithStages],
count: {
all: 1,
finished: 1,
pending: 0,
running: 0,
},
}, {
'POLL-INTERVAL': 100,
})
.onGet(pipelineWithStages.details.stages[0].dropdown_path)
.reply(200, stageReply);
vm = mountComponent(PipelinesComponent, {
store: new Store(),
hasGitlabCi: true,
canCreatePipeline: true,
...paths,
});
});
describe('when a request is being made', () => {
it('stops polling, cancels the request, fetches pipelines & restarts polling', (done) => {
spyOn(vm.poll, 'stop');
spyOn(vm.poll, 'restart');
spyOn(vm, 'getPipelines').and.returnValue(Promise.resolve());
spyOn(vm.service.cancelationSource, 'cancel').and.callThrough();
setTimeout(() => {
vm.isMakingRequest = true;
return vm.$nextTick()
.then(() => {
vm.$el.querySelector('.js-builds-dropdown-button').click();
})
.then(() => {
expect(vm.service.cancelationSource.cancel).toHaveBeenCalled();
expect(vm.poll.stop).toHaveBeenCalled();
setTimeout(() => {
expect(vm.getPipelines).toHaveBeenCalled();
expect(vm.poll.restart).toHaveBeenCalled();
done();
}, 0);
});
}, 0);
});
});
describe('when no request is being made', () => {
it('stops polling, fetches pipelines & restarts polling', (done) => {
spyOn(vm.poll, 'stop');
spyOn(vm.poll, 'restart');
spyOn(vm, 'getPipelines').and.returnValue(Promise.resolve());
setTimeout(() => {
vm.$el.querySelector('.js-builds-dropdown-button').click();
expect(vm.poll.stop).toHaveBeenCalled();
setTimeout(() => {
expect(vm.getPipelines).toHaveBeenCalled();
expect(vm.poll.restart).toHaveBeenCalled();
done();
}, 0);
}, 0);
});
});
});
}); });
...@@ -2,6 +2,7 @@ import Vue from 'vue'; ...@@ -2,6 +2,7 @@ import Vue from 'vue';
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import stage from '~/pipelines/components/stage.vue'; import stage from '~/pipelines/components/stage.vue';
import eventHub from '~/pipelines/event_hub';
import mountComponent from 'spec/helpers/vue_mount_component_helper'; import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('Pipelines stage component', () => { describe('Pipelines stage component', () => {
...@@ -43,13 +44,15 @@ describe('Pipelines stage component', () => { ...@@ -43,13 +44,15 @@ describe('Pipelines stage component', () => {
mock.onGet('path.json').reply(200, { html: 'foo' }); mock.onGet('path.json').reply(200, { html: 'foo' });
}); });
it('should render the received data', done => { it('should render the received data and emit `clickedDropdown` event', done => {
spyOn(eventHub, '$emit');
component.$el.querySelector('button').click(); component.$el.querySelector('button').click();
setTimeout(() => { setTimeout(() => {
expect( expect(
component.$el.querySelector('.js-builds-dropdown-container ul').textContent.trim(), component.$el.querySelector('.js-builds-dropdown-container ul').textContent.trim(),
).toEqual('foo'); ).toEqual('foo');
expect(eventHub.$emit).toHaveBeenCalledWith('clickedDropdown');
done(); done();
}, 0); }, 0);
}); });
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment