Commit 52be9e20 authored by Kamil Trzcinski's avatar Kamil Trzcinski

Merge remote-tracking branch 'origin/master' into ci-commit-as-pipeline

# Conflicts:
#	app/views/projects/commits/_commit.html.haml
parents af7214d0 7998725e
......@@ -691,7 +691,7 @@ Style/ZeroLengthPredicate:
# branches, and conditions.
Metrics/AbcSize:
Enabled: true
Max: 70
Max: 60
# Avoid excessive block nesting.
Metrics/BlockNesting:
......
class @Compare
constructor: (@opts) ->
@source_loading = $ ".js-source-loading"
@target_loading = $ ".js-target-loading"
$('.js-compare-dropdown').each (i, dropdown) =>
$dropdown = $(dropdown)
$dropdown.glDropdown(
selectable: true
fieldName: $dropdown.data 'field-name'
filterable: true
id: (obj, $el) ->
$el.data 'id'
toggleLabel: (obj, $el) ->
$el.text().trim()
clicked: (e, el) =>
if $dropdown.is '.js-target-branch'
@getTargetHtml()
else if $dropdown.is '.js-source-branch'
@getSourceHtml()
else if $dropdown.is '.js-target-project'
@getTargetProject()
)
@initialState()
initialState: ->
@getSourceHtml()
@getTargetHtml()
getTargetProject: ->
$.ajax(
url: @opts.targetProjectUrl
data:
target_project_id: $("input[name='merge_request[target_project_id]']").val()
beforeSend: ->
$('.mr_target_commit').empty()
success: (html) ->
$('.js-target-branch-dropdown .dropdown-content').html html
)
getSourceHtml: ->
@sendAjax(@opts.sourceBranchUrl, @source_loading, '.mr_source_commit',
ref: $("input[name='merge_request[source_branch]']").val()
)
getTargetHtml: ->
@sendAjax(@opts.targetBranchUrl, @target_loading, '.mr_target_commit',
target_project_id: $("input[name='merge_request[target_project_id]']").val()
ref: $("input[name='merge_request[target_branch]']").val()
)
sendAjax: (url, loading, target, data) ->
$target = $(target)
$.ajax(
url: url
data: data
beforeSend: ->
loading.show()
$target.empty()
success: (html) ->
loading.hide()
$target.html html
$('.js-timeago', $target).timeago()
)
......@@ -57,14 +57,30 @@ class GitLabDropdownFilter
filter: (search_text) ->
data = @options.data()
if data?
results = data
if search_text isnt ""
if search_text isnt ''
results = fuzzaldrinPlus.filter(data, search_text,
key: @options.keys
)
@options.callback results
else
elements = @options.elements()
if search_text
elements.each ->
$el = $(@)
matches = fuzzaldrinPlus.match($el.text().trim(), search_text)
if matches.length
$el.show()
else
$el.hide()
else
elements.show()
class GitLabDropdownRemote
constructor: (@dataEndpoint, @options) ->
......@@ -123,7 +139,7 @@ class GitLabDropdown
if _.isString(@filterInput)
@filterInput = @getElement(@filterInput)
search_fields = if @options.search then @options.search.fields else [];
searchFields = if @options.search then @options.search.fields else [];
if @options.data
# If data is an array
......@@ -147,7 +163,14 @@ class GitLabDropdown
filterInputBlur: @filterInputBlur
remote: @options.filterRemote
query: @options.data
keys: @options.search.fields
keys: searchFields
elements: =>
selector = '.dropdown-content li:not(.divider)'
if @dropdown.find('.dropdown-toggle-page').length
selector = ".dropdown-page-one #{selector}"
return $(selector)
data: =>
return @fullData
callback: (data) =>
......@@ -376,7 +399,7 @@ class GitLabDropdown
# Toggle the dropdown label
if @options.toggleLabel
$(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selectedObject)
$(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selectedObject, el)
if value?
if !field.length and fieldName
# Create hidden input for form
......
......@@ -85,15 +85,21 @@ class @MilestoneSelect
# display:block overrides the hide-collapse rule
$value.removeAttr('style')
clicked: (selected) ->
page = $('body').data 'page'
isIssueIndex = page is 'projects:issues:index'
isMRIndex = page is page is 'projects:merge_requests:index'
if $dropdown.hasClass 'js-filter-bulk-update'
return
if $dropdown.hasClass('js-filter-submit')
if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
if selected.name?
selectedMilestone = selected.name
else
selectedMilestone = ''
Issues.filterResults $dropdown.closest('form')
else if $dropdown.hasClass('js-filter-submit')
$dropdown.closest('form').submit()
else
selected = $selectbox
.find('input[type="hidden"]')
......
......@@ -248,7 +248,7 @@
.dropdown-title {
position: relative;
padding: 0 0 15px;
padding: 0 25px 15px;
margin: 0 10px 10px;
font-weight: 600;
line-height: 1;
......@@ -275,7 +275,7 @@
}
.dropdown-menu-close {
right: 7px;
right: 5px;
width: 20px;
height: 20px;
top: -1px;
......
......@@ -47,6 +47,7 @@ li.commit {
.commit_short_id {
min-width: 65px;
color: $gl-dark-link-color;
font-family: $monospace_font;
}
......@@ -88,6 +89,10 @@ li.commit {
padding: 0;
margin: 0;
}
a {
color: $gl-dark-link-color;
}
}
.commit-row-info {
......
......@@ -59,6 +59,9 @@
position: relative;
overflow-y: auto;
padding: 15px;
.form-actions {
margin: -$gl-padding+1;
}
}
body.modal-open {
......
......@@ -123,6 +123,8 @@
.mr_source_commit,
.mr_target_commit {
margin-bottom: 0;
.commit {
margin: 0;
padding: 2px 0;
......@@ -174,10 +176,6 @@
display: none;
}
.merge-request-form .select2-container {
width: 250px !important;
}
#modal_merge_info .modal-dialog {
width: 600px;
......@@ -200,3 +198,76 @@
overflow-x: scroll;
}
}
.panel-new-merge-request {
.panel-heading {
padding: 5px 10px;
font-weight: 600;
line-height: 25px;
}
.panel-body {
padding: 10px 5px;
}
.panel-footer {
padding: 5px 10px;
}
.commit {
.commit-row-title {
margin-bottom: 4px;
}
.avatar {
width: 20px;
height: 20px;
margin-right: 5px;
}
.commit-row-info {
line-height: 20px;
}
}
.btn-clipboard {
margin-right: 5px;
padding: 0;
background: transparent;
}
.ci-status-link {
margin-right: 5px;
}
}
.merge-request-select {
padding-left: 5px;
padding-right: 5px;
margin-bottom: 10px;
&:last-child {
margin-bottom: 0;
}
@media (min-width: $screen-sm-min) {
float: left;
width: 50%;
margin-bottom: 0;
}
.dropdown-menu-toggle {
width: 100%;
}
.dropdown-menu {
left: 5px;
right: 5px;
width: auto;
}
}
.issuable-form-select-holder {
display: inline-block;
width: 250px;
}
......@@ -208,20 +208,20 @@ class Projects::MergeRequestsController < Projects::ApplicationController
#This is always source
@source_project = @merge_request.nil? ? @project : @merge_request.source_project
@commit = @repository.commit(params[:ref]) if params[:ref].present?
render layout: false
end
def branch_to
@target_project = selected_target_project
@commit = @target_project.commit(params[:ref]) if params[:ref].present?
render layout: false
end
def update_branches
@target_project = selected_target_project
@target_branches = @target_project.repository.branch_names
respond_to do |format|
format.js
end
render layout: false
end
def ci_status
......
......@@ -28,7 +28,7 @@ module CommitsHelper
def commit_to_html(commit, project, inline = true)
template = inline ? "inline_commit" : "commit"
escape_javascript(render "projects/commits/#{template}", commit: commit, project: project) unless commit.nil?
render "projects/commits/#{template}", commit: commit, project: project unless commit.nil?
end
# Breadcrumb links for a Project and, if applicable, a tree path
......@@ -117,7 +117,7 @@ module CommitsHelper
end
end
link_to(
"Browse Files »",
"Browse Files",
namespace_project_tree_path(project.namespace, project, commit),
class: "pull-right"
)
......
module FormHelper
def form_errors(model)
return unless model.errors.any?
pluralized = 'error'.pluralize(model.errors.count)
headline = "The form contains the following #{pluralized}:"
content_tag(:div, class: 'alert alert-danger', id: 'error_explanation') do
content_tag(:h4, headline) <<
content_tag(:ul) do
model.errors.full_messages.
map { |msg| content_tag(:li, msg) }.
join.
html_safe
end
end
end
end
......@@ -3,11 +3,9 @@
%p Please use this form to report users who create spam issues, comments or behave inappropriately.
%hr
= form_for @abuse_report, html: { class: 'form-horizontal js-quick-submit js-requires-input'} do |f|
= form_errors(@abuse_report)
= f.hidden_field :user_id
- if @abuse_report.errors.any?
.alert.alert-danger
- @abuse_report.errors.full_messages.each do |msg|
%p= msg
.form-group
= f.label :user_id, class: 'control-label'
.col-sm-10
......
= form_for @appearance, url: admin_appearances_path, html: { class: 'form-horizontal'} do |f|
- if @appearance.errors.any?
.alert.alert-danger
- @appearance.errors.full_messages.each do |msg|
%p= msg
= form_errors(@appearance)
%fieldset.sign-in
%legend
......
= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
- if @application_setting.errors.any?
#error_explanation
.alert.alert-danger
- @application_setting.errors.full_messages.each do |msg|
%p= msg
= form_errors(@application_setting)
%fieldset
%legend Visibility and Access Controls
......
= form_for [:admin, @application], url: @url, html: {class: 'form-horizontal', role: 'form'} do |f|
- if application.errors.any?
.alert.alert-danger
%button{ type: "button", class: "close", "data-dismiss" => "alert"} &times;
- application.errors.full_messages.each do |msg|
%p= msg
= form_errors(application)
= content_tag :div, class: 'form-group' do
= f.label :name, class: 'col-sm-2 control-label'
.col-sm-10
......
......@@ -4,10 +4,8 @@
= render_broadcast_message(@broadcast_message.message.presence || "Your message here")
= form_for [:admin, @broadcast_message], html: { class: 'broadcast-message-form form-horizontal js-quick-submit js-requires-input'} do |f|
-if @broadcast_message.errors.any?
.alert.alert-danger
- @broadcast_message.errors.full_messages.each do |msg|
%p= msg
= form_errors(@broadcast_message)
.form-group
= f.label :message, class: 'control-label'
.col-sm-10
......
......@@ -4,11 +4,7 @@
%div
= form_for [:admin, @deploy_key], html: { class: 'deploy-key-form form-horizontal' } do |f|
-if @deploy_key.errors.any?
.alert.alert-danger
%ul
- @deploy_key.errors.full_messages.each do |msg|
%li= msg
= form_errors(@deploy_key)
.form-group
= f.label :title, class: "control-label"
......
= form_for [:admin, @group], html: { class: "form-horizontal" } do |f|
- if @group.errors.any?
.alert.alert-danger
%span= @group.errors.full_messages.first
= form_errors(@group)
= render 'shared/group_form', f: f
.form-group.group-description-holder
......
......@@ -10,10 +10,8 @@
= form_for @hook, as: :hook, url: admin_hooks_path, html: { class: 'form-horizontal' } do |f|
-if @hook.errors.any?
.alert.alert-danger
- @hook.errors.full_messages.each do |msg|
%p= msg
= form_errors(@hook)
.form-group
= f.label :url, "URL:", class: 'control-label'
.col-sm-10
......
= form_for [:admin, @user, @identity], html: { class: 'form-horizontal fieldset-form' } do |f|
- if @identity.errors.any?
#error_explanation
.alert.alert-danger
- @identity.errors.full_messages.each do |msg|
%p= msg
= form_errors(@identity)
.form-group
= f.label :provider, class: 'control-label'
......
= form_for [:admin, @label], html: { class: 'form-horizontal label-form js-requires-input' } do |f|
-if @label.errors.any?
.row
.col-sm-offset-2.col-sm-10
.alert.alert-danger
- @label.errors.full_messages.each do |msg|
%span= msg
%br
= form_errors(@label)
.form-group
= f.label :title, class: 'control-label'
......
.user_new
= form_for [:admin, @user], html: { class: 'form-horizontal fieldset-form' } do |f|
-if @user.errors.any?
#error_explanation
.alert.alert-danger
- @user.errors.full_messages.each do |msg|
%p= msg
= form_errors(@user)
%fieldset
%legend Account
......
= form_for application, url: doorkeeper_submit_path(application), html: {role: 'form'} do |f|
- if application.errors.any?
.alert.alert-danger
%ul
- application.errors.full_messages.each do |msg|
%li= msg
= form_errors(application)
.form-group
= f.label :name, class: 'label-light'
......
......@@ -5,9 +5,7 @@
Group settings
.panel-body
= form_for @group, html: { multipart: true, class: "form-horizontal" }, authenticity_token: true do |f|
- if @group.errors.any?
.alert.alert-danger
%span= @group.errors.full_messages.first
= form_errors(@group)
= render 'shared/group_form', f: f
.form-group
......
......@@ -6,10 +6,7 @@
%hr
= form_for @group, html: { class: 'group-form form-horizontal' } do |f|
- if @group.errors.any?
.alert.alert-danger
%span= @group.errors.full_messages.first
= form_errors(@group)
= render 'shared/group_form', f: f, autofocus: true
.form-group.group-description-holder
......
%div
= form_for [:profile, @key], html: { class: 'js-requires-input' } do |f|
- if @key.errors.any?
.alert.alert-danger
%ul
- @key.errors.full_messages.each do |msg|
%li= msg
= form_errors(@key)
.form-group
= f.label :key, class: 'label-light'
......
......@@ -2,11 +2,7 @@
- header_title page_title, profile_notifications_path
= form_for @user, url: profile_notifications_path, method: :put, html: { class: 'update-notifications prepend-top-default' } do |f|
-if @user.errors.any?
%div.alert.alert-danger
%ul
- @user.errors.full_messages.each do |msg|
%li= msg
= form_errors(@user)
= hidden_field_tag :notification_type, 'global'
.row
......
......@@ -13,11 +13,8 @@
- unless @user.password_automatically_set?
or recover your current one
= form_for @user, url: profile_password_path, method: :put, html: {class: "update-password"} do |f|
-if @user.errors.any?
.alert.alert-danger
%ul
- @user.errors.full_messages.each do |msg|
%li= msg
= form_errors(@user)
- unless @user.password_automatically_set?
.form-group
= f.label :current_password, class: 'label-light'
......
......@@ -7,11 +7,8 @@
Please set a new password before proceeding.
%br
After a successful password update you will be redirected to login screen.
-if @user.errors.any?
.alert.alert-danger
%ul
- @user.errors.full_messages.each do |msg|
%li= msg
= form_errors(@user)
- unless @user.password_automatically_set?
.form-group
......
= form_for @user, url: profile_path, method: :put, html: { multipart: true, class: "edit-user prepend-top-default" }, authenticity_token: true do |f|
-if @user.errors.any?
%div.alert.alert-danger
%ul
- @user.errors.full_messages.each do |msg|
%li= msg
= form_errors(@user)
.row
.col-lg-3.profile-settings-sidebar
%h4.prepend-top-0
......
- if @project.errors.any?
.alert.alert-danger
%button{ type: "button", class: "close", "data-dismiss" => "alert"} &times;
= @project.errors.full_messages.first
= form_errors(@project)
......@@ -18,24 +18,17 @@
.pull-right
- if commit.status
= render_ci_status(commit)
&nbsp;
= clipboard_button(clipboard_text: commit.id)
= link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
.notes_count
- if note_count > 0
%span.light
%i.fa.fa-comments
= note_count
- if commit.description?
.commit-row-description.js-toggle-content
%pre
= preserve(markdown(escape_once(commit.description), pipeline: :single_line))
.commit-row-info
by
= commit_author_link(commit, avatar: true, size: 24)
authored
.committed_ago
#{time_ago_with_tooltip(commit.committed_date, skip_js: true)} &nbsp;
= link_to_browse_code(project, commit)
%div
= form_for [@project.namespace.becomes(Namespace), @project, @key], url: namespace_project_deploy_keys_path, html: { class: 'deploy-key-form form-horizontal js-requires-input' } do |f|
-if @key.errors.any?
.alert.alert-danger
%ul
- @key.errors.full_messages.each do |msg|
%li= msg
= form_errors(@key)
.form-group
= f.label :title, class: "control-label"
......
......@@ -9,10 +9,8 @@
%hr.clearfix
= form_for [@project.namespace.becomes(Namespace), @project, @hook], as: :hook, url: namespace_project_hooks_path(@project.namespace, @project), html: { class: 'form-horizontal' } do |f|
-if @hook.errors.any?
.alert.alert-danger
- @hook.errors.full_messages.each do |msg|
%p= msg
= form_errors(@hook)
.form-group
= f.label :url, "URL", class: 'control-label'
.col-sm-10
......
= form_for [@project.namespace.becomes(Namespace), @project, @label], html: { class: 'form-horizontal label-form js-quick-submit js-requires-input' } do |f|
-if @label.errors.any?
.row
.col-sm-offset-2.col-sm-10
.alert.alert-danger
- @label.errors.full_messages.each do |msg|
%span= msg
%br
= form_errors(@label)
.form-group
= f.label :title, class: 'control-label'
......
......@@ -5,33 +5,74 @@
.hide.alert.alert-danger.mr-compare-errors
.merge-request-branches.row
.col-md-6
.panel.panel-default
.panel.panel-default.panel-new-merge-request
.panel-heading
%strong Source branch
.panel-body
= f.select(:source_project_id, [[@merge_request.source_project_path,@merge_request.source_project.id]] , {}, { class: 'source_project select2 span3', disabled: @merge_request.persisted?, required: true })
&nbsp;
= f.select(:source_branch, @merge_request.source_branches, { include_blank: true }, { class: 'source_branch select2 span2', required: true, data: { placeholder: "Select source branch" } })
Source branch
.panel-body.clearfix
.merge-request-select.dropdown
= f.hidden_field :source_project_id
= dropdown_toggle @merge_request.source_project_path, { toggle: "dropdown", field_name: "#{f.object_name}[source_project_id]", disabled: @merge_request.persisted? }, { toggle_class: "js-compare-dropdown js-source-project" }
.dropdown-menu.dropdown-menu-selectable.dropdown-source-project
= dropdown_title("Select source project")
= dropdown_filter("Search projects")
= dropdown_content do
- is_active = f.object.source_project_id == @merge_request.source_project.id
%ul
%li
%a{ href: "#", class: "#{("is-active" if is_active)}", data: { id: @merge_request.source_project.id } }
= @merge_request.source_project_path
.merge-request-select.dropdown
= f.hidden_field :source_branch
= dropdown_toggle "Select source branch", { toggle: "dropdown", field_name: "#{f.object_name}[source_branch]" }, { toggle_class: "js-compare-dropdown js-source-branch" }
.dropdown-menu.dropdown-menu-selectable.dropdown-source-branch
= dropdown_title("Select source branch")
= dropdown_filter("Search branches")
= dropdown_content do
%ul
- @merge_request.source_branches.each do |branch|
%li
%a{ href: "#", class: "#{("is-active" if f.object.source_branch == branch)}", data: { id: branch } }
= branch
.panel-footer
.mr_source_commit
= icon('spinner spin', class: 'js-source-loading')
%ul.list-unstyled.mr_source_commit
.col-md-6
.panel.panel-default
.panel.panel-default.panel-new-merge-request
.panel-heading
%strong Target branch
.panel-body
Target branch
.panel-body.clearfix
- projects = @project.forked_from_project.nil? ? [@project] : [@project, @project.forked_from_project]
= f.select(:target_project_id, options_from_collection_for_select(projects, 'id', 'path_with_namespace', f.object.target_project_id), {}, { class: 'target_project select2 span3', disabled: @merge_request.persisted?, required: true })
&nbsp;
= f.select(:target_branch, @merge_request.target_branches, { include_blank: true }, { class: 'target_branch select2 span2', required: true, data: { placeholder: "Select target branch" } })
.merge-request-select.dropdown
= f.hidden_field :target_project_id
= dropdown_toggle f.object.target_project.path_with_namespace, { toggle: "dropdown", field_name: "#{f.object_name}[target_project_id]", disabled: @merge_request.persisted? }, { toggle_class: "js-compare-dropdown js-target-project" }
.dropdown-menu.dropdown-menu-selectable.dropdown-target-project
= dropdown_title("Select target project")
= dropdown_filter("Search projects")
= dropdown_content do
%ul
- projects.each do |project|
%li
%a{ href: "#", class: "#{("is-active" if f.object.target_project_id == project.id)}", data: { id: project.id } }
= project.path_with_namespace
.merge-request-select.dropdown
= f.hidden_field :target_branch
= dropdown_toggle f.object.target_branch, { toggle: "dropdown", field_name: "#{f.object_name}[target_branch]" }, { toggle_class: "js-compare-dropdown js-target-branch" }
.dropdown-menu.dropdown-menu-selectable.dropdown-target-branch.js-target-branch-dropdown
= dropdown_title("Select target branch")
= dropdown_filter("Search branches")
= dropdown_content do
%ul
- @merge_request.target_branches.each do |branch|
%li
%a{ href: "#", class: "#{("is-active" if f.object.target_branch == branch)}", data: { id: branch } }
= branch
.panel-footer
.mr_target_commit
= icon('spinner spin', class: "js-target-loading")
%ul.list-unstyled.mr_target_commit
- if @merge_request.errors.any?
.alert.alert-danger
- @merge_request.errors.full_messages.each do |msg|
%div= msg
= form_errors(@merge_request)
- elsif @merge_request.source_branch.present? && @merge_request.target_branch.present?
.light-well.append-bottom-default
.center
......@@ -45,40 +86,11 @@
and
%span.label-branch #{@merge_request.target_branch}
are the same.
.form-actions
= f.submit 'Compare branches and continue', class: "btn btn-new mr-compare-btn"
:javascript
var source_branch = $("#merge_request_source_branch")
, target_branch = $("#merge_request_target_branch")
, target_project = $("#merge_request_target_project_id");
$.get("#{branch_from_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}", {ref: source_branch.val() });
$.get("#{branch_to_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}", {target_project_id: target_project.val(),ref: target_branch.val() });
target_project.on("change", function() {
$.get("#{update_branches_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}", {target_project_id: $(this).val() });
});
source_branch.on("change", function() {
$.get("#{branch_from_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}", {ref: $(this).val() });
$(".mr-compare-errors").fadeOut();
$(".mr-compare-btn").enable();
});
target_branch.on("change", function() {
$.get("#{branch_to_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}", {target_project_id: target_project.val(),ref: $(this).val() });
$(".mr-compare-errors").fadeOut();
$(".mr-compare-btn").enable();
});
:javascript
$(".merge-request-form").on('submit', function () {
if ($("#merge_request_source_branch").val() === "" || $('#merge_request_target_branch').val() === "") {
$(".mr-compare-errors").html("You must select source and target branch to proceed");
$(".mr-compare-errors").fadeIn();
event.preventDefault();
return;
}
new Compare({
targetProjectUrl: "#{update_branches_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}",
sourceBranchUrl: "#{branch_from_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}",
targetBranchUrl: "#{branch_to_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}"
});
= commit_to_html(@commit, @source_project, false)
:plain
$(".mr_source_commit").html("#{commit_to_html(@commit, @source_project, false)}");
$('.js-timeago').timeago()
= commit_to_html(@commit, @target_project, false)
:plain
$(".mr_target_commit").html("#{commit_to_html(@commit, @target_project, false)}");
$('.js-timeago').timeago()
%ul
- @target_branches.each do |branch|
%li
%a{ href: "#", class: "#{("is-active" if "a" == branch)}", data: { id: branch } }
= branch
:plain
$(".target_branch").html("#{escape_javascript(options_for_select(@target_branches))}");
$('select.target_branch').select2({
width: 'resolve',
dropdownAutoWidth: true
});
$(".mr_target_commit").html("");
= form_for [@project.namespace.becomes(Namespace), @project, @milestone], html: {class: 'form-horizontal milestone-form gfm-form js-quick-submit js-requires-input'} do |f|
-if @milestone.errors.any?
.alert.alert-danger
%ul
- @milestone.errors.full_messages.each do |msg|
%li= msg
= form_errors(@milestone)
.row
.col-md-6
.form-group
......
......@@ -13,11 +13,7 @@
- if can? current_user, :admin_project, @project
= form_for [@project.namespace.becomes(Namespace), @project, @protected_branch], html: { class: 'form-horizontal' } do |f|
-if @protected_branch.errors.any?
.alert.alert-danger
%ul
- @protected_branch.errors.full_messages.each do |msg|
%li= msg
= form_errors(@protected_branch)
.form-group
= f.label :name, "Branch", class: 'control-label'
......
......@@ -13,13 +13,7 @@
= nested_form_for @project, url: url_for(controller: 'projects/variables', action: 'update'), html: { class: 'form-horizontal' } do |f|
- if @project.errors.any?
#error_explanation
%p.lead= "#{pluralize(@project.errors.count, "error")} prohibited this project from being saved:"
.alert.alert-error
%ul
- @project.errors.full_messages.each do |msg|
%li= msg
= form_errors(@project)
= f.fields_for :variables do |variable_form|
.form-group
......
= form_for [@project.namespace.becomes(Namespace), @project, @page], method: @page.persisted? ? :put : :post, html: { class: 'form-horizontal wiki-form gfm-form prepend-top-default js-quick-submit' } do |f|
-if @page.errors.any?
#error_explanation
.alert.alert-danger
- @page.errors.full_messages.each do |msg|
%p= msg
= form_errors(@page)
= f.hidden_field :title, value: @page.title
.form-group
......
- if @service.errors.any?
#error_explanation
.alert.alert-danger
%ul
- @service.errors.full_messages.each do |msg|
%li= msg
= form_errors(@service)
- if @service.help.present?
.well
......
- if issuable.errors.any?
.row
.col-sm-offset-2.col-sm-10
.alert.alert-danger
- issuable.errors.full_messages.each do |msg|
%span= msg
%br
= form_errors(issuable)
.form-group
= f.label :title, class: 'control-label'
.col-sm-10
......@@ -53,6 +48,7 @@
.issue-assignee
= f.label :assignee_id, "Assignee", class: 'control-label'
.col-sm-10
.issuable-form-select-holder
= users_select_tag("#{issuable.class.model_name.param_key}[assignee_id]",
placeholder: 'Select assignee', class: 'custom-form-control', null_user: true,
selected: issuable.assignee_id, project: @target_project || @project,
......@@ -64,6 +60,7 @@
= f.label :milestone_id, "Milestone", class: 'control-label'
.col-sm-10
- if milestone_options(issuable).present?
.issuable-form-select-holder
= f.select(:milestone_id, milestone_options(issuable),
{ include_blank: true }, { class: 'select2', data: { placeholder: 'Select milestone' } })
- else
......
.snippet-form-holder
= form_for @snippet, url: url, html: { class: "form-horizontal snippet-form js-requires-input" } do |f|
- if @snippet.errors.any?
.alert.alert-danger
%ul
- @snippet.errors.full_messages.each do |msg|
%li= msg
= form_errors(@snippet)
.form-group
= f.label :title, class: 'control-label'
......
......@@ -75,6 +75,29 @@ if Gitlab::Metrics.enabled?
config.instrument_methods(const)
config.instrument_instance_methods(const)
end
# Instruments all Banzai filters
Dir[Rails.root.join('lib', 'banzai', 'filter', '*.rb')].each do |file|
klass = File.basename(file, File.extname(file)).camelize
const = Banzai::Filter.const_get(klass)
config.instrument_methods(const)
config.instrument_instance_methods(const)
end
config.instrument_methods(Banzai::ReferenceExtractor)
config.instrument_instance_methods(Banzai::ReferenceExtractor)
config.instrument_methods(Banzai::Renderer)
config.instrument_methods(Banzai::Querying)
[Issuable, Mentionable, Participable].each do |klass|
config.instrument_instance_methods(klass)
config.instrument_instance_methods(klass::ClassMethods)
end
config.instrument_methods(Gitlab::ReferenceExtractor)
config.instrument_instance_methods(Gitlab::ReferenceExtractor)
end
GC::Profiler.enable
......
......@@ -2,6 +2,8 @@
- [Architecture](architecture.md) of GitLab
- [CI setup](ci_setup.md) for testing GitLab
- [Code review guidelines](code_review.md) for reviewing code and having code
reviewed.
- [Gotchas](gotchas.md) to avoid
- [How to dump production data to staging](db_dump.md)
- [Instrumentation](instrumentation.md)
......
# Code Review Guidelines
This guide contains advice and best practices for performing code review, and
having your code reviewed.
All merge requests for GitLab CE and EE, whether written by a GitLab team member
or a volunteer contributor, must go through a code review process to ensure the
code is effective, understandable, and maintainable.
Any developer can, and is encouraged to, perform code review on merge requests
of colleagues and contributors. However, the final decision to accept a merge
request is up to one of our merge request "endbosses", denoted on the
[team page](https://about.gitlab.com/team).
## Everyone
- Accept that many programming decisions are opinions. Discuss tradeoffs, which
you prefer, and reach a resolution quickly.
- Ask questions; don't make demands. ("What do you think about naming this
`:user_id`?")
- Ask for clarification. ("I didn't understand. Can you clarify?")
- Avoid selective ownership of code. ("mine", "not mine", "yours")
- Avoid using terms that could be seen as referring to personal traits. ("dumb",
"stupid"). Assume everyone is attractive, intelligent, and well-meaning.
- Be explicit. Remember people don't always understand your intentions online.
- Be humble. ("I'm not sure - let's look it up.")
- Don't use hyperbole. ("always", "never", "endlessly", "nothing")
- Be careful about the use of sarcasm. Everything we do is public; what seems
like good-natured ribbing to you and a long-time colleague might come off as
mean and unwelcoming to a person new to the project.
- Consider one-on-one chats or video calls if there are too many "I didn't
understand" or "Alternative solution:" comments. Post a follow-up comment
summarizing one-on-one discussion.
## Having your code reviewed
- The first reviewer of your code is _you_. Before you perform that first push
of your shiny new branch, read through the entire diff. Does it make sense?
Did you include something unrelated to the overall purpose of the changes? Did
you forget to remove any debugging code?
- Be grateful for the reviewer's suggestions. ("Good call. I'll make that
change.")
- Don't take it personally. The review is of the code, not of you.
- Explain why the code exists. ("It's like that because of these reasons. Would
it be more clear if I rename this class/file/method/variable?")
- Extract unrelated changes and refactorings into future merge requests/issues.
- Seek to understand the reviewer's perspective.
- Try to respond to every comment.
- Push commits based on earlier rounds of feedback as isolated commits to the
branch. Do not squash until the branch is ready to merge. Reviewers should be
able to read individual updates based on their earlier feedback.
## Reviewing code
Understand why the change is necessary (fixes a bug, improves the user
experience, refactors the existing code). Then:
- Communicate which ideas you feel strongly about and those you don't.
- Identify ways to simplify the code while still solving the problem.
- Offer alternative implementations, but assume the author already considered
them. ("What do you think about using a custom validator here?")
- Seek to understand the author's perspective.
- If you don't understand a piece of code, _say so_. There's a good chance
someone else would be confused by it as well.
- After a round of line notes, it can be helpful to post a summary note such as
"LGTM :thumbsup:", or "Just a couple things to address."
- Avoid accepting a merge request before the build succeeds ("Merge when build
succeeds" is fine).
## Credits
Largely based on the [thoughtbot code review guide].
[thoughtbot code review guide]: https://github.com/thoughtbot/guides/tree/master/code-review
---
[Return to Development documentation](README.md)
......@@ -2,36 +2,35 @@
GitLab Performance Monitoring allows instrumenting of custom blocks of Ruby
code. This can be used to measure the time spent in a specific part of a larger
chunk of code. The resulting data is written to a separate series.
chunk of code. The resulting data is stored as a field in the transaction that
executed the block.
To start measuring a block of Ruby code you should use
`Gitlab::Metrics.measure` and give it a name for the series to store the data
in:
To start measuring a block of Ruby code you should use `Gitlab::Metrics.measure`
and give it a name:
```ruby
Gitlab::Metrics.measure(:user_logins) do
Gitlab::Metrics.measure(:foo) do
...
end
```
The first argument of this method is the series name and should be plural. This
name will be prefixed with `rails_` or `sidekiq_` depending on whether the code
was run in the Rails application or one of the Sidekiq workers. In the
above example the final series names would be as follows:
3 values are measured for a block:
- rails_user_logins
- sidekiq_user_logins
1. The real time elapsed, stored in NAME_real_time.
2. The CPU time elapsed, stored in NAME_cpu_time.
3. The call count, stored in NAME_call_count.
Series names should be plural as this keeps the naming style in line with the
other series names.
Both the real and CPU timings are measured in milliseconds.
By default metrics measured using a block contain a single value, "duration",
which contains the number of milliseconds it took to execute the block. Custom
values can be added by passing a Hash as the 2nd argument. Custom tags can be
added by passing a Hash as the 3rd argument. A simple example is as follows:
Multiple calls to the same block will result in the final values being the sum
of all individual values. Take this code for example:
```ruby
Gitlab::Metrics.measure(:example_series, { number: 10 }, { class: self.class.to_s }) do
...
3.times do
Gitlab::Metrics.measure(:sleep) do
sleep 1
end
end
```
Here the final value of `sleep_real_time` will be `3`, _not_ `1`.
......@@ -352,7 +352,7 @@ GitLab Shell is an SSH access and repository management software developed speci
cd /home/git
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git
cd gitlab-workhorse
sudo -u git -H git checkout v0.7.2
sudo -u git -H git checkout v0.7.1
sudo -u git -H make
### Initialize Database and Activate Advanced Features
......
......@@ -58,7 +58,7 @@ GitLab 8.1.
```bash
cd /home/git/gitlab-workhorse
sudo -u git -H git fetch --all
sudo -u git -H git checkout v0.7.2
sudo -u git -H git checkout v0.7.1
sudo -u git -H make
```
......
......@@ -4,6 +4,7 @@ Feature: Project Forked Merge Requests
And I am a member of project "Shop"
And I have a project forked off of "Shop" called "Forked Shop"
@javascript
Scenario: I submit new unassigned merge request to a forked project
Given I visit project "Forked Shop" merge requests page
And I click link "New Merge Request"
......
......@@ -70,6 +70,7 @@ Feature: Project Merge Requests
When I click link "Reopen"
Then I should see reopened merge request "Bug NS-04"
@javascript
Scenario: I submit new unassigned merge request
Given I click link "New Merge Request"
And I submit new merge request "Wiki Feature"
......
......@@ -34,10 +34,14 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
end
step 'I fill out a "Merge Request On Forked Project" merge request' do
select @forked_project.path_with_namespace, from: "merge_request_source_project_id"
select @project.path_with_namespace, from: "merge_request_target_project_id"
select "fix", from: "merge_request_source_branch"
select "master", from: "merge_request_target_branch"
first('.js-source-project').click
first('.dropdown-source-project a', text: @forked_project.path_with_namespace)
first('.js-target-project').click
first('.dropdown-target-project a', text: @project.path_with_namespace)
first('.js-source-branch').click
first('.dropdown-source-branch .dropdown-content a', text: 'fix').click
click_button "Compare branches and continue"
......@@ -115,10 +119,10 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
end
step 'I fill out an invalid "Merge Request On Forked Project" merge request' do
expect(find(:select, "merge_request_source_project_id", {}).value).to eq @forked_project.id.to_s
expect(find(:select, "merge_request_target_project_id", {}).value).to eq @project.id.to_s
expect(find(:select, "merge_request_source_branch", {}).value).to eq ""
expect(find(:select, "merge_request_target_branch", {}).value).to eq "master"
expect(find_by_id("merge_request_source_project_id", visible: false).value).to eq @forked_project.id.to_s
expect(find_by_id("merge_request_target_project_id", visible: false).value).to eq @project.id.to_s
expect(find_by_id("merge_request_source_branch", visible: false).value).to eq nil
expect(find_by_id("merge_request_target_branch", visible: false).value).to eq "master"
click_button "Compare branches"
end
......@@ -127,7 +131,7 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
end
step 'the target repository should be the original repository' do
expect(page).to have_select("merge_request_target_project_id", selected: @project.path_with_namespace)
expect(find_by_id("merge_request_target_project_id").value).to eq "#{@project.id}"
end
step 'I click "Assign to" dropdown"' do
......
......@@ -93,8 +93,12 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end
step 'I submit new merge request "Wiki Feature"' do
select "fix", from: "merge_request_source_branch"
select "feature", from: "merge_request_target_branch"
find('.js-source-branch').click
find('.dropdown-source-branch .dropdown-content a', text: 'fix').click
find('.js-target-branch').click
first('.dropdown-target-branch .dropdown-content a', text: 'feature').click
click_button "Compare branches"
fill_in "merge_request_title", with: "Wiki Feature"
click_button "Submit merge request"
......
......@@ -213,13 +213,12 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end
step 'I see Browse file link' do
expect(page).to have_link 'Browse File »'
expect(page).not_to have_link 'Browse Files »'
expect(page).to have_link 'Browse File'
expect(page).not_to have_link 'Browse Files'
end
step 'I see Browse code link' do
expect(page).to have_link 'Browse Files »'
expect(page).not_to have_link 'Browse File »'
expect(page).to have_link 'Browse Files'
expect(page).not_to have_link 'Browse Directory »'
end
......
......@@ -19,9 +19,11 @@ module Banzai
cache_key = full_cache_key(cache_key, context[:pipeline])
if cache_key
Gitlab::Metrics.measure(:banzai_cached_render) do
Rails.cache.fetch(cache_key) do
cacheless_render(text, context)
end
end
else
cacheless_render(text, context)
end
......@@ -64,6 +66,7 @@ module Banzai
private
def self.cacheless_render(text, context = {})
Gitlab::Metrics.measure(:banzai_cacheless_render) do
result = render_result(text, context)
output = result[:output]
......@@ -73,6 +76,7 @@ module Banzai
output.to_s
end
end
end
def self.full_cache_key(cache_key, pipeline_name)
return unless cache_key
......
......@@ -74,24 +74,32 @@ module Gitlab
#
# Example:
#
# Gitlab::Metrics.measure(:find_by_username_timings) do
# Gitlab::Metrics.measure(:find_by_username_duration) do
# User.find_by_username(some_username)
# end
#
# series - The name of the series to store the data in.
# values - A Hash containing extra values to add to the metric.
# tags - A Hash containing extra tags to add to the metric.
# name - The name of the field to store the execution time in.
#
# Returns the value yielded by the supplied block.
def self.measure(series, values = {}, tags = {})
return yield unless Transaction.current
def self.measure(name)
trans = current_transaction
return yield unless trans
real_start = Time.now.to_f
cpu_start = System.cpu_time
start = Time.now.to_f
retval = yield
duration = (Time.now.to_f - start) * 1000.0
values = values.merge(duration: duration)
Transaction.current.add_metric(series, values, tags)
cpu_stop = System.cpu_time
real_stop = Time.now.to_f
real_time = (real_stop - real_start) * 1000.0
cpu_time = cpu_stop - cpu_start
trans.increment("#{name}_real_time", real_time)
trans.increment("#{name}_cpu_time", cpu_time)
trans.increment("#{name}_call_count", 1)
retval
end
......@@ -107,5 +115,11 @@ module Gitlab
new(udp: { host: host, port: port })
end
end
private
def self.current_transaction
Transaction.current
end
end
end
......@@ -30,6 +30,17 @@ module Gitlab
0
end
end
# THREAD_CPUTIME is not supported on OS X
if Process.const_defined?(:CLOCK_THREAD_CPUTIME_ID)
def self.cpu_time
Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :millisecond)
end
else
def self.cpu_time
Process.clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID, :millisecond)
end
end
end
end
end
require 'spec_helper'
describe "Dashboard Issues filtering", feature: true, js: true do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:milestone) { create(:milestone, project: project) }
context 'filtering by milestone' do
before do
project.team << [user, :master]
login_as(user)
create(:issue, project: project, author: user, assignee: user)
create(:issue, project: project, author: user, assignee: user, milestone: milestone)
visit_issues
end
it 'should show all issues with no milestone' do
show_milestone_dropdown
click_link 'No Milestone'
expect(page).to have_selector('.issue', count: 1)
end
it 'should show all issues with any milestone' do
show_milestone_dropdown
click_link 'Any Milestone'
expect(page).to have_selector('.issue', count: 2)
end
it 'should show all issues with the selected milestone' do
show_milestone_dropdown
page.within '.dropdown-content' do
click_link milestone.title
end
expect(page).to have_selector('.issue', count: 1)
end
end
def show_milestone_dropdown
click_button 'Milestone'
expect(page).to have_selector('.dropdown-content', visible: true)
end
def visit_issues
visit issues_dashboard_path
end
end
require 'spec_helper'
feature 'Create New Merge Request', feature: true, js: false do
feature 'Create New Merge Request', feature: true, js: true do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
......@@ -13,9 +13,12 @@ feature 'Create New Merge Request', feature: true, js: false do
it 'generates a diff for an orphaned branch' do
click_link 'New Merge Request'
select "orphaned-branch", from: "merge_request_source_branch"
select "master", from: "merge_request_target_branch"
first('.js-source-branch').click
first('.dropdown-source-branch .dropdown-content a', text: 'orphaned-branch').click
click_button "Compare branches"
click_link "Changes"
expect(page).to have_content "README.md"
expect(page).to have_content "wm.png"
......@@ -23,6 +26,8 @@ feature 'Create New Merge Request', feature: true, js: false do
fill_in "merge_request_title", with: "Orphaned MR test"
click_button "Submit merge request"
click_link "Check out branch"
expect(page).to have_content 'git checkout -b orphaned-branch origin/orphaned-branch'
end
end
require 'rails_helper'
describe FormHelper do
describe 'form_errors' do
it 'returns nil when model has no errors' do
model = double(errors: [])
expect(helper.form_errors(model)).to be_nil
end
it 'renders an alert div' do
model = double(errors: errors_stub('Error 1'))
expect(helper.form_errors(model)).
to include('<div class="alert alert-danger" id="error_explanation">')
end
it 'contains a summary message' do
single_error = double(errors: errors_stub('A'))
multi_errors = double(errors: errors_stub('A', 'B', 'C'))
expect(helper.form_errors(single_error)).
to include('<h4>The form contains the following error:')
expect(helper.form_errors(multi_errors)).
to include('<h4>The form contains the following errors:')
end
it 'renders each message' do
model = double(errors: errors_stub('Error 1', 'Error 2', 'Error 3'))
errors = helper.form_errors(model)
aggregate_failures do
expect(errors).to include('<li>Error 1</li>')
expect(errors).to include('<li>Error 2</li>')
expect(errors).to include('<li>Error 3</li>')
end
end
def errors_stub(*messages)
ActiveModel::Errors.new(double).tap do |errors|
messages.each { |msg| errors.add(:base, msg) }
end
end
end
end
......@@ -26,4 +26,10 @@ describe Gitlab::Metrics::System do
end
end
end
describe '.cpu_time' do
it 'returns a Fixnum' do
expect(described_class.cpu_time).to be_an_instance_of(Fixnum)
end
end
end
......@@ -74,24 +74,21 @@ describe Gitlab::Metrics do
let(:transaction) { Gitlab::Metrics::Transaction.new }
before do
allow(Gitlab::Metrics::Transaction).to receive(:current).
allow(Gitlab::Metrics).to receive(:current_transaction).
and_return(transaction)
end
it 'adds a metric to the current transaction' do
expect(transaction).to receive(:add_metric).
with(:foo, { duration: a_kind_of(Numeric) }, { tag: 'value' })
expect(transaction).to receive(:increment).
with('foo_real_time', a_kind_of(Numeric))
Gitlab::Metrics.measure(:foo, {}, tag: 'value') { 10 }
end
it 'supports adding of custom values' do
values = { duration: a_kind_of(Numeric), number: 10 }
expect(transaction).to receive(:increment).
with('foo_cpu_time', a_kind_of(Numeric))
expect(transaction).to receive(:add_metric).
with(:foo, values, { tag: 'value' })
expect(transaction).to receive(:increment).
with('foo_call_count', 1)
Gitlab::Metrics.measure(:foo, { number: 10 }, tag: 'value') { 10 }
Gitlab::Metrics.measure(:foo) { 10 }
end
it 'returns the return value of the block' do
......
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