Merge branch 'rename-ci-commit-phase-2' into rename-ci-commit-phase-3
This commit is contained in:
commit
d225de32cb
|
@ -194,7 +194,7 @@ Style/EmptyLines:
|
|||
|
||||
# Keep blank lines around access modifiers.
|
||||
Style/EmptyLinesAroundAccessModifier:
|
||||
Enabled: false
|
||||
Enabled: true
|
||||
|
||||
# Keeps track of empty lines around block bodies.
|
||||
Style/EmptyLinesAroundBlockBody:
|
||||
|
@ -771,7 +771,7 @@ Metrics/PerceivedComplexity:
|
|||
# Checks for ambiguous operators in the first argument of a method invocation
|
||||
# without parentheses.
|
||||
Lint/AmbiguousOperator:
|
||||
Enabled: false
|
||||
Enabled: true
|
||||
|
||||
# Checks for ambiguous regexp literals in the first argument of a method
|
||||
# invocation without parentheses.
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
Please view this file on the master branch, on stable branches it's out of date.
|
||||
|
||||
v 8.9.0 (unreleased)
|
||||
- Bulk assign/unassign labels to issues.
|
||||
- Allow enabling wiki page events from Webhook management UI
|
||||
- Make EmailsOnPushWorker use Sidekiq mailers queue
|
||||
- Fix wiki page events' webhook to point to the wiki repository
|
||||
|
@ -35,13 +36,13 @@ v 8.9.0 (unreleased)
|
|||
- Reduce number of queries needed to render issue labels in the sidebar
|
||||
- Improve error handling importing projects
|
||||
- Put project Files and Commits tabs under Code tab
|
||||
|
||||
v 8.8.4
|
||||
- Fix todos page throwing errors when you have a project pending deletion
|
||||
- Reduce number of SQL queries when rendering user references
|
||||
- Replace Colorize with Rainbow for coloring console output in Rake tasks.
|
||||
|
||||
v 8.8.4 (unreleased)
|
||||
- Ensure branch cleanup regardless of whether the GitHub import process succeeds
|
||||
- Fix issue with arrow keys not working in search autocomplete dropdown
|
||||
- Fix todos page throwing errors when you have a project pending deletion
|
||||
- Reduce number of SQL queries when rendering user references
|
||||
|
||||
v 8.8.3
|
||||
- Fix 404 page when viewing TODOs that contain milestones or labels in different projects. !4312
|
||||
|
|
|
@ -96,7 +96,7 @@ The designs are made using Antetype (`.atype` files). You can use the
|
|||
[free Antetype viewer (Mac OSX only)] or grab an exported PNG from the design
|
||||
(the PNG is 1:1).
|
||||
|
||||
The current designs can be found in the [`gitlab1.atype` file].
|
||||
The current designs can be found in the [`gitlab8.atype` file].
|
||||
|
||||
### UI development kit
|
||||
|
||||
|
@ -530,4 +530,4 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor
|
|||
[scss-styleguide]: doc/development/scss_styleguide.md "SCSS styleguide"
|
||||
[gitlab-design]: https://gitlab.com/gitlab-org/gitlab-design
|
||||
[free Antetype viewer (Mac OSX only)]: https://itunes.apple.com/us/app/antetype-viewer/id824152298?mt=12
|
||||
[`gitlab1.atype` file]: https://gitlab.com/gitlab-org/gitlab-design/tree/master/gitlab1.atype/
|
||||
[`gitlab8.atype` file]: https://gitlab.com/gitlab-org/gitlab-design/tree/master/current/
|
||||
|
|
2
Gemfile
2
Gemfile
|
@ -143,7 +143,7 @@ gem 'redis-namespace'
|
|||
gem "httparty", '~> 0.13.3'
|
||||
|
||||
# Colored output to console
|
||||
gem "colorize", '~> 0.7.0'
|
||||
gem "rainbow", '~> 2.1.0'
|
||||
|
||||
# GitLab settings
|
||||
gem 'settingslogic', '~> 2.0.9'
|
||||
|
|
|
@ -823,7 +823,6 @@ DEPENDENCIES
|
|||
carrierwave (~> 0.10.0)
|
||||
charlock_holmes (~> 0.7.3)
|
||||
coffee-rails (~> 4.1.0)
|
||||
colorize (~> 0.7.0)
|
||||
connection_pool (~> 2.0)
|
||||
coveralls (~> 0.8.2)
|
||||
creole (~> 0.5.0)
|
||||
|
@ -914,6 +913,7 @@ DEPENDENCIES
|
|||
rack-oauth2 (~> 1.2.1)
|
||||
rails (= 4.2.6)
|
||||
rails-deprecated_sanitizer (~> 1.0.3)
|
||||
rainbow (~> 2.1.0)
|
||||
raphael-rails (~> 2.1.2)
|
||||
rblineprof
|
||||
rdoc (~> 3.6)
|
||||
|
|
|
@ -1,201 +1,300 @@
|
|||
class @AwardsHandler
|
||||
constructor: (@getEmojisUrl, @postEmojiUrl, @noteableType, @noteableId, @unicodes) ->
|
||||
$('.js-add-award').on 'click', (event) =>
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
|
||||
@showEmojiMenu()
|
||||
constructor: ->
|
||||
|
||||
@aliases = emojiAliases()
|
||||
|
||||
$(document)
|
||||
.off 'click', '.js-add-award'
|
||||
.on 'click', '.js-add-award', (event) =>
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
|
||||
@showEmojiMenu $(event.currentTarget)
|
||||
|
||||
$('html').on 'click', (event) ->
|
||||
if !$(event.target).closest('.emoji-menu').length
|
||||
unless $(event.target).closest('.emoji-menu').length
|
||||
if $('.emoji-menu').is(':visible')
|
||||
$('.js-add-award.is-active').removeClass 'is-active'
|
||||
$('.emoji-menu').removeClass 'is-visible'
|
||||
|
||||
$('.awards')
|
||||
.off 'click'
|
||||
.on 'click', '.js-emoji-btn', @handleClick
|
||||
$(document)
|
||||
.off 'click', '.js-emoji-btn'
|
||||
.on 'click', '.js-emoji-btn', @handleClick
|
||||
|
||||
@renderFrequentlyUsedBlock()
|
||||
|
||||
handleClick: (e) ->
|
||||
handleClick: (e) =>
|
||||
|
||||
e.preventDefault()
|
||||
emoji = $(this)
|
||||
.find('.icon')
|
||||
.data 'emoji'
|
||||
|
||||
if emoji is 'thumbsup' and awardsHandler.didUserClickEmoji $(this), 'thumbsdown'
|
||||
awardsHandler.addAward 'thumbsdown'
|
||||
emoji = $(e.currentTarget).find('.icon').data 'emoji'
|
||||
@getVotesBlock().addClass 'js-awards-block'
|
||||
@addAward @getAwardUrl(), emoji
|
||||
|
||||
else if emoji is 'thumbsdown' and awardsHandler.didUserClickEmoji $(this), 'thumbsup'
|
||||
awardsHandler.addAward 'thumbsup'
|
||||
|
||||
awardsHandler.addAward emoji
|
||||
showEmojiMenu: ($addBtn) ->
|
||||
|
||||
$(this).trigger 'blur'
|
||||
$menu = $('.emoji-menu')
|
||||
|
||||
didUserClickEmoji: (that, emoji) ->
|
||||
if $(that).siblings("button:has([data-emoji=#{emoji}])").attr('data-original-title')
|
||||
$(that).siblings("button:has([data-emoji=#{emoji}])").attr('data-original-title').indexOf('me') > -1
|
||||
if $menu.length
|
||||
$holder = $addBtn.closest('.js-award-holder')
|
||||
|
||||
showEmojiMenu: ->
|
||||
if $('.emoji-menu').length
|
||||
if $('.emoji-menu').is '.is-visible'
|
||||
$('.emoji-menu').removeClass 'is-visible'
|
||||
if $menu.is '.is-visible'
|
||||
$addBtn.removeClass 'is-active'
|
||||
$menu.removeClass 'is-visible'
|
||||
$('#emoji_search').blur()
|
||||
else
|
||||
$('.emoji-menu').addClass 'is-visible'
|
||||
$addBtn.addClass 'is-active'
|
||||
@positionMenu($menu, $addBtn)
|
||||
|
||||
$menu.addClass 'is-visible'
|
||||
$('#emoji_search').focus()
|
||||
else
|
||||
$('.js-add-award').addClass 'is-loading'
|
||||
$.get @getEmojisUrl, (response) =>
|
||||
$('.js-add-award').removeClass 'is-loading'
|
||||
$('.js-award-holder').append response
|
||||
$addBtn.addClass 'is-loading is-active'
|
||||
url = $addBtn.data 'award-menu-url'
|
||||
|
||||
@createEmojiMenu url, =>
|
||||
$addBtn.removeClass 'is-loading'
|
||||
$menu = $('.emoji-menu')
|
||||
@positionMenu($menu, $addBtn)
|
||||
@renderFrequentlyUsedBlock()
|
||||
|
||||
setTimeout =>
|
||||
$('.emoji-menu').addClass 'is-visible'
|
||||
$menu.addClass 'is-visible'
|
||||
$('#emoji_search').focus()
|
||||
@setupSearch()
|
||||
, 200
|
||||
|
||||
addAward: (emoji) ->
|
||||
@postEmoji emoji, =>
|
||||
@addAwardToEmojiBar(emoji)
|
||||
|
||||
createEmojiMenu: (awardMenuUrl, callback) ->
|
||||
|
||||
$.get awardMenuUrl, (response) =>
|
||||
$('body').append response
|
||||
callback()
|
||||
|
||||
|
||||
positionMenu: ($menu, $addBtn) ->
|
||||
position = $addBtn.data('position')
|
||||
|
||||
# The menu could potentially be off-screen or in a hidden overflow element
|
||||
# So we position the element absolute in the body
|
||||
css =
|
||||
top: "#{$addBtn.offset().top + $addBtn.outerHeight()}px"
|
||||
|
||||
if position? and position is 'right'
|
||||
css.left = "#{($addBtn.offset().left - $menu.outerWidth()) + 20}px"
|
||||
$menu.addClass 'is-aligned-right'
|
||||
else
|
||||
css.left = "#{$addBtn.offset().left}px"
|
||||
$menu.removeClass 'is-aligned-right'
|
||||
|
||||
$menu.css(css)
|
||||
|
||||
|
||||
addAward: (awardUrl, emoji, checkMutuality = yes) ->
|
||||
|
||||
emoji = @normilizeEmojiName(emoji)
|
||||
@postEmoji awardUrl, emoji, =>
|
||||
@addAwardToEmojiBar(emoji, checkMutuality)
|
||||
|
||||
$('.js-awards-block-current').removeClass 'js-awards-block-current'
|
||||
|
||||
$('.emoji-menu').removeClass 'is-visible'
|
||||
|
||||
addAwardToEmojiBar: (emoji) ->
|
||||
|
||||
addAwardToEmojiBar: (emoji, checkForMutuality = yes) ->
|
||||
|
||||
@checkMutuality emoji if checkForMutuality
|
||||
@addEmojiToFrequentlyUsedList(emoji)
|
||||
|
||||
if @exist(emoji)
|
||||
if @isActive(emoji)
|
||||
@decrementCounter(emoji)
|
||||
emoji = @normilizeEmojiName(emoji)
|
||||
$emojiBtn = @findEmojiIcon(emoji).parent()
|
||||
|
||||
if $emojiBtn.length > 0
|
||||
if @isActive($emojiBtn)
|
||||
@decrementCounter($emojiBtn, emoji)
|
||||
else
|
||||
counter = @findEmojiIcon(emoji).siblings('.js-counter')
|
||||
counter = $emojiBtn.find('.js-counter')
|
||||
counter.text(parseInt(counter.text()) + 1)
|
||||
counter.parent().addClass('active')
|
||||
@addMeToAuthorList(emoji)
|
||||
$emojiBtn.addClass('active')
|
||||
@addMeToUserList(emoji)
|
||||
else
|
||||
@createEmoji(emoji)
|
||||
|
||||
exist: (emoji) ->
|
||||
@findEmojiIcon(emoji).length > 0
|
||||
|
||||
isActive: (emoji) ->
|
||||
@findEmojiIcon(emoji).parent().hasClass('active')
|
||||
getVotesBlock: -> return $ '.awards.js-awards-block'
|
||||
|
||||
decrementCounter: (emoji) ->
|
||||
counter = @findEmojiIcon(emoji).siblings('.js-counter')
|
||||
emojiIcon = counter.parent()
|
||||
if parseInt(counter.text()) > 1
|
||||
counter.text(parseInt(counter.text()) - 1)
|
||||
emojiIcon.removeClass('active')
|
||||
@removeMeFromAuthorList(emoji)
|
||||
else if emoji == 'thumbsup' || emoji == 'thumbsdown'
|
||||
emojiIcon.tooltip('destroy')
|
||||
counter.text(0)
|
||||
emojiIcon.removeClass('active')
|
||||
@removeMeFromAuthorList(emoji)
|
||||
|
||||
getAwardUrl: -> @getVotesBlock().data 'award-url'
|
||||
|
||||
|
||||
checkMutuality: (emoji) ->
|
||||
|
||||
awardUrl = @getAwardUrl()
|
||||
|
||||
if emoji in [ 'thumbsup', 'thumbsdown' ]
|
||||
mutualVote = if emoji is 'thumbsup' then 'thumbsdown' else 'thumbsup'
|
||||
|
||||
isAlreadyVoted = $("[data-emoji=#{mutualVote}]").parent().hasClass 'active'
|
||||
@addAward awardUrl, mutualVote, no if isAlreadyVoted
|
||||
|
||||
|
||||
isActive: ($emojiBtn) -> $emojiBtn.hasClass 'active'
|
||||
|
||||
|
||||
decrementCounter: ($emojiBtn, emoji) ->
|
||||
isntNoteBody = $emojiBtn.closest('.note-body').length is 0
|
||||
counter = $('.js-counter', $emojiBtn)
|
||||
counterNumber = parseInt(counter.text())
|
||||
|
||||
if !isntNoteBody
|
||||
# If this is a note body, we just hide the award emoji row like the initial state
|
||||
$emojiBtn.closest('.js-awards-block').addClass 'hidden'
|
||||
|
||||
if counterNumber > 1
|
||||
counter.text(counterNumber - 1)
|
||||
@removeMeFromUserList($emojiBtn, emoji)
|
||||
else if (emoji == 'thumbsup' || emoji == 'thumbsdown') && isntNoteBody
|
||||
$emojiBtn.tooltip('destroy')
|
||||
counter.text('0')
|
||||
@removeMeFromUserList($emojiBtn, emoji)
|
||||
else
|
||||
emojiIcon.tooltip('destroy')
|
||||
emojiIcon.remove()
|
||||
$emojiBtn.tooltip('destroy')
|
||||
$emojiBtn.remove()
|
||||
|
||||
$emojiBtn.removeClass('active')
|
||||
|
||||
|
||||
getAwardTooltip: ($awardBlock) ->
|
||||
|
||||
return $awardBlock.attr('data-original-title') or $awardBlock.attr('data-title')
|
||||
|
||||
|
||||
removeMeFromUserList: ($emojiBtn, emoji) ->
|
||||
|
||||
awardBlock = $emojiBtn
|
||||
originalTitle = @getAwardTooltip awardBlock
|
||||
|
||||
authors = originalTitle.split ', '
|
||||
authors.splice authors.indexOf('me'), 1
|
||||
|
||||
newAuthors = authors.join ', '
|
||||
|
||||
removeMeFromAuthorList: (emoji) ->
|
||||
awardBlock = @findEmojiIcon(emoji).parent()
|
||||
authors = awardBlock
|
||||
.attr('data-original-title')
|
||||
.split(', ')
|
||||
authors.splice(authors.indexOf('me'),1)
|
||||
awardBlock
|
||||
.closest('.js-emoji-btn')
|
||||
.attr('data-original-title', authors.join(', '))
|
||||
.closest '.js-emoji-btn'
|
||||
.removeData 'original-title'
|
||||
.removeData 'title'
|
||||
.attr 'data-original-title', newAuthors
|
||||
.attr 'data-title', newAuthors
|
||||
|
||||
@resetTooltip(awardBlock)
|
||||
|
||||
addMeToAuthorList: (emoji) ->
|
||||
|
||||
addMeToUserList: (emoji) ->
|
||||
|
||||
awardBlock = @findEmojiIcon(emoji).parent()
|
||||
origTitle = awardBlock.attr('data-original-title').trim()
|
||||
authors = []
|
||||
origTitle = @getAwardTooltip awardBlock
|
||||
users = []
|
||||
|
||||
if origTitle
|
||||
authors = origTitle.split(', ')
|
||||
authors.push('me')
|
||||
awardBlock.attr('data-original-title', authors.join(', '))
|
||||
users = origTitle.trim().split(', ')
|
||||
|
||||
users.push('me')
|
||||
awardBlock.attr('title', users.join(', '))
|
||||
|
||||
@resetTooltip(awardBlock)
|
||||
|
||||
|
||||
resetTooltip: (award) ->
|
||||
award.tooltip('destroy')
|
||||
|
||||
# "destroy" call is asynchronous and there is no appropriate callback on it, this is why we need to set timeout.
|
||||
# 'destroy' call is asynchronous and there is no appropriate callback on it, this is why we need to set timeout.
|
||||
setTimeout (->
|
||||
award.tooltip()
|
||||
), 200
|
||||
|
||||
|
||||
createEmoji: (emoji) ->
|
||||
emojiCssClass = @resolveNameToCssClass(emoji)
|
||||
createEmoji_: (emoji) ->
|
||||
|
||||
nodes = []
|
||||
nodes.push(
|
||||
"<button class='btn award-control js-emoji-btn has-tooltip active' data-original-title='me'>",
|
||||
"<div class='icon emoji-icon #{emojiCssClass}' data-emoji='#{emoji}'></div>",
|
||||
"<span class='award-control-text js-counter'>1</span>",
|
||||
"</button>"
|
||||
)
|
||||
emojiCssClass = @resolveNameToCssClass emoji
|
||||
|
||||
buttonHtml = "<button class='btn award-control js-emoji-btn has-tooltip active' title='me' data-placement='bottom'>
|
||||
<div class='icon emoji-icon #{emojiCssClass}' data-emoji='#{emoji}'></div>
|
||||
<span class='award-control-text js-counter'>1</span>
|
||||
</button>"
|
||||
|
||||
emoji_node = $(buttonHtml)
|
||||
.insertBefore '.js-awards-block .js-award-holder:not(.js-award-action-btn)'
|
||||
.find '.emoji-icon'
|
||||
.data 'emoji', emoji
|
||||
|
||||
$(nodes.join("\n"))
|
||||
.insertBefore('.js-award-holder')
|
||||
.find('.emoji-icon')
|
||||
.data('emoji', emoji)
|
||||
$('.award-control').tooltip()
|
||||
|
||||
resolveNameToCssClass: (emoji) ->
|
||||
emojiIcon = $(".emoji-menu-content [data-emoji='#{emoji}']")
|
||||
$currentBlock = $ '.js-awards-block'
|
||||
|
||||
if emojiIcon.length > 0
|
||||
unicodeName = emojiIcon.data('unicode-name')
|
||||
if $currentBlock.is '.hidden'
|
||||
$currentBlock.removeClass 'hidden'
|
||||
|
||||
|
||||
createEmoji: (emoji) ->
|
||||
|
||||
return @createEmoji_ emoji if $('.emoji-menu').length
|
||||
|
||||
awardMenuUrl = gl.awardMenuUrl or '/emojis'
|
||||
@createEmojiMenu awardMenuUrl, => @createEmoji emoji
|
||||
|
||||
|
||||
resolveNameToCssClass: (emoji) ->
|
||||
|
||||
emoji_icon = $(".emoji-menu-content [data-emoji='#{emoji}']")
|
||||
|
||||
if emoji_icon.length > 0
|
||||
unicodeName = emoji_icon.data('unicode-name')
|
||||
else
|
||||
# Find by alias
|
||||
unicodeName = $(".emoji-menu-content [data-aliases*=':#{emoji}:']").data('unicode-name')
|
||||
|
||||
"emoji-#{unicodeName}"
|
||||
return "emoji-#{unicodeName}"
|
||||
|
||||
postEmoji: (emoji, callback) ->
|
||||
$.post @postEmojiUrl, { note: {
|
||||
note: ":#{emoji}:"
|
||||
noteable_type: @noteableType
|
||||
noteable_id: @noteableId
|
||||
}},(data) ->
|
||||
|
||||
postEmoji: (awardUrl, emoji, callback) ->
|
||||
$.post awardUrl, { name: emoji }, (data) ->
|
||||
if data.ok
|
||||
callback.call()
|
||||
|
||||
findEmojiIcon: (emoji) ->
|
||||
$(".awards > .js-emoji-btn [data-emoji='#{emoji}']")
|
||||
$(".js-awards-block.awards > .js-emoji-btn [data-emoji='#{emoji}']")
|
||||
|
||||
scrollToAwards: ->
|
||||
$('body, html').animate({
|
||||
scrollTop: $('.awards').offset().top - 80
|
||||
}, 200)
|
||||
|
||||
normilizeEmojiName: (emoji) ->
|
||||
@aliases[emoji] || emoji
|
||||
|
||||
addEmojiToFrequentlyUsedList: (emoji) ->
|
||||
frequentlyUsedEmojis = @getFrequentlyUsedEmojis()
|
||||
frequentlyUsedEmojis.push(emoji)
|
||||
$.cookie('frequently_used_emojis', frequentlyUsedEmojis.join(','), { expires: 365 })
|
||||
frequently_used_emojis = @getFrequentlyUsedEmojis()
|
||||
frequently_used_emojis.push(emoji)
|
||||
$.cookie('frequently_used_emojis', frequently_used_emojis.join(','), { expires: 365 })
|
||||
|
||||
getFrequentlyUsedEmojis: ->
|
||||
frequentlyUsedEmojis = ($.cookie('frequently_used_emojis') || '').split(',')
|
||||
_.compact(_.uniq(frequentlyUsedEmojis))
|
||||
frequently_used_emojis = ($.cookie('frequently_used_emojis') || '').split(',')
|
||||
_.compact(_.uniq(frequently_used_emojis))
|
||||
|
||||
renderFrequentlyUsedBlock: ->
|
||||
if $.cookie('frequently_used_emojis')
|
||||
frequentlyUsedEmojis = @getFrequentlyUsedEmojis()
|
||||
frequently_used_emojis = @getFrequentlyUsedEmojis()
|
||||
|
||||
ul = $('<ul>')
|
||||
ul = $("<ul class='clearfix emoji-menu-list'>")
|
||||
|
||||
for emoji in frequentlyUsedEmojis
|
||||
do (emoji) ->
|
||||
$(".emoji-menu-content [data-emoji='#{emoji}']").closest('li').clone().appendTo(ul)
|
||||
for emoji in frequently_used_emojis
|
||||
$(".emoji-menu-content [data-emoji='#{emoji}']").closest('li').clone().appendTo(ul)
|
||||
|
||||
$('input.emoji-search').after(ul).after($('<h5>').text('Frequently used'))
|
||||
|
||||
setupSearch: ->
|
||||
$('input.emoji-search').keyup (ev) =>
|
||||
$('input.emoji-search').on 'keyup', (ev) =>
|
||||
term = $(ev.target).val()
|
||||
|
||||
# Clean previous search results
|
||||
|
@ -204,12 +303,12 @@ class @AwardsHandler
|
|||
if term
|
||||
# Generate a search result block
|
||||
h5 = $('<h5>').text('Search results').addClass('emoji-search')
|
||||
foundEmojis = @searchEmojis(term).show()
|
||||
ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(foundEmojis)
|
||||
found_emojis = @searchEmojis(term).show()
|
||||
ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(found_emojis)
|
||||
$('.emoji-menu-content ul, .emoji-menu-content h5').hide()
|
||||
$('.emoji-menu-content').append(h5).append(ul)
|
||||
else
|
||||
$('.emoji-menu-content').children().show()
|
||||
|
||||
searchEmojis: (term)->
|
||||
$(".emoji-menu-content [data-emoji*='#{term}']").closest("li").clone()
|
||||
$(".emoji-menu-content [data-emoji*='#{term}']").closest('li').clone()
|
||||
|
|
|
@ -17,11 +17,13 @@ class Dispatcher
|
|||
switch page
|
||||
when 'projects:issues:index'
|
||||
Issuable.init()
|
||||
new IssuableBulkActions()
|
||||
shortcut_handler = new ShortcutsNavigation()
|
||||
when 'projects:issues:show'
|
||||
new Issue()
|
||||
shortcut_handler = new ShortcutsIssuable()
|
||||
new ZenMode()
|
||||
window.awardsHandler = new AwardsHandler()
|
||||
when 'projects:milestones:show', 'groups:milestones:show', 'dashboard:milestones:show'
|
||||
new Milestone()
|
||||
when 'dashboard:todos:index'
|
||||
|
@ -52,6 +54,7 @@ class Dispatcher
|
|||
new Diff()
|
||||
shortcut_handler = new ShortcutsIssuable(true)
|
||||
new ZenMode()
|
||||
window.awardsHandler = new AwardsHandler()
|
||||
when "projects:merge_requests:diffs"
|
||||
new Diff()
|
||||
new ZenMode()
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
class @Flash
|
||||
constructor: (message, type)->
|
||||
constructor: (message, type = 'alert')->
|
||||
@flash = $(".flash-container")
|
||||
@flash.html("")
|
||||
|
||||
|
|
|
@ -11,6 +11,8 @@ class GitLabDropdownFilter
|
|||
$inputContainer = @input.parent()
|
||||
$clearButton = $inputContainer.find('.js-dropdown-input-clear')
|
||||
|
||||
@indeterminateIds = []
|
||||
|
||||
# Clear click
|
||||
$clearButton.on 'click', (e) =>
|
||||
e.preventDefault()
|
||||
|
@ -35,20 +37,20 @@ class GitLabDropdownFilter
|
|||
if keyCode is 13
|
||||
return false
|
||||
|
||||
clearTimeout timeout
|
||||
timeout = setTimeout =>
|
||||
blur_field = @shouldBlur keyCode
|
||||
search_text = @input.val()
|
||||
# Only filter asynchronously only if option remote is set
|
||||
if @options.remote
|
||||
clearTimeout timeout
|
||||
timeout = setTimeout =>
|
||||
blur_field = @shouldBlur keyCode
|
||||
|
||||
if blur_field and @filterInputBlur
|
||||
@input.blur()
|
||||
if blur_field and @filterInputBlur
|
||||
@input.blur()
|
||||
|
||||
if @options.remote
|
||||
@options.query search_text, (data) =>
|
||||
@options.query @input.val(), (data) =>
|
||||
@options.callback(data)
|
||||
else
|
||||
@filter search_text
|
||||
, 250
|
||||
, 250
|
||||
else
|
||||
@filter @input.val()
|
||||
|
||||
shouldBlur: (keyCode) ->
|
||||
return BLUR_KEYCODES.indexOf(keyCode) >= 0
|
||||
|
@ -142,6 +144,7 @@ class GitLabDropdown
|
|||
LOADING_CLASS = "is-loading"
|
||||
PAGE_TWO_CLASS = "is-page-two"
|
||||
ACTIVE_CLASS = "is-active"
|
||||
INDETERMINATE_CLASS = "is-indeterminate"
|
||||
currentIndex = -1
|
||||
|
||||
FILTER_INPUT = '.dropdown-input .dropdown-input-field'
|
||||
|
@ -182,9 +185,6 @@ class GitLabDropdown
|
|||
@fullData = data
|
||||
|
||||
@parseData @fullData
|
||||
|
||||
if @options.filterable
|
||||
@filterInput.trigger 'keyup'
|
||||
}
|
||||
|
||||
# Init filterable
|
||||
|
@ -298,6 +298,13 @@ class GitLabDropdown
|
|||
opened: =>
|
||||
@addArrowKeyEvent()
|
||||
|
||||
if @options.setIndeterminateIds
|
||||
@options.setIndeterminateIds.call(@)
|
||||
|
||||
# Makes indeterminate items effective
|
||||
if @fullData and @dropdown.find('.dropdown-menu-toggle').hasClass('js-filter-bulk-update')
|
||||
@parseData @fullData
|
||||
|
||||
contentHtml = $('.dropdown-content', @dropdown).html()
|
||||
if @remote && contentHtml is ""
|
||||
@remote.execute()
|
||||
|
@ -309,12 +316,18 @@ class GitLabDropdown
|
|||
|
||||
hidden: (e) =>
|
||||
@removeArrayKeyEvent()
|
||||
|
||||
$input = @dropdown.find(".dropdown-input-field")
|
||||
|
||||
if @options.filterable
|
||||
@dropdown
|
||||
.find(".dropdown-input-field")
|
||||
$input
|
||||
.blur()
|
||||
.val("")
|
||||
.trigger("keyup")
|
||||
|
||||
# Triggering 'keyup' will re-render the dropdown which is not always required
|
||||
# specially if we want to keep the state of the dropdown needed for bulk-assignment
|
||||
if not @options.persistWhenHide
|
||||
$input.trigger("keyup")
|
||||
|
||||
if @dropdown.find(".dropdown-toggle-page").length
|
||||
$('.dropdown-menu', @dropdown).removeClass PAGE_TWO_CLASS
|
||||
|
@ -358,7 +371,7 @@ class GitLabDropdown
|
|||
|
||||
if @options.renderRow
|
||||
# Call the render function
|
||||
html = @options.renderRow(data)
|
||||
html = @options.renderRow.call(@options, data, @)
|
||||
else
|
||||
if not selected
|
||||
value = if @options.id then @options.id(data) else data.id
|
||||
|
@ -443,6 +456,17 @@ class GitLabDropdown
|
|||
$(@el).find(".dropdown-toggle-text").text @options.toggleLabel
|
||||
else
|
||||
selectedObject
|
||||
else if el.hasClass(INDETERMINATE_CLASS)
|
||||
el.addClass ACTIVE_CLASS
|
||||
el.removeClass INDETERMINATE_CLASS
|
||||
|
||||
if not value?
|
||||
field.remove()
|
||||
|
||||
if not field.length and fieldName
|
||||
@addInput(fieldName, value)
|
||||
|
||||
return selectedObject
|
||||
else
|
||||
if not @options.multiSelect or el.hasClass('dropdown-clear-active')
|
||||
@dropdown.find(".#{ACTIVE_CLASS}").removeClass ACTIVE_CLASS
|
||||
|
@ -459,31 +483,42 @@ class GitLabDropdown
|
|||
$(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selectedObject, el)
|
||||
if value?
|
||||
if !field.length and fieldName
|
||||
# Create hidden input for form
|
||||
input = "<input type='hidden' name='#{fieldName}' value='#{value}' />"
|
||||
if @options.inputId?
|
||||
input = $(input)
|
||||
.attr('id', @options.inputId)
|
||||
@dropdown.before input
|
||||
@addInput(fieldName, value)
|
||||
else
|
||||
field.val value
|
||||
|
||||
return selectedObject
|
||||
|
||||
selectRowAtIndex: (index) ->
|
||||
selector = ".dropdown-content li:not(.divider):eq(#{index}) a"
|
||||
addInput: (fieldName, value)->
|
||||
# Create hidden input for form
|
||||
$input = $('<input>').attr('type', 'hidden')
|
||||
.attr('name', fieldName)
|
||||
.val(value)
|
||||
|
||||
if @options.inputId?
|
||||
$input.attr('id', @options.inputId)
|
||||
|
||||
@dropdown.before $input
|
||||
|
||||
selectRowAtIndex: (e, index) ->
|
||||
selector = ".dropdown-content li:not(.divider,.dropdown-header,.separator):eq(#{index}) a"
|
||||
|
||||
if @dropdown.find(".dropdown-toggle-page").length
|
||||
selector = ".dropdown-page-one #{selector}"
|
||||
|
||||
# simulate a click on the first link
|
||||
$(selector, @dropdown).trigger "click"
|
||||
$el = $(selector, @dropdown)
|
||||
|
||||
if $el.length
|
||||
e.preventDefault()
|
||||
e.stopImmediatePropagation()
|
||||
$(selector, @dropdown)[0].click()
|
||||
|
||||
addArrowKeyEvent: ->
|
||||
ARROW_KEY_CODES = [38, 40]
|
||||
$input = @dropdown.find(".dropdown-input-field")
|
||||
|
||||
selector = '.dropdown-content li:not(.divider)'
|
||||
selector = '.dropdown-content li:not(.divider,.dropdown-header,.separator)'
|
||||
if @dropdown.find(".dropdown-toggle-page").length
|
||||
selector = ".dropdown-page-one #{selector}"
|
||||
|
||||
|
@ -511,8 +546,8 @@ class GitLabDropdown
|
|||
|
||||
return false
|
||||
|
||||
if currentKeyCode is 13
|
||||
@selectRowAtIndex if currentIndex < 0 then 0 else currentIndex
|
||||
if currentKeyCode is 13 and currentIndex isnt -1
|
||||
@selectRowAtIndex e, currentIndex
|
||||
|
||||
removeArrayKeyEvent: ->
|
||||
$('body').off 'keydown'
|
||||
|
|
|
@ -0,0 +1,109 @@
|
|||
class @IssuableBulkActions
|
||||
constructor: (opts = {}) ->
|
||||
# Set defaults
|
||||
{
|
||||
@container = $('.content')
|
||||
@form = @getElement('.bulk-update')
|
||||
@issues = @getElement('.issues-list .issue')
|
||||
} = opts
|
||||
|
||||
@bindEvents()
|
||||
|
||||
getElement: (selector) ->
|
||||
@container.find selector
|
||||
|
||||
bindEvents: ->
|
||||
@form.off('submit').on('submit', @onFormSubmit.bind(@))
|
||||
|
||||
onFormSubmit: (e) ->
|
||||
e.preventDefault()
|
||||
@submit()
|
||||
|
||||
submit: ->
|
||||
_this = @
|
||||
|
||||
xhr = $.ajax
|
||||
url: @form.attr 'action'
|
||||
method: @form.attr 'method'
|
||||
dataType: 'JSON',
|
||||
data: @getFormDataAsObject()
|
||||
|
||||
xhr.done (response, status, xhr) ->
|
||||
location.reload()
|
||||
|
||||
xhr.fail ->
|
||||
new Flash("Issue update failed")
|
||||
|
||||
xhr.always @onFormSubmitAlways.bind(@)
|
||||
|
||||
onFormSubmitAlways: ->
|
||||
@form.find('[type="submit"]').enable()
|
||||
|
||||
getSelectedIssues: ->
|
||||
@issues.has('.selected_issue:checked')
|
||||
|
||||
getLabelsFromSelection: ->
|
||||
labels = []
|
||||
|
||||
@getSelectedIssues().map ->
|
||||
_labels = $(@).data('labels')
|
||||
if _labels
|
||||
_labels.map (labelId) ->
|
||||
labels.push(labelId) if labels.indexOf(labelId) is -1
|
||||
|
||||
labels
|
||||
|
||||
###*
|
||||
* Will return only labels that were marked previously and the user has unmarked
|
||||
* @return {Array} Label IDs
|
||||
###
|
||||
getUnmarkedIndeterminedLabels: ->
|
||||
result = []
|
||||
labelsToKeep = []
|
||||
|
||||
for el in @getElement('.labels-filter .is-indeterminate')
|
||||
labelsToKeep.push $(el).data('labelId')
|
||||
|
||||
for id in @getLabelsFromSelection()
|
||||
# Only the ones that we are not going to keep
|
||||
result.push(id) if labelsToKeep.indexOf(id) is -1
|
||||
|
||||
result
|
||||
|
||||
###*
|
||||
* Simple form serialization, it will return just what we need
|
||||
* Returns key/value pairs from form data
|
||||
###
|
||||
getFormDataAsObject: ->
|
||||
formData =
|
||||
update:
|
||||
state_event : @form.find('input[name="update[state_event]"]').val()
|
||||
assignee_id : @form.find('input[name="update[assignee_id]"]').val()
|
||||
milestone_id : @form.find('input[name="update[milestone_id]"]').val()
|
||||
issues_ids : @form.find('input[name="update[issues_ids]"]').val()
|
||||
add_label_ids : []
|
||||
remove_label_ids : []
|
||||
|
||||
@getLabelsToApply().map (id) ->
|
||||
formData.update.add_label_ids.push id
|
||||
|
||||
@getLabelsToRemove().map (id) ->
|
||||
formData.update.remove_label_ids.push id
|
||||
|
||||
formData
|
||||
|
||||
getLabelsToApply: ->
|
||||
labelIds = []
|
||||
$labels = @form.find('.labels-filter input[name="update[label_ids][]"]')
|
||||
|
||||
$labels.each (k, label) ->
|
||||
labelIds.push $(label).val() if label
|
||||
|
||||
labelIds
|
||||
|
||||
###*
|
||||
* Just an alias of @getUnmarkedIndeterminedLabels
|
||||
* @return {Array} Array of labels
|
||||
###
|
||||
getLabelsToRemove: ->
|
||||
@getUnmarkedIndeterminedLabels()
|
|
@ -1,5 +1,7 @@
|
|||
class @LabelsSelect
|
||||
constructor: ->
|
||||
_this = @
|
||||
|
||||
$('.js-label-select').each (i, dropdown) ->
|
||||
$dropdown = $(dropdown)
|
||||
projectId = $dropdown.data('project-id')
|
||||
|
@ -196,10 +198,18 @@ class @LabelsSelect
|
|||
|
||||
callback data
|
||||
|
||||
renderRow: (label) ->
|
||||
removesAll = label.id is 0 or not label.id?
|
||||
renderRow: (label, instance) ->
|
||||
$li = $('<li>')
|
||||
$a = $('<a href="#">')
|
||||
|
||||
selectedClass = []
|
||||
removesAll = label.id is 0 or not label.id?
|
||||
|
||||
if $dropdown.hasClass('js-filter-bulk-update')
|
||||
indeterminate = instance.indeterminateIds
|
||||
if indeterminate.indexOf(label.id) isnt -1
|
||||
selectedClass.push 'is-indeterminate'
|
||||
|
||||
if $form.find("input[type='hidden']\
|
||||
[name='#{$dropdown.data('fieldName')}']\
|
||||
[value='#{this.id(label)}']").length
|
||||
|
@ -230,13 +240,17 @@ class @LabelsSelect
|
|||
else
|
||||
colorEl = ''
|
||||
|
||||
"<li>
|
||||
<a href='#' class='#{selectedClass.join(' ')}'>
|
||||
#{colorEl}
|
||||
#{_.escape(label.title)}
|
||||
</a>
|
||||
</li>"
|
||||
filterable: true
|
||||
# We need to identify which items are actually labels
|
||||
if label.id
|
||||
selectedClass.push('label-item')
|
||||
$a.attr('data-label-id', label.id)
|
||||
|
||||
$a.addClass(selectedClass.join(' '))
|
||||
.html("#{colorEl} #{_.escape(label.title)}")
|
||||
|
||||
# Return generated html
|
||||
$li.html($a).prop('outerHTML')
|
||||
persistWhenHide: $dropdown.data('persistWhenHide')
|
||||
search:
|
||||
fields: ['title']
|
||||
selectable: true
|
||||
|
@ -280,10 +294,19 @@ class @LabelsSelect
|
|||
else if $dropdown.hasClass('js-filter-submit')
|
||||
$dropdown.closest('form').submit()
|
||||
else
|
||||
saveLabelData()
|
||||
if not $dropdown.hasClass 'js-filter-bulk-update'
|
||||
saveLabelData()
|
||||
|
||||
if $dropdown.hasClass('js-filter-bulk-update')
|
||||
# If we are persisting state we need the classes
|
||||
if not @options.persistWhenHide
|
||||
$dropdown.parent().find('.is-active, .is-indeterminate').removeClass()
|
||||
|
||||
multiSelect: $dropdown.hasClass 'js-multiselect'
|
||||
clicked: (label) ->
|
||||
if $dropdown.hasClass('js-filter-bulk-update')
|
||||
return
|
||||
|
||||
page = $('body').data 'page'
|
||||
isIssueIndex = page is 'projects:issues:index'
|
||||
isMRIndex = page is 'projects:merge_requests:index'
|
||||
|
@ -298,4 +321,31 @@ class @LabelsSelect
|
|||
return
|
||||
else
|
||||
saveLabelData()
|
||||
|
||||
setIndeterminateIds: ->
|
||||
if @dropdown.find('.dropdown-menu-toggle').hasClass('js-filter-bulk-update')
|
||||
@indeterminateIds = _this.getIndeterminateIds()
|
||||
)
|
||||
|
||||
@bindEvents()
|
||||
|
||||
bindEvents: ->
|
||||
$('body').on 'change', '.selected_issue', @onSelectCheckboxIssue
|
||||
|
||||
onSelectCheckboxIssue: ->
|
||||
return if $('.selected_issue:checked').length
|
||||
|
||||
# Remove inputs
|
||||
$('.issues_bulk_update .labels-filter input[type="hidden"]').remove()
|
||||
|
||||
# Also restore button text
|
||||
$('.issues_bulk_update .labels-filter .dropdown-toggle-text').text('Label')
|
||||
|
||||
getIndeterminateIds: ->
|
||||
label_ids = []
|
||||
|
||||
$('.selected_issue:checked').each (i, el) ->
|
||||
issue_id = $(el).data('id')
|
||||
label_ids.push $("#issue_#{issue_id}").data('labels')
|
||||
|
||||
_.flatten(label_ids)
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
window.emojiAliases = ->
|
||||
JSON.parse('<%= Gitlab::AwardEmoji.aliases.to_json %>')
|
|
@ -167,7 +167,7 @@ class @Notes
|
|||
return
|
||||
|
||||
if note.award
|
||||
awardsHandler.addAwardToEmojiBar(note.note)
|
||||
awardsHandler.addAwardToEmojiBar(note.name)
|
||||
awardsHandler.scrollToAwards()
|
||||
|
||||
# render note if it not present in loaded list
|
||||
|
|
|
@ -156,11 +156,14 @@ class @SearchAutocomplete
|
|||
# No need to enable anything if user is not logged in
|
||||
return if !gon.current_user_id
|
||||
|
||||
_this = @
|
||||
@loadingSuggestions = false
|
||||
unless @dropdown.hasClass('open')
|
||||
_this = @
|
||||
@loadingSuggestions = false
|
||||
|
||||
@dropdown.addClass('open')
|
||||
@searchInput.removeClass('disabled')
|
||||
@dropdown
|
||||
.addClass('open')
|
||||
.trigger('shown.bs.dropdown')
|
||||
@searchInput.removeClass('disabled')
|
||||
|
||||
onSearchInputKeyDown: =>
|
||||
# Saves last length of the entered text
|
||||
|
@ -191,7 +194,7 @@ class @SearchAutocomplete
|
|||
@disableAutocomplete()
|
||||
else
|
||||
# We should display the menu only when input is not empty
|
||||
@enableAutocomplete()
|
||||
@enableAutocomplete() if e.keyCode isnt KEYCODE.ENTER
|
||||
|
||||
@wrap.toggleClass 'has-value', !!e.target.value
|
||||
|
||||
|
|
|
@ -232,9 +232,8 @@
|
|||
a {
|
||||
padding-left: 25px;
|
||||
|
||||
&.is-active {
|
||||
&.is-indeterminate, &.is-active {
|
||||
&::before {
|
||||
content: "\f00c";
|
||||
position: absolute;
|
||||
left: 5px;
|
||||
top: 50%;
|
||||
|
@ -246,6 +245,14 @@
|
|||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-indeterminate::before {
|
||||
content: "\f068";
|
||||
}
|
||||
|
||||
&.is-active::before {
|
||||
content: "\f00c";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,18 +2,10 @@
|
|||
* Generic mixins
|
||||
*/
|
||||
@mixin box-shadow($shadow) {
|
||||
-webkit-box-shadow: $shadow;
|
||||
-moz-box-shadow: $shadow;
|
||||
-ms-box-shadow: $shadow;
|
||||
-o-box-shadow: $shadow;
|
||||
box-shadow: $shadow;
|
||||
}
|
||||
|
||||
@mixin border-radius($radius) {
|
||||
-webkit-border-radius: $radius;
|
||||
-moz-border-radius: $radius;
|
||||
-ms-border-radius: $radius;
|
||||
-o-border-radius: $radius;
|
||||
border-radius: $radius;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
.awards {
|
||||
line-height: 34px;
|
||||
|
||||
.emoji-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
|
@ -9,8 +7,6 @@
|
|||
|
||||
.emoji-menu {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
margin-top: 3px;
|
||||
z-index: 1000;
|
||||
min-width: 160px;
|
||||
|
@ -23,7 +19,12 @@
|
|||
opacity: 0;
|
||||
transform: scale(.2);
|
||||
transform-origin: 0 -45px;
|
||||
transition: all .3s cubic-bezier(.87,-.41,.19,1.44);
|
||||
transition: .3s cubic-bezier(.87,-.41,.19,1.44);
|
||||
transition-property: transform, opacity;
|
||||
|
||||
&.is-aligned-right {
|
||||
transform-origin: 100% -45px;
|
||||
}
|
||||
|
||||
&.is-visible {
|
||||
pointer-events: all;
|
||||
|
@ -107,7 +108,7 @@
|
|||
}
|
||||
|
||||
&.is-loading {
|
||||
.award-control-icon {
|
||||
.award-control-icon-normal {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,12 +3,7 @@
|
|||
background: #111;
|
||||
color: #fff;
|
||||
font-family: $monospace_font;
|
||||
white-space: pre;
|
||||
white-space: pre-wrap; /* css-3 */
|
||||
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
|
||||
white-space: -pre-wrap; /* Opera 4-6 */
|
||||
white-space: -o-pre-wrap; /* Opera 7 */
|
||||
word-wrap: break-word; /* Internet Explorer 5.5+ */
|
||||
white-space: pre-wrap;
|
||||
overflow: auto;
|
||||
overflow-y: hidden;
|
||||
font-size: 12px;
|
||||
|
|
|
@ -158,13 +158,11 @@
|
|||
.search-holder {
|
||||
@media (min-width: $screen-sm-min) {
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.search-field-holder {
|
||||
-webkit-flex: 1 0 auto;
|
||||
-ms-flex: 1 0 auto;
|
||||
flex: 1 0 auto;
|
||||
position: relative;
|
||||
margin-right: 0;
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
module ToggleAwardEmoji
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
before_action :authenticate_user!, only: [:toggle_award_emoji]
|
||||
end
|
||||
|
||||
def toggle_award_emoji
|
||||
name = params.require(:name)
|
||||
|
||||
awardable.toggle_award_emoji(name, current_user)
|
||||
TodoService.new.new_award_emoji(awardable, current_user)
|
||||
|
||||
render json: { ok: true }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def awardable
|
||||
raise NotImplementedError
|
||||
end
|
||||
end
|
|
@ -1,6 +1,7 @@
|
|||
class Projects::IssuesController < Projects::ApplicationController
|
||||
include ToggleSubscriptionAction
|
||||
include IssuableActions
|
||||
include ToggleAwardEmoji
|
||||
|
||||
before_action :module_enabled
|
||||
before_action :issue, only: [:edit, :update, :show, :referenced_merge_requests,
|
||||
|
@ -62,7 +63,7 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
|
||||
def show
|
||||
@note = @project.notes.new(noteable: @issue)
|
||||
@notes = @issue.notes.nonawards.with_associations.fresh
|
||||
@notes = @issue.notes.with_associations.fresh
|
||||
@noteable = @issue
|
||||
|
||||
respond_to do |format|
|
||||
|
@ -155,7 +156,12 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
|
||||
def bulk_update
|
||||
result = Issues::BulkUpdateService.new(project, current_user, bulk_update_params).execute
|
||||
redirect_back_or_default(default: { action: 'index' }, options: { notice: "#{result[:count]} issues updated" })
|
||||
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: { notice: "#{result[:count]} issues updated" }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
@ -169,6 +175,7 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
end
|
||||
alias_method :subscribable_resource, :issue
|
||||
alias_method :issuable, :issue
|
||||
alias_method :awardable, :issue
|
||||
|
||||
def authorize_read_issue!
|
||||
return render_404 unless can?(current_user, :read_issue, @issue)
|
||||
|
@ -214,7 +221,10 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
:issues_ids,
|
||||
:assignee_id,
|
||||
:milestone_id,
|
||||
:state_event
|
||||
:state_event,
|
||||
label_ids: [],
|
||||
add_label_ids: [],
|
||||
remove_label_ids: []
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,6 +2,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
|||
include ToggleSubscriptionAction
|
||||
include DiffHelper
|
||||
include IssuableActions
|
||||
include ToggleAwardEmoji
|
||||
|
||||
before_action :module_enabled
|
||||
before_action :merge_request, only: [
|
||||
|
@ -201,7 +202,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
|||
|
||||
if params[:merge_when_build_succeeds].present? && @merge_request.pipeline && @merge_request.pipeline.active?
|
||||
MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user, merge_params)
|
||||
.execute(@merge_request)
|
||||
.execute(@merge_request)
|
||||
@status = :merge_when_build_succeeds
|
||||
else
|
||||
MergeWorker.perform_async(@merge_request.id, current_user.id, params)
|
||||
|
@ -270,6 +271,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
|||
end
|
||||
alias_method :subscribable_resource, :merge_request
|
||||
alias_method :issuable, :merge_request
|
||||
alias_method :awardable, :merge_request
|
||||
|
||||
def closes_issues
|
||||
@closes_issues ||= @merge_request.closes_issues
|
||||
|
@ -305,7 +307,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
|||
def define_show_vars
|
||||
# Build a note object for comment form
|
||||
@note = @project.notes.new(noteable: @merge_request)
|
||||
@notes = @merge_request.mr_and_commit_notes.nonawards.inc_author.fresh
|
||||
@notes = @merge_request.mr_and_commit_notes.inc_author.fresh
|
||||
@discussions = @notes.discussions
|
||||
@noteable = @merge_request
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ class Projects::NotesController < Projects::ApplicationController
|
|||
before_action :authorize_read_note!
|
||||
before_action :authorize_create_note!, only: [:create]
|
||||
before_action :authorize_admin_note!, only: [:update, :destroy]
|
||||
before_action :find_current_user_notes, except: [:destroy, :delete_attachment, :award_toggle]
|
||||
before_action :find_current_user_notes, only: [:index]
|
||||
|
||||
def index
|
||||
current_fetched_at = Time.now.to_i
|
||||
|
@ -56,30 +56,6 @@ class Projects::NotesController < Projects::ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def award_toggle
|
||||
noteable = if note_params[:noteable_type] == "issue"
|
||||
project.issues.find(note_params[:noteable_id])
|
||||
else
|
||||
project.merge_requests.find(note_params[:noteable_id])
|
||||
end
|
||||
|
||||
data = {
|
||||
author: current_user,
|
||||
is_award: true,
|
||||
note: note_params[:note].delete(":")
|
||||
}
|
||||
|
||||
note = noteable.notes.find_by(data)
|
||||
|
||||
if note
|
||||
note.destroy
|
||||
else
|
||||
Notes::CreateService.new(project, current_user, note_params).execute
|
||||
end
|
||||
|
||||
render json: { ok: true }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def note
|
||||
|
@ -131,13 +107,20 @@ class Projects::NotesController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
def note_json(note)
|
||||
if note.valid?
|
||||
if note.is_a?(AwardEmoji)
|
||||
{
|
||||
valid: note.valid?,
|
||||
award: true,
|
||||
id: note.id,
|
||||
name: note.name
|
||||
}
|
||||
elsif note.valid?
|
||||
{
|
||||
valid: true,
|
||||
id: note.id,
|
||||
discussion_id: note.discussion_id,
|
||||
html: note_to_html(note),
|
||||
award: note.is_award,
|
||||
award: false,
|
||||
note: note.note,
|
||||
discussion_html: note_to_discussion_html(note),
|
||||
discussion_with_diff_html: note_to_discussion_with_diff_html(note)
|
||||
|
@ -145,7 +128,7 @@ class Projects::NotesController < Projects::ApplicationController
|
|||
else
|
||||
{
|
||||
valid: false,
|
||||
award: note.is_award,
|
||||
award: false,
|
||||
errors: note.errors
|
||||
}
|
||||
end
|
||||
|
|
|
@ -139,7 +139,7 @@ class ProjectsController < Projects::ApplicationController
|
|||
participants = ::Projects::ParticipantsService.new(@project, current_user).execute(note_type, note_id)
|
||||
|
||||
@suggestions = {
|
||||
emojis: AwardEmoji.urls,
|
||||
emojis: Gitlab::AwardEmoji.urls,
|
||||
issues: autocomplete.issues,
|
||||
milestones: autocomplete.milestones,
|
||||
mergerequests: autocomplete.merge_requests,
|
||||
|
|
|
@ -12,9 +12,9 @@ class NotesFinder
|
|||
when "commit"
|
||||
project.notes.for_commit_id(target_id).non_diff_notes
|
||||
when "issue"
|
||||
project.issues.find(target_id).notes.nonawards.inc_author
|
||||
project.issues.find(target_id).notes.inc_author
|
||||
when "merge_request"
|
||||
project.merge_requests.find(target_id).mr_and_commit_notes.nonawards.inc_author
|
||||
project.merge_requests.find(target_id).mr_and_commit_notes.inc_author
|
||||
when "snippet", "project_snippet"
|
||||
project.snippets.find(target_id).notes
|
||||
else
|
||||
|
|
|
@ -96,5 +96,4 @@ module IssuablesHelper
|
|||
issuable.open? ? :opened : :closed
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -145,16 +145,14 @@ module IssuesHelper
|
|||
end
|
||||
end
|
||||
|
||||
def emoji_author_list(notes, current_user)
|
||||
list = notes.map do |note|
|
||||
note.author == current_user ? "me" : note.author.name
|
||||
end
|
||||
|
||||
list.join(", ")
|
||||
def award_user_list(awards, current_user)
|
||||
awards.map do |award|
|
||||
award.user == current_user ? 'me' : award.user.name
|
||||
end.join(', ')
|
||||
end
|
||||
|
||||
def note_active_class(notes, current_user)
|
||||
if current_user && notes.pluck(:author_id).include?(current_user.id)
|
||||
def award_active_class(awards, current_user)
|
||||
if current_user && awards.find { |a| a.user_id == current_user.id }
|
||||
"active"
|
||||
else
|
||||
""
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
class AwardEmoji < ActiveRecord::Base
|
||||
DOWNVOTE_NAME = "thumbsdown".freeze
|
||||
UPVOTE_NAME = "thumbsup".freeze
|
||||
|
||||
include Participable
|
||||
|
||||
belongs_to :awardable, polymorphic: true
|
||||
belongs_to :user
|
||||
|
||||
validates :awardable, :user, presence: true
|
||||
validates :name, presence: true, inclusion: { in: Emoji.emojis_names }
|
||||
validates :name, uniqueness: { scope: [:user, :awardable_type, :awardable_id] }
|
||||
|
||||
participant :user
|
||||
|
||||
scope :downvotes, -> { where(name: DOWNVOTE_NAME) }
|
||||
scope :upvotes, -> { where(name: UPVOTE_NAME) }
|
||||
|
||||
def downvote?
|
||||
self.name == DOWNVOTE_NAME
|
||||
end
|
||||
|
||||
def upvote?
|
||||
self.name == UPVOTE_NAME
|
||||
end
|
||||
end
|
|
@ -0,0 +1,81 @@
|
|||
module Awardable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
has_many :award_emoji, as: :awardable, dependent: :destroy
|
||||
|
||||
if self < Participable
|
||||
participant :award_emoji
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
def order_upvotes_desc
|
||||
order_votes_desc(AwardEmoji::UPVOTE_NAME)
|
||||
end
|
||||
|
||||
def order_downvotes_desc
|
||||
order_votes_desc(AwardEmoji::DOWNVOTE_NAME)
|
||||
end
|
||||
|
||||
def order_votes_desc(emoji_name)
|
||||
awardable_table = self.arel_table
|
||||
awards_table = AwardEmoji.arel_table
|
||||
|
||||
join_clause = awardable_table.join(awards_table, Arel::Nodes::OuterJoin).on(
|
||||
awards_table[:awardable_id].eq(awardable_table[:id]).and(
|
||||
awards_table[:awardable_type].eq(self.name).and(
|
||||
awards_table[:name].eq(emoji_name)
|
||||
)
|
||||
)
|
||||
).join_sources
|
||||
|
||||
joins(join_clause).group(awardable_table[:id]).reorder("COUNT(award_emoji.id) DESC")
|
||||
end
|
||||
end
|
||||
|
||||
def grouped_awards(with_thumbs: true)
|
||||
awards = award_emoji.group_by(&:name)
|
||||
|
||||
if with_thumbs
|
||||
awards[AwardEmoji::UPVOTE_NAME] ||= []
|
||||
awards[AwardEmoji::DOWNVOTE_NAME] ||= []
|
||||
end
|
||||
|
||||
awards
|
||||
end
|
||||
|
||||
def downvotes
|
||||
award_emoji.downvotes.count
|
||||
end
|
||||
|
||||
def upvotes
|
||||
award_emoji.upvotes.count
|
||||
end
|
||||
|
||||
def emoji_awardable?
|
||||
true
|
||||
end
|
||||
|
||||
def awarded_emoji?(emoji_name, current_user)
|
||||
award_emoji.where(name: emoji_name, user: current_user).exists?
|
||||
end
|
||||
|
||||
def create_award_emoji(name, current_user)
|
||||
return unless emoji_awardable?
|
||||
|
||||
award_emoji.create(name: name, user: current_user)
|
||||
end
|
||||
|
||||
def remove_award_emoji(name, current_user)
|
||||
award_emoji.where(name: name, user: current_user).destroy_all
|
||||
end
|
||||
|
||||
def toggle_award_emoji(emoji_name, current_user)
|
||||
if awarded_emoji?(emoji_name, current_user)
|
||||
remove_award_emoji(emoji_name, current_user)
|
||||
else
|
||||
create_award_emoji(emoji_name, current_user)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -10,6 +10,7 @@ module Issuable
|
|||
include Mentionable
|
||||
include Subscribable
|
||||
include StripAttribute
|
||||
include Awardable
|
||||
|
||||
included do
|
||||
belongs_to :author, class_name: "User"
|
||||
|
@ -115,29 +116,6 @@ module Issuable
|
|||
end
|
||||
end
|
||||
|
||||
def order_downvotes_desc
|
||||
order_votes_desc('thumbsdown')
|
||||
end
|
||||
|
||||
def order_upvotes_desc
|
||||
order_votes_desc('thumbsup')
|
||||
end
|
||||
|
||||
def order_votes_desc(award_emoji_name)
|
||||
issuable_table = self.arel_table
|
||||
note_table = Note.arel_table
|
||||
|
||||
join_clause = issuable_table.join(note_table, Arel::Nodes::OuterJoin).on(
|
||||
note_table[:noteable_id].eq(issuable_table[:id]).and(
|
||||
note_table[:noteable_type].eq(self.name).and(
|
||||
note_table[:is_award].eq(true).and(note_table[:note].eq(award_emoji_name))
|
||||
)
|
||||
)
|
||||
).join_sources
|
||||
|
||||
joins(join_clause).group(issuable_table[:id]).reorder("COUNT(notes.id) DESC")
|
||||
end
|
||||
|
||||
def with_label(title, sort = nil)
|
||||
if title.is_a?(Array) && title.size > 1
|
||||
joins(:labels).where(labels: { title: title }).group(*grouping_columns(sort)).having("COUNT(DISTINCT labels.title) = #{title.size}")
|
||||
|
@ -179,14 +157,6 @@ module Issuable
|
|||
opened? || reopened?
|
||||
end
|
||||
|
||||
def downvotes
|
||||
notes.awards.where(note: "thumbsdown").count
|
||||
end
|
||||
|
||||
def upvotes
|
||||
notes.awards.where(note: "thumbsup").count
|
||||
end
|
||||
|
||||
def user_notes_count
|
||||
notes.user.count
|
||||
end
|
||||
|
|
|
@ -110,6 +110,10 @@ class LegacyDiffNote < Note
|
|||
@active
|
||||
end
|
||||
|
||||
def award_emoji_supported?
|
||||
false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_diff
|
||||
|
|
|
@ -21,11 +21,8 @@ class Note < ActiveRecord::Base
|
|||
delegate :name, :email, to: :author, prefix: true
|
||||
delegate :title, to: :noteable, allow_nil: true
|
||||
|
||||
before_validation :set_award!
|
||||
|
||||
validates :note, :project, presence: true
|
||||
validates :note, uniqueness: { scope: [:author, :noteable_type, :noteable_id] }, if: ->(n) { n.is_award }
|
||||
validates :note, inclusion: { in: Emoji.emojis_names }, if: ->(n) { n.is_award }
|
||||
|
||||
# Attachments are deprecated and are handled by Markdown uploader
|
||||
validates :attachment, file_size: { maximum: :max_attachment_size }
|
||||
|
||||
|
@ -43,8 +40,6 @@ class Note < ActiveRecord::Base
|
|||
mount_uploader :attachment, AttachmentUploader
|
||||
|
||||
# Scopes
|
||||
scope :awards, ->{ where(is_award: true) }
|
||||
scope :nonawards, ->{ where(is_award: false) }
|
||||
scope :for_commit_id, ->(commit_id) { where(noteable_type: "Commit", commit_id: commit_id) }
|
||||
scope :system, ->{ where(system: true) }
|
||||
scope :user, ->{ where(system: false) }
|
||||
|
@ -109,19 +104,6 @@ class Note < ActiveRecord::Base
|
|||
found_notes.where('issues.confidential IS NULL OR issues.confidential IS FALSE')
|
||||
end
|
||||
end
|
||||
|
||||
def grouped_awards
|
||||
notes = {}
|
||||
|
||||
awards.select(:note).distinct.map do |note|
|
||||
notes[note.note] = where(note: note.note)
|
||||
end
|
||||
|
||||
notes["thumbsup"] ||= Note.none
|
||||
notes["thumbsdown"] ||= Note.none
|
||||
|
||||
notes
|
||||
end
|
||||
end
|
||||
|
||||
def cross_reference?
|
||||
|
@ -205,44 +187,24 @@ class Note < ActiveRecord::Base
|
|||
Event.reset_event_cache_for(self)
|
||||
end
|
||||
|
||||
def downvote?
|
||||
is_award && note == "thumbsdown"
|
||||
end
|
||||
|
||||
def upvote?
|
||||
is_award && note == "thumbsup"
|
||||
end
|
||||
|
||||
def editable?
|
||||
!system? && !is_award
|
||||
!system?
|
||||
end
|
||||
|
||||
def cross_reference_not_visible_for?(user)
|
||||
cross_reference? && referenced_mentionables(user).empty?
|
||||
end
|
||||
|
||||
# Checks if note is an award added as a comment
|
||||
#
|
||||
# If note is an award, this method sets is_award to true
|
||||
# and changes content of the note to award name.
|
||||
#
|
||||
# Method is executed as a before_validation callback.
|
||||
#
|
||||
def set_award!
|
||||
return unless awards_supported? && contains_emoji_only?
|
||||
|
||||
self.is_award = true
|
||||
self.note = award_emoji_name
|
||||
def award_emoji?
|
||||
award_emoji_supported? && contains_emoji_only?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def clear_blank_line_code!
|
||||
self.line_code = nil if self.line_code.blank?
|
||||
end
|
||||
|
||||
def awards_supported?
|
||||
(for_issue? || for_merge_request?) && !diff_note?
|
||||
def award_emoji_supported?
|
||||
noteable.is_a?(Awardable)
|
||||
end
|
||||
|
||||
def contains_emoji_only?
|
||||
|
@ -251,6 +213,6 @@ class Note < ActiveRecord::Base
|
|||
|
||||
def award_emoji_name
|
||||
original_name = note.match(Banzai::Filter::EmojiFilter.emoji_pattern)[1]
|
||||
AwardEmoji.normilize_emoji_name(original_name)
|
||||
Gitlab::AwardEmoji.normalize_emoji_name(original_name)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -83,7 +83,7 @@ class IrkerService < Service
|
|||
self.channels = recipients.split(/\s+/).map do |recipient|
|
||||
format_channel(recipient)
|
||||
end
|
||||
channels.reject! &:nil?
|
||||
channels.reject!(&:nil?)
|
||||
end
|
||||
|
||||
def format_channel(recipient)
|
||||
|
|
|
@ -84,6 +84,7 @@ class User < ActiveRecord::Base
|
|||
has_many :builds, dependent: :nullify, class_name: 'Ci::Build'
|
||||
has_many :todos, dependent: :destroy
|
||||
has_many :notification_settings, dependent: :destroy
|
||||
has_many :award_emoji, as: :awardable, dependent: :destroy
|
||||
|
||||
#
|
||||
# Validations
|
||||
|
|
|
@ -45,6 +45,8 @@ class IssuableBaseService < BaseService
|
|||
|
||||
unless can?(current_user, ability, project)
|
||||
params.delete(:milestone_id)
|
||||
params.delete(:add_label_ids)
|
||||
params.delete(:remove_label_ids)
|
||||
params.delete(:label_ids)
|
||||
params.delete(:assignee_id)
|
||||
end
|
||||
|
@ -67,10 +69,34 @@ class IssuableBaseService < BaseService
|
|||
end
|
||||
|
||||
def filter_labels
|
||||
return if params[:label_ids].to_a.empty?
|
||||
if params[:add_label_ids].present? || params[:remove_label_ids].present?
|
||||
params.delete(:label_ids)
|
||||
|
||||
params[:label_ids] =
|
||||
project.labels.where(id: params[:label_ids]).pluck(:id)
|
||||
filter_labels_in_param(:add_label_ids)
|
||||
filter_labels_in_param(:remove_label_ids)
|
||||
else
|
||||
filter_labels_in_param(:label_ids)
|
||||
end
|
||||
end
|
||||
|
||||
def filter_labels_in_param(key)
|
||||
return if params[key].to_a.empty?
|
||||
|
||||
params[key] = project.labels.where(id: params[key]).pluck(:id)
|
||||
end
|
||||
|
||||
def update_issuable(issuable, attributes)
|
||||
issuable.with_transaction_returning_status do
|
||||
add_label_ids = attributes.delete(:add_label_ids)
|
||||
remove_label_ids = attributes.delete(:remove_label_ids)
|
||||
|
||||
issuable.label_ids |= add_label_ids if add_label_ids
|
||||
issuable.label_ids -= remove_label_ids if remove_label_ids
|
||||
|
||||
issuable.assign_attributes(attributes.merge(updated_by: current_user))
|
||||
|
||||
issuable.save
|
||||
end
|
||||
end
|
||||
|
||||
def update(issuable)
|
||||
|
@ -78,7 +104,7 @@ class IssuableBaseService < BaseService
|
|||
filter_params
|
||||
old_labels = issuable.labels.to_a
|
||||
|
||||
if params.present? && issuable.update_attributes(params.merge(updated_by: current_user))
|
||||
if params.present? && update_issuable(issuable, params)
|
||||
issuable.reset_events_cache
|
||||
handle_common_system_notes(issuable, old_labels: old_labels)
|
||||
handle_changes(issuable, old_labels: old_labels)
|
||||
|
|
|
@ -4,9 +4,9 @@ module Issues
|
|||
issues_ids = params.delete(:issues_ids).split(",")
|
||||
issue_params = params
|
||||
|
||||
issue_params.delete(:state_event) unless issue_params[:state_event].present?
|
||||
issue_params.delete(:milestone_id) unless issue_params[:milestone_id].present?
|
||||
issue_params.delete(:assignee_id) unless issue_params[:assignee_id].present?
|
||||
%i(state_event milestone_id assignee_id add_label_ids remove_label_ids).each do |key|
|
||||
issue_params.delete(key) unless issue_params[key].present?
|
||||
end
|
||||
|
||||
issues = Issue.where(id: issues_ids)
|
||||
issues.each do |issue|
|
||||
|
|
|
@ -24,6 +24,7 @@ module Issues
|
|||
@new_issue = create_new_issue
|
||||
|
||||
rewrite_notes
|
||||
rewrite_award_emoji
|
||||
add_note_moved_from
|
||||
|
||||
# Old issue tasks
|
||||
|
@ -72,6 +73,14 @@ module Issues
|
|||
end
|
||||
end
|
||||
|
||||
def rewrite_award_emoji
|
||||
@old_issue.award_emoji.each do |award|
|
||||
new_award = award.dup
|
||||
new_award.awardable = @new_issue
|
||||
new_award.save
|
||||
end
|
||||
end
|
||||
|
||||
def rewrite_content(content)
|
||||
return unless content
|
||||
|
||||
|
|
|
@ -5,6 +5,13 @@ module Notes
|
|||
note.author = current_user
|
||||
note.system = false
|
||||
|
||||
if note.award_emoji?
|
||||
noteable = note.noteable
|
||||
todo_service.new_award_emoji(noteable, current_user)
|
||||
|
||||
return noteable.create_award_emoji(note.award_emoji_name, current_user)
|
||||
end
|
||||
|
||||
if note.save
|
||||
# Finish the harder work in the background
|
||||
NewNoteWorker.perform_in(2.seconds, note.id, params)
|
||||
|
|
|
@ -8,7 +8,7 @@ module Notes
|
|||
|
||||
def execute
|
||||
# Skip system notes, like status changes and cross-references and awards
|
||||
unless @note.system || @note.is_award
|
||||
unless @note.system?
|
||||
EventCreateService.new.leave_note(@note, @note.author)
|
||||
@note.create_cross_references!
|
||||
execute_note_hooks
|
||||
|
|
|
@ -130,8 +130,7 @@ class NotificationService
|
|||
|
||||
# ignore gitlab service messages
|
||||
return true if note.note.start_with?('Status changed to closed')
|
||||
return true if note.cross_reference? && note.system == true
|
||||
return true if note.is_award
|
||||
return true if note.cross_reference? && note.system?
|
||||
|
||||
target = note.noteable
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ module Oauth2::AccessTokenValidationService
|
|||
end
|
||||
|
||||
protected
|
||||
|
||||
# True if the token's scope is a superset of required scopes,
|
||||
# or the required scopes is empty.
|
||||
def sufficient_scope?(token, scopes)
|
||||
|
|
|
@ -122,6 +122,14 @@ class TodoService
|
|||
handle_note(note, current_user)
|
||||
end
|
||||
|
||||
# When an emoji is awarded we should:
|
||||
#
|
||||
# * mark all pending todos related to the awardable for the current user as done
|
||||
#
|
||||
def new_award_emoji(awardable, current_user)
|
||||
mark_pending_todos_as_done(awardable, current_user)
|
||||
end
|
||||
|
||||
# When marking pending todos as done we should:
|
||||
#
|
||||
# * mark all pending todos related to the target for the current user as done
|
||||
|
|
|
@ -99,8 +99,8 @@
|
|||
|
||||
%td.build-link
|
||||
- if project
|
||||
= link_to ci_status_path(build.commit) do
|
||||
%strong #{build.commit.short_sha}
|
||||
= link_to ci_status_path(build.pipeline) do
|
||||
%strong #{build.pipeline.short_sha}
|
||||
|
||||
%td.timestamp
|
||||
- if build.finished_at
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
- grouped_emojis = awardable.grouped_awards(with_thumbs: inline)
|
||||
.awards.js-awards-block{ class: ("hidden" if !inline && grouped_emojis.empty?), data: { award_url: url_for([:toggle_award_emoji, @project.namespace.becomes(Namespace), @project, awardable]) } }
|
||||
- awards_sort(grouped_emojis).each do |emoji, awards|
|
||||
%button.btn.award-control.js-emoji-btn.has-tooltip{ type: "button", class: (award_active_class(awards, current_user)), data: { placement: "bottom", title: award_user_list(awards, current_user) } }
|
||||
= emoji_icon(emoji, sprite: false)
|
||||
%span.award-control-text.js-counter
|
||||
= awards.count
|
||||
|
||||
- if current_user
|
||||
:javascript
|
||||
gl.awardMenuUrl = "#{emojis_path}"
|
||||
|
||||
.award-menu-holder.js-award-holder
|
||||
%button.btn.award-control.js-add-award{ type: "button", data: { award_menu_url: emojis_path } }
|
||||
= icon('smile-o', class: "award-control-icon award-control-icon-normal")
|
||||
= icon('spinner spin', class: "award-control-icon award-control-icon-loading")
|
||||
%span.award-control-text
|
||||
Add
|
|
@ -5,7 +5,7 @@
|
|||
.login-body
|
||||
= form_for(resource, as: resource_name, url: session_path(resource_name), method: :post) do |f|
|
||||
= f.hidden_field :remember_me, value: params[resource_name][:remember_me]
|
||||
= f.text_field :otp_attempt, class: 'form-control', placeholder: 'Two-factor Authentication code', required: true, autofocus: true
|
||||
= f.text_field :otp_attempt, class: 'form-control', placeholder: 'Two-factor Authentication code', required: true, autofocus: true, autocomplete: 'off'
|
||||
%p.help-block.hint Enter the code from the two-factor app on your mobile device. If you've lost your device, you may enter one of your recovery codes.
|
||||
.prepend-top-20
|
||||
= f.submit "Verify code", class: "btn btn-save"
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
.emoji-menu
|
||||
.emoji-menu-content
|
||||
= text_field_tag :emoji_search, "", class: "emoji-search search-input form-control"
|
||||
- AwardEmoji.emoji_by_category.each do |category, emojis|
|
||||
- Gitlab::AwardEmoji.emoji_by_category.each do |category, emojis|
|
||||
%h5.emoji-menu-title
|
||||
= AwardEmoji::CATEGORIES[category]
|
||||
= Gitlab::AwardEmoji::CATEGORIES[category]
|
||||
%ul.clearfix.emoji-menu-list
|
||||
- emojis.each do |emoji|
|
||||
%li.pull-left.text-center.emoji-menu-list-item
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
= link_to project_container_registry_path(@project), title: 'Container Registry', class: 'shortcuts-container-registry' do
|
||||
= icon('hdd-o fw')
|
||||
%span
|
||||
Container Registry
|
||||
Registry
|
||||
|
||||
- if project_nav_tab? :graphs
|
||||
= nav_link(controller: %w(graphs)) do
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
%p
|
||||
Commit: #{link_to @build.short_sha, namespace_project_commit_url(@build.project.namespace, @build.project, @build.sha)}
|
||||
%p
|
||||
Author: #{@build.commit.git_author_name}
|
||||
Author: #{@build.pipeline.git_author_name}
|
||||
%p
|
||||
Branch: #{@build.ref}
|
||||
%p
|
||||
|
@ -18,7 +18,7 @@
|
|||
%p
|
||||
Job: #{@build.name}
|
||||
%p
|
||||
Message: #{@build.commit.git_commit_message}
|
||||
Message: #{@build.pipeline.git_commit_message}
|
||||
|
||||
%p
|
||||
Build details: #{link_to "Build #{@build.id}", namespace_project_build_url(@build.project.namespace, @build.project, @build)}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
Build failed for <%= @project.name %>
|
||||
|
||||
Status: <%= @build.status %>
|
||||
Commit: <%= @build.commit.short_sha %>
|
||||
Author: <%= @build.commit.git_author_name %>
|
||||
Commit: <%= @build.pipeline.short_sha %>
|
||||
Author: <%= @build.pipeline.git_author_name %>
|
||||
Branch: <%= @build.ref %>
|
||||
Stage: <%= @build.stage %>
|
||||
Job: <%= @build.name %>
|
||||
Message: <%= @build.commit.git_commit_message %>
|
||||
Message: <%= @build.pipeline.git_commit_message %>
|
||||
|
||||
Url: <%= namespace_project_build_url(@build.project.namespace, @build.project, @build) %>
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
%p
|
||||
Commit: #{link_to @build.short_sha, namespace_project_commit_url(@build.project.namespace, @build.project, @build.sha)}
|
||||
%p
|
||||
Author: #{@build.commit.git_author_name}
|
||||
Author: #{@build.pipeline.git_author_name}
|
||||
%p
|
||||
Branch: #{@build.ref}
|
||||
%p
|
||||
|
@ -18,7 +18,7 @@
|
|||
%p
|
||||
Job: #{@build.name}
|
||||
%p
|
||||
Message: #{@build.commit.git_commit_message}
|
||||
Message: #{@build.pipeline.git_commit_message}
|
||||
|
||||
%p
|
||||
Build details: #{link_to "Build #{@build.id}", namespace_project_build_url(@build.project.namespace, @build.project, @build)}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
Build successful for <%= @project.name %>
|
||||
|
||||
Status: <%= @build.status %>
|
||||
Commit: <%= @build.commit.short_sha %>
|
||||
Author: <%= @build.commit.git_author_name %>
|
||||
Commit: <%= @build.pipeline.short_sha %>
|
||||
Author: <%= @build.pipeline.git_author_name %>
|
||||
Branch: <%= @build.ref %>
|
||||
Stage: <%= @build.stage %>
|
||||
Job: <%= @build.name %>
|
||||
Message: <%= @build.commit.git_commit_message %>
|
||||
Message: <%= @build.pipeline.git_commit_message %>
|
||||
|
||||
Url: <%= namespace_project_build_url(@build.project.namespace, @build.project, @build) %>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
.build-page
|
||||
.row-content-block.top-block
|
||||
Build ##{@build.id} for commit
|
||||
%strong.monospace= link_to @build.commit.short_sha, ci_status_path(@build.commit)
|
||||
%strong.monospace= link_to @build.pipeline.short_sha, ci_status_path(@build.pipeline)
|
||||
from
|
||||
= link_to @build.ref, namespace_project_commits_path(@project.namespace, @project, @build.ref)
|
||||
- merge_request = @build.merge_request
|
||||
|
@ -13,7 +13,7 @@
|
|||
= link_to "merge request #{merge_request.to_reference}", merge_request_path(merge_request)
|
||||
|
||||
#up-build-trace
|
||||
- builds = @build.commit.builds.latest.to_a
|
||||
- builds = @build.pipeline.builds.latest.to_a
|
||||
- if builds.size > 1
|
||||
%ul.nav-links.no-top.no-bottom
|
||||
- builds.each do |build|
|
||||
|
@ -178,16 +178,16 @@
|
|||
Commit
|
||||
.pull-right
|
||||
%small
|
||||
= link_to @build.commit.short_sha, ci_status_path(@build.commit), class: "monospace"
|
||||
= link_to @build.pipeline.short_sha, ci_status_path(@build.pipeline), class: "monospace"
|
||||
%p
|
||||
%span.attr-name Branch:
|
||||
= link_to @build.ref, namespace_project_commits_path(@project.namespace, @project, @build.ref)
|
||||
%p
|
||||
%span.attr-name Author:
|
||||
#{@build.commit.git_author_name}
|
||||
#{@build.pipeline.git_author_name}
|
||||
%p
|
||||
%span.attr-name Message:
|
||||
#{@build.commit.git_commit_message}
|
||||
#{@build.pipeline.git_commit_message}
|
||||
|
||||
- if @build.tags.any?
|
||||
.build-widget
|
||||
|
@ -201,7 +201,7 @@
|
|||
.build-widget
|
||||
%h4.title #{pluralize(@builds.count(:id), "other build")} for
|
||||
= succeed ":" do
|
||||
= link_to @build.commit.short_sha, ci_status_path(@build.commit), class: "monospace"
|
||||
= link_to @build.pipeline.short_sha, ci_status_path(@build.pipeline), class: "monospace"
|
||||
%table.table.builds
|
||||
- @builds.each_with_index do |build, i|
|
||||
%tr.build
|
||||
|
|
|
@ -1,55 +1,55 @@
|
|||
- status = commit.status
|
||||
- status = pipeline.status
|
||||
%tr.commit
|
||||
%td.commit-link
|
||||
= link_to namespace_project_pipeline_path(@project.namespace, @project, commit.id), class: "ci-status ci-#{status}" do
|
||||
= link_to namespace_project_pipeline_path(@project.namespace, @project, pipeline.id), class: "ci-status ci-#{status}" do
|
||||
= ci_icon_for_status(status)
|
||||
%strong ##{commit.id}
|
||||
%strong ##{pipeline.id}
|
||||
|
||||
%td
|
||||
%div.branch-commit
|
||||
- if commit.ref
|
||||
= link_to commit.ref, namespace_project_commits_path(@project.namespace, @project, commit.ref), class: "monospace"
|
||||
- if pipeline.ref
|
||||
= link_to pipeline.ref, namespace_project_commits_path(@project.namespace, @project, pipeline.ref), class: "monospace"
|
||||
·
|
||||
= link_to commit.short_sha, namespace_project_commit_path(@project.namespace, @project, commit.sha), class: "commit-id monospace"
|
||||
= link_to pipeline.short_sha, namespace_project_commit_path(@project.namespace, @project, pipeline.sha), class: "commit-id monospace"
|
||||
|
||||
- if commit.tag?
|
||||
- if pipeline.tag?
|
||||
%span.label.label-primary tag
|
||||
- elsif commit.latest?
|
||||
- elsif pipeline.latest?
|
||||
%span.label.label-success.has-tooltip{ title: 'Latest build for this branch' } latest
|
||||
- if commit.triggered?
|
||||
- if pipeline.triggered?
|
||||
%span.label.label-primary triggered
|
||||
- if commit.yaml_errors.present?
|
||||
%span.label.label-danger.has-tooltip{ title: "#{commit.yaml_errors}" } yaml invalid
|
||||
- if commit.builds.any?(&:stuck?)
|
||||
- if pipeline.yaml_errors.present?
|
||||
%span.label.label-danger.has-tooltip{ title: "#{pipeline.yaml_errors}" } yaml invalid
|
||||
- if pipeline.builds.any?(&:stuck?)
|
||||
%span.label.label-warning stuck
|
||||
|
||||
%p.commit-title
|
||||
- if commit_data = commit.commit_data
|
||||
- if commit_data = pipeline.commit_data
|
||||
= link_to_gfm truncate(commit_data.title, length: 60), namespace_project_commit_path(@project.namespace, @project, commit_data.id), class: "commit-row-message"
|
||||
- else
|
||||
Cant find HEAD commit for this branch
|
||||
|
||||
|
||||
- stages_status = commit.statuses.stages_status
|
||||
- stages_status = pipeline.statuses.stages_status
|
||||
- stages.each do |stage|
|
||||
%td
|
||||
- status = stages_status[stage]
|
||||
- tooltip = "#{stage.titleize}: #{status || 'not found'}"
|
||||
- if status
|
||||
= link_to namespace_project_pipeline_path(@project.namespace, @project, commit.id, anchor: stage), class: "has-tooltip ci-status-icon-#{status}", title: tooltip do
|
||||
= link_to namespace_project_pipeline_path(@project.namespace, @project, pipeline.id, anchor: stage), class: "has-tooltip ci-status-icon-#{status}", title: tooltip do
|
||||
= ci_icon_for_status(status)
|
||||
- else
|
||||
.light.has-tooltip{ title: tooltip }
|
||||
\-
|
||||
|
||||
%td
|
||||
- if commit.started_at && commit.finished_at
|
||||
- if pipeline.started_at && pipeline.finished_at
|
||||
%p.duration
|
||||
#{duration_in_words(commit.finished_at, commit.started_at)}
|
||||
#{duration_in_words(pipeline.finished_at, pipeline.started_at)}
|
||||
|
||||
%td
|
||||
.controls.hidden-xs.pull-right
|
||||
- artifacts = commit.builds.latest.select { |b| b.artifacts? }
|
||||
- artifacts = pipeline.builds.latest.select { |b| b.artifacts? }
|
||||
- if artifacts.present?
|
||||
.dropdown.inline.build-artifacts
|
||||
%button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
|
||||
|
@ -63,9 +63,9 @@
|
|||
%span #{build.name}
|
||||
|
||||
- if can?(current_user, :update_pipeline, @project)
|
||||
- if commit.retryable?
|
||||
= link_to retry_namespace_project_pipeline_path(@project.namespace, @project, commit.id), class: 'btn has-tooltip', title: "Retry", method: :post do
|
||||
- if pipeline.retryable?
|
||||
= link_to retry_namespace_project_pipeline_path(@project.namespace, @project, pipeline.id), class: 'btn has-tooltip', title: "Retry", method: :post do
|
||||
= icon("repeat")
|
||||
- if commit.cancelable?
|
||||
= link_to cancel_namespace_project_pipeline_path(@project.namespace, @project, commit.id), class: 'btn btn-remove has-tooltip', title: "Cancel", method: :post do
|
||||
- if pipeline.cancelable?
|
||||
= link_to cancel_namespace_project_pipeline_path(@project.namespace, @project, pipeline.id), class: 'btn btn-remove has-tooltip', title: "Cancel", method: :post do
|
||||
= icon("remove")
|
|
@ -1,4 +1,4 @@
|
|||
%li{ id: dom_id(issue), class: issue_css_classes(issue), url: issue_path(issue) }
|
||||
%li{ id: dom_id(issue), class: issue_css_classes(issue), url: issue_path(issue), data: { labels: issue.label_ids, id: issue.id } }
|
||||
- if controller.controller_name == 'issues' && can?(current_user, :admin_issue, @project)
|
||||
.issue-check
|
||||
= check_box_tag dom_id(issue,"selected"), nil, false, 'data-id' => issue.id, class: "selected_issue"
|
||||
|
@ -27,7 +27,7 @@
|
|||
= icon('thumbs-down')
|
||||
= downvotes
|
||||
|
||||
- note_count = issue.notes.user.nonawards.count
|
||||
- note_count = issue.notes.user.count
|
||||
%li
|
||||
= link_to issue_path(issue, anchor: 'notes'), class: ('issue-no-comments' if note_count.zero?) do
|
||||
= icon('comments')
|
||||
|
|
|
@ -68,9 +68,9 @@
|
|||
#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 'award_emoji/awards_block', awardable: @issue, inline: true
|
||||
|
||||
%section.issuable-discussion
|
||||
= render 'projects/issues/discussion'
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
%li{id: dom_id(label)}
|
||||
%li{ id: dom_id(label), data: { id: label.id } }
|
||||
= render "shared/label_row", label: label
|
||||
|
||||
.pull-info-right
|
||||
%span.append-right-20
|
||||
= link_to_label(label, type: :merge_request) do
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
= icon('thumbs-down')
|
||||
= downvotes
|
||||
|
||||
- note_count = merge_request.mr_and_commit_notes.user.nonawards.count
|
||||
- note_count = merge_request.mr_and_commit_notes.user.count
|
||||
%li
|
||||
= link_to merge_request_path(merge_request, anchor: 'notes'), class: ('merge-request-no-comments' if note_count.zero?) do
|
||||
= icon('comments')
|
||||
|
|
|
@ -6,4 +6,3 @@
|
|||
|
||||
- if @merge_requests.present?
|
||||
= paginate @merge_requests, theme: "gitlab"
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
%li.notes-tab
|
||||
= link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#notes', action: 'notes', toggle: 'tab'} do
|
||||
Discussion
|
||||
%span.badge= @merge_request.mr_and_commit_notes.user.nonawards.count
|
||||
%span.badge= @merge_request.mr_and_commit_notes.user.count
|
||||
%li.commits-tab
|
||||
= link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#commits', action: 'commits', toggle: 'tab'} do
|
||||
Commits
|
||||
|
@ -67,7 +67,7 @@
|
|||
.tab-content
|
||||
#notes.notes.tab-pane.voting_notes
|
||||
.content-block.content-block-small.oneline-block
|
||||
= render 'votes/votes_block', votable: @merge_request
|
||||
= render 'award_emoji/awards_block', awardable: @merge_request, inline: true
|
||||
|
||||
.row
|
||||
%section.col-md-12
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
|
||||
- if controller.controller_name == 'issues'
|
||||
.issues_bulk_update.hide
|
||||
= form_tag bulk_update_namespace_project_issues_path(@project.namespace, @project), method: :post do
|
||||
= form_tag bulk_update_namespace_project_issues_path(@project.namespace, @project), method: :post, class: 'bulk-update' do
|
||||
.filter-item.inline
|
||||
= dropdown_tag("Status", options: { toggle_class: "js-issue-status", title: "Change status", dropdown_class: "dropdown-menu-status dropdown-menu-selectable", data: { field_name: "update[state_event]" } } ) do
|
||||
%ul
|
||||
|
@ -44,6 +44,10 @@
|
|||
placeholder: "Search authors", data: { first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: @project.id, field_name: "update[assignee_id]" } })
|
||||
.filter-item.inline
|
||||
= dropdown_tag("Milestone", options: { title: "Assign milestone", toggle_class: 'js-milestone-select js-extra-options js-filter-submit js-filter-bulk-update', filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone", placeholder: "Search milestones", data: { show_no: true, field_name: "update[milestone_id]", project_id: @project.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), use_id: true } })
|
||||
|
||||
.filter-item.inline.labels-filter
|
||||
= render "shared/issuable/label_dropdown", classes: ['js-filter-bulk-update', 'js-multiselect'], show_create: false, show_footer: false, extra_options: false, filter_submit: false, show_footer: false, data_options: { persist_when_hide: "true", field_name: "update[label_ids][]", show_no: false, show_any: false, use_id: true }
|
||||
|
||||
= hidden_field_tag 'update[issues_ids]', []
|
||||
= hidden_field_tag :state_event, params[:state_event]
|
||||
.filter-item.inline
|
||||
|
|
|
@ -1,14 +1,25 @@
|
|||
- show_create = local_assigns.fetch(:show_create, true)
|
||||
- extra_options = local_assigns.fetch(:extra_options, true)
|
||||
- filter_submit = local_assigns.fetch(:filter_submit, true)
|
||||
- show_footer = local_assigns.fetch(:show_footer, true)
|
||||
- data_options = local_assigns.fetch(:data_options, {})
|
||||
- classes = local_assigns.fetch(:classes, [])
|
||||
- dropdown_data = {toggle: 'dropdown', field_name: 'label_name[]', show_no: "true", show_any: "true", selected: params[:label_name], project_id: @project.try(:id), labels: labels_filter_path, default_label: "Label"}
|
||||
- dropdown_data.merge!(data_options)
|
||||
- classes << 'js-extra-options' if extra_options
|
||||
- classes << 'js-filter-submit' if filter_submit
|
||||
|
||||
- if params[:label_name].present?
|
||||
- if params[:label_name].respond_to?('any?')
|
||||
- params[:label_name].each do |label|
|
||||
= hidden_field_tag "label_name[]", label, id: nil
|
||||
.dropdown
|
||||
%button.dropdown-menu-toggle.js-label-select.js-filter-submit.js-multiselect.js-extra-options{type: "button", data: {toggle: "dropdown", field_name: "label_name[]", show_no: "true", show_any: "true", selected: params[:label_name], project_id: @project.try(:id), labels: labels_filter_path, default_label: "Label"}}
|
||||
%button.dropdown-menu-toggle.js-label-select.js-multiselect{class: classes.join(' '), type: "button", data: dropdown_data}
|
||||
%span.dropdown-toggle-text
|
||||
= h(multi_label_name(params[:label_name], "Label"))
|
||||
= icon('chevron-down')
|
||||
.dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
|
||||
= render partial: "shared/issuable/label_page_default", locals: { title: "Filter by label" }
|
||||
- if can? current_user, :admin_label, @project and @project
|
||||
= render partial: "shared/issuable/label_page_default", locals: { title: "Filter by label", show_footer: show_footer, show_create: show_create }
|
||||
- if show_create and @project and can?(current_user, :admin_label, @project)
|
||||
= render partial: "shared/issuable/label_page_create"
|
||||
= dropdown_loading
|
||||
|
|
|
@ -1,20 +1,22 @@
|
|||
- title = local_assigns.fetch(:title, 'Assign labels')
|
||||
- show_create = local_assigns.fetch(:show_create, true)
|
||||
- show_footer = local_assigns.fetch(:show_footer, true)
|
||||
- filter_placeholder = local_assigns.fetch(:filter_placeholder, 'Search labels')
|
||||
.dropdown-page-one
|
||||
= dropdown_title(title)
|
||||
= dropdown_filter(filter_placeholder)
|
||||
= dropdown_content
|
||||
- if @project
|
||||
- if @project && show_footer
|
||||
= dropdown_footer do
|
||||
%ul.dropdown-footer-list
|
||||
- if can? current_user, :admin_label, @project
|
||||
- if can?(current_user, :admin_label, @project)
|
||||
%li
|
||||
%a.dropdown-toggle-page{href: "#"}
|
||||
Create new
|
||||
%li
|
||||
= link_to namespace_project_labels_path(@project.namespace, @project), :"data-is-link" => true do
|
||||
- if can? current_user, :admin_label, @project
|
||||
- if show_create && @project && can?(current_user, :admin_label, @project)
|
||||
Manage labels
|
||||
- else
|
||||
View labels
|
||||
= dropdown_loading
|
||||
= dropdown_loading
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
.awards.votes-block
|
||||
- awards_sort(votable.notes.awards.grouped_awards).each do |emoji, notes|
|
||||
%button.btn.award-control.js-emoji-btn.has-tooltip{class: (note_active_class(notes, current_user)), data: {placement: "top", original_title: emoji_author_list(notes, current_user)}}
|
||||
= emoji_icon(emoji, sprite: false)
|
||||
%span.award-control-text.js-counter
|
||||
= notes.count
|
||||
|
||||
- if current_user
|
||||
%div.award-menu-holder.js-award-holder
|
||||
%a.btn.award-control.js-add-award{"href" => "#"}
|
||||
= icon('smile-o', {class: "award-control-icon"})
|
||||
= icon('spinner spin', {class: "award-control-icon award-control-icon-loading"})
|
||||
%span.award-control-text
|
||||
Add
|
||||
|
||||
- if current_user
|
||||
:javascript
|
||||
var getEmojisUrl = "#{emojis_path}";
|
||||
var postEmojiUrl = "#{award_toggle_namespace_project_notes_path(@project.namespace, @project)}";
|
||||
var noteableType = "#{votable.class.name.underscore}";
|
||||
var noteableId = "#{votable.id}";
|
||||
var unicodes = #{AwardEmoji.unicode.to_json};
|
||||
|
||||
window.awardsHandler = new AwardsHandler(
|
||||
getEmojisUrl,
|
||||
postEmojiUrl,
|
||||
noteableType,
|
||||
noteableId,
|
||||
unicodes
|
||||
);
|
|
@ -8,3 +8,7 @@
|
|||
# inflect.irregular 'person', 'people'
|
||||
# inflect.uncountable %w( fish sheep )
|
||||
# end
|
||||
#
|
||||
ActiveSupport::Inflector.inflections do |inflect|
|
||||
inflect.uncountable %w(award_emoji)
|
||||
end
|
||||
|
|
|
@ -652,6 +652,7 @@ Rails.application.routes.draw do
|
|||
post :cancel_merge_when_build_succeeds
|
||||
get :ci_status
|
||||
post :toggle_subscription
|
||||
post :toggle_award_emoji
|
||||
post :remove_wip
|
||||
end
|
||||
|
||||
|
@ -727,6 +728,7 @@ Rails.application.routes.draw do
|
|||
resources :issues, constraints: { id: /\d+/ } do
|
||||
member do
|
||||
post :toggle_subscription
|
||||
post :toggle_award_emoji
|
||||
get :referenced_merge_requests
|
||||
get :related_branches
|
||||
get :can_create_branch
|
||||
|
@ -757,10 +759,6 @@ Rails.application.routes.draw do
|
|||
member do
|
||||
delete :delete_attachment
|
||||
end
|
||||
|
||||
collection do
|
||||
post :award_toggle
|
||||
end
|
||||
end
|
||||
|
||||
resources :uploads, only: [:create] do
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
class AddAwardEmoji < ActiveRecord::Migration
|
||||
def change
|
||||
create_table :award_emoji do |t|
|
||||
t.string :name
|
||||
t.references :user
|
||||
t.references :awardable, polymorphic: true
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
add_index :award_emoji, :user_id
|
||||
add_index :award_emoji, [:awardable_type, :awardable_id]
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
class ConvertAwardNoteToEmojiAward < ActiveRecord::Migration
|
||||
def change
|
||||
def up
|
||||
execute "INSERT INTO award_emoji (awardable_type, awardable_id, user_id, name, created_at, updated_at) (SELECT noteable_type, noteable_id, author_id, note, created_at, updated_at FROM notes WHERE is_award = true)"
|
||||
|
||||
execute "DELETE FROM notes WHERE is_award = true"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,5 @@
|
|||
class RemoveNoteIsAward < ActiveRecord::Migration
|
||||
def change
|
||||
remove_column :notes, :is_award, :boolean
|
||||
end
|
||||
end
|
14
db/schema.rb
14
db/schema.rb
|
@ -100,6 +100,18 @@ ActiveRecord::Schema.define(version: 20160530150109) do
|
|||
add_index "audit_events", ["entity_id", "entity_type"], name: "index_audit_events_on_entity_id_and_entity_type", using: :btree
|
||||
add_index "audit_events", ["type"], name: "index_audit_events_on_type", using: :btree
|
||||
|
||||
create_table "award_emoji", force: :cascade do |t|
|
||||
t.string "name"
|
||||
t.integer "user_id"
|
||||
t.integer "awardable_id"
|
||||
t.string "awardable_type"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
end
|
||||
|
||||
add_index "award_emoji", ["awardable_type", "awardable_id"], name: "index_award_emoji_on_awardable_type_and_awardable_id", using: :btree
|
||||
add_index "award_emoji", ["user_id"], name: "index_award_emoji_on_user_id", using: :btree
|
||||
|
||||
create_table "broadcast_messages", force: :cascade do |t|
|
||||
t.text "message", null: false
|
||||
t.datetime "starts_at"
|
||||
|
@ -638,7 +650,6 @@ ActiveRecord::Schema.define(version: 20160530150109) do
|
|||
t.boolean "system", default: false, null: false
|
||||
t.text "st_diff"
|
||||
t.integer "updated_by_id"
|
||||
t.boolean "is_award", default: false, null: false
|
||||
t.string "type"
|
||||
end
|
||||
|
||||
|
@ -646,7 +657,6 @@ ActiveRecord::Schema.define(version: 20160530150109) do
|
|||
add_index "notes", ["commit_id"], name: "index_notes_on_commit_id", using: :btree
|
||||
add_index "notes", ["created_at", "id"], name: "index_notes_on_created_at_and_id", using: :btree
|
||||
add_index "notes", ["created_at"], name: "index_notes_on_created_at", using: :btree
|
||||
add_index "notes", ["is_award"], name: "index_notes_on_is_award", using: :btree
|
||||
add_index "notes", ["line_code"], name: "index_notes_on_line_code", using: :btree
|
||||
add_index "notes", ["note"], name: "index_notes_on_note_trigram", using: :gin, opclasses: {"note"=>"gin_trgm_ops"}
|
||||
add_index "notes", ["noteable_id", "noteable_type"], name: "index_notes_on_noteable_id_and_noteable_type", using: :btree
|
||||
|
|
|
@ -278,6 +278,30 @@ Response:
|
|||
|
||||
[ce-2893]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2893
|
||||
|
||||
## Get a trace file
|
||||
|
||||
Get a trace of a specific build of a project
|
||||
|
||||
```
|
||||
GET /projects/:id/builds/:build_id/trace
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|------------|---------|----------|---------------------|
|
||||
| id | integer | yes | The ID of a project |
|
||||
| build_id | integer | yes | The ID of a build |
|
||||
|
||||
```
|
||||
curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/8/trace"
|
||||
```
|
||||
|
||||
Response:
|
||||
|
||||
| Status | Description |
|
||||
|-----------|-----------------------------------|
|
||||
| 200 | Serves the trace file |
|
||||
| 404 | Build not found or no trace file |
|
||||
|
||||
## Cancel a build
|
||||
|
||||
Cancel a single build of a project
|
||||
|
|
|
@ -29,7 +29,7 @@ class Spinach::Features::ProjectIssuesFilterLabels < Spinach::FeatureSteps
|
|||
end
|
||||
|
||||
step 'I click link "bug"' do
|
||||
page.find('.js-label-select').click
|
||||
page.find('.js-label-select', visible: true).click
|
||||
sleep 0.5
|
||||
execute_script("$('.dropdown-menu-labels li:contains(\"bug\") a').click()")
|
||||
end
|
||||
|
|
|
@ -191,15 +191,15 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
|
|||
end
|
||||
|
||||
step 'issue "Release 0.4" have 2 upvotes and 1 downvote' do
|
||||
issue = Issue.find_by(title: 'Release 0.4')
|
||||
create_list(:upvote_note, 2, project: project, noteable: issue)
|
||||
create(:downvote_note, project: project, noteable: issue)
|
||||
awardable = Issue.find_by(title: 'Release 0.4')
|
||||
create_list(:award_emoji, 2, awardable: awardable)
|
||||
create(:award_emoji, :downvote, awardable: awardable)
|
||||
end
|
||||
|
||||
step 'issue "Tweet control" have 1 upvote and 2 downvotes' do
|
||||
issue = Issue.find_by(title: 'Tweet control')
|
||||
create(:upvote_note, project: project, noteable: issue)
|
||||
create_list(:downvote_note, 2, project: project, noteable: issue)
|
||||
awardable = Issue.find_by(title: 'Tweet control')
|
||||
create(:award_emoji, :upvote, awardable: awardable)
|
||||
create_list(:award_emoji, 2, awardable: awardable, name: 'thumbsdown')
|
||||
end
|
||||
|
||||
step 'The list should be sorted by "Least popular"' do
|
||||
|
|
|
@ -179,14 +179,14 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
|
|||
|
||||
step 'merge request "Bug NS-04" have 2 upvotes and 1 downvote' do
|
||||
merge_request = MergeRequest.find_by(title: 'Bug NS-04')
|
||||
create_list(:upvote_note, 2, project: project, noteable: merge_request)
|
||||
create(:downvote_note, project: project, noteable: merge_request)
|
||||
create_list(:award_emoji, 2, awardable: merge_request)
|
||||
create(:award_emoji, :downvote, awardable: merge_request)
|
||||
end
|
||||
|
||||
step 'merge request "Bug NS-06" have 1 upvote and 2 downvotes' do
|
||||
merge_request = MergeRequest.find_by(title: 'Bug NS-06')
|
||||
create(:upvote_note, project: project, noteable: merge_request)
|
||||
create_list(:downvote_note, 2, project: project, noteable: merge_request)
|
||||
awardable = MergeRequest.find_by(title: 'Bug NS-06')
|
||||
create(:award_emoji, awardable: awardable)
|
||||
create_list(:award_emoji, 2, :downvote, awardable: awardable)
|
||||
end
|
||||
|
||||
step 'The list should be sorted by "Least popular"' do
|
||||
|
|
|
@ -171,15 +171,17 @@ module API
|
|||
expose :label_names, as: :labels
|
||||
expose :milestone, using: Entities::Milestone
|
||||
expose :assignee, :author, using: Entities::UserBasic
|
||||
|
||||
expose :subscribed do |issue, options|
|
||||
issue.subscribed?(options[:current_user])
|
||||
end
|
||||
expose :user_notes_count
|
||||
expose :upvotes, :downvotes
|
||||
end
|
||||
|
||||
class MergeRequest < ProjectEntity
|
||||
expose :target_branch, :source_branch
|
||||
expose :upvotes, :downvotes
|
||||
expose :upvotes, :downvotes
|
||||
expose :author, :assignee, using: Entities::UserBasic
|
||||
expose :source_project_id, :target_project_id
|
||||
expose :label_names, as: :labels
|
||||
|
@ -217,8 +219,8 @@ module API
|
|||
expose :system?, as: :system
|
||||
expose :noteable_id, :noteable_type
|
||||
# upvote? and downvote? are deprecated, always return false
|
||||
expose :upvote?, as: :upvote
|
||||
expose :downvote?, as: :downvote
|
||||
expose(:upvote?) { |note| false }
|
||||
expose(:downvote?) { |note| false }
|
||||
end
|
||||
|
||||
class MRNote < Grape::Entity
|
||||
|
|
|
@ -57,7 +57,7 @@ module API
|
|||
not_found! "File" unless blob
|
||||
|
||||
content_type 'text/plain'
|
||||
header *Gitlab::Workhorse.send_git_blob(repo, blob)
|
||||
header(*Gitlab::Workhorse.send_git_blob(repo, blob))
|
||||
end
|
||||
|
||||
# Get a raw blob contents by blob sha
|
||||
|
@ -83,7 +83,7 @@ module API
|
|||
env['api.format'] = :txt
|
||||
|
||||
content_type blob.mime_type
|
||||
header *Gitlab::Workhorse.send_git_blob(repo, blob)
|
||||
header(*Gitlab::Workhorse.send_git_blob(repo, blob))
|
||||
end
|
||||
|
||||
# Get a an archive of the repository
|
||||
|
@ -98,7 +98,7 @@ module API
|
|||
authorize! :download_code, user_project
|
||||
|
||||
begin
|
||||
header *Gitlab::Workhorse.send_git_archive(user_project, params[:sha], params[:format])
|
||||
header(*Gitlab::Workhorse.send_git_archive(user_project, params[:sha], params[:format]))
|
||||
rescue
|
||||
not_found!('File')
|
||||
end
|
||||
|
|
|
@ -1,84 +0,0 @@
|
|||
class AwardEmoji
|
||||
CATEGORIES = {
|
||||
other: "Other",
|
||||
objects: "Objects",
|
||||
places: "Places",
|
||||
travel_places: "Travel",
|
||||
emoticons: "Emoticons",
|
||||
objects_symbols: "Symbols",
|
||||
nature: "Nature",
|
||||
celebration: "Celebration",
|
||||
people: "People",
|
||||
activity: "Activity",
|
||||
flags: "Flags",
|
||||
food_drink: "Food"
|
||||
}.with_indifferent_access
|
||||
|
||||
CATEGORY_ALIASES = {
|
||||
symbols: "objects_symbols",
|
||||
foods: "food_drink",
|
||||
travel: "travel_places"
|
||||
}.with_indifferent_access
|
||||
|
||||
def self.normilize_emoji_name(name)
|
||||
aliases[name] || name
|
||||
end
|
||||
|
||||
def self.emoji_by_category
|
||||
unless @emoji_by_category
|
||||
@emoji_by_category = Hash.new { |h, key| h[key] = [] }
|
||||
|
||||
emojis.each do |emoji_name, data|
|
||||
data["name"] = emoji_name
|
||||
|
||||
# Skip Fitzpatrick(tone) modifiers
|
||||
next if data["category"] == "modifier"
|
||||
|
||||
category = CATEGORY_ALIASES[data["category"]] || data["category"]
|
||||
|
||||
@emoji_by_category[category] << data
|
||||
end
|
||||
|
||||
@emoji_by_category = @emoji_by_category.sort.to_h
|
||||
end
|
||||
|
||||
@emoji_by_category
|
||||
end
|
||||
|
||||
def self.emojis
|
||||
@emojis ||= begin
|
||||
json_path = File.join(Rails.root, 'fixtures', 'emojis', 'index.json' )
|
||||
JSON.parse(File.read(json_path))
|
||||
end
|
||||
end
|
||||
|
||||
def self.unicode
|
||||
@unicode ||= emojis.map {|key, value| { key => emojis[key]["unicode"] } }.inject(:merge!)
|
||||
end
|
||||
|
||||
def self.aliases
|
||||
@aliases ||= begin
|
||||
json_path = File.join(Rails.root, 'fixtures', 'emojis', 'aliases.json' )
|
||||
JSON.parse(File.read(json_path))
|
||||
end
|
||||
end
|
||||
|
||||
# Returns an Array of Emoji names and their asset URLs.
|
||||
def self.urls
|
||||
@urls ||= begin
|
||||
path = File.join(Rails.root, 'fixtures', 'emojis', 'digests.json')
|
||||
prefix = Gitlab::Application.config.assets.prefix
|
||||
digest = Gitlab::Application.config.assets.digest
|
||||
|
||||
JSON.parse(File.read(path)).map do |hash|
|
||||
if digest
|
||||
fname = "#{hash['unicode']}-#{hash['digest']}"
|
||||
else
|
||||
fname = hash['unicode']
|
||||
end
|
||||
|
||||
{ name: hash['name'], path: "#{prefix}/#{fname}.png" }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -86,9 +86,9 @@ module Backup
|
|||
|
||||
def report_success(success)
|
||||
if success
|
||||
$progress.puts '[DONE]'.green
|
||||
$progress.puts '[DONE]'.color(:green)
|
||||
else
|
||||
$progress.puts '[FAILED]'.red
|
||||
$progress.puts '[FAILED]'.color(:red)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -27,9 +27,9 @@ module Backup
|
|||
# Set file permissions on open to prevent chmod races.
|
||||
tar_system_options = {out: [tar_file, 'w', Gitlab.config.backup.archive_permissions]}
|
||||
if Kernel.system('tar', '-cf', '-', *backup_contents, tar_system_options)
|
||||
$progress.puts "done".green
|
||||
$progress.puts "done".color(:green)
|
||||
else
|
||||
puts "creating archive #{tar_file} failed".red
|
||||
puts "creating archive #{tar_file} failed".color(:red)
|
||||
abort 'Backup failed'
|
||||
end
|
||||
|
||||
|
@ -43,7 +43,7 @@ module Backup
|
|||
|
||||
connection_settings = Gitlab.config.backup.upload.connection
|
||||
if connection_settings.blank?
|
||||
$progress.puts "skipped".yellow
|
||||
$progress.puts "skipped".color(:yellow)
|
||||
return
|
||||
end
|
||||
|
||||
|
@ -53,9 +53,9 @@ module Backup
|
|||
if directory.files.create(key: tar_file, body: File.open(tar_file), public: false,
|
||||
multipart_chunk_size: Gitlab.config.backup.upload.multipart_chunk_size,
|
||||
encryption: Gitlab.config.backup.upload.encryption)
|
||||
$progress.puts "done".green
|
||||
$progress.puts "done".color(:green)
|
||||
else
|
||||
puts "uploading backup to #{remote_directory} failed".red
|
||||
puts "uploading backup to #{remote_directory} failed".color(:red)
|
||||
abort 'Backup failed'
|
||||
end
|
||||
end
|
||||
|
@ -67,9 +67,9 @@ module Backup
|
|||
next unless File.exist?(File.join(Gitlab.config.backup.path, dir))
|
||||
|
||||
if FileUtils.rm_rf(File.join(Gitlab.config.backup.path, dir))
|
||||
$progress.puts "done".green
|
||||
$progress.puts "done".color(:green)
|
||||
else
|
||||
puts "deleting tmp directory '#{dir}' failed".red
|
||||
puts "deleting tmp directory '#{dir}' failed".color(:red)
|
||||
abort 'Backup failed'
|
||||
end
|
||||
end
|
||||
|
@ -95,9 +95,9 @@ module Backup
|
|||
end
|
||||
end
|
||||
|
||||
$progress.puts "done. (#{removed} removed)".green
|
||||
$progress.puts "done. (#{removed} removed)".color(:green)
|
||||
else
|
||||
$progress.puts "skipping".yellow
|
||||
$progress.puts "skipping".color(:yellow)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -124,20 +124,20 @@ module Backup
|
|||
$progress.print "Unpacking backup ... "
|
||||
|
||||
unless Kernel.system(*%W(tar -xf #{tar_file}))
|
||||
puts "unpacking backup failed".red
|
||||
puts "unpacking backup failed".color(:red)
|
||||
exit 1
|
||||
else
|
||||
$progress.puts "done".green
|
||||
$progress.puts "done".color(:green)
|
||||
end
|
||||
|
||||
ENV["VERSION"] = "#{settings[:db_version]}" if settings[:db_version].to_i > 0
|
||||
|
||||
# restoring mismatching backups can lead to unexpected problems
|
||||
if settings[:gitlab_version] != Gitlab::VERSION
|
||||
puts "GitLab version mismatch:".red
|
||||
puts " Your current GitLab version (#{Gitlab::VERSION}) differs from the GitLab version in the backup!".red
|
||||
puts " Please switch to the following version and try again:".red
|
||||
puts " version: #{settings[:gitlab_version]}".red
|
||||
puts "GitLab version mismatch:".color(:red)
|
||||
puts " Your current GitLab version (#{Gitlab::VERSION}) differs from the GitLab version in the backup!".color(:red)
|
||||
puts " Please switch to the following version and try again:".color(:red)
|
||||
puts " version: #{settings[:gitlab_version]}".color(:red)
|
||||
puts
|
||||
puts "Hint: git checkout v#{settings[:gitlab_version]}"
|
||||
exit 1
|
||||
|
|
|
@ -14,14 +14,14 @@ module Backup
|
|||
FileUtils.mkdir_p(File.join(backup_repos_path, project.namespace.path)) if project.namespace
|
||||
|
||||
if project.empty_repo?
|
||||
$progress.puts "[SKIPPED]".cyan
|
||||
$progress.puts "[SKIPPED]".color(:cyan)
|
||||
else
|
||||
cmd = %W(tar -cf #{path_to_bundle(project)} -C #{path_to_repo(project)} .)
|
||||
output, status = Gitlab::Popen.popen(cmd)
|
||||
if status.zero?
|
||||
$progress.puts "[DONE]".green
|
||||
$progress.puts "[DONE]".color(:green)
|
||||
else
|
||||
puts "[FAILED]".red
|
||||
puts "[FAILED]".color(:red)
|
||||
puts "failed: #{cmd.join(' ')}"
|
||||
puts output
|
||||
abort 'Backup failed'
|
||||
|
@ -33,14 +33,14 @@ module Backup
|
|||
if File.exists?(path_to_repo(wiki))
|
||||
$progress.print " * #{wiki.path_with_namespace} ... "
|
||||
if wiki.repository.empty?
|
||||
$progress.puts " [SKIPPED]".cyan
|
||||
$progress.puts " [SKIPPED]".color(:cyan)
|
||||
else
|
||||
cmd = %W(#{Gitlab.config.git.bin_path} --git-dir=#{path_to_repo(wiki)} bundle create #{path_to_bundle(wiki)} --all)
|
||||
output, status = Gitlab::Popen.popen(cmd)
|
||||
if status.zero?
|
||||
$progress.puts " [DONE]".green
|
||||
$progress.puts " [DONE]".color(:green)
|
||||
else
|
||||
puts " [FAILED]".red
|
||||
puts " [FAILED]".color(:red)
|
||||
puts "failed: #{cmd.join(' ')}"
|
||||
abort 'Backup failed'
|
||||
end
|
||||
|
@ -71,9 +71,9 @@ module Backup
|
|||
end
|
||||
|
||||
if system(*cmd, silent)
|
||||
$progress.puts "[DONE]".green
|
||||
$progress.puts "[DONE]".color(:green)
|
||||
else
|
||||
puts "[FAILED]".red
|
||||
puts "[FAILED]".color(:red)
|
||||
puts "failed: #{cmd.join(' ')}"
|
||||
abort 'Restore failed'
|
||||
end
|
||||
|
@ -90,21 +90,21 @@ module Backup
|
|||
cmd = %W(#{Gitlab.config.git.bin_path} clone --bare #{path_to_bundle(wiki)} #{path_to_repo(wiki)})
|
||||
|
||||
if system(*cmd, silent)
|
||||
$progress.puts " [DONE]".green
|
||||
$progress.puts " [DONE]".color(:green)
|
||||
else
|
||||
puts " [FAILED]".red
|
||||
puts " [FAILED]".color(:red)
|
||||
puts "failed: #{cmd.join(' ')}"
|
||||
abort 'Restore failed'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
$progress.print 'Put GitLab hooks in repositories dirs'.yellow
|
||||
$progress.print 'Put GitLab hooks in repositories dirs'.color(:yellow)
|
||||
cmd = "#{Gitlab.config.gitlab_shell.path}/bin/create-hooks"
|
||||
if system(cmd)
|
||||
$progress.puts " [DONE]".green
|
||||
$progress.puts " [DONE]".color(:green)
|
||||
else
|
||||
puts " [FAILED]".red
|
||||
puts " [FAILED]".color(:red)
|
||||
puts "failed: #{cmd}"
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
module Gitlab
|
||||
class AwardEmoji
|
||||
CATEGORIES = {
|
||||
other: "Other",
|
||||
objects: "Objects",
|
||||
places: "Places",
|
||||
travel_places: "Travel",
|
||||
emoticons: "Emoticons",
|
||||
objects_symbols: "Symbols",
|
||||
nature: "Nature",
|
||||
celebration: "Celebration",
|
||||
people: "People",
|
||||
activity: "Activity",
|
||||
flags: "Flags",
|
||||
food_drink: "Food"
|
||||
}.with_indifferent_access
|
||||
|
||||
CATEGORY_ALIASES = {
|
||||
symbols: "objects_symbols",
|
||||
foods: "food_drink",
|
||||
travel: "travel_places"
|
||||
}.with_indifferent_access
|
||||
|
||||
def self.normalize_emoji_name(name)
|
||||
aliases[name] || name
|
||||
end
|
||||
|
||||
def self.emoji_by_category
|
||||
unless @emoji_by_category
|
||||
@emoji_by_category = Hash.new { |h, key| h[key] = [] }
|
||||
|
||||
emojis.each do |emoji_name, data|
|
||||
data["name"] = emoji_name
|
||||
|
||||
# Skip Fitzpatrick(tone) modifiers
|
||||
next if data["category"] == "modifier"
|
||||
|
||||
category = CATEGORY_ALIASES[data["category"]] || data["category"]
|
||||
|
||||
@emoji_by_category[category] << data
|
||||
end
|
||||
|
||||
@emoji_by_category = @emoji_by_category.sort.to_h
|
||||
end
|
||||
|
||||
@emoji_by_category
|
||||
end
|
||||
|
||||
def self.emojis
|
||||
@emojis ||=
|
||||
begin
|
||||
json_path = File.join(Rails.root, 'fixtures', 'emojis', 'index.json' )
|
||||
JSON.parse(File.read(json_path))
|
||||
end
|
||||
end
|
||||
|
||||
def self.aliases
|
||||
@aliases ||=
|
||||
begin
|
||||
json_path = File.join(Rails.root, 'fixtures', 'emojis', 'aliases.json' )
|
||||
JSON.parse(File.read(json_path))
|
||||
end
|
||||
end
|
||||
|
||||
# Returns an Array of Emoji names and their asset URLs.
|
||||
def self.urls
|
||||
@urls ||= begin
|
||||
path = File.join(Rails.root, 'fixtures', 'emojis', 'digests.json')
|
||||
prefix = Gitlab::Application.config.assets.prefix
|
||||
digest = Gitlab::Application.config.assets.digest
|
||||
|
||||
JSON.parse(File.read(path)).map do |hash|
|
||||
if digest
|
||||
fname = "#{hash['unicode']}-#{hash['digest']}"
|
||||
else
|
||||
fname = hash['unicode']
|
||||
end
|
||||
|
||||
{ name: hash['name'], path: "#{prefix}/#{fname}.png" }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -17,9 +17,9 @@ module Gitlab
|
|||
file.rewind
|
||||
|
||||
cmd = []
|
||||
cmd.push *%W(ssh-keygen)
|
||||
cmd.push *%W(-E md5) if explicit_fingerprint_algorithm?
|
||||
cmd.push *%W(-lf #{file.path})
|
||||
cmd.push('ssh-keygen')
|
||||
cmd.push('-E', 'md5') if explicit_fingerprint_algorithm?
|
||||
cmd.push('-lf', file.path)
|
||||
|
||||
cmd_output, cmd_status = popen(cmd, '/tmp')
|
||||
end
|
||||
|
|
|
@ -93,6 +93,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
protected
|
||||
|
||||
def base_config
|
||||
Gitlab.config.ldap
|
||||
end
|
||||
|
|
|
@ -5,7 +5,7 @@ module Gitlab
|
|||
SeedFu.quiet = true
|
||||
yield
|
||||
SeedFu.quiet = false
|
||||
puts "\nOK".green
|
||||
puts "\nOK".color(:green)
|
||||
end
|
||||
|
||||
def self.by_user(user)
|
||||
|
|
|
@ -40,14 +40,14 @@ namespace :gitlab do
|
|||
removed.
|
||||
MSG
|
||||
ask_to_continue
|
||||
puts 'Removing all tables. Press `Ctrl-C` within 5 seconds to abort'.yellow
|
||||
puts 'Removing all tables. Press `Ctrl-C` within 5 seconds to abort'.color(:yellow)
|
||||
sleep(5)
|
||||
end
|
||||
# Drop all tables Load the schema to ensure we don't have any newer tables
|
||||
# hanging out from a failed upgrade
|
||||
$progress.puts 'Cleaning the database ... '.blue
|
||||
$progress.puts 'Cleaning the database ... '.color(:blue)
|
||||
Rake::Task['gitlab:db:drop_tables'].invoke
|
||||
$progress.puts 'done'.green
|
||||
$progress.puts 'done'.color(:green)
|
||||
Rake::Task['gitlab:backup:db:restore'].invoke
|
||||
end
|
||||
Rake::Task['gitlab:backup:repo:restore'].invoke unless backup.skipped?('repositories')
|
||||
|
@ -63,141 +63,141 @@ namespace :gitlab do
|
|||
|
||||
namespace :repo do
|
||||
task create: :environment do
|
||||
$progress.puts "Dumping repositories ...".blue
|
||||
$progress.puts "Dumping repositories ...".color(:blue)
|
||||
|
||||
if ENV["SKIP"] && ENV["SKIP"].include?("repositories")
|
||||
$progress.puts "[SKIPPED]".cyan
|
||||
$progress.puts "[SKIPPED]".color(:cyan)
|
||||
else
|
||||
Backup::Repository.new.dump
|
||||
$progress.puts "done".green
|
||||
$progress.puts "done".color(:green)
|
||||
end
|
||||
end
|
||||
|
||||
task restore: :environment do
|
||||
$progress.puts "Restoring repositories ...".blue
|
||||
$progress.puts "Restoring repositories ...".color(:blue)
|
||||
Backup::Repository.new.restore
|
||||
$progress.puts "done".green
|
||||
$progress.puts "done".color(:green)
|
||||
end
|
||||
end
|
||||
|
||||
namespace :db do
|
||||
task create: :environment do
|
||||
$progress.puts "Dumping database ... ".blue
|
||||
$progress.puts "Dumping database ... ".color(:blue)
|
||||
|
||||
if ENV["SKIP"] && ENV["SKIP"].include?("db")
|
||||
$progress.puts "[SKIPPED]".cyan
|
||||
$progress.puts "[SKIPPED]".color(:cyan)
|
||||
else
|
||||
Backup::Database.new.dump
|
||||
$progress.puts "done".green
|
||||
$progress.puts "done".color(:green)
|
||||
end
|
||||
end
|
||||
|
||||
task restore: :environment do
|
||||
$progress.puts "Restoring database ... ".blue
|
||||
$progress.puts "Restoring database ... ".color(:blue)
|
||||
Backup::Database.new.restore
|
||||
$progress.puts "done".green
|
||||
$progress.puts "done".color(:green)
|
||||
end
|
||||
end
|
||||
|
||||
namespace :builds do
|
||||
task create: :environment do
|
||||
$progress.puts "Dumping builds ... ".blue
|
||||
$progress.puts "Dumping builds ... ".color(:blue)
|
||||
|
||||
if ENV["SKIP"] && ENV["SKIP"].include?("builds")
|
||||
$progress.puts "[SKIPPED]".cyan
|
||||
$progress.puts "[SKIPPED]".color(:cyan)
|
||||
else
|
||||
Backup::Builds.new.dump
|
||||
$progress.puts "done".green
|
||||
$progress.puts "done".color(:green)
|
||||
end
|
||||
end
|
||||
|
||||
task restore: :environment do
|
||||
$progress.puts "Restoring builds ... ".blue
|
||||
$progress.puts "Restoring builds ... ".color(:blue)
|
||||
Backup::Builds.new.restore
|
||||
$progress.puts "done".green
|
||||
$progress.puts "done".color(:green)
|
||||
end
|
||||
end
|
||||
|
||||
namespace :uploads do
|
||||
task create: :environment do
|
||||
$progress.puts "Dumping uploads ... ".blue
|
||||
$progress.puts "Dumping uploads ... ".color(:blue)
|
||||
|
||||
if ENV["SKIP"] && ENV["SKIP"].include?("uploads")
|
||||
$progress.puts "[SKIPPED]".cyan
|
||||
$progress.puts "[SKIPPED]".color(:cyan)
|
||||
else
|
||||
Backup::Uploads.new.dump
|
||||
$progress.puts "done".green
|
||||
$progress.puts "done".color(:green)
|
||||
end
|
||||
end
|
||||
|
||||
task restore: :environment do
|
||||
$progress.puts "Restoring uploads ... ".blue
|
||||
$progress.puts "Restoring uploads ... ".color(:blue)
|
||||
Backup::Uploads.new.restore
|
||||
$progress.puts "done".green
|
||||
$progress.puts "done".color(:green)
|
||||
end
|
||||
end
|
||||
|
||||
namespace :artifacts do
|
||||
task create: :environment do
|
||||
$progress.puts "Dumping artifacts ... ".blue
|
||||
$progress.puts "Dumping artifacts ... ".color(:blue)
|
||||
|
||||
if ENV["SKIP"] && ENV["SKIP"].include?("artifacts")
|
||||
$progress.puts "[SKIPPED]".cyan
|
||||
$progress.puts "[SKIPPED]".color(:cyan)
|
||||
else
|
||||
Backup::Artifacts.new.dump
|
||||
$progress.puts "done".green
|
||||
$progress.puts "done".color(:green)
|
||||
end
|
||||
end
|
||||
|
||||
task restore: :environment do
|
||||
$progress.puts "Restoring artifacts ... ".blue
|
||||
$progress.puts "Restoring artifacts ... ".color(:blue)
|
||||
Backup::Artifacts.new.restore
|
||||
$progress.puts "done".green
|
||||
$progress.puts "done".color(:green)
|
||||
end
|
||||
end
|
||||
|
||||
namespace :lfs do
|
||||
task create: :environment do
|
||||
$progress.puts "Dumping lfs objects ... ".blue
|
||||
$progress.puts "Dumping lfs objects ... ".color(:blue)
|
||||
|
||||
if ENV["SKIP"] && ENV["SKIP"].include?("lfs")
|
||||
$progress.puts "[SKIPPED]".cyan
|
||||
$progress.puts "[SKIPPED]".color(:cyan)
|
||||
else
|
||||
Backup::Lfs.new.dump
|
||||
$progress.puts "done".green
|
||||
$progress.puts "done".color(:green)
|
||||
end
|
||||
end
|
||||
|
||||
task restore: :environment do
|
||||
$progress.puts "Restoring lfs objects ... ".blue
|
||||
$progress.puts "Restoring lfs objects ... ".color(:blue)
|
||||
Backup::Lfs.new.restore
|
||||
$progress.puts "done".green
|
||||
$progress.puts "done".color(:green)
|
||||
end
|
||||
end
|
||||
|
||||
namespace :registry do
|
||||
task create: :environment do
|
||||
$progress.puts "Dumping container registry images ... ".blue
|
||||
$progress.puts "Dumping container registry images ... ".color(:blue)
|
||||
|
||||
if Gitlab.config.registry.enabled
|
||||
if ENV["SKIP"] && ENV["SKIP"].include?("registry")
|
||||
$progress.puts "[SKIPPED]".cyan
|
||||
$progress.puts "[SKIPPED]".color(:cyan)
|
||||
else
|
||||
Backup::Registry.new.dump
|
||||
$progress.puts "done".green
|
||||
$progress.puts "done".color(:green)
|
||||
end
|
||||
else
|
||||
$progress.puts "[DISABLED]".cyan
|
||||
$progress.puts "[DISABLED]".color(:cyan)
|
||||
end
|
||||
end
|
||||
|
||||
task restore: :environment do
|
||||
$progress.puts "Restoring container registry images ... ".blue
|
||||
$progress.puts "Restoring container registry images ... ".color(:blue)
|
||||
if Gitlab.config.registry.enabled
|
||||
Backup::Registry.new.restore
|
||||
$progress.puts "done".green
|
||||
$progress.puts "done".color(:green)
|
||||
else
|
||||
$progress.puts "[DISABLED]".cyan
|
||||
$progress.puts "[DISABLED]".color(:cyan)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -50,14 +50,14 @@ namespace :gitlab do
|
|||
end
|
||||
|
||||
if correct_options.all?
|
||||
puts "yes".green
|
||||
puts "yes".color(:green)
|
||||
else
|
||||
print "Trying to fix Git error automatically. ..."
|
||||
|
||||
if auto_fix_git_config(options)
|
||||
puts "Success".green
|
||||
puts "Success".color(:green)
|
||||
else
|
||||
puts "Failed".red
|
||||
puts "Failed".color(:red)
|
||||
try_fixing_it(
|
||||
sudo_gitlab("\"#{Gitlab.config.git.bin_path}\" config --global core.autocrlf \"#{options["core.autocrlf"]}\"")
|
||||
)
|
||||
|
@ -74,9 +74,9 @@ namespace :gitlab do
|
|||
database_config_file = Rails.root.join("config", "database.yml")
|
||||
|
||||
if File.exists?(database_config_file)
|
||||
puts "yes".green
|
||||
puts "yes".color(:green)
|
||||
else
|
||||
puts "no".red
|
||||
puts "no".color(:red)
|
||||
try_fixing_it(
|
||||
"Copy config/database.yml.<your db> to config/database.yml",
|
||||
"Check that the information in config/database.yml is correct"
|
||||
|
@ -95,9 +95,9 @@ namespace :gitlab do
|
|||
gitlab_config_file = Rails.root.join("config", "gitlab.yml")
|
||||
|
||||
if File.exists?(gitlab_config_file)
|
||||
puts "yes".green
|
||||
puts "yes".color(:green)
|
||||
else
|
||||
puts "no".red
|
||||
puts "no".color(:red)
|
||||
try_fixing_it(
|
||||
"Copy config/gitlab.yml.example to config/gitlab.yml",
|
||||
"Update config/gitlab.yml to match your setup"
|
||||
|
@ -114,14 +114,14 @@ namespace :gitlab do
|
|||
|
||||
gitlab_config_file = Rails.root.join("config", "gitlab.yml")
|
||||
unless File.exists?(gitlab_config_file)
|
||||
puts "can't check because of previous errors".magenta
|
||||
puts "can't check because of previous errors".color(:magenta)
|
||||
end
|
||||
|
||||
# omniauth or ldap could have been deleted from the file
|
||||
unless Gitlab.config['git_host']
|
||||
puts "no".green
|
||||
puts "no".color(:green)
|
||||
else
|
||||
puts "yes".red
|
||||
puts "yes".color(:red)
|
||||
try_fixing_it(
|
||||
"Backup your config/gitlab.yml",
|
||||
"Copy config/gitlab.yml.example to config/gitlab.yml",
|
||||
|
@ -138,16 +138,16 @@ namespace :gitlab do
|
|||
print "Init script exists? ... "
|
||||
|
||||
if omnibus_gitlab?
|
||||
puts 'skipped (omnibus-gitlab has no init script)'.magenta
|
||||
puts 'skipped (omnibus-gitlab has no init script)'.color(:magenta)
|
||||
return
|
||||
end
|
||||
|
||||
script_path = "/etc/init.d/gitlab"
|
||||
|
||||
if File.exists?(script_path)
|
||||
puts "yes".green
|
||||
puts "yes".color(:green)
|
||||
else
|
||||
puts "no".red
|
||||
puts "no".color(:red)
|
||||
try_fixing_it(
|
||||
"Install the init script"
|
||||
)
|
||||
|
@ -162,7 +162,7 @@ namespace :gitlab do
|
|||
print "Init script up-to-date? ... "
|
||||
|
||||
if omnibus_gitlab?
|
||||
puts 'skipped (omnibus-gitlab has no init script)'.magenta
|
||||
puts 'skipped (omnibus-gitlab has no init script)'.color(:magenta)
|
||||
return
|
||||
end
|
||||
|
||||
|
@ -170,7 +170,7 @@ namespace :gitlab do
|
|||
script_path = "/etc/init.d/gitlab"
|
||||
|
||||
unless File.exists?(script_path)
|
||||
puts "can't check because of previous errors".magenta
|
||||
puts "can't check because of previous errors".color(:magenta)
|
||||
return
|
||||
end
|
||||
|
||||
|
@ -178,9 +178,9 @@ namespace :gitlab do
|
|||
script_content = File.read(script_path)
|
||||
|
||||
if recipe_content == script_content
|
||||
puts "yes".green
|
||||
puts "yes".color(:green)
|
||||
else
|
||||
puts "no".red
|
||||
puts "no".color(:red)
|
||||
try_fixing_it(
|
||||
"Redownload the init script"
|
||||
)
|
||||
|
@ -197,9 +197,9 @@ namespace :gitlab do
|
|||
migration_status, _ = Gitlab::Popen.popen(%W(bundle exec rake db:migrate:status))
|
||||
|
||||
unless migration_status =~ /down\s+\d{14}/
|
||||
puts "yes".green
|
||||
puts "yes".color(:green)
|
||||
else
|
||||
puts "no".red
|
||||
puts "no".color(:red)
|
||||
try_fixing_it(
|
||||
sudo_gitlab("bundle exec rake db:migrate RAILS_ENV=production")
|
||||
)
|
||||
|
@ -210,13 +210,13 @@ namespace :gitlab do
|
|||
def check_orphaned_group_members
|
||||
print "Database contains orphaned GroupMembers? ... "
|
||||
if GroupMember.where("user_id not in (select id from users)").count > 0
|
||||
puts "yes".red
|
||||
puts "yes".color(:red)
|
||||
try_fixing_it(
|
||||
"You can delete the orphaned records using something along the lines of:",
|
||||
sudo_gitlab("bundle exec rails runner -e production 'GroupMember.where(\"user_id NOT IN (SELECT id FROM users)\").delete_all'")
|
||||
)
|
||||
else
|
||||
puts "no".green
|
||||
puts "no".color(:green)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -226,9 +226,9 @@ namespace :gitlab do
|
|||
log_path = Rails.root.join("log")
|
||||
|
||||
if File.writable?(log_path)
|
||||
puts "yes".green
|
||||
puts "yes".color(:green)
|
||||
else
|
||||
puts "no".red
|
||||
puts "no".color(:red)
|
||||
try_fixing_it(
|
||||
"sudo chown -R gitlab #{log_path}",
|
||||
"sudo chmod -R u+rwX #{log_path}"
|
||||
|
@ -246,9 +246,9 @@ namespace :gitlab do
|
|||
tmp_path = Rails.root.join("tmp")
|
||||
|
||||
if File.writable?(tmp_path)
|
||||
puts "yes".green
|
||||
puts "yes".color(:green)
|
||||
else
|
||||
puts "no".red
|
||||
puts "no".color(:red)
|
||||
try_fixing_it(
|
||||
"sudo chown -R gitlab #{tmp_path}",
|
||||
"sudo chmod -R u+rwX #{tmp_path}"
|
||||
|
@ -264,7 +264,7 @@ namespace :gitlab do
|
|||
print "Uploads directory setup correctly? ... "
|
||||
|
||||
unless File.directory?(Rails.root.join('public/uploads'))
|
||||
puts "no".red
|
||||
puts "no".color(:red)
|
||||
try_fixing_it(
|
||||
"sudo -u #{gitlab_user} mkdir #{Rails.root}/public/uploads"
|
||||
)
|
||||
|
@ -280,16 +280,16 @@ namespace :gitlab do
|
|||
|
||||
if File.stat(upload_path).mode == 040700
|
||||
unless Dir.exists?(upload_path_tmp)
|
||||
puts 'skipped (no tmp uploads folder yet)'.magenta
|
||||
puts 'skipped (no tmp uploads folder yet)'.color(:magenta)
|
||||
return
|
||||
end
|
||||
|
||||
# If tmp upload dir has incorrect permissions, assume others do as well
|
||||
# Verify drwx------ permissions
|
||||
if File.stat(upload_path_tmp).mode == 040700 && File.owned?(upload_path_tmp)
|
||||
puts "yes".green
|
||||
puts "yes".color(:green)
|
||||
else
|
||||
puts "no".red
|
||||
puts "no".color(:red)
|
||||
try_fixing_it(
|
||||
"sudo chown -R #{gitlab_user} #{upload_path}",
|
||||
"sudo find #{upload_path} -type f -exec chmod 0644 {} \\;",
|
||||
|
@ -301,7 +301,7 @@ namespace :gitlab do
|
|||
fix_and_rerun
|
||||
end
|
||||
else
|
||||
puts "no".red
|
||||
puts "no".color(:red)
|
||||
try_fixing_it(
|
||||
"sudo chmod 700 #{upload_path}"
|
||||
)
|
||||
|
@ -320,9 +320,9 @@ namespace :gitlab do
|
|||
redis_version = redis_version.try(:match, /redis-cli (\d+\.\d+\.\d+)/)
|
||||
if redis_version &&
|
||||
(Gem::Version.new(redis_version[1]) > Gem::Version.new(min_redis_version))
|
||||
puts "yes".green
|
||||
puts "yes".color(:green)
|
||||
else
|
||||
puts "no".red
|
||||
puts "no".color(:red)
|
||||
try_fixing_it(
|
||||
"Update your redis server to a version >= #{min_redis_version}"
|
||||
)
|
||||
|
@ -361,10 +361,10 @@ namespace :gitlab do
|
|||
repo_base_path = Gitlab.config.gitlab_shell.repos_path
|
||||
|
||||
if File.exists?(repo_base_path)
|
||||
puts "yes".green
|
||||
puts "yes".color(:green)
|
||||
else
|
||||
puts "no".red
|
||||
puts "#{repo_base_path} is missing".red
|
||||
puts "no".color(:red)
|
||||
puts "#{repo_base_path} is missing".color(:red)
|
||||
try_fixing_it(
|
||||
"This should have been created when setting up GitLab Shell.",
|
||||
"Make sure it's set correctly in config/gitlab.yml",
|
||||
|
@ -382,14 +382,14 @@ namespace :gitlab do
|
|||
|
||||
repo_base_path = Gitlab.config.gitlab_shell.repos_path
|
||||
unless File.exists?(repo_base_path)
|
||||
puts "can't check because of previous errors".magenta
|
||||
puts "can't check because of previous errors".color(:magenta)
|
||||
return
|
||||
end
|
||||
|
||||
unless File.symlink?(repo_base_path)
|
||||
puts "no".green
|
||||
puts "no".color(:green)
|
||||
else
|
||||
puts "yes".red
|
||||
puts "yes".color(:red)
|
||||
try_fixing_it(
|
||||
"Make sure it's set to the real directory in config/gitlab.yml"
|
||||
)
|
||||
|
@ -402,14 +402,14 @@ namespace :gitlab do
|
|||
|
||||
repo_base_path = Gitlab.config.gitlab_shell.repos_path
|
||||
unless File.exists?(repo_base_path)
|
||||
puts "can't check because of previous errors".magenta
|
||||
puts "can't check because of previous errors".color(:magenta)
|
||||
return
|
||||
end
|
||||
|
||||
if File.stat(repo_base_path).mode.to_s(8).ends_with?("2770")
|
||||
puts "yes".green
|
||||
puts "yes".color(:green)
|
||||
else
|
||||
puts "no".red
|
||||
puts "no".color(:red)
|
||||
try_fixing_it(
|
||||
"sudo chmod -R ug+rwX,o-rwx #{repo_base_path}",
|
||||
"sudo chmod -R ug-s #{repo_base_path}",
|
||||
|
@ -429,17 +429,17 @@ namespace :gitlab do
|
|||
|
||||
repo_base_path = Gitlab.config.gitlab_shell.repos_path
|
||||
unless File.exists?(repo_base_path)
|
||||
puts "can't check because of previous errors".magenta
|
||||
puts "can't check because of previous errors".color(:magenta)
|
||||
return
|
||||
end
|
||||
|
||||
uid = uid_for(gitlab_shell_ssh_user)
|
||||
gid = gid_for(gitlab_shell_owner_group)
|
||||
if File.stat(repo_base_path).uid == uid && File.stat(repo_base_path).gid == gid
|
||||
puts "yes".green
|
||||
puts "yes".color(:green)
|
||||
else
|
||||
puts "no".red
|
||||
puts " User id for #{gitlab_shell_ssh_user}: #{uid}. Groupd id for #{gitlab_shell_owner_group}: #{gid}".blue
|
||||
puts "no".color(:red)
|
||||
puts " User id for #{gitlab_shell_ssh_user}: #{uid}. Groupd id for #{gitlab_shell_owner_group}: #{gid}".color(:blue)
|
||||
try_fixing_it(
|
||||
"sudo chown -R #{gitlab_shell_ssh_user}:#{gitlab_shell_owner_group} #{repo_base_path}"
|
||||
)
|
||||
|
@ -456,7 +456,7 @@ namespace :gitlab do
|
|||
gitlab_shell_hooks_path = Gitlab.config.gitlab_shell.hooks_path
|
||||
|
||||
unless Project.count > 0
|
||||
puts "can't check, you have no projects".magenta
|
||||
puts "can't check, you have no projects".color(:magenta)
|
||||
return
|
||||
end
|
||||
puts ""
|
||||
|
@ -466,12 +466,12 @@ namespace :gitlab do
|
|||
project_hook_directory = File.join(project.repository.path_to_repo, "hooks")
|
||||
|
||||
if project.empty_repo?
|
||||
puts "repository is empty".magenta
|
||||
puts "repository is empty".color(:magenta)
|
||||
elsif File.directory?(project_hook_directory) && File.directory?(gitlab_shell_hooks_path) &&
|
||||
(File.realpath(project_hook_directory) == File.realpath(gitlab_shell_hooks_path))
|
||||
puts 'ok'.green
|
||||
puts 'ok'.color(:green)
|
||||
else
|
||||
puts "wrong or missing hooks".red
|
||||
puts "wrong or missing hooks".color(:red)
|
||||
try_fixing_it(
|
||||
sudo_gitlab("#{File.join(gitlab_shell_path, 'bin/create-hooks')}"),
|
||||
'Check the hooks_path in config/gitlab.yml',
|
||||
|
@ -491,9 +491,9 @@ namespace :gitlab do
|
|||
check_cmd = File.expand_path('bin/check', gitlab_shell_repo_base)
|
||||
puts "Running #{check_cmd}"
|
||||
if system(check_cmd, chdir: gitlab_shell_repo_base)
|
||||
puts 'gitlab-shell self-check successful'.green
|
||||
puts 'gitlab-shell self-check successful'.color(:green)
|
||||
else
|
||||
puts 'gitlab-shell self-check failed'.red
|
||||
puts 'gitlab-shell self-check failed'.color(:red)
|
||||
try_fixing_it(
|
||||
'Make sure GitLab is running;',
|
||||
'Check the gitlab-shell configuration file:',
|
||||
|
@ -507,7 +507,7 @@ namespace :gitlab do
|
|||
print "projects have namespace: ... "
|
||||
|
||||
unless Project.count > 0
|
||||
puts "can't check, you have no projects".magenta
|
||||
puts "can't check, you have no projects".color(:magenta)
|
||||
return
|
||||
end
|
||||
puts ""
|
||||
|
@ -516,9 +516,9 @@ namespace :gitlab do
|
|||
print sanitized_message(project)
|
||||
|
||||
if project.namespace
|
||||
puts "yes".green
|
||||
puts "yes".color(:green)
|
||||
else
|
||||
puts "no".red
|
||||
puts "no".color(:red)
|
||||
try_fixing_it(
|
||||
"Migrate global projects"
|
||||
)
|
||||
|
@ -576,9 +576,9 @@ namespace :gitlab do
|
|||
print "Running? ... "
|
||||
|
||||
if sidekiq_process_count > 0
|
||||
puts "yes".green
|
||||
puts "yes".color(:green)
|
||||
else
|
||||
puts "no".red
|
||||
puts "no".color(:red)
|
||||
try_fixing_it(
|
||||
sudo_gitlab("RAILS_ENV=production bin/background_jobs start")
|
||||
)
|
||||
|
@ -596,9 +596,9 @@ namespace :gitlab do
|
|||
|
||||
print 'Number of Sidekiq processes ... '
|
||||
if process_count == 1
|
||||
puts '1'.green
|
||||
puts '1'.color(:green)
|
||||
else
|
||||
puts "#{process_count}".red
|
||||
puts "#{process_count}".color(:red)
|
||||
try_fixing_it(
|
||||
'sudo service gitlab stop',
|
||||
"sudo pkill -u #{gitlab_user} -f sidekiq",
|
||||
|
@ -646,16 +646,16 @@ namespace :gitlab do
|
|||
print "Init.d configured correctly? ... "
|
||||
|
||||
if omnibus_gitlab?
|
||||
puts 'skipped (omnibus-gitlab has no init script)'.magenta
|
||||
puts 'skipped (omnibus-gitlab has no init script)'.color(:magenta)
|
||||
return
|
||||
end
|
||||
|
||||
path = "/etc/default/gitlab"
|
||||
|
||||
if File.exist?(path) && File.read(path).include?("mail_room_enabled=true")
|
||||
puts "yes".green
|
||||
puts "yes".color(:green)
|
||||
else
|
||||
puts "no".red
|
||||
puts "no".color(:red)
|
||||
try_fixing_it(
|
||||
"Enable mail_room in the init.d configuration."
|
||||
)
|
||||
|
@ -672,9 +672,9 @@ namespace :gitlab do
|
|||
path = Rails.root.join("Procfile")
|
||||
|
||||
if File.exist?(path) && File.read(path) =~ /^mail_room:/
|
||||
puts "yes".green
|
||||
puts "yes".color(:green)
|
||||
else
|
||||
puts "no".red
|
||||
puts "no".color(:red)
|
||||
try_fixing_it(
|
||||
"Enable mail_room in your Procfile."
|
||||
)
|
||||
|
@ -691,14 +691,14 @@ namespace :gitlab do
|
|||
path = "/etc/default/gitlab"
|
||||
|
||||
unless File.exist?(path) && File.read(path).include?("mail_room_enabled=true")
|
||||
puts "can't check because of previous errors".magenta
|
||||
puts "can't check because of previous errors".color(:magenta)
|
||||
return
|
||||
end
|
||||
|
||||
if mail_room_running?
|
||||
puts "yes".green
|
||||
puts "yes".color(:green)
|
||||
else
|
||||
puts "no".red
|
||||
puts "no".color(:red)
|
||||
try_fixing_it(
|
||||
sudo_gitlab("RAILS_ENV=production bin/mail_room start")
|
||||
)
|
||||
|
@ -729,9 +729,9 @@ namespace :gitlab do
|
|||
end
|
||||
|
||||
if connected
|
||||
puts "yes".green
|
||||
puts "yes".color(:green)
|
||||
else
|
||||
puts "no".red
|
||||
puts "no".color(:red)
|
||||
try_fixing_it(
|
||||
"Check that the information in config/gitlab.yml is correct"
|
||||
)
|
||||
|
@ -799,7 +799,7 @@ namespace :gitlab do
|
|||
namespace :user do
|
||||
desc "GitLab | Check the integrity of a specific user's repositories"
|
||||
task :check_repos, [:username] => :environment do |t, args|
|
||||
username = args[:username] || prompt("Check repository integrity for which username? ".blue)
|
||||
username = args[:username] || prompt("Check repository integrity for which username? ".color(:blue))
|
||||
user = User.find_by(username: username)
|
||||
if user
|
||||
repo_dirs = user.authorized_projects.map do |p|
|
||||
|
@ -811,7 +811,7 @@ namespace :gitlab do
|
|||
|
||||
repo_dirs.each { |repo_dir| check_repo_integrity(repo_dir) }
|
||||
else
|
||||
puts "\nUser '#{username}' not found".red
|
||||
puts "\nUser '#{username}' not found".color(:red)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -820,13 +820,13 @@ namespace :gitlab do
|
|||
##########################
|
||||
|
||||
def fix_and_rerun
|
||||
puts " Please #{"fix the error above"} and rerun the checks.".red
|
||||
puts " Please #{"fix the error above"} and rerun the checks.".color(:red)
|
||||
end
|
||||
|
||||
def for_more_information(*sources)
|
||||
sources = sources.shift if sources.first.is_a?(Array)
|
||||
|
||||
puts " For more information see:".blue
|
||||
puts " For more information see:".color(:blue)
|
||||
sources.each do |source|
|
||||
puts " #{source}"
|
||||
end
|
||||
|
@ -834,7 +834,7 @@ namespace :gitlab do
|
|||
|
||||
def finished_checking(component)
|
||||
puts ""
|
||||
puts "Checking #{component.yellow} ... #{"Finished".green}"
|
||||
puts "Checking #{component.color(:yellow)} ... #{"Finished".color(:green)}"
|
||||
puts ""
|
||||
end
|
||||
|
||||
|
@ -855,14 +855,14 @@ namespace :gitlab do
|
|||
end
|
||||
|
||||
def start_checking(component)
|
||||
puts "Checking #{component.yellow} ..."
|
||||
puts "Checking #{component.color(:yellow)} ..."
|
||||
puts ""
|
||||
end
|
||||
|
||||
def try_fixing_it(*steps)
|
||||
steps = steps.shift if steps.first.is_a?(Array)
|
||||
|
||||
puts " Try fixing it:".blue
|
||||
puts " Try fixing it:".color(:blue)
|
||||
steps.each do |step|
|
||||
puts " #{step}"
|
||||
end
|
||||
|
@ -874,9 +874,9 @@ namespace :gitlab do
|
|||
|
||||
print "GitLab Shell version >= #{required_version} ? ... "
|
||||
if current_version.valid? && required_version <= current_version
|
||||
puts "OK (#{current_version})".green
|
||||
puts "OK (#{current_version})".color(:green)
|
||||
else
|
||||
puts "FAIL. Please update gitlab-shell to #{required_version} from #{current_version}".red
|
||||
puts "FAIL. Please update gitlab-shell to #{required_version} from #{current_version}".color(:red)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -887,9 +887,9 @@ namespace :gitlab do
|
|||
print "Ruby version >= #{required_version} ? ... "
|
||||
|
||||
if current_version.valid? && required_version <= current_version
|
||||
puts "yes (#{current_version})".green
|
||||
puts "yes (#{current_version})".color(:green)
|
||||
else
|
||||
puts "no".red
|
||||
puts "no".color(:red)
|
||||
try_fixing_it(
|
||||
"Update your ruby to a version >= #{required_version} from #{current_version}"
|
||||
)
|
||||
|
@ -905,9 +905,9 @@ namespace :gitlab do
|
|||
print "Git version >= #{required_version} ? ... "
|
||||
|
||||
if current_version.valid? && required_version <= current_version
|
||||
puts "yes (#{current_version})".green
|
||||
puts "yes (#{current_version})".color(:green)
|
||||
else
|
||||
puts "no".red
|
||||
puts "no".color(:red)
|
||||
try_fixing_it(
|
||||
"Update your git to a version >= #{required_version} from #{current_version}"
|
||||
)
|
||||
|
@ -925,9 +925,9 @@ namespace :gitlab do
|
|||
|
||||
def sanitized_message(project)
|
||||
if should_sanitize?
|
||||
"#{project.namespace_id.to_s.yellow}/#{project.id.to_s.yellow} ... "
|
||||
"#{project.namespace_id.to_s.color(:yellow)}/#{project.id.to_s.color(:yellow)} ... "
|
||||
else
|
||||
"#{project.name_with_namespace.yellow} ... "
|
||||
"#{project.name_with_namespace.color(:yellow)} ... "
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -940,7 +940,7 @@ namespace :gitlab do
|
|||
end
|
||||
|
||||
def check_repo_integrity(repo_dir)
|
||||
puts "\nChecking repo at #{repo_dir.yellow}"
|
||||
puts "\nChecking repo at #{repo_dir.color(:yellow)}"
|
||||
|
||||
git_fsck(repo_dir)
|
||||
check_config_lock(repo_dir)
|
||||
|
@ -948,25 +948,25 @@ namespace :gitlab do
|
|||
end
|
||||
|
||||
def git_fsck(repo_dir)
|
||||
puts "Running `git fsck`".yellow
|
||||
puts "Running `git fsck`".color(:yellow)
|
||||
system(*%W(#{Gitlab.config.git.bin_path} fsck), chdir: repo_dir)
|
||||
end
|
||||
|
||||
def check_config_lock(repo_dir)
|
||||
config_exists = File.exist?(File.join(repo_dir,'config.lock'))
|
||||
config_output = config_exists ? 'yes'.red : 'no'.green
|
||||
puts "'config.lock' file exists?".yellow + " ... #{config_output}"
|
||||
config_output = config_exists ? 'yes'.color(:red) : 'no'.color(:green)
|
||||
puts "'config.lock' file exists?".color(:yellow) + " ... #{config_output}"
|
||||
end
|
||||
|
||||
def check_ref_locks(repo_dir)
|
||||
lock_files = Dir.glob(File.join(repo_dir,'refs/heads/*.lock'))
|
||||
if lock_files.present?
|
||||
puts "Ref lock files exist:".red
|
||||
puts "Ref lock files exist:".color(:red)
|
||||
lock_files.each do |lock_file|
|
||||
puts " #{lock_file}"
|
||||
end
|
||||
else
|
||||
puts "No ref lock files exist".green
|
||||
puts "No ref lock files exist".color(:green)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace :gitlab do
|
|||
git_base_path = Gitlab.config.gitlab_shell.repos_path
|
||||
all_dirs = Dir.glob(git_base_path + '/*')
|
||||
|
||||
puts git_base_path.yellow
|
||||
puts git_base_path.color(:yellow)
|
||||
puts "Looking for directories to remove... "
|
||||
|
||||
all_dirs.reject! do |dir|
|
||||
|
@ -29,17 +29,17 @@ namespace :gitlab do
|
|||
|
||||
if remove_flag
|
||||
if FileUtils.rm_rf dir_path
|
||||
puts "Removed...#{dir_path}".red
|
||||
puts "Removed...#{dir_path}".color(:red)
|
||||
else
|
||||
puts "Cannot remove #{dir_path}".red
|
||||
puts "Cannot remove #{dir_path}".color(:red)
|
||||
end
|
||||
else
|
||||
puts "Can be removed: #{dir_path}".red
|
||||
puts "Can be removed: #{dir_path}".color(:red)
|
||||
end
|
||||
end
|
||||
|
||||
unless remove_flag
|
||||
puts "To cleanup this directories run this command with REMOVE=true".yellow
|
||||
puts "To cleanup this directories run this command with REMOVE=true".color(:yellow)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -75,19 +75,19 @@ namespace :gitlab do
|
|||
next unless user.ldap_user?
|
||||
print "#{user.name} (#{user.ldap_identity.extern_uid}) ..."
|
||||
if Gitlab::LDAP::Access.allowed?(user)
|
||||
puts " [OK]".green
|
||||
puts " [OK]".color(:green)
|
||||
else
|
||||
if block_flag
|
||||
user.block! unless user.blocked?
|
||||
puts " [BLOCKED]".red
|
||||
puts " [BLOCKED]".color(:red)
|
||||
else
|
||||
puts " [NOT IN LDAP]".yellow
|
||||
puts " [NOT IN LDAP]".color(:yellow)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless block_flag
|
||||
puts "To block these users run this command with BLOCK=true".yellow
|
||||
puts "To block these users run this command with BLOCK=true".color(:yellow)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,22 +3,22 @@ namespace :gitlab do
|
|||
desc 'GitLab | Manually insert schema migration version'
|
||||
task :mark_migration_complete, [:version] => :environment do |_, args|
|
||||
unless args[:version]
|
||||
puts "Must specify a migration version as an argument".red
|
||||
puts "Must specify a migration version as an argument".color(:red)
|
||||
exit 1
|
||||
end
|
||||
|
||||
version = args[:version].to_i
|
||||
if version == 0
|
||||
puts "Version '#{args[:version]}' must be a non-zero integer".red
|
||||
puts "Version '#{args[:version]}' must be a non-zero integer".color(:red)
|
||||
exit 1
|
||||
end
|
||||
|
||||
sql = "INSERT INTO schema_migrations (version) VALUES (#{version})"
|
||||
begin
|
||||
ActiveRecord::Base.connection.execute(sql)
|
||||
puts "Successfully marked '#{version}' as complete".green
|
||||
puts "Successfully marked '#{version}' as complete".color(:green)
|
||||
rescue ActiveRecord::RecordNotUnique
|
||||
puts "Migration version '#{version}' is already marked complete".yellow
|
||||
puts "Migration version '#{version}' is already marked complete".color(:yellow)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ namespace :gitlab do
|
|||
task repack: :environment do
|
||||
failures = perform_git_cmd(%W(git repack -a --quiet), "Repacking repo")
|
||||
if failures.empty?
|
||||
puts "Done".green
|
||||
puts "Done".color(:green)
|
||||
else
|
||||
output_failures(failures)
|
||||
end
|
||||
|
@ -15,7 +15,7 @@ namespace :gitlab do
|
|||
task gc: :environment do
|
||||
failures = perform_git_cmd(%W(git gc --auto --quiet), "Garbage Collecting")
|
||||
if failures.empty?
|
||||
puts "Done".green
|
||||
puts "Done".color(:green)
|
||||
else
|
||||
output_failures(failures)
|
||||
end
|
||||
|
@ -25,7 +25,7 @@ namespace :gitlab do
|
|||
task prune: :environment do
|
||||
failures = perform_git_cmd(%W(git prune), "Git Prune")
|
||||
if failures.empty?
|
||||
puts "Done".green
|
||||
puts "Done".color(:green)
|
||||
else
|
||||
output_failures(failures)
|
||||
end
|
||||
|
@ -47,7 +47,7 @@ namespace :gitlab do
|
|||
end
|
||||
|
||||
def output_failures(failures)
|
||||
puts "The following repositories reported errors:".red
|
||||
puts "The following repositories reported errors:".color(:red)
|
||||
failures.each { |f| puts "- #{f}" }
|
||||
end
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ namespace :gitlab do
|
|||
group_name, name = File.split(path)
|
||||
group_name = nil if group_name == '.'
|
||||
|
||||
puts "Processing #{repo_path}".yellow
|
||||
puts "Processing #{repo_path}".color(:yellow)
|
||||
|
||||
if path.end_with?('.wiki')
|
||||
puts " * Skipping wiki repo"
|
||||
|
@ -51,9 +51,9 @@ namespace :gitlab do
|
|||
group.path = group_name
|
||||
group.owner = user
|
||||
if group.save
|
||||
puts " * Created Group #{group.name} (#{group.id})".green
|
||||
puts " * Created Group #{group.name} (#{group.id})".color(:green)
|
||||
else
|
||||
puts " * Failed trying to create group #{group.name}".red
|
||||
puts " * Failed trying to create group #{group.name}".color(:red)
|
||||
end
|
||||
end
|
||||
# set project group
|
||||
|
@ -63,17 +63,17 @@ namespace :gitlab do
|
|||
project = Projects::CreateService.new(user, project_params).execute
|
||||
|
||||
if project.persisted?
|
||||
puts " * Created #{project.name} (#{repo_path})".green
|
||||
puts " * Created #{project.name} (#{repo_path})".color(:green)
|
||||
project.update_repository_size
|
||||
project.update_commit_count
|
||||
else
|
||||
puts " * Failed trying to create #{project.name} (#{repo_path})".red
|
||||
puts " Errors: #{project.errors.messages}".red
|
||||
puts " * Failed trying to create #{project.name} (#{repo_path})".color(:red)
|
||||
puts " Errors: #{project.errors.messages}".color(:red)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
puts "Done!".green
|
||||
puts "Done!".color(:green)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -15,15 +15,15 @@ namespace :gitlab do
|
|||
rake_version = run_and_match(%W(rake --version), /[\d\.]+/).try(:to_s)
|
||||
|
||||
puts ""
|
||||
puts "System information".yellow
|
||||
puts "System:\t\t#{os_name || "unknown".red}"
|
||||
puts "System information".color(:yellow)
|
||||
puts "System:\t\t#{os_name || "unknown".color(:red)}"
|
||||
puts "Current User:\t#{run(%W(whoami))}"
|
||||
puts "Using RVM:\t#{rvm_version.present? ? "yes".green : "no"}"
|
||||
puts "Using RVM:\t#{rvm_version.present? ? "yes".color(:green) : "no"}"
|
||||
puts "RVM Version:\t#{rvm_version}" if rvm_version.present?
|
||||
puts "Ruby Version:\t#{ruby_version || "unknown".red}"
|
||||
puts "Gem Version:\t#{gem_version || "unknown".red}"
|
||||
puts "Bundler Version:#{bunder_version || "unknown".red}"
|
||||
puts "Rake Version:\t#{rake_version || "unknown".red}"
|
||||
puts "Ruby Version:\t#{ruby_version || "unknown".color(:red)}"
|
||||
puts "Gem Version:\t#{gem_version || "unknown".color(:red)}"
|
||||
puts "Bundler Version:#{bunder_version || "unknown".color(:red)}"
|
||||
puts "Rake Version:\t#{rake_version || "unknown".color(:red)}"
|
||||
puts "Sidekiq Version:#{Sidekiq::VERSION}"
|
||||
|
||||
|
||||
|
@ -39,7 +39,7 @@ namespace :gitlab do
|
|||
omniauth_providers.map! { |provider| provider['name'] }
|
||||
|
||||
puts ""
|
||||
puts "GitLab information".yellow
|
||||
puts "GitLab information".color(:yellow)
|
||||
puts "Version:\t#{Gitlab::VERSION}"
|
||||
puts "Revision:\t#{Gitlab::REVISION}"
|
||||
puts "Directory:\t#{Rails.root}"
|
||||
|
@ -47,9 +47,9 @@ namespace :gitlab do
|
|||
puts "URL:\t\t#{Gitlab.config.gitlab.url}"
|
||||
puts "HTTP Clone URL:\t#{http_clone_url}"
|
||||
puts "SSH Clone URL:\t#{ssh_clone_url}"
|
||||
puts "Using LDAP:\t#{Gitlab.config.ldap.enabled ? "yes".green : "no"}"
|
||||
puts "Using Omniauth:\t#{Gitlab.config.omniauth.enabled ? "yes".green : "no"}"
|
||||
puts "Omniauth Providers: #{omniauth_providers.map(&:magenta).join(', ')}" if Gitlab.config.omniauth.enabled
|
||||
puts "Using LDAP:\t#{Gitlab.config.ldap.enabled ? "yes".color(:green) : "no"}"
|
||||
puts "Using Omniauth:\t#{Gitlab.config.omniauth.enabled ? "yes".color(:green) : "no"}"
|
||||
puts "Omniauth Providers: #{omniauth_providers.join(', ')}" if Gitlab.config.omniauth.enabled
|
||||
|
||||
|
||||
|
||||
|
@ -60,8 +60,8 @@ namespace :gitlab do
|
|||
end
|
||||
|
||||
puts ""
|
||||
puts "GitLab Shell".yellow
|
||||
puts "Version:\t#{gitlab_shell_version || "unknown".red}"
|
||||
puts "GitLab Shell".color(:yellow)
|
||||
puts "Version:\t#{gitlab_shell_version || "unknown".color(:red)}"
|
||||
puts "Repositories:\t#{Gitlab.config.gitlab_shell.repos_path}"
|
||||
puts "Hooks:\t\t#{Gitlab.config.gitlab_shell.hooks_path}"
|
||||
puts "Git:\t\t#{Gitlab.config.git.bin_path}"
|
||||
|
|
|
@ -118,12 +118,12 @@ namespace :gitlab do
|
|||
puts ""
|
||||
|
||||
unless $?.success?
|
||||
puts "Failed to add keys...".red
|
||||
puts "Failed to add keys...".color(:red)
|
||||
exit 1
|
||||
end
|
||||
|
||||
rescue Gitlab::TaskAbortedByUserError
|
||||
puts "Quitting...".red
|
||||
puts "Quitting...".color(:red)
|
||||
exit 1
|
||||
end
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ module Gitlab
|
|||
class TaskAbortedByUserError < StandardError; end
|
||||
end
|
||||
|
||||
String.disable_colorization = true unless STDOUT.isatty
|
||||
require 'rainbow/ext/string'
|
||||
|
||||
# Prevent StateMachine warnings from outputting during a cron task
|
||||
StateMachines::Machine.ignore_method_conflicts = true if ENV['CRON']
|
||||
|
@ -14,7 +14,7 @@ namespace :gitlab do
|
|||
# Returns "yes" the user chose to continue
|
||||
# Raises Gitlab::TaskAbortedByUserError if the user chose *not* to continue
|
||||
def ask_to_continue
|
||||
answer = prompt("Do you want to continue (yes/no)? ".blue, %w{yes no})
|
||||
answer = prompt("Do you want to continue (yes/no)? ".color(:blue), %w{yes no})
|
||||
raise Gitlab::TaskAbortedByUserError unless answer == "yes"
|
||||
end
|
||||
|
||||
|
@ -98,10 +98,10 @@ namespace :gitlab do
|
|||
gitlab_user = Gitlab.config.gitlab.user
|
||||
current_user = run(%W(whoami)).chomp
|
||||
unless current_user == gitlab_user
|
||||
puts " Warning ".colorize(:black).on_yellow
|
||||
puts " You are running as user #{current_user.magenta}, we hope you know what you are doing."
|
||||
puts " Warning ".color(:black).background(:yellow)
|
||||
puts " You are running as user #{current_user.color(:magenta)}, we hope you know what you are doing."
|
||||
puts " Things may work\/fail for the wrong reasons."
|
||||
puts " For correct results you should run this as user #{gitlab_user.magenta}."
|
||||
puts " For correct results you should run this as user #{gitlab_user.color(:magenta)}."
|
||||
puts ""
|
||||
end
|
||||
@warned_user_not_gitlab = true
|
||||
|
|
|
@ -6,17 +6,17 @@ namespace :gitlab do
|
|||
count = scope.count
|
||||
|
||||
if count > 0
|
||||
puts "This will disable 2FA for #{count.to_s.red} users..."
|
||||
puts "This will disable 2FA for #{count.to_s.color(:red)} users..."
|
||||
|
||||
begin
|
||||
ask_to_continue
|
||||
scope.find_each(&:disable_two_factor!)
|
||||
puts "Successfully disabled 2FA for #{count} users.".green
|
||||
puts "Successfully disabled 2FA for #{count} users.".color(:green)
|
||||
rescue Gitlab::TaskAbortedByUserError
|
||||
puts "Quitting...".red
|
||||
puts "Quitting...".color(:red)
|
||||
end
|
||||
else
|
||||
puts "There are currently no users with 2FA enabled.".yellow
|
||||
puts "There are currently no users with 2FA enabled.".color(:yellow)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,15 +6,15 @@ namespace :gitlab do
|
|||
ask_to_continue unless ENV['force'] == 'yes'
|
||||
|
||||
projects.find_each(batch_size: 100) do |project|
|
||||
print "#{project.name_with_namespace.yellow} ... "
|
||||
print "#{project.name_with_namespace.color(:yellow)} ... "
|
||||
|
||||
unless project.repo_exists?
|
||||
puts "skipping, because the repo is empty".magenta
|
||||
puts "skipping, because the repo is empty".color(:magenta)
|
||||
next
|
||||
end
|
||||
|
||||
project.update_commit_count
|
||||
puts project.commit_count.to_s.green
|
||||
puts project.commit_count.to_s.color(:green)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,14 +2,14 @@ namespace :gitlab do
|
|||
desc "GitLab | Update gitignore"
|
||||
task :update_gitignore do
|
||||
unless clone_gitignores
|
||||
puts "Cloning the gitignores failed".red
|
||||
puts "Cloning the gitignores failed".color(:red)
|
||||
return
|
||||
end
|
||||
|
||||
remove_unneeded_files(gitignore_directory)
|
||||
remove_unneeded_files(global_directory)
|
||||
|
||||
puts "Done".green
|
||||
puts "Done".color(:green)
|
||||
end
|
||||
|
||||
def clone_gitignores
|
||||
|
|
|
@ -12,9 +12,9 @@ namespace :gitlab do
|
|||
print "- #{project.name} ... "
|
||||
web_hook = project.hooks.new(url: web_hook_url)
|
||||
if web_hook.save
|
||||
puts "added".green
|
||||
puts "added".color(:green)
|
||||
else
|
||||
print "failed".red
|
||||
print "failed".color(:red)
|
||||
puts " [#{web_hook.errors.full_messages.to_sentence}]"
|
||||
end
|
||||
end
|
||||
|
@ -57,7 +57,7 @@ namespace :gitlab do
|
|||
if namespace
|
||||
Project.in_namespace(namespace.id)
|
||||
else
|
||||
puts "Namespace not found: #{namespace_path}".red
|
||||
puts "Namespace not found: #{namespace_path}".color(:red)
|
||||
exit 2
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
desc "GitLab | Build internal ids for issues and merge requests"
|
||||
task migrate_iids: :environment do
|
||||
puts 'Issues'.yellow
|
||||
puts 'Issues'.color(:yellow)
|
||||
Issue.where(iid: nil).find_each(batch_size: 100) do |issue|
|
||||
begin
|
||||
issue.set_iid
|
||||
|
@ -15,7 +15,7 @@ task migrate_iids: :environment do
|
|||
end
|
||||
|
||||
puts 'done'
|
||||
puts 'Merge Requests'.yellow
|
||||
puts 'Merge Requests'.color(:yellow)
|
||||
MergeRequest.where(iid: nil).find_each(batch_size: 100) do |mr|
|
||||
begin
|
||||
mr.set_iid
|
||||
|
@ -30,7 +30,7 @@ task migrate_iids: :environment do
|
|||
end
|
||||
|
||||
puts 'done'
|
||||
puts 'Milestones'.yellow
|
||||
puts 'Milestones'.color(:yellow)
|
||||
Milestone.where(iid: nil).find_each(batch_size: 100) do |m|
|
||||
begin
|
||||
m.set_iid
|
||||
|
|
|
@ -52,7 +52,7 @@ def run_spinach_tests(tags)
|
|||
|
||||
tests = File.foreach('tmp/spinach-rerun.txt').map(&:chomp)
|
||||
puts ''
|
||||
puts "Spinach tests for #{tags}: Retrying tests... #{tests}".red
|
||||
puts "Spinach tests for #{tags}: Retrying tests... #{tests}".color(:red)
|
||||
puts ''
|
||||
sleep(3)
|
||||
success = run_spinach_command(tests)
|
||||
|
|
|
@ -31,9 +31,9 @@ describe GroupsController do
|
|||
let(:issue_2) { create(:issue, project: project) }
|
||||
|
||||
before do
|
||||
create_list(:upvote_note, 3, project: project, noteable: issue_2)
|
||||
create_list(:upvote_note, 2, project: project, noteable: issue_1)
|
||||
create_list(:downvote_note, 2, project: project, noteable: issue_2)
|
||||
create_list(:award_emoji, 3, awardable: issue_2)
|
||||
create_list(:award_emoji, 2, awardable: issue_1)
|
||||
create_list(:award_emoji, 2, :downvote, awardable: issue_2,)
|
||||
|
||||
sign_in(user)
|
||||
end
|
||||
|
@ -56,9 +56,9 @@ describe GroupsController do
|
|||
let(:merge_request_2) { create(:merge_request, :simple, source_project: project) }
|
||||
|
||||
before do
|
||||
create_list(:upvote_note, 3, project: project, noteable: merge_request_2)
|
||||
create_list(:upvote_note, 2, project: project, noteable: merge_request_1)
|
||||
create_list(:downvote_note, 2, project: project, noteable: merge_request_2)
|
||||
create_list(:award_emoji, 3, awardable: merge_request_2)
|
||||
create_list(:award_emoji, 2, awardable: merge_request_1)
|
||||
create_list(:award_emoji, 2, :downvote, awardable: merge_request_2)
|
||||
|
||||
sign_in(user)
|
||||
end
|
||||
|
|
|
@ -250,4 +250,20 @@ describe Projects::IssuesController do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST #toggle_award_emoji' do
|
||||
before do
|
||||
sign_in(user)
|
||||
project.team << [user, :developer]
|
||||
end
|
||||
|
||||
it "toggles the award emoji" do
|
||||
expect do
|
||||
post(:toggle_award_emoji, namespace_id: project.namespace.path,
|
||||
project_id: project.path, id: issue.iid, name: "thumbsup")
|
||||
end.to change { issue.award_emoji.count }.by(1)
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue