Merge branch '18681-pipelines-merge-request' into 'master'
Resolve "Pipelines for merge request" ## What does this MR do? Adds `Pipelines` tab in merge request view ## What are the relevant issue numbers? Closes #18681 ## Screenshots (if relevant) ![Screen_Shot_2016-08-16_at_3.22.41_PM](/uploads/c04febab3765b1fac2bf3bbfb9882f9f/Screen_Shot_2016-08-16_at_3.22.41_PM.png) See merge request !5485
This commit is contained in:
commit
eefb2582f4
15 changed files with 205 additions and 27 deletions
|
@ -9,6 +9,8 @@
|
|||
|
||||
MergeRequestTabs.prototype.buildsLoaded = false;
|
||||
|
||||
MergeRequestTabs.prototype.pipelinesLoaded = false;
|
||||
|
||||
MergeRequestTabs.prototype.commitsLoaded = false;
|
||||
|
||||
function MergeRequestTabs(opts) {
|
||||
|
@ -50,6 +52,9 @@
|
|||
} else if (action === 'builds') {
|
||||
this.loadBuilds($target.attr('href'));
|
||||
this.expandView();
|
||||
} else if (action === 'pipelines') {
|
||||
this.loadPipelines($target.attr('href'));
|
||||
this.expandView();
|
||||
} else {
|
||||
this.expandView();
|
||||
}
|
||||
|
@ -81,7 +86,7 @@
|
|||
if (action === 'show') {
|
||||
action = 'notes';
|
||||
}
|
||||
new_state = this._location.pathname.replace(/\/(commits|diffs|builds)(\.html)?\/?$/, '');
|
||||
new_state = this._location.pathname.replace(/\/(commits|diffs|builds|pipelines)(\.html)?\/?$/, '');
|
||||
if (action !== 'notes') {
|
||||
new_state += "/" + action;
|
||||
}
|
||||
|
@ -177,6 +182,21 @@
|
|||
});
|
||||
};
|
||||
|
||||
MergeRequestTabs.prototype.loadPipelines = function(source) {
|
||||
if (this.pipelinesLoaded) {
|
||||
return;
|
||||
}
|
||||
return this._get({
|
||||
url: source + ".json",
|
||||
success: function(data) {
|
||||
$('#pipelines').html(data.html);
|
||||
gl.utils.localTimeAgo($('.js-timeago', '#pipelines'));
|
||||
this.pipelinesLoaded = true;
|
||||
return this.scrollToElement("#pipelines");
|
||||
}.bind(this)
|
||||
});
|
||||
};
|
||||
|
||||
MergeRequestTabs.prototype.toggleLoading = function(status) {
|
||||
return $('.mr-loading-status .loading').toggle(status);
|
||||
};
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
|
||||
MergeRequestWidget.prototype.addEventListeners = function() {
|
||||
var allowedPages;
|
||||
allowedPages = ['show', 'commits', 'builds', 'changes'];
|
||||
allowedPages = ['show', 'commits', 'builds', 'pipelines', 'changes'];
|
||||
return $(document).on('page:change.merge_request', (function(_this) {
|
||||
return function() {
|
||||
var page;
|
||||
|
|
|
@ -229,3 +229,15 @@
|
|||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.pipelines.tab-pane {
|
||||
|
||||
.content-list.pipelines {
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
.stage {
|
||||
max-width: 60px;
|
||||
width: 60px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,15 +9,15 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
|||
|
||||
before_action :module_enabled
|
||||
before_action :merge_request, only: [
|
||||
:edit, :update, :show, :diffs, :commits, :builds, :merge, :merge_check,
|
||||
:edit, :update, :show, :diffs, :commits, :builds, :pipelines, :merge, :merge_check,
|
||||
:ci_status, :toggle_subscription, :cancel_merge_when_build_succeeds, :remove_wip
|
||||
]
|
||||
before_action :validates_merge_request, only: [:show, :diffs, :commits, :builds]
|
||||
before_action :define_show_vars, only: [:show, :diffs, :commits, :builds]
|
||||
before_action :validates_merge_request, only: [:show, :diffs, :commits, :builds, :pipelines]
|
||||
before_action :define_show_vars, only: [:show, :diffs, :commits, :builds, :pipelines]
|
||||
before_action :define_widget_vars, only: [:merge, :cancel_merge_when_build_succeeds, :merge_check]
|
||||
before_action :define_commit_vars, only: [:diffs]
|
||||
before_action :define_diff_comment_vars, only: [:diffs]
|
||||
before_action :ensure_ref_fetched, only: [:show, :diffs, :commits, :builds]
|
||||
before_action :ensure_ref_fetched, only: [:show, :diffs, :commits, :builds, :pipelines]
|
||||
|
||||
# Allow read any merge_request
|
||||
before_action :authorize_read_merge_request!
|
||||
|
@ -141,6 +141,19 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def pipelines
|
||||
@pipelines = @merge_request.all_pipelines
|
||||
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
define_discussion_vars
|
||||
|
||||
render 'show'
|
||||
end
|
||||
format.json { render json: { html: view_to_html_string('projects/merge_requests/show/_pipelines') } }
|
||||
end
|
||||
end
|
||||
|
||||
def new
|
||||
build_merge_request
|
||||
@noteable = @merge_request
|
||||
|
|
|
@ -674,10 +674,21 @@ class MergeRequest < ActiveRecord::Base
|
|||
diverged_commits_count > 0
|
||||
end
|
||||
|
||||
def commits_sha
|
||||
commits.map(&:sha)
|
||||
end
|
||||
|
||||
def pipeline
|
||||
@pipeline ||= source_project.pipeline(diff_head_sha, source_branch) if diff_head_sha && source_project
|
||||
end
|
||||
|
||||
def all_pipelines
|
||||
@all_pipelines ||=
|
||||
if diff_head_sha && source_project
|
||||
source_project.pipelines.order(id: :desc).where(sha: commits_sha, ref: source_branch)
|
||||
end
|
||||
end
|
||||
|
||||
def merge_commit
|
||||
@merge_commit ||= project.commit(merge_commit_sha) if merge_commit_sha
|
||||
end
|
||||
|
|
|
@ -2,14 +2,16 @@
|
|||
%tr.commit
|
||||
%td.commit-link
|
||||
= link_to namespace_project_pipeline_path(@project.namespace, @project, pipeline.id) do
|
||||
- if defined?(status_icon_only) && status_icon_only
|
||||
= ci_icon_for_status(status)
|
||||
- else
|
||||
= ci_status_with_icon(status)
|
||||
|
||||
|
||||
%td
|
||||
.branch-commit
|
||||
= link_to namespace_project_pipeline_path(@project.namespace, @project, pipeline.id) do
|
||||
%span ##{pipeline.id}
|
||||
- if pipeline.ref
|
||||
- unless defined?(hide_branch) && hide_branch
|
||||
.icon-container
|
||||
= pipeline.tag? ? icon('tag') : icon('code-fork')
|
||||
= link_to pipeline.ref, namespace_project_commits_path(@project.namespace, @project, pipeline.ref), class: "monospace branch-name"
|
||||
|
@ -53,7 +55,7 @@
|
|||
- if pipeline.finished_at
|
||||
%p.finished-at
|
||||
= icon("calendar")
|
||||
#{time_ago_with_tooltip(pipeline.finished_at, short_format: true, skip_js: true)}
|
||||
#{time_ago_with_tooltip(pipeline.finished_at, short_format: false, skip_js: true)}
|
||||
|
||||
%td.pipeline-actions
|
||||
.controls.hidden-xs.pull-right
|
||||
|
|
17
app/views/projects/commit/_pipelines_list.haml
Normal file
17
app/views/projects/commit/_pipelines_list.haml
Normal file
|
@ -0,0 +1,17 @@
|
|||
%ul.content-list.pipelines
|
||||
- if pipelines.blank?
|
||||
%li
|
||||
.nothing-here-block No pipelines to show
|
||||
- else
|
||||
.table-holder
|
||||
%table.table.builds
|
||||
%tbody
|
||||
%th Status
|
||||
%th Commit
|
||||
- pipelines.stages.each do |stage|
|
||||
%th.stage
|
||||
%span.has-tooltip{ title: "#{stage.titleize}" }
|
||||
= stage.titleize
|
||||
%th
|
||||
%th
|
||||
= render pipelines, commit_sha: true, stage: true, allow_retry: true, stages: pipelines.stages, status_icon_only: true, hide_branch: true
|
|
@ -53,6 +53,10 @@
|
|||
Commits
|
||||
%span.badge= @commits_count
|
||||
- if @pipeline
|
||||
%li.pipelines-tab
|
||||
= link_to pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: '#pipelines', action: 'pipelines', toggle: 'tab' } do
|
||||
Pipelines
|
||||
%span.badge= @merge_request.all_pipelines.size
|
||||
%li.builds-tab
|
||||
= link_to builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: '#builds', action: 'builds', toggle: 'tab' } do
|
||||
Builds
|
||||
|
@ -76,6 +80,8 @@
|
|||
- # This tab is always loaded via AJAX
|
||||
#builds.builds.tab-pane
|
||||
- # This tab is always loaded via AJAX
|
||||
#pipelines.pipelines.tab-pane
|
||||
- # This tab is always loaded via AJAX
|
||||
#diffs.diffs.tab-pane
|
||||
- # This tab is always loaded via AJAX
|
||||
|
||||
|
|
|
@ -1,2 +1 @@
|
|||
= render "projects/commit/pipeline", pipeline: @pipeline, link_to_commit: true
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
= render "projects/commit/pipelines_list", pipelines: @pipelines, link_to_commit: true
|
|
@ -23,7 +23,8 @@
|
|||
preparing: "{{status}} build",
|
||||
normal: "Build {{status}}"
|
||||
},
|
||||
builds_path: "#{builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}"
|
||||
builds_path: "#{builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
|
||||
pipelines_path: "#{pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}"
|
||||
};
|
||||
|
||||
if (typeof merge_request_widget !== 'undefined') {
|
||||
|
|
|
@ -728,6 +728,7 @@ Rails.application.routes.draw do
|
|||
get :commits
|
||||
get :diffs
|
||||
get :builds
|
||||
get :pipelines
|
||||
get :merge_check
|
||||
post :merge
|
||||
post :cancel_merge_when_build_succeeds
|
||||
|
|
|
@ -26,24 +26,44 @@ class Gitlab::Seeder::Builds
|
|||
begin
|
||||
BUILDS.each { |opts| build_create!(pipeline, opts) }
|
||||
commit_status_create!(pipeline, name: 'jenkins', status: :success)
|
||||
|
||||
print '.'
|
||||
rescue ActiveRecord::RecordInvalid
|
||||
print 'F'
|
||||
ensure
|
||||
pipeline.build_updated
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def pipelines
|
||||
commits = @project.repository.commits('master', limit: 5)
|
||||
commits_sha = commits.map { |commit| commit.raw.id }
|
||||
commits_sha.map do |sha|
|
||||
@project.ensure_pipeline(sha, 'master')
|
||||
master_pipelines + merge_request_pipelines
|
||||
end
|
||||
|
||||
def master_pipelines
|
||||
create_pipelines_for(@project, 'master')
|
||||
rescue
|
||||
[]
|
||||
end
|
||||
|
||||
def merge_request_pipelines
|
||||
@project.merge_requests.last(5).map do |merge_request|
|
||||
create_pipelines(merge_request.source_project, merge_request.source_branch, merge_request.commits.last(5))
|
||||
end.flatten
|
||||
rescue
|
||||
[]
|
||||
end
|
||||
|
||||
def create_pipelines_for(project, ref)
|
||||
commits = project.repository.commits(ref, limit: 5)
|
||||
create_pipelines(project, ref, commits)
|
||||
end
|
||||
|
||||
def create_pipelines(project, ref, commits)
|
||||
commits.map do |commit|
|
||||
project.pipelines.create(sha: commit.id, ref: ref)
|
||||
end
|
||||
end
|
||||
|
||||
def build_create!(pipeline, opts = {})
|
||||
attributes = build_attributes_for(pipeline, opts)
|
||||
|
||||
|
|
48
spec/features/merge_requests/pipelines_spec.rb
Normal file
48
spec/features/merge_requests/pipelines_spec.rb
Normal file
|
@ -0,0 +1,48 @@
|
|||
require 'spec_helper'
|
||||
|
||||
feature 'Pipelines for Merge Requests', feature: true, js: true do
|
||||
include WaitForAjax
|
||||
|
||||
given(:user) { create(:user) }
|
||||
given(:merge_request) { create(:merge_request) }
|
||||
given(:project) { merge_request.target_project }
|
||||
|
||||
before do
|
||||
project.team << [user, :master]
|
||||
login_as user
|
||||
end
|
||||
|
||||
context 'with pipelines' do
|
||||
let!(:pipeline) do
|
||||
create(:ci_empty_pipeline,
|
||||
project: merge_request.source_project,
|
||||
ref: merge_request.source_branch,
|
||||
sha: merge_request.diff_head_sha)
|
||||
end
|
||||
|
||||
before do
|
||||
visit namespace_project_merge_request_path(project.namespace, project, merge_request)
|
||||
end
|
||||
|
||||
scenario 'user visits merge request pipelines tab' do
|
||||
page.within('.merge-request-tabs') do
|
||||
click_link('Pipelines')
|
||||
end
|
||||
wait_for_ajax
|
||||
|
||||
expect(page).to have_selector('.pipeline-actions')
|
||||
end
|
||||
end
|
||||
|
||||
context 'without pipelines' do
|
||||
before do
|
||||
visit namespace_project_merge_request_path(project.namespace, project, merge_request)
|
||||
end
|
||||
|
||||
scenario 'user visits merge request page' do
|
||||
page.within('.merge-request-tabs') do
|
||||
expect(page).to have_no_link('Pipelines')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -456,6 +456,20 @@ describe MergeRequest, models: true do
|
|||
subject { create :merge_request, :simple }
|
||||
end
|
||||
|
||||
describe '#commits_sha' do
|
||||
let(:commit0) { double('commit0', sha: 'sha1') }
|
||||
let(:commit1) { double('commit1', sha: 'sha2') }
|
||||
let(:commit2) { double('commit2', sha: 'sha3') }
|
||||
|
||||
before do
|
||||
allow(subject.merge_request_diff).to receive(:commits).and_return([commit0, commit1, commit2])
|
||||
end
|
||||
|
||||
it 'returns sha of commits' do
|
||||
expect(subject.commits_sha).to contain_exactly('sha1', 'sha2', 'sha3')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#pipeline' do
|
||||
describe 'when the source project exists' do
|
||||
it 'returns the latest pipeline' do
|
||||
|
@ -480,6 +494,19 @@ describe MergeRequest, models: true do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#all_pipelines' do
|
||||
let!(:pipelines) do
|
||||
subject.merge_request_diff.commits.map do |commit|
|
||||
create(:ci_empty_pipeline, project: subject.source_project, sha: commit.id, ref: subject.source_branch)
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns a pipelines from source projects with proper ordering' do
|
||||
expect(subject.all_pipelines).not_to be_empty
|
||||
expect(subject.all_pipelines).to eq(pipelines.reverse)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#participants' do
|
||||
let(:project) { create(:project, :public) }
|
||||
|
||||
|
|
Loading…
Reference in a new issue