Commit b65f4c99 authored by Robert Speicher's avatar Robert Speicher

Merge branch 'ce_upstream' into 'master'

See merge request !107
parents 0a7bea9c 2ba660e4
......@@ -26,6 +26,7 @@ config/initializers/smtp_settings.rb
config/resque.yml
config/unicorn.rb
config/secrets.yml
config/sidekiq.yml
coverage/*
db/*.sqlite3
db/*.sqlite3-journal
......
......@@ -12,6 +12,7 @@ before_script:
spec:feature:
script:
- RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:feature
tags:
- ruby
......
Please view this file on the master branch, on stable branches it's out of date.
v 8.4.0 (unreleased)
- Autocomplete data is now always loaded, instead of when focusing a comment text area (Yorick Peterse)
- Improved performance of finding issues for an entire group (Yorick Peterse)
- Added custom application performance measuring system powered by InfluxDB (Yorick Peterse)
- Bump fog to 1.36.0 (Stan Hu)
- Add user's last used IP addresses to admin page (Stan Hu)
- Add housekeeping function to project settings page
- The default GitLab logo now acts as a loading indicator
- Fix caching issue where build status was not updating in project dashboard (Stan Hu)
......@@ -8,6 +13,7 @@ v 8.4.0 (unreleased)
- Fix missing date of month in network graph when commits span a month (Stan Hu)
- Expire view caches when application settings change (e.g. Gravatar disabled) (Stan Hu)
- Don't notify users twice if they are both project watchers and subscribers (Stan Hu)
- Fix error with file size check with submodules (Stan Hu)
- Implement new UI for group page
- Implement search inside emoji picker
- Add API support for looking up a user by username (Stan Hu)
......@@ -38,6 +44,9 @@ v 8.4.0 (unreleased)
- API: Add support for deleting a tag via the API (Robert Schilling)
- Allow subsequent validations in CI Linter
v 8.3.4
- Use gitlab-workhorse 0.5.4 (fixes API routing bug)
v 8.3.3
- Preserve CE behavior with JIRA integration by only calling API if URL is set
- Fix duplicated branch creation/deletion events when using Web UI (Stan Hu)
......
......@@ -155,6 +155,28 @@ sudo -u git -H bundle exec rake gitlab:env:info)
```
### Issue weight
Issue weight allows us to get an idea of the amount of work required to solve
one or multiple issues. This makes it possible to schedule work more accurately.
You are encouraged to set the weight of any issue. Following the guidelines
below will make it easy to manage this, without unnecessary overhead.
1. Set weight for any issue at the earliest possible convenience
1. If you don't agree with a set weight, discuss with other developers until
consensus is reached about the weight
1. Issue weights are an abstract measurement of complexity of the issue. Do not
relate issue weight directly to time. This is called [anchoring](https://en.wikipedia.org/wiki/Anchoring)
and something you want to avoid.
1. Something that has a weight of 1 (or no weight) is really small and simple.
Something that is 9 is rewriting a large fundamental part of GitLab,
which might lead to many hard problems to solve. Changing some text in GitLab
is probably 1, adding a new Git Hook maybe 4 or 5, big features 7-9.
1. If something is very large, it should probably be split up in multiple
issues or chunks. You can simply not set the weight of a parent issue and set
weights to children issues.
## Merge requests
We welcome merge requests with fixes and improvements to GitLab code, tests,
......
......@@ -22,6 +22,7 @@ gem 'devise', '~> 3.5.3'
gem 'devise-async', '~> 0.9.0'
gem 'doorkeeper', '~> 2.2.0'
gem 'omniauth', '~> 1.2.2'
gem 'omniauth-azure-oauth2', '~> 0.0.6'
gem 'omniauth-bitbucket', '~> 0.0.2'
gem 'omniauth-cas3', '~> 1.1.2'
gem 'omniauth-facebook', '~> 3.0.0'
......@@ -32,10 +33,13 @@ gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos
gem 'omniauth-saml', '~> 1.4.0'
gem 'omniauth-shibboleth', '~> 1.2.0'
gem 'omniauth-twitter', '~> 1.2.0'
gem 'omniauth_crowd'
gem 'omniauth_crowd', '~> 2.2.0'
gem 'gssapi', group: :kerberos
gem 'rack-oauth2', '~> 1.2.1'
# reCAPTCHA protection
gem 'recaptcha', require: 'recaptcha/rails'
# Two-factor authentication
gem 'devise-two-factor', '~> 2.0.0'
gem 'rqrcode-rails3', '~> 0.1.7'
......@@ -46,7 +50,7 @@ gem "browser", '~> 1.0.0'
# Extracting information from a git repository
# Provide access to Gitlab::Git library
gem "gitlab_git", '~> 7.2.20'
gem "gitlab_git", '~> 7.2.22'
# LDAP Auth
# GitLab fork with several improvements to original library. For full list of changes
......@@ -65,13 +69,6 @@ gem 'grape', '~> 0.13.0'
gem 'grape-entity', '~> 0.4.2'
gem 'rack-cors', '~> 0.4.0', require: 'rack/cors'
# Format dates and times
# based on human-friendly examples
gem "stamp", '~> 0.6.0'
# Enumeration fields
gem 'enumerize', '~> 0.7.0'
# Pagination
gem "kaminari", "~> 0.16.3"
......@@ -85,7 +82,7 @@ gem "carrierwave", '~> 0.9.0'
gem 'dropzonejs-rails', '~> 0.7.1'
# for aws storage
gem "fog", "~> 1.25.0"
gem "fog", "~> 1.36.0"
gem "unf", '~> 0.1.4'
# Authorization
......@@ -171,10 +168,10 @@ gem 'asana', '~> 0.4.0'
gem 'ruby-fogbugz', '~> 0.2.1'
# d3
gem 'd3_rails', '~> 3.5.5'
gem 'd3_rails', '~> 3.5.0'
#cal-heatmap
gem "cal-heatmap-rails", "~> 0.0.1"
gem 'cal-heatmap-rails', '~> 3.5.0'
# underscore-rails
gem "underscore-rails", "~> 1.8.0"
......@@ -202,7 +199,7 @@ gem 'turbolinks', '~> 2.5.0'
gem 'jquery-turbolinks', '~> 2.1.0'
gem 'addressable', '~> 2.3.8'
gem 'bootstrap-sass', '~> 3.0'
gem 'bootstrap-sass', '~> 3.3.0'
gem 'font-awesome-rails', '~> 4.2'
gem 'gitlab_emoji', '~> 0.2.0'
gem 'gon', '~> 6.0.1'
......@@ -219,9 +216,17 @@ gem 'net-ssh', '~> 3.0.1'
gem "gitlab-license", "~> 0.0.4"
# Metrics
group :metrics do
gem 'allocations', '~> 1.0', require: false, platform: :mri
gem 'method_source', '~> 0.8', require: false
gem 'influxdb', '~> 0.2', require: false
gem 'connection_pool', '~> 2.0', require: false
end
group :development do
gem "foreman"
gem 'brakeman', '3.0.1', require: false
gem 'brakeman', '~> 3.1.0', require: false
gem "annotate", "~> 2.6.0"
gem "letter_opener", '~> 1.1.2'
......
This diff is collapsed.
Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries.
Copyright 2010, 2012, 2014 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
-----------------------------------------------------------
......
......@@ -10,13 +10,13 @@
#= require jquery.cookie
#= require jquery.endless-scroll
#= require jquery.highlight
#= require jquery.history
#= require jquery.waitforimages
#= require jquery.atwho
#= require jquery.scrollTo
#= require jquery.blockUI
#= require jquery.turbolinks
#= require jquery.tablesorter
#= require d3
#= require cal-heatmap
#= require turbolinks
#= require autosave
#= require bootstrap
......@@ -28,7 +28,6 @@
#= require branch-graph
#= require ace/ace
#= require ace/ext-searchbox
#= require d3
#= require underscore
#= require nprogress
#= require nprogress-turbolinks
......@@ -40,9 +39,9 @@
#= require shortcuts_dashboard_navigation
#= require shortcuts_issuable
#= require shortcuts_network
#= require cal-heatmap
#= require jquery.nicescroll.min
#= require_tree .
#= require fuzzaldrin-plus.min
window.slugify = (text) ->
text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase()
......
......@@ -134,15 +134,16 @@ class @AwardsHandler
_.compact(_.uniq(frequently_used_emojis))
renderFrequentlyUsedBlock: ->
frequently_used_emojis = @getFrequentlyUsedEmojis()
if $.cookie('frequently_used_emojis')
frequently_used_emojis = @getFrequentlyUsedEmojis()
ul = $("<ul>")
ul = $("<ul>")
for emoji in frequently_used_emojis
do (emoji) ->
$(".emoji-menu-content [data-emoji='#{emoji}']").closest("li").clone().appendTo(ul)
for emoji in frequently_used_emojis
do (emoji) ->
$(".emoji-menu-content [data-emoji='#{emoji}']").closest("li").clone().appendTo(ul)
$("input.emoji-search").after(ul).after($("<h5>").text("Frequently used"))
$("input.emoji-search").after(ul).after($("<h5>").text("Frequently used"))
setupSearch: ->
$("input.emoji-search").keyup (ev) =>
......
......@@ -66,7 +66,7 @@ class @BranchGraph
r.rect(40, 0, 30, @barHeight).attr fill: "#444"
for day, mm in @days
if cuday isnt day[0]
if cuday isnt day[0] || cumonth isnt day[1]
# Dates
r.text(55, @offsetY + @unitTime * mm, day[0])
.attr(
......
class @Calendar
options =
month: "short"
day: "numeric"
year: "numeric"
constructor: (timestamps, starting_year, starting_month, calendar_activities_path) ->
cal = new CalHeatMap()
cal.init
......
class @CommitsList
@data =
ref: null
limit: 0
offset: 0
@disable = false
@showProgress: ->
$('.loading').show()
@hideProgress: ->
$('.loading').hide()
@timer = null
@init: (ref, limit) ->
$("body").on "click", ".day-commits-table li.commit", (event) ->
......@@ -18,38 +8,32 @@ class @CommitsList
e.stopPropagation()
return false
@data.ref = ref
@data.limit = limit
@data.offset = limit
Pager.init limit, false
@content = $("#commits-list")
@searchField = $("#commits-search")
@initSearch()
this.initLoadMore()
this.showProgress()
@initSearch: ->
@timer = null
@searchField.keyup =>
clearTimeout(@timer)
@timer = setTimeout(@filterResults, 500)
@filterResults: =>
form = $(".commits-search-form")
search = @searchField.val()
commitsUrl = form.attr("action") + '?' + form.serialize()
@content.fadeTo('fast', 0.5)
@getOld: ->
this.showProgress()
$.ajax
type: "GET"
url: location.href
data: @data
complete: this.hideProgress
success: (data) ->
CommitsList.append(data.count, data.html)
url: form.attr("action")
data: form.serialize()
complete: =>
@content.fadeTo('fast', 1.0)
success: (data) =>
@content.html(data.html)
# Change url so if user reload a page - search results are saved
history.replaceState {page: commitsUrl}, document.title, commitsUrl
dataType: "json"
@append: (count, html) ->
$("#commits-list").append(html)
if count > 0
@data.offset += count
else
@disable = true
@initLoadMore: ->
$(document).unbind('scroll')
$(document).endlessScroll
bottomPixels: 400
fireDelay: 1000
fireOnce: true
ceaseFire: =>
@disable
callback: =>
this.getOld()
......@@ -49,7 +49,7 @@ class Dispatcher
new DropzoneInput($('.release-form'))
when 'projects:merge_requests:show'
new Diff()
shortcut_handler = new ShortcutsIssuable()
shortcut_handler = new ShortcutsIssuable(true)
new ZenMode()
when "projects:merge_requests:diffs"
new Diff()
......@@ -87,7 +87,9 @@ class Dispatcher
new GroupAvatar()
when 'projects:tree:show'
new TreeView()
shortcut_handler = new ShortcutsNavigation()
shortcut_handler = new ShortcutsTree()
when 'projects:find_file:show'
shortcut_handler = true
when 'projects:blob:show'
new LineHighlighter()
shortcut_handler = new ShortcutsNavigation()
......
......@@ -66,7 +66,7 @@ class @DropzoneInput
success: (header, response) ->
child = $(dropzone[0]).children("textarea")
$(child).val $(child).val() + formatLink(response.link) + "\n"
$(child).val $(child).val() + response.link.markdown + "\n"
return
error: (temp, errorMessage) ->
......@@ -99,11 +99,6 @@ class @DropzoneInput
child = $(dropzone[0]).children("textarea")
formatLink = (link) ->
text = "[#{link.alt}](#{link.url})"
text = "!#{text}" if link.is_image
text
handlePaste = (event) ->
pasteEvent = event.originalEvent
if pasteEvent.clipboardData and pasteEvent.clipboardData.items
......@@ -162,7 +157,7 @@ class @DropzoneInput
closeAlertMessage()
success: (e, textStatus, response) ->
insertToTextArea(filename, formatLink(response.responseJSON.link))
insertToTextArea(filename, response.responseJSON.link.markdown)
error: (response) ->
showError(response.responseJSON.message)
......@@ -202,8 +197,3 @@ class @DropzoneInput
e.preventDefault()
$(@).closest('.gfm-form').find('.div-dropzone').click()
return
formatLink: (link) ->
text = "[#{link.alt}](#{link.url})"
text = "!#{text}" if link.is_image
text
......@@ -34,7 +34,7 @@ GitLab.GfmAutoComplete =
searchKey: 'search'
callbacks:
beforeSave: (members) ->
$.map members, (m) ->
$.map members, (m) ->
title = m.name
title += " (#{m.count})" if m.count
......@@ -50,7 +50,7 @@ GitLab.GfmAutoComplete =
insertTpl: '${atwho-at}${id}'
callbacks:
beforeSave: (issues) ->
$.map issues, (i) ->
$.map issues, (i) ->
id: i.iid
title: sanitize(i.title)
search: "#{i.iid} #{i.title}"
......@@ -63,12 +63,12 @@ GitLab.GfmAutoComplete =
insertTpl: '${atwho-at}${id}'
callbacks:
beforeSave: (merges) ->
$.map merges, (m) ->
$.map merges, (m) ->
id: m.iid
title: sanitize(m.title)
search: "#{m.iid} #{m.title}"
input.one 'focus', =>
if @dataSource
$.getJSON(@dataSource).done (data) ->
# load members
input.atwho 'load', '@', data.members
......
#= require flash
#= require jquery.waitforimages
#= require task_list
......@@ -6,13 +7,44 @@ class @Issue
# Prevent duplicate event bindings
@disableTaskList()
if $("a.btn-close").length
if $('a.btn-close').length
@initTaskList()
@initIssueBtnEventListeners()
initTaskList: ->
$('.detail-page-description .js-task-list-container').taskList('enable')
$(document).on 'tasklist:changed', '.detail-page-description .js-task-list-container', @updateTaskList
initIssueBtnEventListeners: ->
issueFailMessage = 'Unable to update this issue at this time.'
$('a.btn-close, a.btn-reopen').on 'click', (e) ->
e.preventDefault()
e.stopImmediatePropagation()
$this = $(this)
isClose = $this.hasClass('btn-close')
$this.prop('disabled', true)
url = $this.attr('href')
$.ajax
type: 'PUT'
url: url,
error: (jqXHR, textStatus, errorThrown) ->
issueStatus = if isClose then 'close' else 'open'
new Flash(issueFailMessage, 'alert')
success: (data, textStatus, jqXHR) ->
if data.saved
$this.addClass('hidden')
if isClose
$('a.btn-reopen').removeClass('hidden')
$('div.status-box-closed').removeClass('hidden')
$('div.status-box-open').addClass('hidden')
else
$('a.btn-close').removeClass('hidden')
$('div.status-box-closed').addClass('hidden')
$('div.status-box-open').removeClass('hidden')
else
new Flash(issueFailMessage, 'alert')
$this.prop('disabled', false)
disableTaskList: ->
$('.detail-page-description .js-task-list-container').taskList('disable')
$(document).off 'tasklist:changed', '.detail-page-description .js-task-list-container'
......
......@@ -15,13 +15,6 @@
$(this).html totalIssues + 1
else
$(this).html totalIssues - 1
$("body").on "click", ".issues-other-filters .dropdown-menu a", ->
$('.issues-list').block(
message: null,
overlayCSS:
backgroundColor: '#DDD'
opacity: .4
)
reload: ->
Issues.initSelects()
......@@ -54,7 +47,7 @@
form = $("#issue_search_form")
search = $("#issue_search").val()
$('.issues-holder').css("opacity", '0.5')
issues_url = form.attr('action') + '? '+ form.serialize()
issues_url = form.attr('action') + '?' + form.serialize()
$.ajax
type: "GET"
......@@ -65,7 +58,7 @@
success: (data) ->
$('.issues-holder').html(data.html)
# Change url so if user reload a page - search results are saved
History.replaceState {page: issues_url}, document.title, issues_url
history.replaceState {page: issues_url}, document.title, issues_url
Issues.reload()
dataType: "json"
......
NProgress.configure(showSpinner: false)
defaultClass = 'tanuki-shape'
pieces = [
'path#tanuki-right-cheek',
'path#tanuki-right-eye, path#tanuki-right-ear',
'path#tanuki-nose',
'path#tanuki-left-eye, path#tanuki-left-ear',
'path#tanuki-left-cheek',
]
pieceIndex = 0
firstPiece = pieces[0]
currentTimer = null
delay = 150
clearHighlights = ->
$(".#{defaultClass}.highlight").attr('class', defaultClass)
start = ->
clearHighlights()
pieceIndex = 0
pieces.reverse() unless pieces[0] == firstPiece
clearInterval(currentTimer) if currentTimer
currentTimer = setInterval(work, delay)
stop = ->
clearInterval(currentTimer)
clearHighlights()
work = ->
clearHighlights()
$(pieces[pieceIndex]).attr('class', "#{defaultClass} highlight")
# If we hit the last piece, reset the index and then reverse the array to
# get a nice back-and-forth sweeping look
if pieceIndex == pieces.length - 1
pieceIndex = 0
pieces.reverse()
else
pieceIndex++
$(document).on('page:fetch', start)
$(document).on('page:change', stop)
......@@ -16,7 +16,7 @@
form = $("#issue_search_form")
search = $("#issue_search").val()
$('.merge-requests-holder').css("opacity", '0.5')
issues_url = form.attr('action') + '? '+ form.serialize()
issues_url = form.attr('action') + '?' + form.serialize()
$.ajax
type: "GET"
......@@ -27,7 +27,7 @@
success: (data) ->
$('.merge-requests-holder').html(data.html)
# Change url so if user reload a page - search results are saved
History.replaceState {page: issues_url}, document.title, issues_url
history.replaceState {page: issues_url}, document.title, issues_url
MergeRequests.reload()
dataType: "json"
......
class @ProjectFindFile
constructor: (@element, @options)->
@filePaths = {}
@inputElement = @element.find(".file-finder-input")
# init event
@initEvent()
# focus text input box
@inputElement.focus()
# load file list
@load(@options.url)
# init event
initEvent: ->
@inputElement.off "keyup"
@inputElement.on "keyup", (event) =>
target = $(event.target)
value = target.val()
oldValue = target.data("oldValue") ? ""
if value != oldValue
target.data("oldValue", value)
@findFile()
@element.find("tr.tree-item").eq(0).addClass("selected").focus()
@element.find(".tree-content-holder .tree-table").on "click", (event) ->
if (event.target.nodeName != "A")
path = @element.find(".tree-item-file-name a", this).attr("href")
location.href = path if path
# find file
findFile: ->
searchText = @inputElement.val()
result = if searchText.length > 0 then fuzzaldrinPlus.filter(@filePaths, searchText) else @filePaths
@renderList result, searchText
# files pathes load
load: (url) ->
$.ajax
url: url
method: "get"
dataType: "json"
success: (data) =>
@element.find(".loading").hide()
@filePaths = data
@findFile()
@element.find(".files-slider tr.tree-item").eq(0).addClass("selected").focus()
# render result
renderList: (filePaths, searchText) ->
@element.find(".tree-table > tbody").empty()
for filePath, i in filePaths
break if i == 20
if searchText
matches = fuzzaldrinPlus.match(filePath, searchText)
blobItemUrl = "#{@options.blobUrlTemplate}/#{filePath}"
html = @makeHtml filePath, matches, blobItemUrl
@element.find(".tree-table > tbody").append(html)
# highlight text(awefwbwgtc -> <b>a</b>wefw<b>b</b>wgt<b>c</b> )
highlighter = (element, text, matches) ->
lastIndex = 0
highlightText = ""
matchedChars = []
for matchIndex in matches
unmatched = text.substring(lastIndex, matchIndex)
if unmatched
element.append(matchedChars.join("").bold()) if matchedChars.length
matchedChars = []
element.append(document.createTextNode(unmatched))
matchedChars.push(text[matchIndex])
lastIndex = matchIndex + 1
element.append(matchedChars.join("").bold()) if matchedChars.length
element.append(document.createTextNode(text.substring(lastIndex)))
# make tbody row html
makeHtml: (filePath, matches, blobItemUrl) ->
$tr = $("<tr class='tree-item'><td class='tree-item-file-name'><i class='fa fa-file-text-o fa-fw'></i><span class='str-truncated'><a></a></span></td></tr>")
if matches
$tr.find("a").replaceWith(highlighter($tr.find("a"), filePath, matches).attr("href", blobItemUrl))
else
$tr.find("a").attr("href", blobItemUrl).text(filePath)
return $tr
selectRow: (type) ->
rows = @element.find(".files-slider tr.tree-item")
selectedRow = @element.find(".files-slider tr.tree-item.selected")
if rows && rows.length > 0
if selectedRow && selectedRow.length > 0
if type == "UP"
next = selectedRow.prev()
else if type == "DOWN"
next = selectedRow.next()
if next.length > 0
selectedRow.removeClass "selected"
selectedRow = next
else
selectedRow = rows.eq(0)
selectedRow.addClass("selected").focus()
selectRowUp: =>
@selectRow "UP"
selectRowDown: =>
@selectRow "DOWN"
goToTree: =>
location.href = @options.treeUrl
goToBlob: =>
path = @element.find(".tree-item.selected .tree-item-file-name a").attr("href")
location.href = path if path
......@@ -7,7 +7,7 @@ class @Shortcuts
selectiveHelp: (e) =>
Shortcuts.showHelp(e, @enabledHelp)
@showHelp: (e, location) ->
if $('#modal-shortcuts').length > 0
$('#modal-shortcuts').modal('show')
......@@ -17,8 +17,7 @@ class @Shortcuts
dataType: 'script',
success: (e) ->
if location and location.length > 0
for l in location
$(l).show()
$(l).show() for l in location
else
$('.hidden-shortcut').show()
$('.js-more-help-button').remove()
......@@ -28,3 +27,8 @@ class @Shortcuts
@focusSearch: (e) ->
$('#search').focus()
e.preventDefault()
$(document).on 'click.more_help', '.js-more-help-button', (e) ->
$(@).remove()
$('.hidden-shortcut').show()
e.preventDefault()
#= require shortcuts_navigation
class @ShortcutsFindFile extends ShortcutsNavigation
constructor: (@projectFindFile) ->
super()
_oldStopCallback = Mousetrap.stopCallback
# override to fire shortcuts action when focus in textbox
Mousetrap.stopCallback = (event, element, combo) =>
if element == @projectFindFile.inputElement[0] and (combo == 'up' or combo == 'down' or combo == 'esc' or combo == 'enter')
# when press up/down key in textbox, cusor prevent to move to home/end
event.preventDefault()
return false
return _oldStopCallback(event, element, combo)
Mousetrap.bind('up', @projectFindFile.selectRowUp)
Mousetrap.bind('down', @projectFindFile.selectRowDown)
Mousetrap.bind('esc', @projectFindFile.goToTree)
Mousetrap.bind('enter', @projectFindFile.goToBlob)
class @ShortcutsTree extends ShortcutsNavigation
constructor: ->
super()
Mousetrap.bind('t', -> ShortcutsTree.findAndFollowLink('.shortcuts-find-file'))
......@@ -121,5 +121,5 @@ class @UsersSelect
callback(users)
buildUrl: (url) ->
url = gon.relative_url_root + url if gon.relative_url_root?
url = gon.relative_url_root.replace(/\/$/, '') + url if gon.relative_url_root?
return url
# Zen Mode (full screen) textarea
#
#= provides zen_mode:enter
#= provides zen_mode:leave
#
#= require jquery.scrollTo
#= require dropzone
#= require mousetrap
#= require mousetrap/pause
#
# ### Events
#
# `zen_mode:enter`
#
# Fired when the "Edit in fullscreen" link is clicked.
#
# **Synchronicity** Sync
# **Bubbles** Yes
# **Cancelable** No
# **Target** a.js-zen-enter
#
# `zen_mode:leave`
#
# Fired when the "Leave Fullscreen" link is clicked.
#
# **Synchronicity** Sync
# **Bubbles** Yes
# **Cancelable** No
# **Target** a.js-zen-leave
#
class @ZenMode
constructor: ->
@active_zen_area = null
@active_checkbox = null
@scroll_position = 0
$(window).scroll =>
if not @active_checkbox
@scroll_position = window.pageYOffset
@active_backdrop = null
@active_textarea = null
$('body').on 'click', '.zen-enter-link', (e) =>
$(document).on 'click', '.js-zen-enter', (e) ->
e.preventDefault()
$(e.currentTarget).closest('.zennable').find('.zen-toggle-comment').prop('checked', true).change()
$(e.currentTarget).trigger('zen_mode:enter')
$('body').on 'click', '.zen-leave-link', (e) =>
$(document).on 'click', '.js-zen-leave', (e) ->
e.preventDefault()
$(e.currentTarget).closest('.zennable').find('.zen-toggle-comment').prop('checked', false).change()
$('body').on 'change', '.zen-toggle-comment', (e) =>
checkbox = e.currentTarget
if checkbox.checked
# Disable other keyboard shortcuts in ZEN mode
Mousetrap.pause()
@updateActiveZenArea(checkbox)
else
@exitZenMode()
$(document).on 'keydown', (e) =>
if e.keyCode is 27 # Esc
@exitZenMode()
$(e.currentTarget).trigger('zen_mode:leave')
$(document).on 'zen_mode:enter', (e) =>
@enter(e.target.parentNode)
$(document).on 'zen_mode:leave', (e) =>
@exit()
$(document).on 'keydown', (e) ->
if e.keyCode == 27 # Esc
e.preventDefault()
$(document).trigger('zen_mode:leave')
enter: (backdrop) ->
Mousetrap.pause()
@active_backdrop = $(backdrop)
@active_backdrop.addClass('fullscreen')
@active_textarea = @active_backdrop.find('textarea')
updateActiveZenArea: (checkbox) =>
@active_checkbox = $(checkbox)
@active_checkbox.prop('checked', true)
@active_zen_area = @active_checkbox.parent().find('textarea')
# Prevent a user-resized textarea from persisting to fullscreen
@active_zen_area.removeAttr('style')
@active_zen_area.focus()
@active_textarea.removeAttr('style')
@active_textarea.focus()
exitZenMode: =>
if @active_zen_area isnt null
exit: ->
if @active_textarea
Mousetrap.unpause()
@active_checkbox.prop('checked', false)
@active_zen_area = null
@active_checkbox = null
@restoreScroll(@scroll_position)
# Enable dropzone when leaving ZEN mode
@active_textarea.closest('.zen-backdrop').removeClass('fullscreen')
@scrollTo(@active_textarea)
@active_textarea = null
@active_backdrop = null
Dropzone.forElement('.div-dropzone').enable()
restoreScroll: (y) ->
window.scrollTo(window.pageXOffset, y)
scrollTo: (zen_area) ->
$.scrollTo(zen_area, 0, offset: -150)
......@@ -72,6 +72,15 @@
> p:last-child {
margin-bottom: 0;
}
.block-controls {
float: right;
.control {
float: left;
margin-left: 10px;
}
}
}
.cover-block {
......
......@@ -19,38 +19,33 @@
}
}
}
/**
* This overwrites the default values of the cal-heatmap gem
*/
.calendar {
.qi {
background-color: #999;
fill: #fff;
}
.q1 {
background-color: #dae289;
fill: #ededed;
fill: #ededed !important;
}
.q2 {
background-color: #cedb9c;
fill: #ACD5F2;
fill: #ACD5F2 !important;
}
.q3 {
background-color: #b5cf6b;
fill: #7FA8D1;
fill: #7FA8D1 !important;
}
.q4 {
background-color: #637939;
fill: #49729B;
fill: #49729B !important;
}
.q5 {
background-color: #3b6427;
fill: #254E77;
fill: #254E77 !important;
}
.domain-background {
......@@ -59,32 +54,7 @@
}
.ch-tooltip {
position: absolute;
display: none;
margin-top: 22px;
margin-left: 1px;
font-size: 13px;
padding: 3px;
font-weight: 550;
background-color: #222;
span {
position: absolute;
width: 200px;
text-align: center;
visibility: hidden;
border-radius: 10px;
&:after {
content: '';
position: absolute;
top: 100%;
left: 50%;
margin-left: -8px;
width: 0;
height: 0;
border-top: 8px solid #000000;
border-right: 8px solid transparent;
border-left: 8px solid transparent;
}
}
}
}
......@@ -3,23 +3,23 @@
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 300;
src: local('Source Sans Pro Light'), local('SourceSansPro-Light'), font-url('SourceSansPro-Light.ttf');
src: local('Source Sans Pro Light'), local('SourceSansPro-Light'), font-url('SourceSansPro-Light.ttf.woff');
}
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
src: local('Source Sans Pro'), local('SourceSansPro-Regular'), font-url('SourceSansPro-Regular.ttf');
src: local('Source Sans Pro'), local('SourceSansPro-Regular'), font-url('SourceSansPro-Regular.ttf.woff');
}
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
src: local('Source Sans Pro Semibold'), local('SourceSansPro-Semibold'), font-url('SourceSansPro-Semibold.ttf');
src: local('Source Sans Pro Semibold'), local('SourceSansPro-Semibold'), font-url('SourceSansPro-Semibold.ttf.woff');
}
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 700;
src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), font-url('SourceSansPro-Bold.ttf');
src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), font-url('SourceSansPro-Bold.ttf.woff');
}
......@@ -105,7 +105,7 @@
.tanuki-shape {
transition: all 0.8s;
&:hover {
&:hover, &.highlight {
fill: rgb(255, 255, 255);
transition: all 0.1s;
}
......
......@@ -54,17 +54,17 @@
h3 {
margin: 24px 0 12px 0;
font-size: 1.25em;
font-size: 1.1em;
}
h4 {
margin: 24px 0 12px 0;
font-size: 1.1em;
font-size: 0.98em;
}
h5 {
margin: 24px 0 12px 0;
font-size: 1em;
font-size: 0.95em;
}
h6 {
......
.zennable {
.zen-toggle-comment {
display: none;
}
.zen-enter-link {
a.js-zen-enter {
color: $gl-gray;
position: absolute;
top: 0px;
......@@ -11,7 +7,7 @@
line-height: 40px;
}
.zen-leave-link {
a.js-zen-leave {
display: none;
color: $gl-text-color;
position: absolute;
......@@ -25,62 +21,41 @@
}
}
// Hide the Enter link when we're in Zen mode
input:checked ~ .zen-backdrop .zen-enter-link {
display: none;
}
// Show the Leave link when we're in Zen mode
input:checked ~ .zen-backdrop .zen-leave-link {
display: block;
position: absolute;
top: 0;
}
input:checked ~ .zen-backdrop {
background-color: white;
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 1031;
textarea {
border: none;
box-shadow: none;
border-radius: 0;
color: #000;
font-size: 20px;
line-height: 26px;
padding: 30px;
display: block;
outline: none;
resize: none;
height: 100vh;
max-width: 900px;
margin: 0 auto;
.zen-backdrop {
&.fullscreen {
background-color: white;
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 1031;
textarea {
border: none;
box-shadow: none;
border-radius: 0;
color: #000;
font-size: 20px;
line-height: 26px;
padding: 30px;
display: block;
outline: none;
resize: none;
height: 100vh;
max-width: 900px;
margin: 0 auto;
}
a.js-zen-enter {
display: none;
}
a.js-zen-leave {
display: block;
position: absolute;
top: 0;
}
}
}
// Make the color of the placeholder text in the Zenned-out textarea darker,
// so it becomes visible
input:checked ~ .zen-backdrop textarea::-webkit-input-placeholder {
color: #A8A8A8;
}
input:checked ~ .zen-backdrop textarea:-moz-placeholder {
color: #A8A8A8;
opacity: 1;
}
input:checked ~ .zen-backdrop textarea::-moz-placeholder {
color: #A8A8A8;
opacity: 1;
}
input:checked ~ .zen-backdrop textarea:-ms-input-placeholder {
color: #A8A8A8;
}
}
......@@ -28,10 +28,6 @@
}
}
.commits-feed-holder {
float: right;
}
li.commit {
list-style: none;
......@@ -122,3 +118,59 @@ li.commit {
color: $gl-gray;
}
}
.divergence-graph {
padding: 12px 12px 0 0;
float: right;
.graph-side {
position: relative;
width: 80px;
height: 22px;
padding: 5px 0 13px;
float: left;
.bar {
position: absolute;
height: 4px;
background-color: #ccc;
}
.bar-behind {
right: 0;
border-radius: 3px 0 0 3px;
}
.bar-ahead {
left: 0;
border-radius: 0 3px 3px 0;
}
.count {
padding-top: 6px;
padding-bottom: 0px;
font-size: 12px;
color: #333;
display: block;
}
.count-behind {
padding-right: 4px;
text-align: right;
}
.count-ahead {
padding-left: 4px;
text-align: left;
}
}
.graph-separator {
position: relative;
width: 1px;
height: 18px;
margin: 5px 0 0;
float: left;
background-color: #ccc;
}
}
......@@ -138,6 +138,7 @@
*/
.event-last-push {
overflow: auto;
width: 100%;
.event-last-push-text {
@include str-truncated(100%);
padding: 5px 0;
......
......@@ -94,8 +94,16 @@
}
.cross-project-reference {
font-weight: bold;
color: $gl-link-color;
span {
white-space: nowrap;
width: 85%;
overflow: hidden;
position: relative;
display: inline-block;
text-overflow: ellipsis;
}
button {
float: right;
......
......@@ -26,6 +26,13 @@
}
.project-home-panel {
.cover-controls {
.project-settings-dropdown {
margin-left: 10px;
}
}
.project-identicon-holder {
margin-bottom: 16px;
......@@ -292,10 +299,9 @@
border: 1px solid #c6cacf !important;
background-color: #e4e7ed !important;
text-transform: uppercase;
text-transform: none;
color: #313236 !important;
font-size: 13px;
font-weight: 600;
font-size: 15px;
}
.dropdown-menu {
......@@ -408,10 +414,15 @@ ul.nav.nav-projects-tabs {
}
}
.last-push-widget {
margin-top: -1px;
}
.top-area {
border-bottom: 1px solid #EEE;
margin: 0 -16px;
padding: 0 $gl-padding;
height: 42px;
ul.left-top-menu {
display: inline-block;
......@@ -518,6 +529,7 @@ pre.light-well {
.projects-search-form {
margin: -$gl-padding;
padding: $gl-padding;
padding-bottom: 0;
margin-bottom: 0px;
input {
......
.tree-holder {
.file-finder {
width: 50%;
.file-finder-input {
width: 95%;
display: inline-block;
}
}
.tree-table {
margin-bottom: 0;
......
......@@ -9,12 +9,10 @@ class AbuseReportsController < ApplicationController
@abuse_report.reporter = current_user
if @abuse_report.save
if current_application_settings.admin_notification_email.present?
AbuseReportMailer.notify(@abuse_report.id).deliver_later
end
@abuse_report.notify
message = "Thank you for your report. A GitLab administrator will look into it shortly."
redirect_to root_path, notice: message
redirect_to @abuse_report.user, notice: message
else
render :new
end
......@@ -23,6 +21,9 @@ class AbuseReportsController < ApplicationController
private
def report_params
params.require(:abuse_report).permit(:user_id, :message)
params.require(:abuse_report).permit(%i(
message
user_id
))
end
end
......@@ -69,6 +69,15 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:shared_runners_enabled,
:max_artifacts_size,
:max_pages_size,
:metrics_enabled,
:metrics_host,
:metrics_port,
:metrics_pool_size,
:metrics_timeout,
:metrics_method_call_threshold,
:recaptcha_enabled,
:recaptcha_site_key,
:recaptcha_private_key,
restricted_visibility_levels: [],
import_sources: []
)
......
......@@ -5,12 +5,12 @@ class Admin::BuildsController < Admin::ApplicationController
@builds = @all_builds.order('created_at DESC')
@builds =
case @scope
when 'all'
@builds
when 'running'
@builds.running_or_pending.reverse_order
when 'finished'
@builds.finished
else
@builds.running_or_pending.reverse_order
@builds
end
@builds = @builds.page(params[:page]).per(30)
end
......
......@@ -292,7 +292,7 @@ class ApplicationController < ActionController::Base
end
def set_filters_params
params[:sort] ||= 'created_desc'
params[:sort] ||= 'id_desc'
params[:scope] = 'all' if params[:scope].blank?
params[:state] = 'opened' if params[:state].blank?
......
......@@ -6,11 +6,13 @@ module Ci
end
def create
if params[:content].blank?
@content = params[:content]
if @content.blank?
@status = false
@error = "Please provide content of .gitlab-ci.yml"
else
@config_processor = Ci::GitlabCiYamlProcessor.new params[:content]
@config_processor = Ci::GitlabCiYamlProcessor.new(@content)
@stages = @config_processor.stages
@builds = @config_processor.builds
@status = true
......
class Explore::GroupsController < Explore::ApplicationController
def index
@groups = GroupsFinder.new.execute(current_user)
@groups = Group.order_id_desc
@groups = @groups.search(params[:search]) if params[:search].present?
@groups = @groups.sort(@sort = params[:sort])
@groups = @groups.page(params[:page]).per(PER_PAGE)
......
......@@ -9,6 +9,11 @@ class Projects::BranchesController < Projects::ApplicationController
@sort = params[:sort] || 'name'
@branches = @repository.branches_sorted_by(@sort)
@branches = Kaminari.paginate_array(@branches).page(params[:page]).per(PER_PAGE)
@max_commits = @branches.reduce(0) do |memo, branch|
diverging_commit_counts = repository.diverging_commit_counts(branch)
[memo, diverging_commit_counts[:behind], diverging_commit_counts[:ahead]].max
end
end
def recent
......
......@@ -12,12 +12,12 @@ class Projects::BuildsController < Projects::ApplicationController
@builds = @all_builds.order('created_at DESC')
@builds =
case @scope
when 'all'
@builds
when 'running'
@builds.running_or_pending.reverse_order
when 'finished'
@builds.finished
else
@builds.running_or_pending.reverse_order
@builds
end
@builds = @builds.page(params[:page]).per(30)
end
......
......@@ -8,10 +8,16 @@ class Projects::CommitsController < Projects::ApplicationController
before_action :authorize_download_code!
def show
@repo = @project.repository
@limit, @offset = (params[:limit] || 40), (params[:offset] || 0)
@limit, @offset = (params[:limit] || 40).to_i, (params[:offset] || 0).to_i
search = params[:search]
@commits =
if search.present?
@repository.find_commits_by_message(search, @ref, @path, @limit, @offset).compact
else
@repository.commits(@ref, @path, @limit, @offset)
end
@commits = @repo.commits(@ref, @path, @limit, @offset)
@note_counts = project.notes.where(commit_id: @commits.map(&:id)).
group(:commit_id).count
......
# Controller for viewing a repository's file structure
class Projects::FindFileController < Projects::ApplicationController
include ExtractsPath
include ActionView::Helpers::SanitizeHelper
include TreeHelper
before_action :require_non_empty_project
before_action :assign_ref_vars
before_action :authorize_download_code!
def show
return render_404 unless @repository.commit(@ref)
respond_to do |format|
format.html
end
end
def list
file_paths = @repo.ls_files(@ref)
respond_to do |format|
format.json { render json: file_paths }
end
end
end
......@@ -159,7 +159,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def merge_check
@merge_request.check_if_can_be_merged if @merge_request.unchecked?
@merge_request.check_if_can_be_merged
render partial: "projects/merge_requests/widget/show.html.haml", layout: false
end
......
......@@ -20,6 +20,8 @@ class Projects::RefsController < Projects::ApplicationController
namespace_project_network_path(@project.namespace, @project, @id, @options)
when "graphs"
namespace_project_graph_path(@project.namespace, @project, @id)
when "find_file"
namespace_project_find_file_path(@project.namespace, @project, @id)
when "graphs_commits"
commits_namespace_project_graph_path(@project.namespace, @project, @id)
else
......
......@@ -8,7 +8,7 @@ class ProjectsController < ApplicationController
before_action :assign_ref_vars, :tree, only: [:show], if: :repo_exists?
# Authorize
before_action :authorize_admin_project!, only: [:edit, :update]
before_action :authorize_admin_project!, only: [:edit, :update, :housekeeping]
before_action :event_filter, only: [:show, :activity]
layout :determine_layout
......@@ -177,6 +177,15 @@ class ProjectsController < ApplicationController
end
end
def housekeeping
::Projects::HousekeepingService.new(@project).execute
respond_to do |format|
flash[:notice] = "Housekeeping successfully started."
format.html { redirect_to project_path(@project) }
end
end
def toggle_star
current_user.toggle_star(@project)
@project.reload
......@@ -189,7 +198,7 @@ class ProjectsController < ApplicationController
def markdown_preview
text = params[:text]
ext = Gitlab::ReferenceExtractor.new(@project, current_user)
ext = Gitlab::ReferenceExtractor.new(@project, current_user, current_user)
ext.analyze(text)
render json: {
......
class RegistrationsController < Devise::RegistrationsController
before_action :signup_enabled?
include Recaptcha::Verify
def new
redirect_to(new_user_session_path)
end
def create
if !Gitlab::Recaptcha.load_configurations! || verify_recaptcha
super
else
flash[:alert] = "There was an error with the reCAPTCHA code below. Please re-enter the code."
flash.delete :recaptcha_error
render action: 'new'
end
end
def destroy
DeleteUserService.new(current_user).execute(current_user)
......@@ -38,4 +49,16 @@ class RegistrationsController < Devise::RegistrationsController
def sign_up_params
params.require(:user).permit(:username, :email, :name, :password, :password_confirmation)
end
def resource_name
:user
end
def resource
@resource ||= User.new(sign_up_params)
end
def devise_mapping
@devise_mapping ||= Devise.mappings[:user]
end
end
class SessionsController < Devise::SessionsController
include AuthenticatesWithTwoFactor
include Recaptcha::ClientHelper
prepend_before_action :authenticate_with_two_factor, only: [:create]
prepend_before_action :store_redirect_path, only: [:new]
before_action :auto_sign_in_with_provider, only: [:new]
before_action :load_recaptcha
def new
if Gitlab.config.ldap.enabled
......@@ -40,7 +42,7 @@ class SessionsController < Devise::SessionsController
User.find(session[:otp_user_id])
end
end
def store_redirect_path
redirect_path =
if request.referer.present? && (params['redirect_to_referer'] == 'yes')
......@@ -87,14 +89,14 @@ class SessionsController < Devise::SessionsController
provider = Gitlab.config.omniauth.auto_sign_in_with_provider
return unless provider.present?
# Auto sign in with an Omniauth provider only if the standard "you need to sign-in" alert is
# registered or no alert at all. In case of another alert (such as a blocked user), it is safer
# Auto sign in with an Omniauth provider only if the standard "you need to sign-in" alert is
# registered or no alert at all. In case of another alert (such as a blocked user), it is safer
# to do nothing to prevent redirection loops with certain Omniauth providers.
return unless flash[:alert].blank? || flash[:alert] == I18n.t('devise.failure.unauthenticated')
# Prevent alert from popping up on the first page shown after authentication.
flash[:alert] = nil
flash[:alert] = nil
redirect_to user_omniauth_authorize_path(provider.to_sym)
end
......@@ -107,4 +109,8 @@ class SessionsController < Devise::SessionsController
AuditEventService.new(user, user, options).
for_authentication.security_event
end
def load_recaptcha
Gitlab::Recaptcha.load_configurations!
end
end
......@@ -7,7 +7,7 @@ class UsersController < ApplicationController
@projects = PersonalProjectsFinder.new(@user).execute(current_user)
@groups = JoinedGroupsFinder.new(@user).execute(current_user)
@groups = @user.groups.order_id_desc
respond_to do |format|
format.html
......
class GroupsFinder
# Finds the groups available to the given user.
#
# current_user - The user to find the groups for.
#
# Returns an ActiveRecord::Relation.
def execute(current_user = nil)
if current_user
relation = groups_visible_to_user(current_user)
else
relation = public_groups
end
relation.order_id_desc
end
private
# This method returns the groups "current_user" can see.
def groups_visible_to_user(current_user)
base = groups_for_projects(public_and_internal_projects)
union = Gitlab::SQL::Union.
new([base.select(:id), current_user.authorized_groups.select(:id)])
Group.where("namespaces.id IN (#{union.to_sql})")
end
def public_groups
groups_for_projects(public_projects)
end
def groups_for_projects(projects)
Group.public_and_given_groups(projects.select(:namespace_id))
end
def public_projects
Project.unscoped.public_only
end
def public_and_internal_projects
Project.unscoped.public_and_internal_only
end
end
......@@ -80,9 +80,9 @@ class IssuableFinder
if project?
@projects = project
elsif current_user && params[:authorized_only].presence && !current_user_related?
@projects = current_user.authorized_projects
@projects = current_user.authorized_projects.reorder(nil)
else
@projects = ProjectsFinder.new.execute(current_user)
@projects = ProjectsFinder.new.execute(current_user).reorder(nil)
end
end
......
# Class for finding the groups a user is a member of.
class JoinedGroupsFinder
def initialize(user = nil)
@user = user
end
# Finds the groups of the source user, optionally limited to those visible to
# the current user.
#
# current_user - If given the groups of "@user" will only include the groups
# "current_user" can also see.
#
# Returns an ActiveRecord::Relation.
def execute(current_user = nil)
if current_user
relation = groups_visible_to_user(current_user)
else
relation = public_groups
end
relation.order_id_desc
end
private
# Returns the groups the user in "current_user" can see.
#
# This list includes all public/internal projects as well as the projects of
# "@user" that "current_user" also has access to.
def groups_visible_to_user(current_user)
base = @user.authorized_groups.visible_to_user(current_user)
extra = public_and_internal_groups
union = Gitlab::SQL::Union.new([base.select(:id), extra.select(:id)])
Group.where("namespaces.id IN (#{union.to_sql})")
end
def public_groups
groups_for_projects(@user.authorized_projects.public_only)
end
def public_and_internal_groups
groups_for_projects(@user.authorized_projects.public_and_internal_only)
end
def groups_for_projects(projects)
@user.groups.public_and_given_groups(projects.select(:namespace_id))
end
end
......@@ -72,7 +72,7 @@ module ApplicationHelper
if user_or_email.is_a?(User)
user = user_or_email
else
user = User.find_by(email: user_or_email)
user = User.find_by(email: user_or_email.downcase)
end
if user
......@@ -206,7 +206,7 @@ module ApplicationHelper
element = content_tag :time, time.to_s,
class: "#{html_class} js-timeago js-timeago-pending",
datetime: time.to_time.getutc.iso8601,
title: time.in_time_zone.stamp('Aug 21, 2011 9:23pm'),
title: time.in_time_zone.to_s(:medium),
data: { toggle: 'tooltip', placement: placement, container: 'body' }
unless skip_js
......
module AuthHelper
PROVIDERS_WITH_ICONS = %w(twitter github gitlab bitbucket google_oauth2 facebook).freeze
PROVIDERS_WITH_ICONS = %w(twitter github gitlab bitbucket google_oauth2 facebook azure_oauth2).freeze
FORM_BASED_PROVIDERS = [/\Aldap/, 'kerberos', 'crowd'].freeze
def ldap_enabled?
......
......@@ -69,6 +69,10 @@ module IssuesHelper
end
end
def issue_button_visibility(issue, closed)
return 'hidden' if issue.closed? == closed
end
def issue_to_atom(xml, issue)
xml.entry do
xml.id namespace_project_issue_url(issue.project.namespace,
......@@ -76,7 +80,7 @@ module IssuesHelper
xml.link href: namespace_project_issue_url(issue.project.namespace,
issue.project, issue)
xml.title truncate(issue.title, length: 80)
xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
xml.updated issue.created_at.xmlschema
xml.media :thumbnail, width: "40", height: "40", url: image_url(avatar_icon(issue.author_email))
xml.author do |author|
xml.name issue.author_name
......@@ -95,13 +99,16 @@ module IssuesHelper
end
def emoji_icon(name, unicode = nil, aliases = [])
unicode ||= Emoji.emoji_filename(name)
unicode ||= Emoji.emoji_filename(name) rescue ""
content_tag :div, "",
class: "icon emoji-icon emoji-#{unicode}",
"data-emoji" => name,
"data-aliases" => aliases.join(" "),
"data-unicode-name" => unicode
title: name,
data: {
aliases: aliases.join(' '),
emoji: name,
unicode_name: unicode
}
end
def emoji_author_list(notes, current_user)
......
......@@ -67,7 +67,7 @@ module NotesHelper
line_type: line_type
}
button_tag class: 'btn reply-btn js-discussion-reply-button',
button_tag class: 'btn btn-nr reply-btn js-discussion-reply-button',
data: data, title: 'Add a reply' do
link_text = icon('comment')
link_text << ' Reply'
......
......@@ -27,35 +27,20 @@ module PageLayoutHelper
#
# Returns an HTML-safe String.
def page_description(description = nil)
@page_description ||= page_description_default
if description.present?
@page_description = description.squish
else
elsif @page_description.present?
sanitize(@page_description, tags: []).truncate_words(30)
end
end
# Default value for page_description when one hasn't been defined manually by
# a view
def page_description_default
if @project
@project.description || brand_title
else
brand_title
end
end
def page_image
default = image_url('gitlab_logo.png')
if @project
@project.avatar_url || default
elsif @user
avatar_icon(@user)
else
default
end
subject = @project || @user || @group
image = subject.avatar_url if subject.present?
image || default
end
# Define or get attributes to be used as Twitter card metadata
......
......@@ -70,7 +70,7 @@ module SearchHelper
# Autocomplete results for the current user's groups
def groups_autocomplete(term, limit = 5)
GroupsFinder.new.execute(current_user).search(term).limit(limit).map do |group|
Group.search(term).limit(limit).map do |group|
{
label: "group: #{search_result_sanitize(group.name)}",
url: group_path(group)
......
......@@ -19,7 +19,7 @@ module SortingHelper
end
def sort_title_recently_updated
'Recently updated'
'Last updated'
end
def sort_title_oldest_created
......@@ -27,7 +27,7 @@ module SortingHelper
end
def sort_title_recently_created
'Recently created'
'Last created'
end
def sort_title_milestone_soon
......@@ -63,11 +63,11 @@ module SortingHelper
end
def sort_value_oldest_created
'created_asc'
'id_asc'
end
def sort_value_recently_created
'created_desc'
'id_desc'
end
def sort_value_milestone_soon
......
......@@ -2,11 +2,19 @@ class AbuseReportMailer < BaseMailer
include Gitlab::CurrentSettings
def notify(abuse_report_id)
return unless deliverable?
@abuse_report = AbuseReport.find(abuse_report_id)
mail(
to: current_application_settings.admin_notification_email,
to: current_application_settings.admin_notification_email,
subject: "#{@abuse_report.user.name} (#{@abuse_report.user.username}) was reported for abuse"
)
end
private
def deliverable?
current_application_settings.admin_notification_email.present?
end
end
......@@ -48,7 +48,7 @@ module Emails
yield
SentNotification.record(@note, recipient_id, reply_key)
SentNotification.record_note(@note, recipient_id, reply_key)
end
end
end
......@@ -85,7 +85,7 @@ class Ability
subject.group
end
if group && group.public_profile?
if group && group.projects.public_only.any?
[:read_group]
else
[]
......
......@@ -18,4 +18,10 @@ class AbuseReport < ActiveRecord::Base
validates :user, presence: true
validates :message, presence: true
validates :user_id, uniqueness: true
def notify
return unless self.persisted?
AbuseReportMailer.notify(self.id).deliver_later
end
end
......@@ -28,9 +28,20 @@
# admin_notification_email :string(255)
# shared_runners_enabled :boolean default(TRUE), not null
# max_artifacts_size :integer default(100), not null
# runners_registration_token :string(255)
# require_two_factor_authentication :boolean default(TRUE)
# runners_registration_token :string
# require_two_factor_authentication :boolean default(FALSE)
# two_factor_grace_period :integer default(48)
# metrics_enabled :boolean default(FALSE)
# metrics_host :string default("localhost")
# metrics_username :string
# metrics_password :string
# metrics_pool_size :integer default(16)
# metrics_timeout :integer default(10)
# metrics_method_call_threshold :integer default(10)
# recaptcha_enabled :boolean default(FALSE)
# recaptcha_site_key :string
# recaptcha_private_key :string
# metrics_port :integer default(8089)
#
class ApplicationSetting < ActiveRecord::Base
......@@ -45,24 +56,32 @@ class ApplicationSetting < ActiveRecord::Base
attr_accessor :restricted_signup_domains_raw
validates :session_expire_delay,
presence: true,
numericality: { only_integer: true, greater_than_or_equal_to: 0 }
presence: true,
numericality: { only_integer: true, greater_than_or_equal_to: 0 }
validates :home_page_url,
allow_blank: true,
url: true,
if: :home_page_url_column_exist
allow_blank: true,
url: true,
if: :home_page_url_column_exist
validates :after_sign_out_path,
allow_blank: true,
url: true
allow_blank: true,
url: true
validates :admin_notification_email,
allow_blank: true,
email: true
allow_blank: true,
email: true
validates :two_factor_grace_period,
numericality: { greater_than_or_equal_to: 0 }
numericality: { greater_than_or_equal_to: 0 }
validates :recaptcha_site_key,
presence: true,
if: :recaptcha_enabled
validates :recaptcha_private_key,
presence: true,
if: :recaptcha_enabled
validates_each :restricted_visibility_levels do |record, attr, value|
unless value.nil?
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment