Merge branch 'after-script' into make-before-after-overridable
This commit is contained in:
commit
2ab8d3e652
13
CHANGELOG
13
CHANGELOG
|
@ -1,6 +1,9 @@
|
|||
Please view this file on the master branch, on stable branches it's out of date.
|
||||
|
||||
v 8.7.0 (unreleased)
|
||||
- Method instrumentation now uses Module#prepend instead of aliasing methods
|
||||
- Repository.clean_old_archives is now instrumented
|
||||
- Add support for environment variables on a job level in CI configuration file
|
||||
- The Projects::HousekeepingService class has extra instrumentation
|
||||
- All service classes (those residing in app/services) are now instrumented
|
||||
- Developers can now add custom tags to transactions
|
||||
|
@ -58,6 +61,7 @@ v 8.7.0 (unreleased)
|
|||
- Decouple membership and notifications
|
||||
- Fix creation of merge requests for orphaned branches (Stan Hu)
|
||||
- API: Ability to retrieve a single tag (Robert Schilling)
|
||||
- While signing up, don't persist the user password across form redisplays
|
||||
- Fall back to `In-Reply-To` and `References` headers when sub-addressing is not available (David Padilla)
|
||||
- Remove "Congratulations!" tweet button on newly-created project. (Connor Shea)
|
||||
- Fix admin/projects when using visibility levels on search (PotHix)
|
||||
|
@ -74,13 +78,20 @@ v 8.7.0 (unreleased)
|
|||
- Delete tags using Rugged for performance reasons (Robert Schilling)
|
||||
- Diffs load at the correct point when linking from from number
|
||||
- Selected diff rows highlight
|
||||
- Fix emoji catgories in the emoji picker
|
||||
- Fix emoji categories in the emoji picker
|
||||
- Add encrypted credentials for imported projects and migrate old ones
|
||||
- Author and participants are displayed first on users autocompletion
|
||||
- Show number sign on external issue reference text (Florent Baldino)
|
||||
- Updated print style for issues
|
||||
|
||||
v 8.6.6
|
||||
- Expire the exists cache before deletion to ensure project dir actually exists (Stan Hu). !3413
|
||||
- Fix error on language detection when repository has no HEAD (e.g., master branch) (Jeroen Bobbeldijk). !3654
|
||||
- Fix revoking of authorized OAuth applications (Connor Shea). !3690
|
||||
- Fix error on language detection when repository has no HEAD (e.g., master branch). !3654 (Jeroen Bobbeldijk)
|
||||
- Project switcher uses new dropdown styling
|
||||
- Issuable header is consistent between issues and merge requests
|
||||
- Improved spacing in issuable header on mobile
|
||||
|
||||
v 8.6.5
|
||||
- Fix importing from GitHub Enterprise. !3529
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
window.GitLab ?= {}
|
||||
GitLab.GfmAutoComplete =
|
||||
dataLoading: false
|
||||
|
||||
dataSource: ''
|
||||
|
||||
# Emoji
|
||||
|
@ -17,17 +19,41 @@ GitLab.GfmAutoComplete =
|
|||
template: '<li><small>${id}</small> ${title}</li>'
|
||||
|
||||
# Add GFM auto-completion to all input fields, that accept GFM input.
|
||||
setup: ->
|
||||
input = $('.js-gfm-input')
|
||||
setup: (wrap) ->
|
||||
@input = $('.js-gfm-input')
|
||||
|
||||
# destroy previous instances
|
||||
@destroyAtWho()
|
||||
|
||||
# set up instances
|
||||
@setupAtWho()
|
||||
|
||||
if @dataSource
|
||||
if !@dataLoading
|
||||
@dataLoading = true
|
||||
|
||||
# We should wait until initializations are done
|
||||
# and only trigger the last .setup since
|
||||
# The previous .dataSource belongs to the previous issuable
|
||||
# and the last one will have the **proper** .dataSource property
|
||||
# TODO: Make this a singleton and turn off events when moving to another page
|
||||
setTimeout( =>
|
||||
fetch = @fetchData(@dataSource)
|
||||
fetch.done (data) =>
|
||||
@dataLoading = false
|
||||
@loadData(data)
|
||||
, 1000)
|
||||
|
||||
|
||||
setupAtWho: ->
|
||||
# Emoji
|
||||
input.atwho
|
||||
@input.atwho
|
||||
at: ':'
|
||||
displayTpl: @Emoji.template
|
||||
insertTpl: ':${name}:'
|
||||
|
||||
# Team Members
|
||||
input.atwho
|
||||
@input.atwho
|
||||
at: '@'
|
||||
displayTpl: @Members.template
|
||||
insertTpl: '${atwho-at}${username}'
|
||||
|
@ -42,7 +68,7 @@ GitLab.GfmAutoComplete =
|
|||
title: sanitize(title)
|
||||
search: sanitize("#{m.username} #{m.name}")
|
||||
|
||||
input.atwho
|
||||
@input.atwho
|
||||
at: '#'
|
||||
alias: 'issues'
|
||||
searchKey: 'search'
|
||||
|
@ -55,7 +81,7 @@ GitLab.GfmAutoComplete =
|
|||
title: sanitize(i.title)
|
||||
search: "#{i.iid} #{i.title}"
|
||||
|
||||
input.atwho
|
||||
@input.atwho
|
||||
at: '!'
|
||||
alias: 'mergerequests'
|
||||
searchKey: 'search'
|
||||
|
@ -68,13 +94,18 @@ GitLab.GfmAutoComplete =
|
|||
title: sanitize(m.title)
|
||||
search: "#{m.iid} #{m.title}"
|
||||
|
||||
if @dataSource
|
||||
$.getJSON(@dataSource).done (data) ->
|
||||
# load members
|
||||
input.atwho 'load', '@', data.members
|
||||
# load issues
|
||||
input.atwho 'load', 'issues', data.issues
|
||||
# load merge requests
|
||||
input.atwho 'load', 'mergerequests', data.mergerequests
|
||||
# load emojis
|
||||
input.atwho 'load', ':', data.emojis
|
||||
destroyAtWho: ->
|
||||
@input.atwho('destroy')
|
||||
|
||||
fetchData: (dataSource) ->
|
||||
$.getJSON(dataSource)
|
||||
|
||||
loadData: (data) ->
|
||||
# load members
|
||||
@input.atwho 'load', '@', data.members
|
||||
# load issues
|
||||
@input.atwho 'load', 'issues', data.issues
|
||||
# load merge requests
|
||||
@input.atwho 'load', 'mergerequests', data.mergerequests
|
||||
# load emojis
|
||||
@input.atwho 'load', ':', data.emojis
|
||||
|
|
|
@ -4,18 +4,33 @@ class @ImporterStatus
|
|||
this.setAutoUpdate()
|
||||
|
||||
initStatusPage: ->
|
||||
$(".js-add-to-import").click (event) =>
|
||||
new_namespace = null
|
||||
tr = $(event.currentTarget).closest("tr")
|
||||
id = tr.attr("id").replace("repo_", "")
|
||||
if tr.find(".import-target input").length > 0
|
||||
new_namespace = tr.find(".import-target input").prop("value")
|
||||
tr.find(".import-target").empty().append(new_namespace + "/" + tr.find(".import-target").data("project_name"))
|
||||
$.post @import_url, {repo_id: id, new_namespace: new_namespace}, dataType: 'script'
|
||||
$('.js-add-to-import')
|
||||
.off 'click'
|
||||
.on 'click', (e) =>
|
||||
new_namespace = null
|
||||
$btn = $(e.currentTarget)
|
||||
$tr = $btn.closest('tr')
|
||||
id = $tr.attr('id').replace('repo_', '')
|
||||
if $tr.find('.import-target input').length > 0
|
||||
new_namespace = $tr.find('.import-target input').prop('value')
|
||||
$tr.find('.import-target').empty().append("#{new_namespace} / #{$tr.find('.import-target').data('project_name')}")
|
||||
|
||||
$(".js-import-all").click (event) =>
|
||||
$(".js-add-to-import").each ->
|
||||
$(this).click()
|
||||
$btn
|
||||
.disable()
|
||||
.addClass 'is-loading'
|
||||
|
||||
$.post @import_url, {repo_id: id, new_namespace: new_namespace}, dataType: 'script'
|
||||
|
||||
$('.js-import-all')
|
||||
.off 'click'
|
||||
.on 'click', (e) ->
|
||||
$btn = $(@)
|
||||
$btn
|
||||
.disable()
|
||||
.addClass 'is-loading'
|
||||
|
||||
$('.js-add-to-import').each ->
|
||||
$(this).trigger('click')
|
||||
|
||||
setAutoUpdate: ->
|
||||
setInterval (=>
|
||||
|
|
|
@ -75,6 +75,9 @@ class @Notes
|
|||
# when issue status changes, we need to refresh data
|
||||
$(document).on "issuable:change", @refresh
|
||||
|
||||
# when a key is clicked on the notes
|
||||
$(document).on "keydown", ".js-note-text", @keydownNoteText
|
||||
|
||||
cleanBinding: ->
|
||||
$(document).off "ajax:success", ".js-main-target-form"
|
||||
$(document).off "ajax:success", ".js-discussion-note-form"
|
||||
|
@ -92,10 +95,19 @@ class @Notes
|
|||
$(document).off "click", ".js-note-target-reopen"
|
||||
$(document).off "click", ".js-note-target-close"
|
||||
$(document).off "click", ".js-note-discard"
|
||||
$(document).off "keydown", ".js-note-text"
|
||||
|
||||
$('.note .js-task-list-container').taskList('disable')
|
||||
$(document).off 'tasklist:changed', '.note .js-task-list-container'
|
||||
|
||||
keydownNoteText: (e) ->
|
||||
$this = $(this)
|
||||
if $this.val() is '' and e.which is 38 #aka the up key
|
||||
myLastNote = $("li.note[data-author-id='#{gon.current_user_id}'][data-editable]:last")
|
||||
if myLastNote.length
|
||||
myLastNoteEditBtn = myLastNote.find('.js-note-edit')
|
||||
myLastNoteEditBtn.trigger('click', [true, myLastNote])
|
||||
|
||||
initRefresh: ->
|
||||
clearInterval(Notes.interval)
|
||||
Notes.interval = setInterval =>
|
||||
|
@ -343,7 +355,7 @@ class @Notes
|
|||
Adds a hidden div with the original content of the note to fill the edit note form with
|
||||
if the user cancels
|
||||
###
|
||||
showEditForm: (e) ->
|
||||
showEditForm: (e, scrollTo, myLastNote) ->
|
||||
e.preventDefault()
|
||||
note = $(this).closest(".note")
|
||||
note.addClass "is-editting"
|
||||
|
@ -354,9 +366,27 @@ class @Notes
|
|||
# Show the attachment delete link
|
||||
note.find(".js-note-attachment-delete").show()
|
||||
|
||||
new GLForm form
|
||||
done = ($noteText) ->
|
||||
# Neat little trick to put the cursor at the end
|
||||
noteTextVal = $noteText.val()
|
||||
$noteText.val('').val(noteTextVal);
|
||||
|
||||
form.find(".js-note-text").focus()
|
||||
new GLForm form
|
||||
if scrollTo? and myLastNote?
|
||||
# scroll to the bottom
|
||||
# so the open of the last element doesn't make a jump
|
||||
$('html, body').scrollTop($(document).height());
|
||||
$('html, body').animate({
|
||||
scrollTop: myLastNote.offset().top - 150
|
||||
}, 500, ->
|
||||
$noteText = form.find(".js-note-text")
|
||||
$noteText.focus()
|
||||
done($noteText)
|
||||
);
|
||||
else
|
||||
$noteText = form.find('.js-note-text')
|
||||
$noteText.focus()
|
||||
done($noteText)
|
||||
|
||||
###
|
||||
Called in response to clicking the edit note link
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
.status-box {
|
||||
|
||||
|
||||
/* Extra small devices (phones, less than 768px) */
|
||||
/* No media query since this is the default in Bootstrap */
|
||||
padding: 5px 11px;
|
||||
|
|
|
@ -70,13 +70,6 @@
|
|||
display: none;
|
||||
}
|
||||
|
||||
.issue-details {
|
||||
.creator,
|
||||
.page-title .btn-close {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
%ul.notes .note-role, .note-actions {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
@ -39,8 +39,7 @@
|
|||
.diff-file {
|
||||
border: 1px solid $border-color;
|
||||
border-bottom: none;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
.detail-page-header {
|
||||
padding: 11px 0;
|
||||
padding: $gl-padding-top 0;
|
||||
border-bottom: 1px solid $border-color;
|
||||
color: #5c5d5e;
|
||||
font-size: 16px;
|
||||
|
@ -16,11 +16,6 @@
|
|||
.issue_created_ago, .author_link {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.issue-meta {
|
||||
display: inline-block;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.detail-page-description {
|
||||
|
|
|
@ -16,3 +16,24 @@ i.icon-gitorious-big {
|
|||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.import-jobs-from-col,
|
||||
.import-jobs-to-col {
|
||||
width: 40%;
|
||||
}
|
||||
|
||||
.import-jobs-status-col {
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
.btn-import {
|
||||
.loading-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.is-loading {
|
||||
.loading-icon {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -273,10 +273,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.btn-default.gutter-toggle {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.detail-page-description {
|
||||
small {
|
||||
color: $gray-darkest;
|
||||
|
@ -322,3 +318,50 @@
|
|||
padding-top: 7px;
|
||||
}
|
||||
}
|
||||
|
||||
.issuable-status-box {
|
||||
float: none;
|
||||
display: inline-block;
|
||||
margin-top: 0;
|
||||
|
||||
@media (max-width: $screen-xs-max) {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.issuable-header {
|
||||
position: relative;
|
||||
padding-left: 45px;
|
||||
padding-right: 45px;
|
||||
line-height: 35px;
|
||||
|
||||
@media (min-width: $screen-sm-min) {
|
||||
float: left;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.issuable-actions {
|
||||
padding-top: 10px;
|
||||
|
||||
@media (min-width: $screen-sm-min) {
|
||||
float: right;
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.issuable-gutter-toggle {
|
||||
@media (max-width: $screen-sm-max) {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.issuable-meta {
|
||||
display: inline-block;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
|
|
@ -86,41 +86,9 @@ form.edit-issue {
|
|||
@media (max-width: $screen-xs-max) {
|
||||
.issue-btn-group {
|
||||
width: 100%;
|
||||
margin-top: 5px;
|
||||
|
||||
.btn-group {
|
||||
width: 100%;
|
||||
|
||||
ul {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
|
||||
&:first-child:not(:last-child) {
|
||||
|
||||
}
|
||||
|
||||
&:not(:first-child):not(:last-child) {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
&:last-child:not(:first-child) {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.issue {
|
||||
&:hover .issue-actions {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.issue-updated-at {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -133,11 +101,3 @@ form.edit-issue {
|
|||
color: $gl-text-color;
|
||||
margin-left: 52px;
|
||||
}
|
||||
|
||||
.editor-details {
|
||||
display: block;
|
||||
|
||||
@media (min-width: $screen-sm-min) {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -198,6 +198,12 @@ ul.notes {
|
|||
color: $notes-light-color;
|
||||
}
|
||||
|
||||
.discussion-headline-light {
|
||||
a {
|
||||
color: $gl-link-color;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Actions for Discussions/Notes
|
||||
*/
|
||||
|
@ -209,6 +215,17 @@ ul.notes {
|
|||
color: $notes-action-color;
|
||||
}
|
||||
|
||||
.discussion-actions {
|
||||
@media (max-width: $screen-sm-max) {
|
||||
float: none;
|
||||
margin-left: 0;
|
||||
|
||||
.note-action-button {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.note-action-button,
|
||||
.discussion-action-button {
|
||||
display: inline-block;
|
||||
|
|
|
@ -1,17 +1,37 @@
|
|||
/* Generic print styles */
|
||||
header, nav, nav.main-nav, nav.navbar-collapse, nav.navbar-collapse.collapse {display: none!important;}
|
||||
.profiler-results {display: none;}
|
||||
|
||||
/* Styles targeted specifically at printing files */
|
||||
.tree-ref-holder, .tree-holder .breadcrumb, .blob-commit-info {display: none;}
|
||||
.file-title {display: none;}
|
||||
.file-holder {border: none;}
|
||||
|
||||
.wiki h1, .wiki h2, .wiki h3, .wiki h4, .wiki h5, .wiki h6 {margin-top: 17px; }
|
||||
.wiki h1 {font-size: 30px;}
|
||||
.wiki h2 {font-size: 22px;}
|
||||
.wiki h3 {font-size: 18px; font-weight: bold; }
|
||||
|
||||
.sidebar-wrapper { display: none; }
|
||||
.nav { display: none; }
|
||||
.btn { display: none; }
|
||||
header,
|
||||
nav,
|
||||
nav.main-nav,
|
||||
nav.navbar-collapse,
|
||||
nav.navbar-collapse.collapse,
|
||||
.profiler-results,
|
||||
.tree-ref-holder,
|
||||
.tree-holder .breadcrumb,
|
||||
.blob-commit-info,
|
||||
.file-title,
|
||||
.file-holder,
|
||||
.sidebar-wrapper,
|
||||
.nav,
|
||||
.btn,
|
||||
ul.notes-form,
|
||||
.merge-request-ci-status .ci-status-link:after,
|
||||
.issuable-gutter-toggle,
|
||||
.gutter-toggle,
|
||||
.issuable-details .content-block-small,
|
||||
.edit-link,
|
||||
.note-action-button {
|
||||
display: none!important;
|
||||
}
|
||||
|
||||
.page-gutter {
|
||||
padding-top: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.right-sidebar {
|
||||
top: 0;
|
||||
}
|
||||
|
|
|
@ -51,6 +51,7 @@ class HelpController < ApplicationController
|
|||
end
|
||||
|
||||
def ui
|
||||
@user = User.new(id: 0, name: 'John Doe', username: '@johndoe')
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -55,6 +55,15 @@ module IssuablesHelper
|
|||
h(milestone_title.presence || default_label)
|
||||
end
|
||||
|
||||
def issuable_meta(issuable, project, text)
|
||||
output = content_tag :strong, "#{text} #{issuable.to_reference}", class: "identifier"
|
||||
output << " opened #{time_ago_with_tooltip(issuable.created_at)} by".html_safe
|
||||
output << content_tag(:strong) do
|
||||
author_output = link_to_member(project, issuable.author, size: 24, mobile_classes: "hidden-xs")
|
||||
author_output << link_to_member(project, issuable.author, size: 24, by_username: true, avatar: false, mobile_classes: "hidden-sm hidden-md hidden-lg")
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def sidebar_gutter_collapsed?
|
||||
|
|
|
@ -365,11 +365,23 @@ module Ci
|
|||
self.update(erased_by: user, erased_at: Time.now)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def yaml_variables
|
||||
global_yaml_variables + job_yaml_variables
|
||||
end
|
||||
|
||||
def global_yaml_variables
|
||||
if commit.config_processor
|
||||
commit.config_processor.variables.map do |key, value|
|
||||
commit.config_processor.global_variables.map do |key, value|
|
||||
{ key: key, value: value, public: true }
|
||||
end
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
def job_yaml_variables
|
||||
if commit.config_processor
|
||||
commit.config_processor.job_variables(name).map do |key, value|
|
||||
{ key: key, value: value, public: true }
|
||||
end
|
||||
else
|
||||
|
|
|
@ -37,4 +37,10 @@ class ExternalIssue
|
|||
def to_reference(_from_project = nil)
|
||||
id
|
||||
end
|
||||
|
||||
def reference_link_text(from_project = nil)
|
||||
return "##{id}" if /^\d+$/.match(id)
|
||||
|
||||
id
|
||||
end
|
||||
end
|
||||
|
|
|
@ -12,11 +12,13 @@ class Repository
|
|||
attr_accessor :path_with_namespace, :project
|
||||
|
||||
def self.clean_old_archives
|
||||
repository_downloads_path = Gitlab.config.gitlab.repository_downloads_path
|
||||
Gitlab::Metrics.measure(:clean_old_archives) do
|
||||
repository_downloads_path = Gitlab.config.gitlab.repository_downloads_path
|
||||
|
||||
return unless File.directory?(repository_downloads_path)
|
||||
return unless File.directory?(repository_downloads_path)
|
||||
|
||||
Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete))
|
||||
Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete))
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(path_with_namespace, project)
|
||||
|
|
|
@ -1,28 +1,37 @@
|
|||
module Projects
|
||||
class ParticipantsService < BaseService
|
||||
def execute(note_type, note_id)
|
||||
participating =
|
||||
if note_type && note_id
|
||||
participants_in(note_type, note_id)
|
||||
else
|
||||
[]
|
||||
end
|
||||
def execute(noteable_type, noteable_id)
|
||||
@noteable_type = noteable_type
|
||||
@noteable_id = noteable_id
|
||||
project_members = sorted(project.team.members)
|
||||
participants = all_members + groups + project_members + participating
|
||||
participants = target_owner + participants_in_target + all_members + groups + project_members
|
||||
participants.uniq
|
||||
end
|
||||
|
||||
def participants_in(type, id)
|
||||
target =
|
||||
case type
|
||||
def target
|
||||
@target ||=
|
||||
case @noteable_type
|
||||
when "Issue"
|
||||
project.issues.find_by_iid(id)
|
||||
project.issues.find_by_iid(@noteable_id)
|
||||
when "MergeRequest"
|
||||
project.merge_requests.find_by_iid(id)
|
||||
project.merge_requests.find_by_iid(@noteable_id)
|
||||
when "Commit"
|
||||
project.commit(id)
|
||||
project.commit(@noteable_id)
|
||||
else
|
||||
nil
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def target_owner
|
||||
return [] unless target && target.author.present?
|
||||
|
||||
[{
|
||||
name: target.author.name,
|
||||
username: target.author.username
|
||||
}]
|
||||
end
|
||||
|
||||
def participants_in_target
|
||||
return [] unless target
|
||||
|
||||
users = target.participants(current_user)
|
||||
|
@ -30,13 +39,13 @@ module Projects
|
|||
end
|
||||
|
||||
def sorted(users)
|
||||
users.uniq.to_a.compact.sort_by(&:username).map do |user|
|
||||
users.uniq.to_a.compact.sort_by(&:username).map do |user|
|
||||
{ username: user.username, name: user.name }
|
||||
end
|
||||
end
|
||||
|
||||
def groups
|
||||
current_user.authorized_groups.sort_by(&:path).map do |group|
|
||||
current_user.authorized_groups.sort_by(&:path).map do |group|
|
||||
count = group.users.count
|
||||
{ username: group.path, name: group.name, count: count }
|
||||
end
|
||||
|
|
|
@ -6,18 +6,17 @@
|
|||
.login-heading
|
||||
%h3 Create an account
|
||||
.login-body
|
||||
- user = params[:user].present? ? params[:user] : {}
|
||||
= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f|
|
||||
.devise-errors
|
||||
= devise_error_messages!
|
||||
%div
|
||||
= f.text_field :name, class: "form-control top", value: user[:name], placeholder: "Name", required: true
|
||||
= f.text_field :name, class: "form-control top", placeholder: "Name", required: true
|
||||
%div
|
||||
= f.text_field :username, class: "form-control middle", value: user[:username], placeholder: "Username", required: true
|
||||
= f.text_field :username, class: "form-control middle", placeholder: "Username", required: true
|
||||
%div
|
||||
= f.email_field :email, class: "form-control middle", value: user[:email], placeholder: "Email", required: true
|
||||
= f.email_field :email, class: "form-control middle", placeholder: "Email", required: true
|
||||
.form-group.append-bottom-20#password-strength
|
||||
= f.password_field :password, class: "form-control bottom", value: user[:password], id: "user_password_sign_up", placeholder: "Password", required: true
|
||||
= f.password_field :password, class: "form-control bottom", id: "user_password_sign_up", placeholder: "Password", required: true
|
||||
%div
|
||||
- if current_application_settings.recaptcha_enabled
|
||||
= recaptcha_tags
|
||||
|
|
|
@ -345,11 +345,11 @@
|
|||
%ul
|
||||
%li
|
||||
%a.dropdown-menu-user-link.is-active{href: "#"}
|
||||
= link_to_member_avatar(current_user, size: 30)
|
||||
= link_to_member_avatar(@user, size: 30)
|
||||
%strong.dropdown-menu-user-full-name
|
||||
= current_user.name
|
||||
= @user.name
|
||||
.dropdown-menu-user-username
|
||||
= current_user.to_reference
|
||||
= @user.to_reference
|
||||
|
||||
.example
|
||||
%div
|
||||
|
@ -372,11 +372,11 @@
|
|||
%ul
|
||||
%li
|
||||
%a.dropdown-menu-user-link.is-active{href: "#"}
|
||||
= link_to_member_avatar(current_user, size: 30)
|
||||
= link_to_member_avatar(@user, size: 30)
|
||||
%strong.dropdown-menu-user-full-name
|
||||
= current_user.name
|
||||
= @user.name
|
||||
.dropdown-menu-user-username
|
||||
= current_user.to_reference
|
||||
= @user.to_reference
|
||||
.dropdown-page-two
|
||||
.dropdown-title
|
||||
%button.dropdown-title-button.dropdown-menu-back{aria: {label: "Go back"}}
|
||||
|
|
|
@ -20,10 +20,10 @@
|
|||
job.attr("id", "project_#{@project.id}")
|
||||
target_field = job.find(".import-target")
|
||||
target_field.empty()
|
||||
target_field.append('<strong>#{link_to @project.path_with_namespace, namespace_project_path(@project.namespace, @project)}</strong>')
|
||||
target_field.append('#{link_to @project.path_with_namespace, namespace_project_path(@project.namespace, @project)}')
|
||||
$("table.import-jobs tbody").prepend(job)
|
||||
job.addClass("active").find(".import-actions").html("<i class='fa fa-spinner fa-spin'></i> started")
|
||||
- else
|
||||
:plain
|
||||
job = $("tr#repo_#{@repo_id}")
|
||||
job.find(".import-actions").html("<i class='fa fa-exclamation-circle'> Error saving project: #{escape_javascript(@project.errors.full_messages.join(','))}</i>")
|
||||
job.find(".import-actions").html("<i class='fa fa-exclamation-circle'></i> Error saving project: #{escape_javascript(@project.errors.full_messages.join(','))}")
|
||||
|
|
|
@ -10,13 +10,19 @@
|
|||
%hr
|
||||
%p
|
||||
- if @incompatible_repos.any?
|
||||
= button_tag 'Import all compatible projects', class: "btn btn-success js-import-all"
|
||||
= button_tag class: "btn btn-import btn-success js-import-all" do
|
||||
Import all compatible projects
|
||||
= icon("spinner spin", class: "loading-icon")
|
||||
- else
|
||||
= button_tag 'Import all projects', class: "btn btn-success js-import-all"
|
||||
= button_tag class: "btn btn-success js-import-all" do
|
||||
Import all projects
|
||||
= icon("spinner spin", class: "loading-icon")
|
||||
|
||||
|
||||
.table-holder
|
||||
.table-responsive
|
||||
%table.table.import-jobs
|
||||
%colgroup.import-jobs-from-col
|
||||
%colgroup.import-jobs-to-col
|
||||
%colgroup.import-jobs-status-col
|
||||
%thead
|
||||
%tr
|
||||
%th From Bitbucket
|
||||
|
@ -28,7 +34,7 @@
|
|||
%td
|
||||
= link_to project.import_source, "https://bitbucket.org/#{project.import_source}", target: "_blank"
|
||||
%td
|
||||
%strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
|
||||
= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
|
||||
%td.job-status
|
||||
- if project.import_status == 'finished'
|
||||
%span
|
||||
|
@ -47,7 +53,9 @@
|
|||
%td.import-target
|
||||
= "#{repo["owner"]}/#{repo["slug"]}"
|
||||
%td.import-actions.job-status
|
||||
= button_tag "Import", class: "btn js-add-to-import"
|
||||
= button_tag class: "btn btn-import js-add-to-import" do
|
||||
Import
|
||||
= icon("spinner spin", class: "loading-icon")
|
||||
- @incompatible_repos.each do |repo|
|
||||
%tr{id: "repo_#{repo["owner"]}___#{repo["slug"]}"}
|
||||
%td
|
||||
|
|
|
@ -13,10 +13,15 @@
|
|||
how FogBugz email addresses and usernames are imported into GitLab.
|
||||
%hr
|
||||
%p
|
||||
= button_tag 'Import all projects', class: 'btn btn-success js-import-all'
|
||||
= button_tag class: 'btn btn-import btn-success js-import-all' do
|
||||
Import all projects
|
||||
= icon("spinner spin", class: "loading-icon")
|
||||
|
||||
.table-holder
|
||||
.table-responsive
|
||||
%table.table.import-jobs
|
||||
%colgroup.import-jobs-from-col
|
||||
%colgroup.import-jobs-to-col
|
||||
%colgroup.import-jobs-status-col
|
||||
%thead
|
||||
%tr
|
||||
%th From FogBugz
|
||||
|
@ -28,7 +33,7 @@
|
|||
%td
|
||||
= project.import_source
|
||||
%td
|
||||
%strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
|
||||
= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
|
||||
%td.job-status
|
||||
- if project.import_status == 'finished'
|
||||
%span
|
||||
|
@ -47,7 +52,9 @@
|
|||
%td.import-target
|
||||
= "#{current_user.username}/#{repo.name}"
|
||||
%td.import-actions.job-status
|
||||
= button_tag "Import", class: "btn js-add-to-import"
|
||||
= button_tag class: "btn btn-import js-add-to-import" do
|
||||
Import
|
||||
= icon("spinner spin", class: "loading-icon")
|
||||
|
||||
:javascript
|
||||
new ImporterStatus("#{jobs_import_fogbugz_path}", "#{import_fogbugz_path}");
|
||||
|
|
|
@ -8,10 +8,15 @@
|
|||
Select projects you want to import.
|
||||
%hr
|
||||
%p
|
||||
= button_tag 'Import all projects', class: "btn btn-success js-import-all"
|
||||
= button_tag class: "btn btn-import btn-success js-import-all" do
|
||||
Import all projects
|
||||
= icon("spinner spin", class: "loading-icon")
|
||||
|
||||
.table-holder
|
||||
.table-responsive
|
||||
%table.table.import-jobs
|
||||
%colgroup.import-jobs-from-col
|
||||
%colgroup.import-jobs-to-col
|
||||
%colgroup.import-jobs-status-col
|
||||
%thead
|
||||
%tr
|
||||
%th From GitHub
|
||||
|
@ -23,7 +28,7 @@
|
|||
%td
|
||||
= link_to project.import_source, "https://github.com/#{project.import_source}", target: "_blank"
|
||||
%td
|
||||
%strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
|
||||
= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
|
||||
%td.job-status
|
||||
- if project.import_status == 'finished'
|
||||
%span
|
||||
|
@ -42,7 +47,9 @@
|
|||
%td.import-target
|
||||
= repo.full_name
|
||||
%td.import-actions.job-status
|
||||
= button_tag "Import", class: "btn js-add-to-import"
|
||||
= button_tag class: "btn btn-import js-add-to-import" do
|
||||
Import
|
||||
= icon("spinner spin", class: "loading-icon")
|
||||
|
||||
:javascript
|
||||
new ImporterStatus("#{jobs_import_github_path}", "#{import_github_path}");
|
||||
|
|
|
@ -8,10 +8,15 @@
|
|||
Select projects you want to import.
|
||||
%hr
|
||||
%p
|
||||
= button_tag 'Import all projects', class: "btn btn-success js-import-all"
|
||||
= button_tag class: "btn btn-import btn-success js-import-all" do
|
||||
Import all projects
|
||||
= icon("spinner spin", class: "loading-icon")
|
||||
|
||||
.table-holder
|
||||
.table-responsive
|
||||
%table.table.import-jobs
|
||||
%colgroup.import-jobs-from-col
|
||||
%colgroup.import-jobs-to-col
|
||||
%colgroup.import-jobs-status-col
|
||||
%thead
|
||||
%tr
|
||||
%th From GitLab.com
|
||||
|
@ -23,7 +28,7 @@
|
|||
%td
|
||||
= link_to project.import_source, "https://gitlab.com/#{project.import_source}", target: "_blank"
|
||||
%td
|
||||
%strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
|
||||
= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
|
||||
%td.job-status
|
||||
- if project.import_status == 'finished'
|
||||
%span
|
||||
|
@ -42,7 +47,9 @@
|
|||
%td.import-target
|
||||
= repo["path_with_namespace"]
|
||||
%td.import-actions.job-status
|
||||
= button_tag "Import", class: "btn js-add-to-import"
|
||||
= button_tag class: "btn js-add-to-import" do
|
||||
Import
|
||||
= icon("spinner spin", class: "loading-icon")
|
||||
|
||||
:javascript
|
||||
new ImporterStatus("#{jobs_import_gitlab_path}", "#{import_gitlab_path}");
|
||||
|
|
|
@ -8,10 +8,15 @@
|
|||
Select projects you want to import.
|
||||
%hr
|
||||
%p
|
||||
= button_tag 'Import all projects', class: "btn btn-success js-import-all"
|
||||
= button_tag class: "btn btn-import btn-success js-import-all" do
|
||||
Import all projects
|
||||
= icon("spinner spin", class: "loading-icon")
|
||||
|
||||
.table-holder
|
||||
.table-responsive
|
||||
%table.table.import-jobs
|
||||
%colgroup.import-jobs-from-col
|
||||
%colgroup.import-jobs-to-col
|
||||
%colgroup.import-jobs-status-col
|
||||
%thead
|
||||
%tr
|
||||
%th From Gitorious.org
|
||||
|
@ -23,7 +28,7 @@
|
|||
%td
|
||||
= link_to project.import_source, "https://gitorious.org/#{project.import_source}", target: "_blank"
|
||||
%td
|
||||
%strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
|
||||
= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
|
||||
%td.job-status
|
||||
- if project.import_status == 'finished'
|
||||
%span
|
||||
|
@ -42,7 +47,9 @@
|
|||
%td.import-target
|
||||
= repo.full_name
|
||||
%td.import-actions.job-status
|
||||
= button_tag "Import", class: "btn js-add-to-import"
|
||||
= button_tag class: "btn btn-import js-add-to-import" do
|
||||
Import
|
||||
= icon("spinner spin", class: "loading-icon")
|
||||
|
||||
:javascript
|
||||
new ImporterStatus("#{jobs_import_gitorious_path}", "#{import_gitorious_path}");
|
||||
|
|
|
@ -14,12 +14,19 @@
|
|||
%hr
|
||||
%p
|
||||
- if @incompatible_repos.any?
|
||||
= button_tag 'Import all compatible projects', class: "btn btn-success js-import-all"
|
||||
= button_tag class: "btn btn-import btn-success js-import-all" do
|
||||
Import all compatible projects
|
||||
= icon("spinner spin", class: "loading-icon")
|
||||
- else
|
||||
= button_tag 'Import all projects', class: "btn btn-success js-import-all"
|
||||
= button_tag class: "btn btn-import btn-success js-import-all" do
|
||||
Import all projects
|
||||
= icon("spinner spin", class: "loading-icon")
|
||||
|
||||
.table-holder
|
||||
.table-responsive
|
||||
%table.table.import-jobs
|
||||
%colgroup.import-jobs-from-col
|
||||
%colgroup.import-jobs-to-col
|
||||
%colgroup.import-jobs-status-col
|
||||
%thead
|
||||
%tr
|
||||
%th From Google Code
|
||||
|
@ -31,7 +38,7 @@
|
|||
%td
|
||||
= link_to project.import_source, "https://code.google.com/p/#{project.import_source}", target: "_blank"
|
||||
%td
|
||||
%strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
|
||||
= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
|
||||
%td.job-status
|
||||
- if project.import_status == 'finished'
|
||||
%span
|
||||
|
@ -50,7 +57,9 @@
|
|||
%td.import-target
|
||||
= "#{current_user.username}/#{repo.name}"
|
||||
%td.import-actions.job-status
|
||||
= button_tag "Import", class: "btn js-add-to-import"
|
||||
= button_tag class: "btn btn-import js-add-to-import" do
|
||||
Import
|
||||
= icon("spinner spin", class: "loading-icon")
|
||||
- @incompatible_repos.each do |repo|
|
||||
%tr{id: "repo_#{repo.id}"}
|
||||
%td
|
||||
|
|
|
@ -52,6 +52,9 @@
|
|||
%li
|
||||
phpunit --coverage-text --colors=never (PHP) -
|
||||
%code ^\s*Lines:\s*\d+.\d+\%
|
||||
%li
|
||||
gcovr (C/C++) -
|
||||
%code ^TOTAL.*\s+(\d+\%)$
|
||||
|
||||
.form-group
|
||||
.col-sm-offset-2.col-sm-10
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
.md-area
|
||||
.md-header
|
||||
%ul.nav-links
|
||||
%ul.nav-links.clearfix
|
||||
%li.active
|
||||
%a.js-md-write-button{ href: "#md-write-holder", tabindex: -1 }
|
||||
Write
|
||||
|
|
|
@ -1,82 +1,79 @@
|
|||
- page_title "#{@issue.title} (##{@issue.iid})", "Issues"
|
||||
- page_description @issue.description
|
||||
- page_card_attributes @issue.card_attributes
|
||||
- header_title project_title(@project, "Issues", namespace_project_issues_path(@project.namespace, @project))
|
||||
|
||||
= render "header_title"
|
||||
.clearfix.detail-page-header
|
||||
.issuable-header
|
||||
.issuable-status-box.status-box.status-box-closed{ class: issue_button_visibility(@issue, false) }
|
||||
= icon('check', class: "hidden-sm hidden-md hidden-lg")
|
||||
%span.hidden-xs
|
||||
Closed
|
||||
.issuable-status-box.status-box.status-box-open{ class: issue_button_visibility(@issue, true) }
|
||||
= icon('circle-o', class: "hidden-sm hidden-md hidden-lg")
|
||||
%span.hidden-xs Open
|
||||
|
||||
.issue
|
||||
.detail-page-header.issuable-header
|
||||
.pull-left
|
||||
.status-box{ class: "status-box-closed #{issue_button_visibility(@issue, false)}"}
|
||||
%span.hidden-xs
|
||||
Closed
|
||||
%span.hidden-sm.hidden-md.hidden-lg
|
||||
= icon('check')
|
||||
.status-box{ class: "status-box-open #{issue_button_visibility(@issue, true)}"}
|
||||
%span.hidden-xs
|
||||
Open
|
||||
%span.hidden-sm.hidden-md.hidden-lg
|
||||
= icon('circle-o')
|
||||
|
||||
%a.btn.btn-default.pull-right.visible-xs-block.gutter-toggle.js-sidebar-toggle{ href: "#" }
|
||||
%a.btn.btn-default.pull-right.visible-xs-block.gutter-toggle.issuable-gutter-toggle.js-sidebar-toggle{ href: "#" }
|
||||
= icon('angle-double-left')
|
||||
|
||||
.issue-meta
|
||||
.issuable-meta
|
||||
= confidential_icon(@issue)
|
||||
%strong.identifier
|
||||
Issue ##{@issue.iid}
|
||||
%span.creator
|
||||
opened
|
||||
.editor-details
|
||||
.editor-details
|
||||
= time_ago_with_tooltip(@issue.created_at)
|
||||
by
|
||||
%strong
|
||||
= link_to_member(@project, @issue.author, size: 24, mobile_classes: "hidden-xs")
|
||||
%strong
|
||||
= link_to_member(@project, @issue.author, size: 24, mobile_classes: "hidden-sm hidden-md hidden-lg",
|
||||
by_username: true, avatar: false)
|
||||
= issuable_meta(@issue, @project, "Issue")
|
||||
|
||||
.pull-right.issue-btn-group
|
||||
- if can?(current_user, :create_issue, @project)
|
||||
= link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'btn btn-nr btn-grouped new-issue-link btn-success', title: 'New issue', id: 'new_issue_link' do
|
||||
= icon('plus')
|
||||
New issue
|
||||
- if can?(current_user, :update_issue, @issue)
|
||||
= link_to 'Reopen issue', issue_path(@issue, issue: {state_event: :reopen}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
|
||||
= link_to 'Close issue', issue_path(@issue, issue: {state_event: :close}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
|
||||
= link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'btn btn-nr btn-grouped issuable-edit' do
|
||||
= icon('pencil-square-o')
|
||||
Edit
|
||||
- if can?(current_user, :create_issue, @project) || can?(current_user, :update_issue, @issue)
|
||||
.issuable-actions
|
||||
.clearfix.issue-btn-group.dropdown
|
||||
%button.btn.btn-default.pull-left.hidden-md.hidden-lg{ data: { toggle: "dropdown" } }
|
||||
%span.caret
|
||||
Options
|
||||
.dropdown-menu.dropdown-menu-align-right.hidden-lg
|
||||
%ul
|
||||
- if can?(current_user, :create_issue, @project)
|
||||
%li
|
||||
= link_to 'New issue', new_namespace_project_issue_path(@project.namespace, @project), title: 'New issue', id: 'new_issue_link'
|
||||
- if can?(current_user, :update_issue, @issue)
|
||||
%li
|
||||
= link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
|
||||
%li
|
||||
= link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
|
||||
%li
|
||||
= link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue)
|
||||
- if can?(current_user, :create_issue, @project)
|
||||
= link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'hidden-xs hidden-sm btn btn-nr btn-grouped new-issue-link btn-success', title: 'New issue', id: 'new_issue_link' do
|
||||
= icon('plus')
|
||||
New issue
|
||||
- if can?(current_user, :update_issue, @issue)
|
||||
= link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-nr btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
|
||||
= link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-nr btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
|
||||
= link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'hidden-xs hidden-sm btn btn-nr btn-grouped issuable-edit' do
|
||||
= icon('pencil-square-o')
|
||||
Edit
|
||||
|
||||
|
||||
.issue-details.issuable-details
|
||||
.detail-page-description.content-block
|
||||
%h2.title
|
||||
= markdown escape_once(@issue.title), pipeline: :single_line
|
||||
%div
|
||||
- if @issue.description.present?
|
||||
.description{class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : ''}
|
||||
.wiki
|
||||
= preserve do
|
||||
= markdown(@issue.description, cache_key: [@issue, "description"])
|
||||
%textarea.hidden.js-task-list-field
|
||||
= @issue.description
|
||||
= edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue_edited_ago')
|
||||
.issue-details.issuable-details
|
||||
.detail-page-description.content-block
|
||||
%h2.title
|
||||
= markdown escape_once(@issue.title), pipeline: :single_line
|
||||
- if @issue.description.present?
|
||||
.description{ class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : '' }
|
||||
.wiki
|
||||
= preserve do
|
||||
= markdown(@issue.description, cache_key: [@issue, "description"])
|
||||
%textarea.hidden.js-task-list-field
|
||||
= @issue.description
|
||||
= edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue_edited_ago')
|
||||
|
||||
#merge-requests{'data-url' => referenced_merge_requests_namespace_project_issue_url(@project.namespace, @project, @issue)}
|
||||
// This element is filled in using JavaScript.
|
||||
#merge-requests{ data: { url: referenced_merge_requests_namespace_project_issue_url(@project.namespace, @project, @issue) } }
|
||||
// This element is filled in using JavaScript.
|
||||
|
||||
#related-branches{'data-url' => related_branches_namespace_project_issue_url(@project.namespace, @project, @issue)}
|
||||
// This element is filled in using JavaScript.
|
||||
#related-branches{ data: { url: related_branches_namespace_project_issue_url(@project.namespace, @project, @issue) } }
|
||||
// This element is filled in using JavaScript.
|
||||
|
||||
.content-block.content-block-small
|
||||
= render 'new_branch'
|
||||
= render 'votes/votes_block', votable: @issue
|
||||
.content-block.content-block-small
|
||||
= render 'new_branch'
|
||||
= render 'votes/votes_block', votable: @issue
|
||||
|
||||
.row
|
||||
%section.col-md-12
|
||||
.issuable-discussion
|
||||
= render 'projects/issues/discussion'
|
||||
%section.issuable-discussion
|
||||
= render 'projects/issues/discussion'
|
||||
|
||||
= render 'shared/issuable/sidebar', issuable: @issue
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
$('aside.right-sidebar')[0].outerHTML = "#{escape_javascript(render 'shared/issuable/sidebar', issuable: @issue)}";
|
||||
$('aside.right-sidebar').effect('highlight');
|
||||
new IssuableContext();
|
|
@ -1,8 +1,7 @@
|
|||
- page_title "#{@merge_request.title} (#{@merge_request.to_reference})", "Merge Requests"
|
||||
- page_description @merge_request.description
|
||||
- page_card_attributes @merge_request.card_attributes
|
||||
|
||||
= render "header_title"
|
||||
- header_title project_title(@project, "Merge Requests", namespace_project_merge_requests_path(@project.namespace, @project))
|
||||
|
||||
- if params[:view] == 'parallel'
|
||||
- fluid_layout true
|
||||
|
|
|
@ -1,35 +1,32 @@
|
|||
.detail-page-header
|
||||
.status-box{ class: status_box_class(@merge_request) }
|
||||
%span.hidden-xs
|
||||
= @merge_request.state_human_name
|
||||
%span.hidden-sm.hidden-md.hidden-lg
|
||||
= icon(@merge_request.state_icon_name)
|
||||
%a.btn.btn-default.pull-right.visible-xs-block.gutter-toggle.js-sidebar-toggle{ href: "#" }
|
||||
= icon('angle-double-left')
|
||||
.issue-meta
|
||||
%strong.identifier
|
||||
%span.hidden-sm.hidden-md.hidden-lg
|
||||
MR
|
||||
.clearfix.detail-page-header
|
||||
.issuable-header
|
||||
.issuable-status-box.status-box{ class: status_box_class(@merge_request) }
|
||||
= icon(@merge_request.state_icon_name, class: "hidden-sm hidden-md hidden-lg")
|
||||
%span.hidden-xs
|
||||
Merge Request
|
||||
!#{@merge_request.iid}
|
||||
%span.creator
|
||||
opened
|
||||
.editor-details
|
||||
= time_ago_with_tooltip(@merge_request.created_at)
|
||||
by
|
||||
%strong
|
||||
= link_to_member(@project, @merge_request.author, size: 24, mobile_classes: "hidden-xs")
|
||||
%strong
|
||||
= link_to_member(@project, @merge_request.author, size: 24, mobile_classes: "hidden-sm hidden-md hidden-lg",
|
||||
by_username: true, avatar: false)
|
||||
= @merge_request.state_human_name
|
||||
|
||||
.issue-btn-group.pull-right
|
||||
- if can?(current_user, :update_merge_request, @merge_request)
|
||||
- if @merge_request.open?
|
||||
= link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: 'btn btn-nr btn-grouped btn-close', title: 'Close merge request'
|
||||
= link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn btn-nr btn-grouped issuable-edit', id: 'edit_merge_request' do
|
||||
%a.btn.btn-default.pull-right.visible-xs-block.gutter-toggle.issuable-gutter-toggle.js-sidebar-toggle{ href: "#" }
|
||||
= icon('angle-double-left')
|
||||
|
||||
.issuable-meta
|
||||
= issuable_meta(@merge_request, @project, "Merge Request")
|
||||
|
||||
- if can?(current_user, :update_merge_request, @merge_request)
|
||||
.issuable-actions
|
||||
.clearfix.issue-btn-group.dropdown
|
||||
%button.btn.btn-default.pull-left.hidden-md.hidden-lg{ data: { toggle: "dropdown" } }
|
||||
%span.caret
|
||||
Options
|
||||
.dropdown-menu.dropdown-menu-align-right.hidden-lg
|
||||
%ul
|
||||
%li{ class: issue_button_visibility(@merge_request, true) }
|
||||
= link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, title: 'Close merge request'
|
||||
%li{ class: issue_button_visibility(@merge_request, false) }
|
||||
= link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'reopen-mr-link', title: 'Reopen merge request'
|
||||
%li
|
||||
= link_to 'Edit', edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'issuable-edit', id: 'edit_merge_request'
|
||||
= link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: "hidden-xs hidden-sm btn btn-nr btn-grouped btn-close #{issue_button_visibility(@merge_request, true)}", title: 'Close merge request'
|
||||
= link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "hidden-xs hidden-sm btn btn-nr btn-grouped btn-reopen reopen-mr-link #{issue_button_visibility(@merge_request, false)}", title: 'Reopen merge request'
|
||||
= link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "hidden-xs hidden-sm btn btn-nr btn-grouped issuable-edit", id: 'edit_merge_request' do
|
||||
= icon('pencil-square-o')
|
||||
Edit
|
||||
- if @merge_request.closed?
|
||||
= link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'btn btn-nr btn-grouped btn-reopen reopen-mr-link', title: 'Reopen merge request'
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
$('aside.right-sidebar')[0].outerHTML = "#{escape_javascript(render 'shared/issuable/sidebar', issuable: @merge_request)}";
|
||||
$('aside.right-sidebar').effect('highlight');
|
||||
new IssuableContext();
|
|
@ -1,5 +1,5 @@
|
|||
- note = discussion_notes.first
|
||||
.timeline-entry
|
||||
%li.note.note-discussion.timeline-entry
|
||||
.timeline-entry-inner
|
||||
.timeline-icon
|
||||
= link_to user_path(note.author) do
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
%li.timeline-entry{ id: dom_id(note), class: [dom_class(note), "note-row-#{note.id}", ('system-note' if note.system)] }
|
||||
- note_editable = note_editable?(note)
|
||||
%li.timeline-entry{ id: dom_id(note), class: [dom_class(note), "note-row-#{note.id}", ('system-note' if note.system)], data: {author_id: note.author.id, editable: note_editable} }
|
||||
.timeline-entry-inner
|
||||
.timeline-icon
|
||||
%a{href: user_path(note.author)}
|
||||
|
@ -15,16 +16,16 @@
|
|||
- if access
|
||||
%span.note-role
|
||||
= access
|
||||
- if note_editable?(note)
|
||||
- if note_editable
|
||||
= link_to '#', title: 'Edit comment', class: 'note-action-button js-note-edit' do
|
||||
= icon('pencil')
|
||||
= link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'note-action-button js-note-delete danger' do
|
||||
= icon('trash-o')
|
||||
.note-body{class: note_editable?(note) ? 'js-task-list-container' : ''}
|
||||
.note-body{class: note_editable ? 'js-task-list-container' : ''}
|
||||
.note-text
|
||||
= preserve do
|
||||
= markdown(note.note, pipeline: :note, cache_key: [note, "note"])
|
||||
- if note_editable?(note)
|
||||
- if note_editable
|
||||
= render 'projects/notes/edit_form', note: note
|
||||
= edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago', include_author: true)
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
%ul#notes-list.notes.main-notes-list.timeline
|
||||
= render "projects/notes/notes"
|
||||
%ul.notes.timeline
|
||||
%ul.notes.notes-form.timeline
|
||||
%li.timeline-entry
|
||||
- if can? current_user, :create_note, @project
|
||||
.timeline-icon.hidden-xs.hidden-sm
|
||||
|
|
|
@ -6,15 +6,11 @@
|
|||
= "#{note.author.to_reference} started a discussion"
|
||||
= link_to diffs_namespace_project_merge_request_path(note.project.namespace, note.project, note.noteable, anchor: note.line_code) do
|
||||
on the diff
|
||||
= time_ago_with_tooltip(note.created_at, placement: "bottom", html_class: "discussion_updated_ago")
|
||||
.discussion-actions
|
||||
= link_to "#", class: "discussion-action-button discussion-toggle-button js-toggle-button" do
|
||||
%i.fa.fa-chevron-up
|
||||
Show/hide discussion
|
||||
.last-update.hide.js-toggle-content
|
||||
- last_note = discussion_notes.last
|
||||
last updated by
|
||||
= link_to_member(@project, last_note.author, avatar: false)
|
||||
#{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')}
|
||||
|
||||
.discussion-body.js-toggle-content
|
||||
= render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note
|
||||
|
|
|
@ -8,21 +8,18 @@
|
|||
= "#{note.author.to_reference} started a discussion on #{commit_description}"
|
||||
- if commit
|
||||
= link_to(commit.short_id, namespace_project_commit_path(note.project.namespace, note.project, note.noteable), class: 'monospace')
|
||||
= time_ago_with_tooltip(note.created_at, placement: "bottom", html_class: "discussion_updated_ago")
|
||||
.discussion-actions
|
||||
= link_to "#", class: "note-action-button discussion-toggle-button js-toggle-button" do
|
||||
%i.fa.fa-chevron-up
|
||||
Show/hide discussion
|
||||
.last-update.hide.js-toggle-content
|
||||
- last_note = discussion_notes.last
|
||||
last updated by
|
||||
= link_to_member(@project, last_note.author, avatar: false)
|
||||
#{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')}
|
||||
.discussion-body.js-toggle-content
|
||||
- if note.for_diff_line?
|
||||
= render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note
|
||||
- else
|
||||
.panel.panel-default
|
||||
.notes{ data: { discussion_id: discussion_notes.first.discussion_id } }
|
||||
= render discussion_notes
|
||||
%ul.notes.timeline
|
||||
= render discussion_notes
|
||||
.discussion-reply-holder
|
||||
= link_to_reply_diff(discussion_notes.first)
|
||||
|
|
|
@ -5,14 +5,10 @@
|
|||
.inline.discussion-headline-light
|
||||
= "#{note.author.to_reference} started a discussion"
|
||||
on the outdated diff
|
||||
= time_ago_with_tooltip(note.created_at, placement: "bottom", html_class: "discussion_updated_ago")
|
||||
.discussion-actions
|
||||
= link_to "#", class: "note-action-button discussion-toggle-button js-toggle-button" do
|
||||
%i.fa.fa-chevron-down
|
||||
Show/hide discussion
|
||||
.last-update.hide.js-toggle-content
|
||||
- last_note = discussion_notes.last
|
||||
last updated by
|
||||
= link_to_member(@project, last_note.author, avatar: false)
|
||||
#{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')}
|
||||
.discussion-body.js-toggle-content.hide
|
||||
= render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
= f.label :title, class: 'control-label'
|
||||
.col-sm-10
|
||||
= f.text_field :title, maxlength: 255, autofocus: true, autocomplete: 'off',
|
||||
class: 'form-control pad js-gfm-input', required: true
|
||||
class: 'form-control pad', required: true
|
||||
|
||||
- if issuable.is_a?(MergeRequest)
|
||||
%p.help-block
|
||||
|
|
|
@ -13,7 +13,7 @@ GitLab offers a [continuous integration][ci] service. If you
|
|||
and configure your GitLab project to use a [Runner], then each merge request or
|
||||
push triggers a build.
|
||||
|
||||
The `.gitlab-ci.yml` file tells the GitLab runner what do to. By default it
|
||||
The `.gitlab-ci.yml` file tells the GitLab runner what to do. By default it
|
||||
runs three [stages]: `build`, `test`, and `deploy`.
|
||||
|
||||
If everything runs OK (no non-zero return values), you'll get a nice green
|
||||
|
|
|
@ -1,17 +1,20 @@
|
|||
## Variables
|
||||
|
||||
When receiving a build from GitLab CI, the runner prepares the build environment.
|
||||
It starts by setting a list of **predefined variables** (Environment Variables) and a list of **user-defined variables**
|
||||
|
||||
The variables can be overwritten. They take precedence over each other in this order:
|
||||
1. Trigger variables
|
||||
1. Secure variables
|
||||
1. YAML-defined variables
|
||||
1. YAML-defined job-level variables
|
||||
1. YAML-defined global variables
|
||||
1. Predefined variables
|
||||
|
||||
For example, if you define:
|
||||
1. API_TOKEN=SECURE as Secure Variable
|
||||
1. API_TOKEN=YAML as YAML-defined variable
|
||||
1. `API_TOKEN=SECURE` as Secure Variable
|
||||
1. `API_TOKEN=YAML` as YAML-defined variable
|
||||
|
||||
The API_TOKEN will take the Secure Variable value: `SECURE`.
|
||||
The `API_TOKEN` will take the Secure Variable value: `SECURE`.
|
||||
|
||||
### Predefined variables (Environment Variables)
|
||||
|
||||
|
@ -70,15 +73,20 @@ These variables can be later used in all executed commands and scripts.
|
|||
|
||||
The YAML-defined variables are also set to all created service containers, thus allowing to fine tune them.
|
||||
|
||||
Variables can be defined at a global level, but also at a job level.
|
||||
|
||||
More information about Docker integration can be found in [Using Docker Images](../docker/using_docker_images.md).
|
||||
|
||||
### User-defined variables (Secure Variables)
|
||||
**This feature requires GitLab Runner 0.4.0 or higher**
|
||||
|
||||
GitLab CI allows you to define per-project **Secure Variables** that are set in build environment.
|
||||
GitLab CI allows you to define per-project **Secure Variables** that are set in
|
||||
the build environment.
|
||||
The secure variables are stored out of the repository (the `.gitlab-ci.yml`).
|
||||
The variables are securely passed to GitLab Runner and are available in build environment.
|
||||
It's desired method to use them for storing passwords, secret keys or whatever you want.
|
||||
The variables are securely passed to GitLab Runner and are available in the
|
||||
build environment.
|
||||
It's desired method to use them for storing passwords, secret keys or whatever
|
||||
you want.
|
||||
|
||||
**The value of the variable can be shown in build log if explicitly asked to do so.**
|
||||
If your project is public or internal you can make the builds private.
|
||||
|
|
|
@ -24,6 +24,7 @@ If you want a quick introduction to GitLab CI, follow our
|
|||
- [Jobs](#jobs)
|
||||
- [script](#script)
|
||||
- [stage](#stage)
|
||||
- [job variables](#job-variables)
|
||||
- [only and except](#only-and-except)
|
||||
- [tags](#tags)
|
||||
- [when](#when)
|
||||
|
@ -188,6 +189,8 @@ These variables can be later used in all executed commands and scripts.
|
|||
The YAML-defined variables are also set to all created service containers,
|
||||
thus allowing to fine tune them.
|
||||
|
||||
Variables can be also defined on [job level](#job-variables).
|
||||
|
||||
### cache
|
||||
|
||||
>**Note:**
|
||||
|
@ -338,6 +341,7 @@ job_name:
|
|||
| services | no | Use docker services, covered in [Using Docker Images](../docker/using_docker_images.md#define-image-and-services-from-gitlab-ciyml) |
|
||||
| stage | no | Defines a build stage (default: `test`) |
|
||||
| type | no | Alias for `stage` |
|
||||
| variables | no | Define build variables on a job level |
|
||||
| only | no | Defines a list of git refs for which build is created |
|
||||
| except | no | Defines a list of git refs for which build is not created |
|
||||
| tags | no | Defines a list of tags which are used to select Runner |
|
||||
|
@ -430,6 +434,18 @@ job:
|
|||
The above example will run `job` for all branches on `gitlab-org/gitlab-ce`,
|
||||
except master.
|
||||
|
||||
### job variables
|
||||
|
||||
It is possible to define build variables using a `variables` keyword on a job
|
||||
level. It works basically the same way as its global-level equivalent but
|
||||
allows you to define job-specific build variables.
|
||||
|
||||
When the `variables` keyword is used on a job level, it overrides global YAML
|
||||
build variables and predefined variables.
|
||||
|
||||
Build variables priority is defined in
|
||||
[variables documentation](../variables/README.md).
|
||||
|
||||
### tags
|
||||
|
||||
`tags` is used to select specific Runners from the list of all Runners that are
|
||||
|
|
|
@ -2,7 +2,7 @@ module SharedIssuable
|
|||
include Spinach::DSL
|
||||
|
||||
def edit_issuable
|
||||
find(:css, '.issuable-edit').click
|
||||
find('.issuable-edit', visible: true).click
|
||||
end
|
||||
|
||||
step 'project "Community" has "Community issue" open issue' do
|
||||
|
|
|
@ -7,9 +7,9 @@ module Ci
|
|||
ALLOWED_YAML_KEYS = [:before_script, :after_script, :image, :services, :types, :stages, :variables, :cache]
|
||||
ALLOWED_JOB_KEYS = [:tags, :script, :only, :except, :type, :image, :services,
|
||||
:allow_failure, :type, :stage, :when, :artifacts, :cache,
|
||||
:dependencies, :before_script, :after_script]
|
||||
:dependencies, :before_script, :after_script, :variables]
|
||||
|
||||
attr_reader :before_script, :after_script, :image, :services, :variables, :path, :cache
|
||||
attr_reader :before_script, :after_script, :image, :services, :path, :cache
|
||||
|
||||
def initialize(config, path = nil)
|
||||
@config = YAML.safe_load(config, [Symbol], [], true)
|
||||
|
@ -40,6 +40,17 @@ module Ci
|
|||
@stages || DEFAULT_STAGES
|
||||
end
|
||||
|
||||
def global_variables
|
||||
@variables
|
||||
end
|
||||
|
||||
def job_variables(name)
|
||||
job = @jobs[name.to_sym]
|
||||
return [] unless job
|
||||
|
||||
job.fetch(:variables, [])
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def initial_parsing
|
||||
|
@ -136,7 +147,7 @@ module Ci
|
|||
end
|
||||
|
||||
unless @variables.nil? || validate_variables(@variables)
|
||||
raise ValidationError, "variables should be a map of key-valued strings"
|
||||
raise ValidationError, "variables should be a map of key-value strings"
|
||||
end
|
||||
|
||||
validate_global_cache! if @cache
|
||||
|
@ -151,9 +162,25 @@ module Ci
|
|||
raise ValidationError, "cache:untracked parameter should be an boolean"
|
||||
end
|
||||
|
||||
<<<<<<< HEAD
|
||||
if @cache[:paths] && !validate_array_of_strings(@cache[:paths])
|
||||
raise ValidationError, "cache:paths parameter should be an array of strings"
|
||||
end
|
||||
=======
|
||||
true
|
||||
end
|
||||
|
||||
def validate_job!(name, job)
|
||||
validate_job_name!(name)
|
||||
validate_job_keys!(name, job)
|
||||
validate_job_types!(name, job)
|
||||
|
||||
validate_job_stage!(name, job) if job[:stage]
|
||||
validate_job_variables!(name, job) if job[:variables]
|
||||
validate_job_cache!(name, job) if job[:cache]
|
||||
validate_job_artifacts!(name, job) if job[:artifacts]
|
||||
validate_job_dependencies!(name, job) if job[:dependencies]
|
||||
>>>>>>> origin/master
|
||||
end
|
||||
|
||||
def validate_job_name!(name)
|
||||
|
@ -218,6 +245,13 @@ module Ci
|
|||
end
|
||||
end
|
||||
|
||||
def validate_job_variables!(name, job)
|
||||
unless validate_variables(job[:variables])
|
||||
raise ValidationError,
|
||||
"#{name} job: variables should be a map of key-value strings"
|
||||
end
|
||||
end
|
||||
|
||||
def validate_job_cache!(name, job)
|
||||
if job[:cache][:key] && !validate_string(job[:cache][:key])
|
||||
raise ValidationError, "#{name} job: cache:key parameter should be a string"
|
||||
|
|
|
@ -11,6 +11,8 @@ module Gitlab
|
|||
module Instrumentation
|
||||
SERIES = 'method_calls'
|
||||
|
||||
PROXY_IVAR = :@__gitlab_instrumentation_proxy
|
||||
|
||||
def self.configure
|
||||
yield self
|
||||
end
|
||||
|
@ -91,6 +93,18 @@ module Gitlab
|
|||
end
|
||||
end
|
||||
|
||||
# Returns true if a module is instrumented.
|
||||
#
|
||||
# mod - The module to check
|
||||
def self.instrumented?(mod)
|
||||
mod.instance_variable_defined?(PROXY_IVAR)
|
||||
end
|
||||
|
||||
# Returns the proxy module (if any) of `mod`.
|
||||
def self.proxy_module(mod)
|
||||
mod.instance_variable_get(PROXY_IVAR)
|
||||
end
|
||||
|
||||
# Instruments a method.
|
||||
#
|
||||
# type - The type (:class or :instance) of method to instrument.
|
||||
|
@ -99,9 +113,8 @@ module Gitlab
|
|||
def self.instrument(type, mod, name)
|
||||
return unless Metrics.enabled?
|
||||
|
||||
name = name.to_sym
|
||||
alias_name = :"_original_#{name}"
|
||||
target = type == :instance ? mod : mod.singleton_class
|
||||
name = name.to_sym
|
||||
target = type == :instance ? mod : mod.singleton_class
|
||||
|
||||
if type == :instance
|
||||
target = mod
|
||||
|
@ -113,6 +126,12 @@ module Gitlab
|
|||
method = mod.method(name)
|
||||
end
|
||||
|
||||
unless instrumented?(target)
|
||||
target.instance_variable_set(PROXY_IVAR, Module.new)
|
||||
end
|
||||
|
||||
proxy_module = self.proxy_module(target)
|
||||
|
||||
# Some code out there (e.g. the "state_machine" Gem) checks the arity of
|
||||
# a method to make sure it only passes arguments when the method expects
|
||||
# any. If we were to always overwrite a method to take an `*args`
|
||||
|
@ -125,17 +144,13 @@ module Gitlab
|
|||
args_signature = '*args, &block'
|
||||
end
|
||||
|
||||
send_signature = "__send__(#{alias_name.inspect}, #{args_signature})"
|
||||
|
||||
target.class_eval <<-EOF, __FILE__, __LINE__ + 1
|
||||
alias_method #{alias_name.inspect}, #{name.inspect}
|
||||
|
||||
proxy_module.class_eval <<-EOF, __FILE__, __LINE__ + 1
|
||||
def #{name}(#{args_signature})
|
||||
trans = Gitlab::Metrics::Instrumentation.transaction
|
||||
|
||||
if trans
|
||||
start = Time.now
|
||||
retval = #{send_signature}
|
||||
retval = super
|
||||
duration = (Time.now - start) * 1000.0
|
||||
|
||||
if duration >= Gitlab::Metrics.method_call_threshold
|
||||
|
@ -148,10 +163,12 @@ module Gitlab
|
|||
|
||||
retval
|
||||
else
|
||||
#{send_signature}
|
||||
super
|
||||
end
|
||||
end
|
||||
EOF
|
||||
|
||||
target.prepend(proxy_module)
|
||||
end
|
||||
|
||||
# Small layer of indirection to make it easier to stub out the current
|
||||
|
|
|
@ -42,11 +42,9 @@ feature 'issue move to another project' do
|
|||
|
||||
expect(current_url).to include project_path(new_project)
|
||||
|
||||
page.within('.issue') do
|
||||
expect(page).to have_content("Text with #{cross_reference}!1")
|
||||
expect(page).to have_content("Moved from #{cross_reference}#1")
|
||||
expect(page).to have_content(issue.title)
|
||||
end
|
||||
expect(page).to have_content("Text with #{cross_reference}!1")
|
||||
expect(page).to have_content("Moved from #{cross_reference}#1")
|
||||
expect(page).to have_content(issue.title)
|
||||
end
|
||||
|
||||
context 'projects user does not have permission to move issue to exist' do
|
||||
|
@ -74,7 +72,7 @@ feature 'issue move to another project' do
|
|||
|
||||
def edit_issue(issue)
|
||||
visit issue_path(issue)
|
||||
page.within('.issuable-header') { click_link 'Edit' }
|
||||
page.within('.issuable-actions') { first(:link, 'Edit').click }
|
||||
end
|
||||
|
||||
def issue_path(issue)
|
||||
|
|
|
@ -292,6 +292,23 @@ describe 'Issues', feature: true do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'new issue' do
|
||||
context 'dropzone upload file', js: true do
|
||||
before do
|
||||
visit new_namespace_project_issue_path(project.namespace, project)
|
||||
end
|
||||
|
||||
it 'should upload file when dragging into textarea' do
|
||||
drop_in_dropzone test_image_file
|
||||
|
||||
# Wait for the file to upload
|
||||
sleep 1
|
||||
|
||||
expect(page.find_field("issue_description").value).to have_content 'banana_sample'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def first_issue
|
||||
page.all('ul.issues-list > li').first.text
|
||||
end
|
||||
|
@ -299,4 +316,25 @@ describe 'Issues', feature: true do
|
|||
def last_issue
|
||||
page.all('ul.issues-list > li').last.text
|
||||
end
|
||||
|
||||
def drop_in_dropzone(file_path)
|
||||
# Generate a fake input selector
|
||||
page.execute_script <<-JS
|
||||
var fakeFileInput = window.$('<input/>').attr(
|
||||
{id: 'fakeFileInput', type: 'file'}
|
||||
).appendTo('body');
|
||||
JS
|
||||
# Attach the file to the fake input selector with Capybara
|
||||
attach_file("fakeFileInput", file_path)
|
||||
# Add the file to a fileList array and trigger the fake drop event
|
||||
page.execute_script <<-JS
|
||||
var fileList = [$('#fakeFileInput')[0].files[0]];
|
||||
var e = jQuery.Event('drop', { dataTransfer : { files : fileList } });
|
||||
$('.div-dropzone')[0].dropzone.listeners[0].events.drop(e);
|
||||
JS
|
||||
end
|
||||
|
||||
def test_image_file
|
||||
File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif')
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
require 'spec_helper'
|
||||
|
||||
feature 'Member autocomplete', feature: true do
|
||||
let(:project) { create(:project, :public) }
|
||||
let(:user) { create(:user) }
|
||||
let(:participant) { create(:user) }
|
||||
let(:author) { create(:user) }
|
||||
|
||||
before do
|
||||
allow_any_instance_of(Commit).to receive(:author).and_return(author)
|
||||
login_as user
|
||||
end
|
||||
|
||||
shared_examples "open suggestions" do
|
||||
it 'suggestions are displayed' do
|
||||
expect(page).to have_selector('.atwho-view', visible: true)
|
||||
end
|
||||
|
||||
it 'author is suggested' do
|
||||
page.within('.atwho-view', visible: true) do
|
||||
expect(page).to have_content(author.username)
|
||||
end
|
||||
end
|
||||
|
||||
it 'participant is suggested' do
|
||||
page.within('.atwho-view', visible: true) do
|
||||
expect(page).to have_content(participant.username)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'adding a new note on a Issue', js: true do
|
||||
before do
|
||||
issue = create(:issue, author: author, project: project)
|
||||
create(:note, note: 'Ultralight Beam', noteable: issue, author: participant)
|
||||
visit_issue(project, issue)
|
||||
end
|
||||
|
||||
context 'when typing @' do
|
||||
include_examples "open suggestions"
|
||||
before do
|
||||
open_member_suggestions
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'adding a new note on a Merge Request ', js: true do
|
||||
before do
|
||||
merge = create(:merge_request, source_project: project, target_project: project, author: author)
|
||||
create(:note, note: 'Ultralight Beam', noteable: merge, author: participant)
|
||||
visit_merge_request(project, merge)
|
||||
end
|
||||
|
||||
context 'when typing @' do
|
||||
include_examples "open suggestions"
|
||||
before do
|
||||
open_member_suggestions
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'adding a new note on a Commit ', js: true do
|
||||
let(:commit) { project.commit }
|
||||
|
||||
before do
|
||||
allow(commit).to receive(:author).and_return(author)
|
||||
create(:note_on_commit, author: participant, project: project, commit_id: project.repository.commit.id, note: 'No More Parties in LA')
|
||||
visit_commit(project, commit)
|
||||
end
|
||||
|
||||
context 'when typing @' do
|
||||
include_examples "open suggestions"
|
||||
before do
|
||||
open_member_suggestions
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def open_member_suggestions
|
||||
sleep 1
|
||||
page.within('.new-note') do
|
||||
sleep 1
|
||||
find('#note_note').native.send_keys('@')
|
||||
end
|
||||
end
|
||||
|
||||
def visit_issue(project, issue)
|
||||
visit namespace_project_issue_path(project.namespace, project, issue)
|
||||
end
|
||||
|
||||
def visit_merge_request(project, merge)
|
||||
visit namespace_project_merge_request_path(project.namespace, project, merge)
|
||||
end
|
||||
|
||||
def visit_commit(project, commit)
|
||||
visit namespace_project_commit_path(project.namespace, project, commit)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,55 @@
|
|||
require 'spec_helper'
|
||||
|
||||
feature 'Signup', feature: true do
|
||||
describe 'signup with no errors' do
|
||||
it 'creates the user account and sends a confirmation email' do
|
||||
user = build(:user)
|
||||
|
||||
visit root_path
|
||||
|
||||
fill_in 'user_name', with: user.name
|
||||
fill_in 'user_username', with: user.username
|
||||
fill_in 'user_email', with: user.email
|
||||
fill_in 'user_password_sign_up', with: user.password
|
||||
click_button "Sign up"
|
||||
|
||||
expect(current_path).to eq user_session_path
|
||||
expect(page).to have_content("A message with a confirmation link has been sent to your email address.")
|
||||
end
|
||||
end
|
||||
|
||||
describe 'signup with errors' do
|
||||
it "displays the errors" do
|
||||
existing_user = create(:user)
|
||||
user = build(:user)
|
||||
|
||||
visit root_path
|
||||
|
||||
fill_in 'user_name', with: user.name
|
||||
fill_in 'user_username', with: user.username
|
||||
fill_in 'user_email', with: existing_user.email
|
||||
fill_in 'user_password_sign_up', with: user.password
|
||||
click_button "Sign up"
|
||||
|
||||
expect(current_path).to eq user_registration_path
|
||||
expect(page).to have_content("error prohibited this user from being saved")
|
||||
expect(page).to have_content("Email has already been taken")
|
||||
end
|
||||
|
||||
it 'does not redisplay the password' do
|
||||
existing_user = create(:user)
|
||||
user = build(:user)
|
||||
|
||||
visit root_path
|
||||
|
||||
fill_in 'user_name', with: user.name
|
||||
fill_in 'user_username', with: user.username
|
||||
fill_in 'user_email', with: existing_user.email
|
||||
fill_in 'user_password_sign_up', with: user.password
|
||||
click_button "Sign up"
|
||||
|
||||
expect(current_path).to eq user_registration_path
|
||||
expect(page.body).not_to match(/#{user.password}/)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -420,20 +420,76 @@ module Ci
|
|||
end
|
||||
end
|
||||
|
||||
describe "Variables" do
|
||||
it "returns variables when defined" do
|
||||
variables = {
|
||||
var1: "value1",
|
||||
var2: "value2",
|
||||
}
|
||||
config = YAML.dump({
|
||||
variables: variables,
|
||||
before_script: ["pwd"],
|
||||
rspec: { script: "rspec" }
|
||||
})
|
||||
describe 'Variables' do
|
||||
context 'when global variables are defined' do
|
||||
it 'returns global variables' do
|
||||
variables = {
|
||||
VAR1: 'value1',
|
||||
VAR2: 'value2',
|
||||
}
|
||||
|
||||
config_processor = GitlabCiYamlProcessor.new(config, path)
|
||||
expect(config_processor.variables).to eq(variables)
|
||||
config = YAML.dump({
|
||||
variables: variables,
|
||||
before_script: ['pwd'],
|
||||
rspec: { script: 'rspec' }
|
||||
})
|
||||
|
||||
config_processor = GitlabCiYamlProcessor.new(config, path)
|
||||
|
||||
expect(config_processor.global_variables).to eq(variables)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when job variables are defined' do
|
||||
context 'when syntax is correct' do
|
||||
it 'returns job variables' do
|
||||
variables = {
|
||||
KEY1: 'value1',
|
||||
SOME_KEY_2: 'value2'
|
||||
}
|
||||
|
||||
config = YAML.dump(
|
||||
{ before_script: ['pwd'],
|
||||
rspec: {
|
||||
variables: variables,
|
||||
script: 'rspec' }
|
||||
})
|
||||
|
||||
config_processor = GitlabCiYamlProcessor.new(config, path)
|
||||
|
||||
expect(config_processor.job_variables(:rspec)).to eq variables
|
||||
end
|
||||
end
|
||||
|
||||
context 'when syntax is incorrect' do
|
||||
it 'raises error' do
|
||||
variables = [:KEY1, 'value1', :KEY2, 'value2']
|
||||
|
||||
config = YAML.dump(
|
||||
{ before_script: ['pwd'],
|
||||
rspec: {
|
||||
variables: variables,
|
||||
script: 'rspec' }
|
||||
})
|
||||
|
||||
expect { GitlabCiYamlProcessor.new(config, path) }
|
||||
.to raise_error(GitlabCiYamlProcessor::ValidationError,
|
||||
/job: variables should be a map/)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when job variables are not defined' do
|
||||
it 'returns empty array' do
|
||||
config = YAML.dump({
|
||||
before_script: ['pwd'],
|
||||
rspec: { script: 'rspec' }
|
||||
})
|
||||
|
||||
config_processor = GitlabCiYamlProcessor.new(config, path)
|
||||
|
||||
expect(config_processor.job_variables(:rspec)).to eq []
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -826,14 +882,14 @@ EOT
|
|||
config = YAML.dump({ variables: "test", rspec: { script: "test" } })
|
||||
expect do
|
||||
GitlabCiYamlProcessor.new(config, path)
|
||||
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "variables should be a map of key-valued strings")
|
||||
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "variables should be a map of key-value strings")
|
||||
end
|
||||
|
||||
it "returns errors if variables is not a map of key-valued strings" do
|
||||
it "returns errors if variables is not a map of key-value strings" do
|
||||
config = YAML.dump({ variables: { test: false }, rspec: { script: "test" } })
|
||||
expect do
|
||||
GitlabCiYamlProcessor.new(config, path)
|
||||
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "variables should be a map of key-valued strings")
|
||||
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "variables should be a map of key-value strings")
|
||||
end
|
||||
|
||||
it "returns errors if job when is not on_success, on_failure or always" do
|
||||
|
|
|
@ -33,8 +33,16 @@ describe Gitlab::Metrics::Instrumentation do
|
|||
described_class.instrument_method(@dummy, :foo)
|
||||
end
|
||||
|
||||
it 'renames the original method' do
|
||||
expect(@dummy).to respond_to(:_original_foo)
|
||||
it 'instruments the Class' do
|
||||
target = @dummy.singleton_class
|
||||
|
||||
expect(described_class.instrumented?(target)).to eq(true)
|
||||
end
|
||||
|
||||
it 'defines a proxy method' do
|
||||
mod = described_class.proxy_module(@dummy.singleton_class)
|
||||
|
||||
expect(mod.method_defined?(:foo)).to eq(true)
|
||||
end
|
||||
|
||||
it 'calls the instrumented method with the correct arguments' do
|
||||
|
@ -76,6 +84,14 @@ describe Gitlab::Metrics::Instrumentation do
|
|||
|
||||
expect(dummy.method(:test).arity).to eq(0)
|
||||
end
|
||||
|
||||
describe 'when a module is instrumented multiple times' do
|
||||
it 'calls the instrumented method with the correct arguments' do
|
||||
described_class.instrument_method(@dummy, :foo)
|
||||
|
||||
expect(@dummy.foo).to eq('foo')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with metrics disabled' do
|
||||
|
@ -86,7 +102,9 @@ describe Gitlab::Metrics::Instrumentation do
|
|||
it 'does not instrument the method' do
|
||||
described_class.instrument_method(@dummy, :foo)
|
||||
|
||||
expect(@dummy).to_not respond_to(:_original_foo)
|
||||
target = @dummy.singleton_class
|
||||
|
||||
expect(described_class.instrumented?(target)).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -100,8 +118,14 @@ describe Gitlab::Metrics::Instrumentation do
|
|||
instrument_instance_method(@dummy, :bar)
|
||||
end
|
||||
|
||||
it 'renames the original method' do
|
||||
expect(@dummy.method_defined?(:_original_bar)).to eq(true)
|
||||
it 'instruments instances of the Class' do
|
||||
expect(described_class.instrumented?(@dummy)).to eq(true)
|
||||
end
|
||||
|
||||
it 'defines a proxy method' do
|
||||
mod = described_class.proxy_module(@dummy)
|
||||
|
||||
expect(mod.method_defined?(:bar)).to eq(true)
|
||||
end
|
||||
|
||||
it 'calls the instrumented method with the correct arguments' do
|
||||
|
@ -144,7 +168,7 @@ describe Gitlab::Metrics::Instrumentation do
|
|||
described_class.
|
||||
instrument_instance_method(@dummy, :bar)
|
||||
|
||||
expect(@dummy.method_defined?(:_original_bar)).to eq(false)
|
||||
expect(described_class.instrumented?(@dummy)).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -167,18 +191,17 @@ describe Gitlab::Metrics::Instrumentation do
|
|||
it 'recursively instruments a class hierarchy' do
|
||||
described_class.instrument_class_hierarchy(@dummy)
|
||||
|
||||
expect(@child1).to respond_to(:_original_child1_foo)
|
||||
expect(@child2).to respond_to(:_original_child2_foo)
|
||||
expect(described_class.instrumented?(@child1.singleton_class)).to eq(true)
|
||||
expect(described_class.instrumented?(@child2.singleton_class)).to eq(true)
|
||||
|
||||
expect(@child1.method_defined?(:_original_child1_bar)).to eq(true)
|
||||
expect(@child2.method_defined?(:_original_child2_bar)).to eq(true)
|
||||
expect(described_class.instrumented?(@child1)).to eq(true)
|
||||
expect(described_class.instrumented?(@child2)).to eq(true)
|
||||
end
|
||||
|
||||
it 'does not instrument the root module' do
|
||||
described_class.instrument_class_hierarchy(@dummy)
|
||||
|
||||
expect(@dummy).to_not respond_to(:_original_foo)
|
||||
expect(@dummy.method_defined?(:_original_bar)).to eq(false)
|
||||
expect(described_class.instrumented?(@dummy)).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -190,7 +213,7 @@ describe Gitlab::Metrics::Instrumentation do
|
|||
it 'instruments all public class methods' do
|
||||
described_class.instrument_methods(@dummy)
|
||||
|
||||
expect(@dummy).to respond_to(:_original_foo)
|
||||
expect(described_class.instrumented?(@dummy.singleton_class)).to eq(true)
|
||||
end
|
||||
|
||||
it 'only instruments methods directly defined in the module' do
|
||||
|
@ -223,7 +246,7 @@ describe Gitlab::Metrics::Instrumentation do
|
|||
it 'instruments all public instance methods' do
|
||||
described_class.instrument_instance_methods(@dummy)
|
||||
|
||||
expect(@dummy.method_defined?(:_original_bar)).to eq(true)
|
||||
expect(described_class.instrumented?(@dummy)).to eq(true)
|
||||
end
|
||||
|
||||
it 'only instruments methods directly defined in the module' do
|
||||
|
|
|
@ -238,6 +238,22 @@ describe Ci::Build, models: true do
|
|||
|
||||
it { is_expected.to eq(predefined_variables + predefined_trigger_variable + yaml_variables + secure_variables + trigger_variables) }
|
||||
end
|
||||
|
||||
context 'when job variables are defined' do
|
||||
##
|
||||
# Job-level variables are defined in gitlab_ci.yml fixture
|
||||
#
|
||||
context 'when job variables are unique' do
|
||||
let(:build) { create(:ci_build, name: 'staging') }
|
||||
|
||||
it 'includes job variables' do
|
||||
expect(subject).to include(
|
||||
{ key: :KEY1, value: 'value1', public: true },
|
||||
{ key: :KEY2, value: 'value2', public: true }
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -36,4 +36,19 @@ describe ExternalIssue, models: true do
|
|||
expect(issue.title).to eq "External Issue #{issue}"
|
||||
end
|
||||
end
|
||||
|
||||
describe '#reference_link_text' do
|
||||
context 'if issue id has a prefix' do
|
||||
it 'returns the issue ID' do
|
||||
expect(issue.reference_link_text).to eq 'EXT-1234'
|
||||
end
|
||||
end
|
||||
|
||||
context 'if issue id is a number' do
|
||||
let(:issue) { described_class.new('1234', project) }
|
||||
it 'returns the issue ID prefixed by #' do
|
||||
expect(issue.reference_link_text).to eq '#1234'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -910,9 +910,32 @@ describe Repository, models: true do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.clean_old_archives' do
|
||||
let(:path) { Gitlab.config.gitlab.repository_downloads_path }
|
||||
|
||||
context 'when the downloads directory does not exist' do
|
||||
it 'does not remove any archives' do
|
||||
expect(File).to receive(:directory?).with(path).and_return(false)
|
||||
|
||||
expect(Gitlab::Popen).not_to receive(:popen)
|
||||
|
||||
described_class.clean_old_archives
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the downloads directory exists' do
|
||||
it 'removes old archives' do
|
||||
expect(File).to receive(:directory?).with(path).and_return(true)
|
||||
|
||||
expect(Gitlab::Popen).to receive(:popen)
|
||||
|
||||
described_class.clean_old_archives
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def create_remote_branch(remote_name, branch_name, target)
|
||||
rugged = repository.rugged
|
||||
rugged.references.create("refs/remotes/#{remote_name}/#{branch_name}", target)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -4,7 +4,7 @@ services:
|
|||
|
||||
before_script:
|
||||
- gem install bundler
|
||||
- bundle install
|
||||
- bundle install
|
||||
- bundle exec rake db:create
|
||||
|
||||
variables:
|
||||
|
@ -17,7 +17,7 @@ types:
|
|||
|
||||
rspec:
|
||||
script: "rake spec"
|
||||
tags:
|
||||
tags:
|
||||
- ruby
|
||||
- postgres
|
||||
only:
|
||||
|
@ -26,27 +26,32 @@ rspec:
|
|||
spinach:
|
||||
script: "rake spinach"
|
||||
allow_failure: true
|
||||
tags:
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
except:
|
||||
- tags
|
||||
|
||||
staging:
|
||||
variables:
|
||||
KEY1: value1
|
||||
KEY2: value2
|
||||
script: "cap deploy stating"
|
||||
type: deploy
|
||||
tags:
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
except:
|
||||
- stable
|
||||
|
||||
production:
|
||||
variables:
|
||||
DB_NAME: mysql
|
||||
type: deploy
|
||||
script:
|
||||
script:
|
||||
- cap deploy production
|
||||
- cap notify
|
||||
tags:
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
only:
|
||||
|
|
Loading…
Reference in New Issue