Fix MR widget with external CI services/integrations

Fix https://gitlab.com/gitlab-org/gitlab-ce/issues/33287

The MR widget was trying to render the pipelines section when
there are no GitLab CI pipelines which was throwing some NPE
errors.
This commit is contained in:
Eric Eastwood 2017-08-31 19:46:18 -05:00
parent f0b089cf78
commit 5ecfd3cd0f
10 changed files with 158 additions and 26 deletions

View file

@ -12,6 +12,9 @@ export default {
ciIcon,
},
computed: {
hasPipeline() {
return this.mr.pipeline && Object.keys(this.mr.pipeline).length > 0;
},
hasCIError() {
const { hasCI, ciStatus } = this.mr;
@ -28,7 +31,9 @@ export default {
},
},
template: `
<div class="mr-widget-heading">
<div
v-if="hasPipeline || hasCIError"
class="mr-widget-heading">
<div class="ci-widget media">
<template v-if="hasCIError">
<div class="ci-status-icon ci-status-icon-failed ci-error js-ci-error append-right-10">
@ -40,7 +45,7 @@ export default {
Could not connect to the CI server. Please check your settings and try again
</div>
</template>
<template v-else>
<template v-else-if="hasPipeline">
<div class="ci-status-icon append-right-10">
<a
class="icon-link"

View file

@ -29,6 +29,9 @@ export default {
statusIcon,
},
computed: {
shouldShowMergeWhenPipelineSucceedsText() {
return this.mr.isPipelineActive;
},
commitMessageLinkTitle() {
const withDesc = 'Include description in commit message';
const withoutDesc = "Don't include description in commit message";
@ -56,7 +59,7 @@ export default {
mergeButtonText() {
if (this.isMergingImmediately) {
return 'Merge in progress';
} else if (this.mr.isPipelineActive) {
} else if (this.shouldShowMergeWhenPipelineSucceedsText) {
return 'Merge when pipeline succeeds';
}
@ -68,7 +71,7 @@ export default {
isMergeButtonDisabled() {
const { commitMessage } = this;
return Boolean(!commitMessage.length
|| !this.isMergeAllowed()
|| !this.shouldShowMergeControls()
|| this.isMakingRequest
|| this.mr.preventMerge);
},
@ -82,7 +85,12 @@ export default {
},
methods: {
isMergeAllowed() {
return !(this.mr.onlyAllowMergeIfPipelineSucceeds && this.mr.isPipelineFailed);
return !this.mr.onlyAllowMergeIfPipelineSucceeds ||
this.mr.isPipelinePassing ||
this.mr.isPipelineSkipped;
},
shouldShowMergeControls() {
return this.isMergeAllowed() || this.shouldShowMergeWhenPipelineSucceedsText;
},
updateCommitMessage() {
const cmwd = this.mr.commitMessageWithDescription;
@ -261,7 +269,7 @@ export default {
</ul>
</span>
<div class="media-body-wrap space-children">
<template v-if="isMergeAllowed()">
<template v-if="shouldShowMergeControls()">
<label>
<input
id="remove-source-branch-input"
@ -286,7 +294,7 @@ export default {
</template>
<template v-else>
<span class="bold">
The pipeline for this merge request failed. Please retry the job or push a new commit to fix the failure
The pipeline for this merge request has not succeeded yet
</span>
</template>
</div>

View file

@ -57,7 +57,7 @@ export default {
return stateMaps.statesToShowHelpWidget.indexOf(this.mr.state) > -1;
},
shouldRenderPipelines() {
return Object.keys(this.mr.pipeline).length || this.mr.hasCI;
return this.mr.hasCI;
},
shouldRenderRelatedLinks() {
return this.mr.relatedLinks;

View file

@ -85,7 +85,9 @@ export default class MergeRequestStore {
this.ciEnvironmentsStatusPath = data.ci_environments_status_path;
this.hasCI = data.has_ci;
this.ciStatus = data.ci_status;
this.isPipelineFailed = this.ciStatus ? (this.ciStatus === 'failed' || this.ciStatus === 'canceled') : false;
this.isPipelineFailed = this.ciStatus === 'failed' || this.ciStatus === 'canceled';
this.isPipelinePassing = this.ciStatus === 'success' || this.ciStatus === 'success_with_warnings';
this.isPipelineSkipped = this.ciStatus === 'skipped';
this.pipelineDetailedStatus = pipelineStatus;
this.isPipelineActive = data.pipeline ? data.pipeline.active : false;
this.isPipelineBlocked = pipelineStatus ? pipelineStatus.group === 'manual' : false;

View file

@ -0,0 +1,5 @@
---
title: Fix errors thrown in merge request widget with external CI service/integration
merge_request:
author:
type: fixed

View file

@ -142,6 +142,24 @@ describe 'Merge request', :js do
end
end
context 'view merge request where project has CI setup but no CI status' do
before do
pipeline = create(:ci_pipeline, project: project,
sha: merge_request.diff_head_sha,
ref: merge_request.source_branch)
create(:ci_build, pipeline: pipeline)
visit project_merge_request_path(project, merge_request)
end
it 'has pipeline error text' do
# Wait for the `ci_status` and `merge_check` requests
wait_for_requests
expect(page).to have_text('Could not connect to the CI server. Please check your settings and try again')
end
end
context 'view merge request with MWPS enabled but automatically merge fails' do
before do
merge_request.update(

View file

@ -37,6 +37,26 @@ describe('MRWidgetPipeline', () => {
});
});
describe('hasPipeline', () => {
it('should return true when there is a pipeline', () => {
expect(Object.keys(mockData.pipeline).length).toBeGreaterThan(0);
const vm = createComponent({
pipeline: mockData.pipeline,
});
expect(vm.hasPipeline).toBeTruthy();
});
it('should return false when there is no pipeline', () => {
const vm = createComponent({
pipeline: null,
});
expect(vm.hasPipeline).toBeFalsy();
});
});
describe('hasCIError', () => {
it('should return false when there is no CI error', () => {
const vm = createComponent({

View file

@ -11,6 +11,7 @@ const createComponent = (customConfig = {}) => {
isPipelineActive: false,
pipeline: null,
isPipelineFailed: false,
isPipelinePassing: false,
onlyAllowMergeIfPipelineSucceeds: false,
hasCI: false,
ciStatus: null,
@ -68,6 +69,18 @@ describe('MRWidgetReadyToMerge', () => {
});
describe('computed', () => {
describe('shouldShowMergeWhenPipelineSucceedsText', () => {
it('should return true with active pipeline', () => {
vm.mr.isPipelineActive = true;
expect(vm.shouldShowMergeWhenPipelineSucceedsText).toBeTruthy();
});
it('should return false with inactive pipeline', () => {
vm.mr.isPipelineActive = false;
expect(vm.shouldShowMergeWhenPipelineSucceedsText).toBeFalsy();
});
});
describe('commitMessageLinkTitle', () => {
const withDesc = 'Include description in commit message';
const withoutDesc = "Don't include description in commit message";
@ -203,20 +216,55 @@ describe('MRWidgetReadyToMerge', () => {
describe('methods', () => {
describe('isMergeAllowed', () => {
it('should return false with initial data', () => {
it('should return true when no pipeline and not required to succeed', () => {
vm.mr.onlyAllowMergeIfPipelineSucceeds = false;
vm.mr.isPipelinePassing = false;
expect(vm.isMergeAllowed()).toBeTruthy();
});
it('should return false when MR is set only merge when pipeline succeeds', () => {
vm.mr.onlyAllowMergeIfPipelineSucceeds = true;
it('should return true when pipeline failed and not required to succeed', () => {
vm.mr.onlyAllowMergeIfPipelineSucceeds = false;
vm.mr.isPipelinePassing = false;
expect(vm.isMergeAllowed()).toBeTruthy();
});
it('should return true true', () => {
it('should return false when pipeline failed and required to succeed', () => {
vm.mr.onlyAllowMergeIfPipelineSucceeds = true;
vm.mr.isPipelineFailed = true;
vm.mr.isPipelinePassing = false;
expect(vm.isMergeAllowed()).toBeFalsy();
});
it('should return true when pipeline succeeded and required to succeed', () => {
vm.mr.onlyAllowMergeIfPipelineSucceeds = true;
vm.mr.isPipelinePassing = true;
expect(vm.isMergeAllowed()).toBeTruthy();
});
});
describe('shouldShowMergeControls', () => {
it('should return false when an external pipeline is running and required to succeed', () => {
spyOn(vm, 'isMergeAllowed').and.returnValue(false);
vm.mr.isPipelineActive = false;
expect(vm.shouldShowMergeControls()).toBeFalsy();
});
it('should return true when the build succeeded or build not required to succeed', () => {
spyOn(vm, 'isMergeAllowed').and.returnValue(true);
vm.mr.isPipelineActive = false;
expect(vm.shouldShowMergeControls()).toBeTruthy();
});
it('should return true when showing the MWPS button and a pipeline is running that needs to be successful', () => {
spyOn(vm, 'isMergeAllowed').and.returnValue(false);
vm.mr.isPipelineActive = true;
expect(vm.shouldShowMergeControls()).toBeTruthy();
});
it('should return true when showing the MWPS button but not required for the pipeline to succeed', () => {
spyOn(vm, 'isMergeAllowed').and.returnValue(true);
vm.mr.isPipelineActive = true;
expect(vm.shouldShowMergeControls()).toBeTruthy();
});
});
describe('updateCommitMessage', () => {

View file

@ -59,23 +59,15 @@ describe('mrWidgetOptions', () => {
});
describe('shouldRenderPipelines', () => {
it('should return true for the initial data', () => {
it('should return true when hasCI is true', () => {
vm.mr.hasCI = true;
expect(vm.shouldRenderPipelines).toBeTruthy();
});
it('should return true when pipeline is empty but MR.hasCI is set to true', () => {
vm.mr.pipeline = {};
expect(vm.shouldRenderPipelines).toBeTruthy();
});
it('should return true when pipeline available', () => {
it('should return false when hasCI is false', () => {
vm.mr.hasCI = false;
expect(vm.shouldRenderPipelines).toBeTruthy();
});
it('should return false when there is no pipeline', () => {
vm.mr.pipeline = {};
vm.mr.hasCI = false;
expect(vm.shouldRenderPipelines).toBeFalsy();
});
});

View file

@ -18,5 +18,39 @@ describe('MergeRequestStore', () => {
store.setData({ ...mockData, work_in_progress: !mockData.work_in_progress });
expect(store.hasSHAChanged).toBe(false);
});
describe('isPipelinePassing', () => {
it('is true when the CI status is `success`', () => {
store.setData({ ...mockData, ci_status: 'success' });
expect(store.isPipelinePassing).toBe(true);
});
it('is true when the CI status is `success_with_warnings`', () => {
store.setData({ ...mockData, ci_status: 'success_with_warnings' });
expect(store.isPipelinePassing).toBe(true);
});
it('is false when the CI status is `failed`', () => {
store.setData({ ...mockData, ci_status: 'failed' });
expect(store.isPipelinePassing).toBe(false);
});
it('is false when the CI status is anything except `success`', () => {
store.setData({ ...mockData, ci_status: 'foobarbaz' });
expect(store.isPipelinePassing).toBe(false);
});
});
describe('isPipelineSkipped', () => {
it('should set isPipelineSkipped=true when the CI status is `skipped`', () => {
store.setData({ ...mockData, ci_status: 'skipped' });
expect(store.isPipelineSkipped).toBe(true);
});
it('should set isPipelineSkipped=false when the CI status is anything except `skipped`', () => {
store.setData({ ...mockData, ci_status: 'foobarbaz' });
expect(store.isPipelineSkipped).toBe(false);
});
});
});
});