Commit eaf8ae2e authored by Jacob Schatz's avatar Jacob Schatz Committed by Ruben Davila

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
parent 58e25cb3
...@@ -9,6 +9,8 @@ ...@@ -9,6 +9,8 @@
MergeRequestTabs.prototype.buildsLoaded = false; MergeRequestTabs.prototype.buildsLoaded = false;
MergeRequestTabs.prototype.pipelinesLoaded = false;
MergeRequestTabs.prototype.commitsLoaded = false; MergeRequestTabs.prototype.commitsLoaded = false;
function MergeRequestTabs(opts) { function MergeRequestTabs(opts) {
...@@ -50,6 +52,9 @@ ...@@ -50,6 +52,9 @@
} else if (action === 'builds') { } else if (action === 'builds') {
this.loadBuilds($target.attr('href')); this.loadBuilds($target.attr('href'));
this.expandView(); this.expandView();
} else if (action === 'pipelines') {
this.loadPipelines($target.attr('href'));
this.expandView();
} else { } else {
this.expandView(); this.expandView();
} }
...@@ -81,7 +86,7 @@ ...@@ -81,7 +86,7 @@
if (action === 'show') { if (action === 'show') {
action = 'notes'; 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') { if (action !== 'notes') {
new_state += "/" + action; new_state += "/" + action;
} }
...@@ -177,6 +182,21 @@ ...@@ -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) { MergeRequestTabs.prototype.toggleLoading = function(status) {
return $('.mr-loading-status .loading').toggle(status); return $('.mr-loading-status .loading').toggle(status);
}; };
......
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
MergeRequestWidget.prototype.addEventListeners = function() { MergeRequestWidget.prototype.addEventListeners = function() {
var allowedPages; var allowedPages;
allowedPages = ['show', 'commits', 'builds', 'changes']; allowedPages = ['show', 'commits', 'builds', 'pipelines', 'changes'];
return $(document).on('page:change.merge_request', (function(_this) { return $(document).on('page:change.merge_request', (function(_this) {
return function() { return function() {
var page; var page;
......
...@@ -229,3 +229,15 @@ ...@@ -229,3 +229,15 @@
box-shadow: none; 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 ...@@ -9,15 +9,15 @@ class Projects::MergeRequestsController < Projects::ApplicationController
before_action :module_enabled before_action :module_enabled
before_action :merge_request, only: [ 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 :ci_status, :toggle_subscription, :cancel_merge_when_build_succeeds, :remove_wip
] ]
before_action :validates_merge_request, 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] 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_widget_vars, only: [:merge, :cancel_merge_when_build_succeeds, :merge_check]
before_action :define_commit_vars, only: [:diffs] before_action :define_commit_vars, only: [:diffs]
before_action :define_diff_comment_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 # Allow read any merge_request
before_action :authorize_read_merge_request! before_action :authorize_read_merge_request!
...@@ -141,6 +141,19 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -141,6 +141,19 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end end
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 def new
apply_diff_view_cookie! apply_diff_view_cookie!
......
...@@ -674,10 +674,21 @@ class MergeRequest < ActiveRecord::Base ...@@ -674,10 +674,21 @@ class MergeRequest < ActiveRecord::Base
diverged_commits_count > 0 diverged_commits_count > 0
end end
def commits_sha
commits.map(&:sha)
end
def pipeline def pipeline
@pipeline ||= source_project.pipeline(diff_head_sha, source_branch) if diff_head_sha && source_project @pipeline ||= source_project.pipeline(diff_head_sha, source_branch) if diff_head_sha && source_project
end 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 def merge_commit
@merge_commit ||= project.commit(merge_commit_sha) if merge_commit_sha @merge_commit ||= project.commit(merge_commit_sha) if merge_commit_sha
end end
......
...@@ -2,19 +2,21 @@ ...@@ -2,19 +2,21 @@
%tr.commit %tr.commit
%td.commit-link %td.commit-link
= link_to namespace_project_pipeline_path(@project.namespace, @project, pipeline.id) do = link_to namespace_project_pipeline_path(@project.namespace, @project, pipeline.id) do
= ci_status_with_icon(status) - if defined?(status_icon_only) && status_icon_only
= ci_icon_for_status(status)
- else
= ci_status_with_icon(status)
%td %td
.branch-commit .branch-commit
= link_to namespace_project_pipeline_path(@project.namespace, @project, pipeline.id) do = link_to namespace_project_pipeline_path(@project.namespace, @project, pipeline.id) do
%span ##{pipeline.id} %span ##{pipeline.id}
- if pipeline.ref - if pipeline.ref
.icon-container - unless defined?(hide_branch) && hide_branch
= pipeline.tag? ? icon('tag') : icon('code-fork') .icon-container
= link_to pipeline.ref, namespace_project_commits_path(@project.namespace, @project, pipeline.ref), class: "monospace branch-name" = pipeline.tag? ? icon('tag') : icon('code-fork')
.icon-container = link_to pipeline.ref, namespace_project_commits_path(@project.namespace, @project, pipeline.ref), class: "monospace branch-name"
= custom_icon("icon_commit") .icon-container
= custom_icon("icon_commit")
= link_to pipeline.short_sha, namespace_project_commit_path(@project.namespace, @project, pipeline.sha), class: "commit-id monospace" = link_to pipeline.short_sha, namespace_project_commit_path(@project.namespace, @project, pipeline.sha), class: "commit-id monospace"
- if pipeline.latest? - if pipeline.latest?
%span.label.label-success.has-tooltip{ title: 'Latest build for this branch' } latest %span.label.label-success.has-tooltip{ title: 'Latest build for this branch' } latest
...@@ -53,7 +55,7 @@ ...@@ -53,7 +55,7 @@
- if pipeline.finished_at - if pipeline.finished_at
%p.finished-at %p.finished-at
= icon("calendar") = 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 %td.pipeline-actions
.controls.hidden-xs.pull-right .controls.hidden-xs.pull-right
......
%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
...@@ -45,20 +45,24 @@ ...@@ -45,20 +45,24 @@
- if @commits_count.nonzero? - if @commits_count.nonzero?
%ul.merge-request-tabs.nav-links.no-top.no-bottom %ul.merge-request-tabs.nav-links.no-top.no-bottom
%li.notes-tab %li.notes-tab
= link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#notes', action: 'notes', toggle: 'tab'} do = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#notes', action: 'notes', toggle: 'tab' } do
Discussion Discussion
%span.badge= @merge_request.mr_and_commit_notes.user.count %span.badge= @merge_request.mr_and_commit_notes.user.count
%li.commits-tab %li.commits-tab
= link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#commits', action: 'commits', toggle: 'tab'} do = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#commits', action: 'commits', toggle: 'tab' } do
Commits Commits
%span.badge= @commits_count %span.badge= @commits_count
- if @pipeline - 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 %li.builds-tab
= link_to builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#builds', action: 'builds', toggle: 'tab'} do = link_to builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: '#builds', action: 'builds', toggle: 'tab' } do
Builds Builds
%span.badge= @statuses.size %span.badge= @statuses.size
%li.diffs-tab %li.diffs-tab
= link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'} do = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#diffs', action: 'diffs', toggle: 'tab' } do
Changes Changes
%span.badge= @merge_request.diff_size %span.badge= @merge_request.diff_size
...@@ -76,6 +80,8 @@ ...@@ -76,6 +80,8 @@
- # This tab is always loaded via AJAX - # This tab is always loaded via AJAX
#builds.builds.tab-pane #builds.builds.tab-pane
- # This tab is always loaded via AJAX - # This tab is always loaded via AJAX
#pipelines.pipelines.tab-pane
- # This tab is always loaded via AJAX
#diffs.diffs.tab-pane #diffs.diffs.tab-pane
- # This tab is always loaded via AJAX - # This tab is always loaded via AJAX
......
= render "projects/commit/pipeline", pipeline: @pipeline, link_to_commit: true = render "projects/commit/pipeline", pipeline: @pipeline, link_to_commit: true
= render "projects/commit/pipelines_list", pipelines: @pipelines, link_to_commit: true
...@@ -23,7 +23,8 @@ ...@@ -23,7 +23,8 @@
preparing: "{{status}} build", preparing: "{{status}} build",
normal: "Build {{status}}" 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') { if (typeof merge_request_widget !== 'undefined') {
......
...@@ -723,6 +723,7 @@ Rails.application.routes.draw do ...@@ -723,6 +723,7 @@ Rails.application.routes.draw do
get :commits get :commits
get :diffs get :diffs
get :builds get :builds
get :pipelines
get :merge_check get :merge_check
post :merge post :merge
post :cancel_merge_when_build_succeeds post :cancel_merge_when_build_succeeds
......
...@@ -26,24 +26,44 @@ class Gitlab::Seeder::Builds ...@@ -26,24 +26,44 @@ class Gitlab::Seeder::Builds
begin begin
BUILDS.each { |opts| build_create!(pipeline, opts) } BUILDS.each { |opts| build_create!(pipeline, opts) }
commit_status_create!(pipeline, name: 'jenkins', status: :success) commit_status_create!(pipeline, name: 'jenkins', status: :success)
print '.' print '.'
rescue ActiveRecord::RecordInvalid rescue ActiveRecord::RecordInvalid
print 'F' print 'F'
ensure
pipeline.build_updated
end end
end end
end end
def pipelines def pipelines
commits = @project.repository.commits('master', limit: 5) master_pipelines + merge_request_pipelines
commits_sha = commits.map { |commit| commit.raw.id } end
commits_sha.map do |sha|
@project.ensure_pipeline(sha, 'master') def master_pipelines
end create_pipelines_for(@project, 'master')
rescue rescue
[] []
end 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 = {}) def build_create!(pipeline, opts = {})
attributes = build_attributes_for(pipeline, opts) attributes = build_attributes_for(pipeline, opts)
......
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 ...@@ -456,6 +456,20 @@ describe MergeRequest, models: true do
subject { create :merge_request, :simple } subject { create :merge_request, :simple }
end 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 '#pipeline' do
describe 'when the source project exists' do describe 'when the source project exists' do
it 'returns the latest pipeline' do it 'returns the latest pipeline' do
...@@ -480,6 +494,19 @@ describe MergeRequest, models: true do ...@@ -480,6 +494,19 @@ describe MergeRequest, models: true do
end end
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 describe '#participants' do
let(:project) { create(:project, :public) } let(:project) { create(:project, :public) }
......
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