Commit 7e6b5749 authored by Nathan Friend's avatar Nathan Friend Committed by Filipa Lacerda

Update merge request widget pipeline block

This commit updates the pipeline block that appears on the merge request
page to include information that will be exposed by the API as part of
the post-merge pipeline feature.
parent 1e8cd7f9
<script> <script>
/* eslint-disable vue/require-default-prop */ /* eslint-disable vue/require-default-prop */
import { GlTooltipDirective, GlLink } from '@gitlab/ui';
import { sprintf, __ } from '~/locale'; import { sprintf, __ } from '~/locale';
import PipelineStage from '~/pipelines/components/stage.vue'; import PipelineStage from '~/pipelines/components/stage.vue';
import CiIcon from '~/vue_shared/components/ci_icon.vue'; import CiIcon from '~/vue_shared/components/ci_icon.vue';
...@@ -14,9 +15,13 @@ export default { ...@@ -14,9 +15,13 @@ export default {
CiIcon, CiIcon,
Icon, Icon,
TooltipOnTruncate, TooltipOnTruncate,
GlLink,
LinkedPipelinesMiniList: () => LinkedPipelinesMiniList: () =>
import('ee_component/vue_shared/components/linked_pipelines_mini_list.vue'), import('ee_component/vue_shared/components/linked_pipelines_mini_list.vue'),
}, },
directives: {
GlTooltip: GlTooltipDirective,
},
mixins: [mrWidgetPipelineMixin], mixins: [mrWidgetPipelineMixin],
props: { props: {
pipeline: { pipeline: {
...@@ -78,12 +83,18 @@ export default { ...@@ -78,12 +83,18 @@ export default {
false, false,
); );
}, },
isTriggeredByMergeRequest() {
return Boolean(this.pipeline.merge_request);
},
isMergeRequestPipeline() {
return Boolean(this.pipeline.flags && this.pipeline.flags.merge_request_pipeline);
},
}, },
}; };
</script> </script>
<template> <template>
<div v-if="hasPipeline || hasCIError" class="ci-widget media"> <div v-if="hasPipeline || hasCIError" class="ci-widget media js-ci-widget">
<template v-if="hasCIError"> <template v-if="hasCIError">
<div <div
class="add-border ci-status-icon ci-status-icon-failed ci-error js-ci-error append-right-default" class="add-border ci-status-icon ci-status-icon-failed ci-error js-ci-error append-right-default"
...@@ -99,21 +110,58 @@ export default { ...@@ -99,21 +110,58 @@ export default {
<div class="ci-widget-container d-flex"> <div class="ci-widget-container d-flex">
<div class="ci-widget-content"> <div class="ci-widget-content">
<div class="media-body"> <div class="media-body">
<div class="font-weight-bold"> <div class="font-weight-bold js-pipeline-info-container">
Pipeline {{ s__('Pipeline|Pipeline') }}
<a :href="pipeline.path" class="pipeline-id font-weight-normal pipeline-number" <gl-link :href="pipeline.path" class="pipeline-id font-weight-normal pipeline-number"
>#{{ pipeline.id }}</a >#{{ pipeline.id }}</gl-link
> >
{{ pipeline.details.status.label }} {{ pipeline.details.status.label }}
<template v-if="hasCommitInfo"> <template v-if="hasCommitInfo">
for {{ s__('Pipeline|for') }}
<a <gl-link
:href="pipeline.commit.commit_path" :href="pipeline.commit.commit_path"
class="commit-sha js-commit-link font-weight-normal" class="commit-sha js-commit-link font-weight-normal"
>{{ pipeline.commit.short_id }}</a >{{ pipeline.commit.short_id }}</gl-link
>
{{ s__('Pipeline|on') }}
<template v-if="isTriggeredByMergeRequest">
<gl-link
v-gl-tooltip
:href="pipeline.merge_request.path"
:title="pipeline.merge_request.title"
class="font-weight-normal"
>!{{ pipeline.merge_request.iid }}</gl-link
>
{{ s__('Pipeline|with') }}
<tooltip-on-truncate
:title="pipeline.merge_request.source_branch"
truncate-target="child"
class="label-branch label-truncate"
>
<gl-link
:href="pipeline.merge_request.source_branch_path"
class="font-weight-normal"
>{{ pipeline.merge_request.source_branch }}</gl-link
> >
on </tooltip-on-truncate>
<template v-if="isMergeRequestPipeline">
{{ s__('Pipeline|into') }}
<tooltip-on-truncate <tooltip-on-truncate
:title="pipeline.merge_request.target_branch"
truncate-target="child"
class="label-branch label-truncate"
>
<gl-link
:href="pipeline.merge_request.target_branch_path"
class="font-weight-normal"
>{{ pipeline.merge_request.target_branch }}</gl-link
>
</tooltip-on-truncate>
</template>
</template>
<tooltip-on-truncate
v-else
:title="sourceBranch" :title="sourceBranch"
truncate-target="child" truncate-target="child"
class="label-branch label-truncate" class="label-branch label-truncate"
...@@ -121,7 +169,9 @@ export default { ...@@ -121,7 +169,9 @@ export default {
/> />
</template> </template>
</div> </div>
<div v-if="pipeline.coverage" class="coverage">Coverage {{ pipeline.coverage }}%</div> <div v-if="pipeline.coverage" class="coverage">
{{ s__('Pipeline|Coverage') }} {{ pipeline.coverage }}%
</div>
</div> </div>
</div> </div>
<div> <div>
......
...@@ -746,6 +746,10 @@ module Ci ...@@ -746,6 +746,10 @@ module Ci
triggered_by_merge_request? && target_sha == merge_request.target_branch_sha triggered_by_merge_request? && target_sha == merge_request.target_branch_sha
end end
def matches_sha_or_source_sha?(sha)
self.sha == sha || self.source_sha == sha
end
private private
def ci_yaml_from_repo def ci_yaml_from_repo
......
...@@ -232,7 +232,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -232,7 +232,7 @@ class MergeRequest < ActiveRecord::Base
# branch head commit, for example checking if a merge request can be merged. # branch head commit, for example checking if a merge request can be merged.
# For more information check: https://gitlab.com/gitlab-org/gitlab-ce/issues/40004 # For more information check: https://gitlab.com/gitlab-org/gitlab-ce/issues/40004
def actual_head_pipeline def actual_head_pipeline
head_pipeline&.sha == diff_head_sha ? head_pipeline : nil head_pipeline&.matches_sha_or_source_sha?(diff_head_sha) ? head_pipeline : nil
end end
def merge_pipeline def merge_pipeline
......
---
title: Update pipeline block on merge request page to accommodate post-merge pipeline
information
merge_request: 25745
author:
type: added
...@@ -5791,6 +5791,9 @@ msgstr "" ...@@ -5791,6 +5791,9 @@ msgstr ""
msgid "Pipeline|Commit" msgid "Pipeline|Commit"
msgstr "" msgstr ""
msgid "Pipeline|Coverage"
msgstr ""
msgid "Pipeline|Create for" msgid "Pipeline|Create for"
msgstr "" msgstr ""
...@@ -5836,9 +5839,21 @@ msgstr "" ...@@ -5836,9 +5839,21 @@ msgstr ""
msgid "Pipeline|all" msgid "Pipeline|all"
msgstr "" msgstr ""
msgid "Pipeline|for"
msgstr ""
msgid "Pipeline|into"
msgstr ""
msgid "Pipeline|on"
msgstr ""
msgid "Pipeline|success" msgid "Pipeline|success"
msgstr "" msgstr ""
msgid "Pipeline|with"
msgstr ""
msgid "Pipeline|with stage" msgid "Pipeline|with stage"
msgstr "" msgstr ""
......
...@@ -145,6 +145,119 @@ describe 'Merge request > User sees merge widget', :js do ...@@ -145,6 +145,119 @@ describe 'Merge request > User sees merge widget', :js do
end end
end end
context 'when merge request has a branch pipeline as the head pipeline' do
let!(:pipeline) do
create(:ci_pipeline,
ref: merge_request.source_branch,
sha: merge_request.source_branch_sha,
project: merge_request.source_project)
end
before do
merge_request.update_head_pipeline
visit project_merge_request_path(project, merge_request)
end
it 'shows head pipeline information' do
within '.ci-widget-content' do
expect(page).to have_content("Pipeline ##{pipeline.id} pending " \
"for #{pipeline.short_sha} " \
"on #{pipeline.ref}")
end
end
end
context 'when merge request has a detached merge request pipeline as the head pipeline' do
let(:merge_request) do
create(:merge_request,
:with_detached_merge_request_pipeline,
source_project: source_project,
target_project: target_project)
end
let!(:pipeline) do
merge_request.all_pipelines.last
end
let(:source_project) { project }
let(:target_project) { project }
before do
merge_request.update_head_pipeline
visit project_merge_request_path(project, merge_request)
end
it 'shows head pipeline information' do
within '.ci-widget-content' do
expect(page).to have_content("Pipeline ##{pipeline.id} pending " \
"for #{pipeline.short_sha} " \
"on #{merge_request.to_reference} " \
"with #{merge_request.source_branch}")
end
end
context 'when source project is a forked project' do
let(:source_project) { fork_project(project, user, repository: true) }
it 'shows head pipeline information' do
within '.ci-widget-content' do
expect(page).to have_content("Pipeline ##{pipeline.id} pending " \
"for #{pipeline.short_sha} " \
"on #{merge_request.to_reference} " \
"with #{merge_request.source_branch}")
end
end
end
end
context 'when merge request has a merge request pipeline as the head pipeline' do
let(:merge_request) do
create(:merge_request,
:with_merge_request_pipeline,
source_project: source_project,
target_project: target_project,
merge_sha: merge_sha)
end
let!(:pipeline) do
merge_request.all_pipelines.last
end
let(:source_project) { project }
let(:target_project) { project }
let(:merge_sha) { project.commit.sha }
before do
merge_request.update_head_pipeline
visit project_merge_request_path(project, merge_request)
end
it 'shows head pipeline information' do
within '.ci-widget-content' do
expect(page).to have_content("Pipeline ##{pipeline.id} pending " \
"for #{pipeline.short_sha} " \
"on #{merge_request.to_reference} " \
"with #{merge_request.source_branch} " \
"into #{merge_request.target_branch}")
end
end
context 'when source project is a forked project' do
let(:source_project) { fork_project(project, user, repository: true) }
let(:merge_sha) { source_project.commit.sha }
it 'shows head pipeline information' do
within '.ci-widget-content' do
expect(page).to have_content("Pipeline ##{pipeline.id} pending " \
"for #{pipeline.short_sha} " \
"on #{merge_request.to_reference} " \
"with #{merge_request.source_branch} " \
"into #{merge_request.target_branch}")
end
end
end
end
context 'view merge request with MWBS button' do context 'view merge request with MWBS button' do
before do before do
commit_status = create(:commit_status, project: project, status: 'pending') commit_status = create(:commit_status, project: project, status: 'pending')
......
import Vue from 'vue'; import Vue from 'vue';
import pipelineComponent from '~/vue_merge_request_widget/components/mr_widget_pipeline.vue'; import pipelineComponent from '~/vue_merge_request_widget/components/mr_widget_pipeline.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper'; import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { trimText } from 'spec/helpers/vue_component_helper';
import mockData from '../mock_data'; import mockData from '../mock_data';
describe('MRWidgetPipeline', () => { describe('MRWidgetPipeline', () => {
...@@ -123,7 +124,7 @@ describe('MRWidgetPipeline', () => { ...@@ -123,7 +124,7 @@ describe('MRWidgetPipeline', () => {
describe('without commit path', () => { describe('without commit path', () => {
beforeEach(() => { beforeEach(() => {
const mockCopy = Object.assign({}, mockData); const mockCopy = JSON.parse(JSON.stringify(mockData));
delete mockCopy.pipeline.commit; delete mockCopy.pipeline.commit;
vm = mountComponent(Component, { vm = mountComponent(Component, {
...@@ -164,7 +165,7 @@ describe('MRWidgetPipeline', () => { ...@@ -164,7 +165,7 @@ describe('MRWidgetPipeline', () => {
describe('without coverage', () => { describe('without coverage', () => {
it('should not render a coverage', () => { it('should not render a coverage', () => {
const mockCopy = Object.assign({}, mockData); const mockCopy = JSON.parse(JSON.stringify(mockData));
delete mockCopy.pipeline.coverage; delete mockCopy.pipeline.coverage;
vm = mountComponent(Component, { vm = mountComponent(Component, {
...@@ -180,7 +181,7 @@ describe('MRWidgetPipeline', () => { ...@@ -180,7 +181,7 @@ describe('MRWidgetPipeline', () => {
describe('without a pipeline graph', () => { describe('without a pipeline graph', () => {
it('should not render a pipeline graph', () => { it('should not render a pipeline graph', () => {
const mockCopy = Object.assign({}, mockData); const mockCopy = JSON.parse(JSON.stringify(mockData));
delete mockCopy.pipeline.details.stages; delete mockCopy.pipeline.details.stages;
vm = mountComponent(Component, { vm = mountComponent(Component, {
...@@ -193,5 +194,81 @@ describe('MRWidgetPipeline', () => { ...@@ -193,5 +194,81 @@ describe('MRWidgetPipeline', () => {
expect(vm.$el.querySelector('.js-mini-pipeline-graph')).toEqual(null); expect(vm.$el.querySelector('.js-mini-pipeline-graph')).toEqual(null);
}); });
}); });
describe('without pipeline.merge_request', () => {
it('should render info that includes the commit and branch details', () => {
const mockCopy = JSON.parse(JSON.stringify(mockData));
delete mockCopy.pipeline.merge_request;
const { pipeline } = mockCopy;
vm = mountComponent(Component, {
pipeline,
hasCi: true,
ciStatus: 'success',
troubleshootingDocsPath: 'help',
sourceBranchLink: mockCopy.source_branch_link,
});
const expected = `Pipeline #${pipeline.id} ${pipeline.details.status.label} for ${
pipeline.commit.short_id
} on ${mockCopy.source_branch_link}`;
const actual = trimText(vm.$el.querySelector('.js-pipeline-info-container').innerText);
expect(actual).toBe(expected);
});
});
describe('with pipeline.merge_request and flags.merge_request_pipeline', () => {
it('should render info that includes the commit, MR, source branch, and target branch details', () => {
const mockCopy = JSON.parse(JSON.stringify(mockData));
const { pipeline } = mockCopy;
pipeline.flags.merge_request_pipeline = true;
pipeline.flags.detached_merge_request_pipeline = false;
vm = mountComponent(Component, {
pipeline,
hasCi: true,
ciStatus: 'success',
troubleshootingDocsPath: 'help',
sourceBranchLink: mockCopy.source_branch_link,
});
const expected = `Pipeline #${pipeline.id} ${pipeline.details.status.label} for ${
pipeline.commit.short_id
} on !${pipeline.merge_request.iid} with ${pipeline.merge_request.source_branch} into ${
pipeline.merge_request.target_branch
}`;
const actual = trimText(vm.$el.querySelector('.js-pipeline-info-container').innerText);
expect(actual).toBe(expected);
});
});
describe('with pipeline.merge_request and flags.detached_merge_request_pipeline', () => {
it('should render info that includes the commit, MR, and source branch details', () => {
const mockCopy = JSON.parse(JSON.stringify(mockData));
const { pipeline } = mockCopy;
pipeline.flags.merge_request_pipeline = false;
pipeline.flags.detached_merge_request_pipeline = true;
vm = mountComponent(Component, {
pipeline,
hasCi: true,
ciStatus: 'success',
troubleshootingDocsPath: 'help',
sourceBranchLink: mockCopy.source_branch_link,
});
const expected = `Pipeline #${pipeline.id} ${pipeline.details.status.label} for ${
pipeline.commit.short_id
} on !${pipeline.merge_request.iid} with ${pipeline.merge_request.source_branch}`;
const actual = trimText(vm.$el.querySelector('.js-pipeline-info-container').innerText);
expect(actual).toBe(expected);
});
});
}); });
}); });
...@@ -134,6 +134,8 @@ export default { ...@@ -134,6 +134,8 @@ export default {
yaml_errors: false, yaml_errors: false,
retryable: true, retryable: true,
cancelable: false, cancelable: false,
merge_request_pipeline: false,
detached_merge_request_pipeline: true,
}, },
ref: { ref: {
name: 'daaaa', name: 'daaaa',
...@@ -141,6 +143,15 @@ export default { ...@@ -141,6 +143,15 @@ export default {
tag: false, tag: false,
branch: true, branch: true,
}, },
merge_request: {
iid: 1,
path: '/root/detached-merge-request-pipelines/merge_requests/1',
title: 'Update README.md',
source_branch: 'feature-1',
source_branch_path: '/root/detached-merge-request-pipelines/branches/feature-1',
target_branch: 'master',
target_branch_path: '/root/detached-merge-request-pipelines/branches/master',
},
commit: { commit: {
id: '104096c51715e12e7ae41f9333e9fa35b73f385d', id: '104096c51715e12e7ae41f9333e9fa35b73f385d',
short_id: '104096c5', short_id: '104096c5',
......
...@@ -362,6 +362,30 @@ describe Ci::Pipeline, :mailer do ...@@ -362,6 +362,30 @@ describe Ci::Pipeline, :mailer do
end end
end end
describe '#matches_sha_or_source_sha?' do
subject { pipeline.matches_sha_or_source_sha?(sample_sha) }
let(:sample_sha) { Digest::SHA1.hexdigest(SecureRandom.hex) }
context 'when sha matches' do
let(:pipeline) { build(:ci_pipeline, sha: sample_sha) }
it { is_expected.to be_truthy }
end
context 'when source_sha matches' do
let(:pipeline) { build(:ci_pipeline, source_sha: sample_sha) }
it { is_expected.to be_truthy }
end
context 'when both sha and source_sha do not matche' do
let(:pipeline) { build(:ci_pipeline, sha: 'test', source_sha: 'test') }
it { is_expected.to be_falsy }
end
end
describe '.triggered_for_branch' do describe '.triggered_for_branch' do
subject { described_class.triggered_for_branch(ref) } subject { described_class.triggered_for_branch(ref) }
......
...@@ -65,7 +65,7 @@ describe EnvironmentStatus do ...@@ -65,7 +65,7 @@ describe EnvironmentStatus do
describe '.for_merge_request' do describe '.for_merge_request' do
let(:admin) { create(:admin) } let(:admin) { create(:admin) }
let(:pipeline) { create(:ci_pipeline, sha: sha) } let!(:pipeline) { create(:ci_pipeline, sha: sha, merge_requests_as_head_pipeline: [merge_request]) }
it 'is based on merge_request.diff_head_sha' do it 'is based on merge_request.diff_head_sha' do
expect(merge_request).to receive(:diff_head_sha) expect(merge_request).to receive(:diff_head_sha)
......
...@@ -1187,8 +1187,10 @@ describe MergeRequest do ...@@ -1187,8 +1187,10 @@ describe MergeRequest do
end end
context 'head pipeline' do context 'head pipeline' do
let(:diff_head_sha) { Digest::SHA1.hexdigest(SecureRandom.hex) }
before do before do
allow(subject).to receive(:diff_head_sha).and_return('lastsha') allow(subject).to receive(:diff_head_sha).and_return(diff_head_sha)
end end
describe '#head_pipeline' do describe '#head_pipeline' do
...@@ -1216,7 +1218,15 @@ describe MergeRequest do ...@@ -1216,7 +1218,15 @@ describe MergeRequest do
end end
it 'returns the pipeline for MR with recent pipeline' do it 'returns the pipeline for MR with recent pipeline' do
pipeline = create(:ci_empty_pipeline, sha: 'lastsha') pipeline = create(:ci_empty_pipeline, sha: diff_head_sha)
subject.update_attribute(:head_pipeline_id, pipeline.id)
expect(subject.actual_head_pipeline).to eq(subject.head_pipeline)
expect(subject.actual_head_pipeline).to eq(pipeline)
end
it 'returns the pipeline for MR with recent merge request pipeline' do
pipeline = create(:ci_empty_pipeline, sha: 'merge-sha', source_sha: diff_head_sha)
subject.update_attribute(:head_pipeline_id, pipeline.id) subject.update_attribute(:head_pipeline_id, pipeline.id)
expect(subject.actual_head_pipeline).to eq(subject.head_pipeline) expect(subject.actual_head_pipeline).to eq(subject.head_pipeline)
......
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