summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorJacob Vosmaer <contact@jacobvosmaer.nl>2016-04-13 17:38:59 +0200
committerJacob Vosmaer <contact@jacobvosmaer.nl>2016-04-13 17:38:59 +0200
commitf07316f27cc1a5d354bc82b4caa1de703a3bcb5e (patch)
tree68c8f99f8df9375c9644993cf0bbd2f92cf9bfdd /app
parent6f6d2d0ad5039fa8ace7e09c4f5867bb680b0f79 (diff)
parent3463ffde9e3ef425fbaa6093f01212cccb01707b (diff)
downloadgitlab-ce-f07316f27cc1a5d354bc82b4caa1de703a3bcb5e.tar.gz
Merge branch 'master' of https://gitlab.com/gitlab-org/gitlab-ce into auto-fsck
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/application.js.coffee15
-rw-r--r--app/assets/javascripts/behaviors/quick_submit.js.coffee6
-rw-r--r--app/assets/javascripts/gl_dropdown.js.coffee4
-rw-r--r--app/assets/javascripts/labels_select.js.coffee2
-rw-r--r--app/assets/javascripts/lib/datetime_utility.js.coffee17
-rw-r--r--app/assets/javascripts/lib/notify.js.coffee5
-rw-r--r--app/assets/javascripts/merge_request_tabs.js.coffee6
-rw-r--r--app/assets/javascripts/merge_request_widget.js.coffee35
-rw-r--r--app/assets/javascripts/notes.js.coffee14
-rw-r--r--app/assets/javascripts/profile.js.coffee7
-rw-r--r--app/assets/javascripts/project.js.coffee17
-rw-r--r--app/assets/javascripts/project_select.js.coffee32
-rw-r--r--app/assets/javascripts/todos.js.coffee2
-rw-r--r--app/assets/stylesheets/framework/header.scss5
-rw-r--r--app/assets/stylesheets/framework/nav.scss6
-rw-r--r--app/assets/stylesheets/framework/tw_bootstrap.scss1
-rw-r--r--app/assets/stylesheets/framework/variables.scss1
-rw-r--r--app/assets/stylesheets/pages/events.scss11
-rw-r--r--app/assets/stylesheets/pages/issuable.scss6
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss1
-rw-r--r--app/assets/stylesheets/pages/note_form.scss14
-rw-r--r--app/assets/stylesheets/pages/notes.scss26
-rw-r--r--app/assets/stylesheets/pages/todos.scss5
-rw-r--r--app/controllers/groups/notification_settings_controller.rb16
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb26
-rw-r--r--app/controllers/profiles/notifications_controller.rb37
-rw-r--r--app/controllers/projects/merge_requests_controller.rb2
-rw-r--r--app/controllers/projects/notification_settings_controller.rb16
-rw-r--r--app/controllers/projects/repositories_controller.rb1
-rw-r--r--app/controllers/projects_controller.rb12
-rw-r--r--app/helpers/application_helper.rb2
-rw-r--r--app/helpers/notifications_helper.rb68
-rw-r--r--app/helpers/projects_helper.rb17
-rw-r--r--app/helpers/todos_helper.rb2
-rw-r--r--app/models/commit.rb8
-rw-r--r--app/models/concerns/notifiable.rb15
-rw-r--r--app/models/group.rb1
-rw-r--r--app/models/member.rb12
-rw-r--r--app/models/members/group_member.rb1
-rw-r--r--app/models/members/project_member.rb1
-rw-r--r--app/models/merge_request.rb6
-rw-r--r--app/models/notification.rb77
-rw-r--r--app/models/notification_setting.rb28
-rw-r--r--app/models/project.rb11
-rw-r--r--app/models/repository.rb2
-rw-r--r--app/models/user.rb18
-rw-r--r--app/services/notification_service.rb52
-rw-r--r--app/services/projects/housekeeping_service.rb14
-rw-r--r--app/views/layouts/header/_default.html.haml2
-rw-r--r--app/views/layouts/project.html.haml8
-rw-r--r--app/views/profiles/notifications/_group_settings.html.haml13
-rw-r--r--app/views/profiles/notifications/_project_settings.html.haml13
-rw-r--r--app/views/profiles/notifications/_settings.html.haml17
-rw-r--r--app/views/profiles/notifications/show.html.haml103
-rw-r--r--app/views/profiles/notifications/update.js.haml6
-rw-r--r--app/views/projects/buttons/_notifications.html.haml21
-rw-r--r--app/views/projects/merge_requests/_form.html.haml2
-rw-r--r--app/views/projects/merge_requests/widget/_show.html.haml16
-rw-r--r--app/views/projects/notes/_note.html.haml12
-rw-r--r--app/views/shared/_commit_message_container.html.haml2
-rw-r--r--app/views/shared/issuable/_label_dropdown.html.haml2
-rw-r--r--app/workers/post_receive.rb2
62 files changed, 534 insertions, 368 deletions
diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee
index f01c67e9474..6f435e4c542 100644
--- a/app/assets/javascripts/application.js.coffee
+++ b/app/assets/javascripts/application.js.coffee
@@ -22,7 +22,17 @@
#= require cal-heatmap
#= require turbolinks
#= require autosave
-#= require bootstrap
+#= require bootstrap/affix
+#= require bootstrap/alert
+#= require bootstrap/button
+#= require bootstrap/collapse
+#= require bootstrap/dropdown
+#= require bootstrap/modal
+#= require bootstrap/scrollspy
+#= require bootstrap/tab
+#= require bootstrap/transition
+#= require bootstrap/tooltip
+#= require bootstrap/popover
#= require select2
#= require raphael
#= require g.raphael
@@ -41,6 +51,7 @@
#= require shortcuts_issuable
#= require shortcuts_network
#= require jquery.nicescroll
+#= require date.format
#= require_tree .
#= require fuzzaldrin-plus
#= require cropper
@@ -163,7 +174,7 @@ $ ->
$('.trigger-submit').on 'change', ->
$(@).parents('form').submit()
- $('abbr.timeago, .js-timeago').timeago()
+ gl.utils.localTimeAgo($('abbr.timeago, .js-timeago'), false)
# Flash
if (flash = $(".flash-container")).length > 0
diff --git a/app/assets/javascripts/behaviors/quick_submit.js.coffee b/app/assets/javascripts/behaviors/quick_submit.js.coffee
index 6e29d374267..3cb96bacaa7 100644
--- a/app/assets/javascripts/behaviors/quick_submit.js.coffee
+++ b/app/assets/javascripts/behaviors/quick_submit.js.coffee
@@ -29,7 +29,11 @@ $(document).on 'keydown.quick_submit', '.js-quick-submit', (e) ->
e.preventDefault()
$form = $(e.target).closest('form')
- $form.find('input[type=submit], button[type=submit]').disable()
+ $submit_button = $form.find('input[type=submit], button[type=submit]')
+
+ return if $submit_button.attr('disabled')
+
+ $submit_button.disable()
$form.submit()
# If the user tabs to a submit button on a `js-quick-submit` form, display a
diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee
index ee1d0fad289..2dc37257e22 100644
--- a/app/assets/javascripts/gl_dropdown.js.coffee
+++ b/app/assets/javascripts/gl_dropdown.js.coffee
@@ -122,7 +122,9 @@ class GitLabDropdown
FILTER_INPUT = '.dropdown-input .dropdown-input-field'
constructor: (@el, @options) ->
- @dropdown = $(@el).parent()
+ self = @
+ selector = $(@el).data "target"
+ @dropdown = if selector? then $(selector) else $(@el).parent()
# Set Defaults
{
diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee
index d1fe116397a..90385621879 100644
--- a/app/assets/javascripts/labels_select.js.coffee
+++ b/app/assets/javascripts/labels_select.js.coffee
@@ -218,7 +218,7 @@ class @LabelsSelect
selectable: true
toggleLabel: (selected) ->
- if selected and selected.title isnt 'Any Label'
+ if selected and selected.title?
selected.title
else
defaultLabel
diff --git a/app/assets/javascripts/lib/datetime_utility.js.coffee b/app/assets/javascripts/lib/datetime_utility.js.coffee
new file mode 100644
index 00000000000..ad1d1c70481
--- /dev/null
+++ b/app/assets/javascripts/lib/datetime_utility.js.coffee
@@ -0,0 +1,17 @@
+((w) ->
+
+ w.gl ?= {}
+ w.gl.utils ?= {}
+
+ w.gl.utils.formatDate = (datetime) ->
+ dateFormat(datetime, 'mmm d, yyyy h:MMtt Z')
+
+ w.gl.utils.localTimeAgo = ($timeagoEls, setTimeago = true) ->
+ $timeagoEls.each( ->
+ $el = $(@)
+ $el.attr('title', gl.utils.formatDate($el.attr('datetime')))
+ )
+
+ $timeagoEls.timeago() if setTimeago
+
+) window
diff --git a/app/assets/javascripts/lib/notify.js.coffee b/app/assets/javascripts/lib/notify.js.coffee
index 3f9ca39912c..9e28353ac34 100644
--- a/app/assets/javascripts/lib/notify.js.coffee
+++ b/app/assets/javascripts/lib/notify.js.coffee
@@ -2,6 +2,11 @@
notificationGranted = (message, opts, onclick) ->
notification = new Notification(message, opts)
+ # Hide the notification after X amount of seconds
+ setTimeout ->
+ notification.close()
+ , 8000
+
if onclick
notification.onclick = onclick
diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee
index 9946249adbf..ef0b534a709 100644
--- a/app/assets/javascripts/merge_request_tabs.js.coffee
+++ b/app/assets/javascripts/merge_request_tabs.js.coffee
@@ -142,7 +142,7 @@ class @MergeRequestTabs
url: "#{source}.json"
success: (data) =>
document.querySelector("div#commits").innerHTML = data.html
- $('.js-timeago').timeago()
+ gl.utils.localTimeAgo($('.js-timeago', 'div#commits'))
@commitsLoaded = true
@scrollToElement("#commits")
@@ -153,7 +153,7 @@ class @MergeRequestTabs
url: "#{source}.json" + @_location.search
success: (data) =>
document.querySelector("div#diffs").innerHTML = data.html
- $('.js-timeago').timeago()
+ gl.utils.localTimeAgo($('.js-timeago', 'div#diffs'))
$('div#diffs .js-syntax-highlight').syntaxHighlight()
@expandViewContainer() if @diffViewType() is 'parallel'
@diffsLoaded = true
@@ -166,7 +166,7 @@ class @MergeRequestTabs
url: "#{source}.json"
success: (data) =>
document.querySelector("div#builds").innerHTML = data.html
- $('.js-timeago').timeago()
+ gl.utils.localTimeAgo($('.js-timeago', 'div#builds'))
@buildsLoaded = true
@scrollToElement("#builds")
diff --git a/app/assets/javascripts/merge_request_widget.js.coffee b/app/assets/javascripts/merge_request_widget.js.coffee
index 84a8887fbce..065626beeb8 100644
--- a/app/assets/javascripts/merge_request_widget.js.coffee
+++ b/app/assets/javascripts/merge_request_widget.js.coffee
@@ -12,10 +12,19 @@ class @MergeRequestWidget
@readyForCICheck = true
clearInterval @fetchBuildStatusInterval
+ @clearEventListeners()
+ @addEventListeners()
@pollCIStatus()
notifyPermissions()
- setOpts: (@opts) ->
+ clearEventListeners: ->
+ $(document).off 'page:change.merge_request'
+
+ addEventListeners: ->
+ $(document).on 'page:change.merge_request', =>
+ if $('body').data('page') isnt 'projects:merge_requests:show'
+ clearInterval @fetchBuildStatusInterval
+ @clearEventListeners()
mergeInProgress: (deleteSourceBranch = false)->
$.ajax
@@ -38,7 +47,7 @@ class @MergeRequestWidget
$('.mr-state-widget').replaceWith(data)
ciLabelForStatus: (status) ->
- if status == 'success'
+ if status is 'success'
'passed'
else
status
@@ -67,18 +76,28 @@ class @MergeRequestWidget
@opts.ci_status = data.status
return
- if data.status isnt @opts.ci_status
+ if data.status isnt @opts.ci_status and data.status?
@showCIStatus data.status
if data.coverage
@showCICoverage data.coverage
if showNotification
- message = @opts.ci_message.replace('{{status}}', @ciLabelForStatus(data.status))
+ status = @ciLabelForStatus(data.status)
+
+ if status is "preparing"
+ title = @opts.ci_title.preparing
+ status = status.charAt(0).toUpperCase() + status.slice(1);
+ message = @opts.ci_message.preparing.replace('{{status}}', status)
+ else
+ title = @opts.ci_title.normal
+ message = @opts.ci_message.normal.replace('{{status}}', status)
+
+ title = title.replace('{{status}}', status)
message = message.replace('{{sha}}', data.sha)
message = message.replace('{{title}}', data.title)
notify(
- "Build #{@ciLabelForStatus(data.status)}",
+ title,
message,
@opts.gitlab_icon,
->
@@ -98,6 +117,8 @@ class @MergeRequestWidget
@setMergeButtonClass('btn-danger')
when "running", "pending"
@setMergeButtonClass('btn-warning')
+ when "success"
+ @setMergeButtonClass('btn-create')
else
$('.ci_widget.ci-error').show()
@setMergeButtonClass('btn-danger')
@@ -107,4 +128,6 @@ class @MergeRequestWidget
$('.ci_widget:visible .ci-coverage').text(text)
setMergeButtonClass: (css_class) ->
- $('.accept_merge_request').removeClass("btn-create").addClass(css_class)
+ $('.accept_merge_request')
+ .removeClass('btn-danger btn-warning btn-create')
+ .addClass(css_class)
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index 86e3b860fcb..a67890200dd 100644
--- a/app/assets/javascripts/notes.js.coffee
+++ b/app/assets/javascripts/notes.js.coffee
@@ -163,9 +163,15 @@ class @Notes
else if @isNewNote(note)
@note_ids.push(note.id)
- $('ul.main-notes-list')
+ $notesList = $('ul.main-notes-list')
+
+ $notesList
.append(note.html)
.syntaxHighlight()
+
+ # Update datetime format on the recent note
+ gl.utils.localTimeAgo($notesList.find("#note_#{note.id} .js-timeago"), false)
+
@initTaskList()
@updateNotesCount(1)
@@ -217,6 +223,8 @@ class @Notes
# append new note to all matching discussions
discussionContainer.append note_html
+ gl.utils.localTimeAgo($('.js-timeago', note_html), false)
+
@updateNotesCount(1)
###
@@ -345,7 +353,9 @@ class @Notes
updateNote: (_xhr, note, _status) =>
# Convert returned HTML to a jQuery object so we can modify it further
$html = $(note.html)
- $('.js-timeago', $html).timeago()
+
+ gl.utils.localTimeAgo($('.js-timeago', $html))
+
$html.syntaxHighlight()
$html.find('.js-task-list-container').taskList('enable')
diff --git a/app/assets/javascripts/profile.js.coffee b/app/assets/javascripts/profile.js.coffee
index ae87c6c4e40..f4a2562885d 100644
--- a/app/assets/javascripts/profile.js.coffee
+++ b/app/assets/javascripts/profile.js.coffee
@@ -18,8 +18,11 @@ class @Profile
$(this).find('.btn-save').enable()
$(this).find('.loading-gif').hide()
- $('.update-notifications').on 'ajax:complete', ->
- $(this).find('.btn-save').enable()
+ $('.update-notifications').on 'ajax:success', (e, data) ->
+ if data.saved
+ new Flash("Notification settings saved", "notice")
+ else
+ new Flash("Failed to save new settings", "alert")
@bindEvents()
diff --git a/app/assets/javascripts/project.js.coffee b/app/assets/javascripts/project.js.coffee
index 87d313ed67c..07be85a32a5 100644
--- a/app/assets/javascripts/project.js.coffee
+++ b/app/assets/javascripts/project.js.coffee
@@ -37,19 +37,20 @@ class @Project
$('.update-notification').on 'click', (e) ->
e.preventDefault()
notification_level = $(@).data 'notification-level'
- $('#notification_level').val(notification_level)
+ label = $(@).data 'notification-title'
+ $('#notification_setting_level').val(notification_level)
$('#notification-form').submit()
- label = null
- switch notification_level
- when 0 then label = ' Disabled '
- when 1 then label = ' Participating '
- when 2 then label = ' Watching '
- when 3 then label = ' Global '
- when 4 then label = ' On Mention '
$('#notifications-button').empty().append("<i class='fa fa-bell'></i>" + label + "<i class='fa fa-angle-down'></i>")
$(@).parents('ul').find('li.active').removeClass 'active'
$(@).parent().addClass 'active'
+ $('#notification-form').on 'ajax:success', (e, data) ->
+ if data.saved
+ new Flash("Notification settings saved", "notice")
+ else
+ new Flash("Failed to save new settings", "alert")
+
+
@projectSelectDropdown()
projectSelectDropdown: ->
diff --git a/app/assets/javascripts/project_select.js.coffee b/app/assets/javascripts/project_select.js.coffee
index be8ab9b428d..704bd8dee53 100644
--- a/app/assets/javascripts/project_select.js.coffee
+++ b/app/assets/javascripts/project_select.js.coffee
@@ -1,5 +1,37 @@
class @ProjectSelect
constructor: ->
+ $('.js-projects-dropdown-toggle').each (i, dropdown) ->
+ $dropdown = $(dropdown)
+
+ $dropdown.glDropdown(
+ filterable: true
+ filterRemote: true
+ search:
+ fields: ['name_with_namespace']
+ data: (term, callback) ->
+ finalCallback = (projects) ->
+ callback projects
+
+ if @includeGroups
+ projectsCallback = (projects) ->
+ groupsCallback = (groups) ->
+ data = groups.concat(projects)
+ finalCallback(data)
+
+ Api.groups term, false, groupsCallback
+ else
+ projectsCallback = finalCallback
+
+ if @groupId
+ Api.groupProjects @groupId, term, projectsCallback
+ else
+ Api.projects term, @orderBy, projectsCallback
+ url: (project) ->
+ project.web_url
+ text: (project) ->
+ project.name_with_namespace
+ )
+
$('.ajax-project-select').each (i, select) ->
@groupId = $(select).data('group-id')
@includeGroups = $(select).data('include-groups')
diff --git a/app/assets/javascripts/todos.js.coffee b/app/assets/javascripts/todos.js.coffee
index 886da72e261..00d2b641723 100644
--- a/app/assets/javascripts/todos.js.coffee
+++ b/app/assets/javascripts/todos.js.coffee
@@ -59,6 +59,8 @@ class @Todos
goToTodoUrl: (e)->
todoLink = $(this).data('url')
+ return unless todoLink
+
if e.metaKey
e.preventDefault()
window.open(todoLink,'_blank')
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index b3397d16016..3f015427d07 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -69,6 +69,7 @@ header {
}
.header-content {
+ position: relative;
height: $header-height;
padding-right: 20px;
@@ -76,6 +77,10 @@ header {
padding-right: 0;
}
+ .dropdown-menu {
+ margin-top: -5px;
+ }
+
.title {
margin: 0;
font-size: 19px;
diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss
index 94f5a12ff6a..192d53b048a 100644
--- a/app/assets/stylesheets/framework/nav.scss
+++ b/app/assets/stylesheets/framework/nav.scss
@@ -58,12 +58,12 @@
.nav-search {
display: inline-block;
- width: 50%;
+ width: 100%;
padding: 11px 0;
/* Small devices (phones, tablets, 768px and lower) */
- @media (max-width: $screen-sm-min) {
- width: 100%;
+ @media (min-width: $screen-sm-min) {
+ width: 50%;
}
}
diff --git a/app/assets/stylesheets/framework/tw_bootstrap.scss b/app/assets/stylesheets/framework/tw_bootstrap.scss
index dd42db1840f..96bab7880c2 100644
--- a/app/assets/stylesheets/framework/tw_bootstrap.scss
+++ b/app/assets/stylesheets/framework/tw_bootstrap.scss
@@ -43,7 +43,6 @@
@import "bootstrap/modals";
@import "bootstrap/tooltip";
@import "bootstrap/popovers";
-@import "bootstrap/carousel";
// Utility classes
.clearfix {
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 1ebbd9b0e57..0b6be86ce6a 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -28,6 +28,7 @@ $gl-link-color: #3084bb;
$gl-dark-link-color: #333;
$gl-placeholder-color: #8f8f8f;
$gl-icon-color: $gl-placeholder-color;
+$gl-grayish-blue: #7f8fa4;
$gl-gray: $gl-text-color;
$gl-header-color: $gl-title-color;
diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss
index c66efe978cd..6fe57c737b3 100644
--- a/app/assets/stylesheets/pages/events.scss
+++ b/app/assets/stylesheets/pages/events.scss
@@ -41,8 +41,17 @@
word-wrap: break-word;
.md {
- color: #7f8fa4;
+ color: $gl-grayish-blue;
font-size: $gl-font-size;
+
+ .label {
+ color: $gl-text-color;
+ font-size: inherit;
+ }
+
+ iframe.twitter-share-button {
+ vertical-align: bottom;
+ }
}
pre {
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 88c1b614c74..8b6f37f21b5 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -263,6 +263,12 @@
}
}
+ .dropdown-content {
+ a:hover {
+ color: inherit;
+ }
+ }
+
.dropdown-menu-toggle {
width: 100%;
padding-top: 6px;
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index b79335eab91..4ef548ffbe7 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -142,6 +142,7 @@
overflow: hidden;
font-size: 90%;
margin: 0 3px;
+ word-break: break-all;
}
.mr-list {
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index 4d4d508396d..f4da17fadaa 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -71,12 +71,24 @@
border-color: $focus-border-color;
}
}
+
+ p {
+ code {
+ white-space: normal;
+ }
+
+ pre {
+ code {
+ white-space: pre;
+ }
+ }
+ }
}
}
.discussion-form {
padding: $gl-padding-top $gl-padding;
- background-color: #fff;
+ background-color: $white-light;
}
.note-edit-form {
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 7295fe51121..e421a31549a 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -81,9 +81,15 @@ ul.notes {
@include md-typography;
// On diffs code should wrap nicely and not overflow
- pre {
+ p {
code {
- white-space: pre;
+ white-space: normal;
+ }
+
+ pre {
+ code {
+ white-space: pre;
+ }
}
}
@@ -112,6 +118,10 @@ ul.notes {
margin: 10px 0;
}
}
+
+ a {
+ word-break: break-all;
+ }
}
.note-header {
@@ -127,7 +137,7 @@ ul.notes {
margin-right: 10px;
}
.line_content {
- white-space: pre-wrap;
+ white-space: pre;
}
}
@@ -145,19 +155,27 @@ ul.notes {
background: $background-color;
color: $text-color;
}
+
&.notes_line2 {
text-align: center;
padding: 10px 0;
border-left: 1px solid #ddd !important;
}
+
&.notes_content {
- background-color: #fff;
+ background-color: $background-color;
border-width: 1px 0;
padding: 0;
vertical-align: top;
+ white-space: normal;
+
&.parallel {
border-width: 1px;
}
+
+ .notes {
+ background-color: $white-light;
+ }
}
}
}
diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss
index e83fa9e3d52..75f78569e3c 100644
--- a/app/assets/stylesheets/pages/todos.scss
+++ b/app/assets/stylesheets/pages/todos.scss
@@ -34,6 +34,11 @@
color: #7f8fa4;
font-size: $gl-font-size;
+ .label {
+ color: $gl-text-color;
+ font-size: inherit;
+ }
+
p {
color: #5c5d5e;
}
diff --git a/app/controllers/groups/notification_settings_controller.rb b/app/controllers/groups/notification_settings_controller.rb
new file mode 100644
index 00000000000..de13b16ccf2
--- /dev/null
+++ b/app/controllers/groups/notification_settings_controller.rb
@@ -0,0 +1,16 @@
+class Groups::NotificationSettingsController < Groups::ApplicationController
+ before_action :authenticate_user!
+
+ def update
+ notification_setting = current_user.notification_settings_for(group)
+ saved = notification_setting.update_attributes(notification_setting_params)
+
+ render json: { saved: saved }
+ end
+
+ private
+
+ def notification_setting_params
+ params.require(:notification_setting).permit(:level)
+ end
+end
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index d28e96c3f18..df98f56a1cd 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -60,6 +60,8 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
continue_login_process
end
+ rescue Gitlab::OAuth::SignupDisabledError
+ handle_signup_error
end
def omniauth_error
@@ -92,16 +94,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
continue_login_process
end
rescue Gitlab::OAuth::SignupDisabledError
- label = Gitlab::OAuth::Provider.label_for(oauth['provider'])
- message = "Signing in using your #{label} account without a pre-existing GitLab account is not allowed."
-
- if current_application_settings.signup_enabled?
- message << " Create a GitLab account first, and then connect it to your #{label} account."
- end
-
- flash[:notice] = message
-
- redirect_to new_user_session_path
+ handle_signup_error
end
def handle_service_ticket provider, ticket
@@ -122,6 +115,19 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
end
end
+ def handle_signup_error
+ label = Gitlab::OAuth::Provider.label_for(oauth['provider'])
+ message = "Signing in using your #{label} account without a pre-existing GitLab account is not allowed."
+
+ if current_application_settings.signup_enabled?
+ message << " Create a GitLab account first, and then connect it to your #{label} account."
+ end
+
+ flash[:notice] = message
+
+ redirect_to new_user_session_path
+ end
+
def oauth
@oauth ||= request.env['omniauth.auth']
end
diff --git a/app/controllers/profiles/notifications_controller.rb b/app/controllers/profiles/notifications_controller.rb
index 1fd1d6882df..18ee55c839a 100644
--- a/app/controllers/profiles/notifications_controller.rb
+++ b/app/controllers/profiles/notifications_controller.rb
@@ -1,39 +1,18 @@
class Profiles::NotificationsController < Profiles::ApplicationController
def show
@user = current_user
- @notification = current_user.notification
- @project_members = current_user.project_members
- @group_members = current_user.group_members
+ @group_notifications = current_user.notification_settings.for_groups
+ @project_notifications = current_user.notification_settings.for_projects
end
def update
- type = params[:notification_type]
-
- @saved = if type == 'global'
- current_user.update_attributes(user_params)
- elsif type == 'group'
- group_member = current_user.group_members.find(params[:notification_id])
- group_member.notification_level = params[:notification_level]
- group_member.save
- else
- project_member = current_user.project_members.find(params[:notification_id])
- project_member.notification_level = params[:notification_level]
- project_member.save
- end
-
- respond_to do |format|
- format.html do
- if @saved
- flash[:notice] = "Notification settings saved"
- else
- flash[:alert] = "Failed to save new settings"
- end
-
- redirect_back_or_default(default: profile_notifications_path)
- end
-
- format.js
+ if current_user.update_attributes(user_params)
+ flash[:notice] = "Notification settings saved"
+ else
+ flash[:alert] = "Failed to save new settings"
end
+
+ redirect_back_or_default(default: profile_notifications_path)
end
def user_params
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index ae613f5e093..3e0cfc6aa65 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -237,6 +237,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
end
+ status = "preparing" if status.nil?
+
response = {
title: merge_request.title,
sha: merge_request.last_commit_short_sha,
diff --git a/app/controllers/projects/notification_settings_controller.rb b/app/controllers/projects/notification_settings_controller.rb
new file mode 100644
index 00000000000..7d81cc03c73
--- /dev/null
+++ b/app/controllers/projects/notification_settings_controller.rb
@@ -0,0 +1,16 @@
+class Projects::NotificationSettingsController < Projects::ApplicationController
+ before_action :authenticate_user!
+
+ def update
+ notification_setting = current_user.notification_settings_for(project)
+ saved = notification_setting.update_attributes(notification_setting_params)
+
+ render json: { saved: saved }
+ end
+
+ private
+
+ def notification_setting_params
+ params.require(:notification_setting).permit(:level)
+ end
+end
diff --git a/app/controllers/projects/repositories_controller.rb b/app/controllers/projects/repositories_controller.rb
index 5c7614cfbaf..bb7a6b6a5ab 100644
--- a/app/controllers/projects/repositories_controller.rb
+++ b/app/controllers/projects/repositories_controller.rb
@@ -11,7 +11,6 @@ class Projects::RepositoriesController < Projects::ApplicationController
end
def archive
- RepositoryArchiveCacheWorker.perform_async
headers.store(*Gitlab::Workhorse.send_git_archive(@project, params[:ref], params[:format]))
head :ok
rescue => ex
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 3cc37e59855..3768efe142a 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -101,14 +101,18 @@ class ProjectsController < Projects::ApplicationController
respond_to do |format|
format.html do
+ if current_user
+ @membership = @project.team.find_member(current_user.id)
+
+ if @membership
+ @notification_setting = current_user.notification_settings_for(@project)
+ end
+ end
+
if @project.repository_exists?
if @project.empty_repo?
render 'projects/empty'
else
- if current_user
- @membership = @project.team.find_member(current_user.id)
- end
-
render :show
end
else
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index e6ceb213532..16e5b8ac223 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -184,7 +184,7 @@ module ApplicationHelper
element = content_tag :time, time.to_s,
class: "#{html_class} js-timeago #{"js-timeago-pending" unless skip_js}",
datetime: time.to_time.getutc.iso8601,
- title: time.in_time_zone.to_s(:medium),
+ title: time.to_time.in_time_zone.to_s(:medium),
data: { toggle: 'tooltip', placement: placement, container: 'body' }
unless skip_js
diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb
index 499c655d2bf..54ab9179efc 100644
--- a/app/helpers/notifications_helper.rb
+++ b/app/helpers/notifications_helper.rb
@@ -1,48 +1,48 @@
module NotificationsHelper
include IconsHelper
- def notification_icon(notification)
- if notification.disabled?
- icon('volume-off', class: 'ns-mute')
- elsif notification.participating?
- icon('volume-down', class: 'ns-part')
- elsif notification.watch?
- icon('volume-up', class: 'ns-watch')
- else
- icon('circle-o', class: 'ns-default')
+ def notification_icon_class(level)
+ case level.to_sym
+ when :disabled
+ 'microphone-slash'
+ when :participating
+ 'volume-up'
+ when :watch
+ 'eye'
+ when :mention
+ 'at'
+ when :global
+ 'globe'
end
end
- def notification_list_item(notification_level, user_membership)
- case notification_level
- when Notification::N_DISABLED
- update_notification_link(Notification::N_DISABLED, user_membership, 'Disabled', 'microphone-slash')
- when Notification::N_PARTICIPATING
- update_notification_link(Notification::N_PARTICIPATING, user_membership, 'Participate', 'volume-up')
- when Notification::N_WATCH
- update_notification_link(Notification::N_WATCH, user_membership, 'Watch', 'eye')
- when Notification::N_MENTION
- update_notification_link(Notification::N_MENTION, user_membership, 'On mention', 'at')
- when Notification::N_GLOBAL
- update_notification_link(Notification::N_GLOBAL, user_membership, 'Global', 'globe')
- else
- # do nothing
- end
+ def notification_icon(level, text = nil)
+ icon("#{notification_icon_class(level)} fw", text: text)
end
- def update_notification_link(notification_level, user_membership, title, icon)
- content_tag(:li, class: active_level_for(user_membership, notification_level)) do
- link_to '#', class: 'update-notification', data: { notification_level: notification_level } do
- icon("#{icon} fw", text: title)
- end
+ def notification_title(level)
+ case level.to_sym
+ when :participating
+ 'Participate'
+ when :mention
+ 'On mention'
+ else
+ level.to_s.titlecase
end
end
- def notification_label(user_membership)
- Notification.new(user_membership).to_s
- end
+ def notification_list_item(level, setting)
+ title = notification_title(level)
+
+ data = {
+ notification_level: level,
+ notification_title: title
+ }
- def active_level_for(user_membership, level)
- 'active' if user_membership.notification_level == level
+ content_tag(:li, class: ('active' if setting.level == level)) do
+ link_to '#', class: 'update-notification', data: data do
+ notification_icon(level, title)
+ end
+ end
end
end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 4e4c6e301d5..7e00aacceaa 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -65,21 +65,14 @@ module ProjectsHelper
link_to(simple_sanitize(owner.name), user_path(owner))
end
- project_link = link_to project_path(project), { class: "project-item-select-holder" } do
- link_output = simple_sanitize(project.name)
+ project_link = link_to simple_sanitize(project.name), project_path(project), { class: "project-item-select-holder" }
- if current_user
- link_output += project_select_tag :project_path,
- class: "project-item-select js-projects-dropdown",
- data: { include_groups: false, order_by: 'last_activity_at' }
- end
-
- link_output
+ if current_user
+ project_link << icon("chevron-down", class: "dropdown-toggle-caret js-projects-dropdown-toggle", data: { target: ".js-dropdown-menu-projects", toggle: "dropdown" })
end
- project_link += icon "chevron-down", class: "dropdown-toggle-caret js-projects-dropdown-toggle" if current_user
- full_title = namespace_link + ' / ' + project_link
- full_title += ' &middot; '.html_safe + link_to(simple_sanitize(name), url) if name
+ full_title = "#{namespace_link} / #{project_link}".html_safe
+ full_title << ' &middot; '.html_safe << link_to(simple_sanitize(name), url) if name
full_title
end
diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb
index edc5686cf08..2f066682180 100644
--- a/app/helpers/todos_helper.rb
+++ b/app/helpers/todos_helper.rb
@@ -20,6 +20,8 @@ module TodosHelper
end
def todo_target_path(todo)
+ return unless todo.target.present?
+
anchor = dom_id(todo.note) if todo.note.present?
if todo.for_commit?
diff --git a/app/models/commit.rb b/app/models/commit.rb
index d09876a07d9..11ecfcace14 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -150,13 +150,11 @@ class Commit
end
def hook_attrs(with_changed_files: false)
- path_with_namespace = project.path_with_namespace
-
data = {
id: id,
message: safe_message,
timestamp: committed_date.xmlschema,
- url: "#{Gitlab.config.gitlab.url}/#{path_with_namespace}/commit/#{id}",
+ url: commit_url,
author: {
name: author_name,
email: author_email
@@ -170,6 +168,10 @@ class Commit
data
end
+ def commit_url
+ project.present? ? "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/commit/#{id}" : ""
+ end
+
# Discover issues should be closed when this commit is pushed to a project's
# default branch.
def closes_issues(current_user = self.committer)
diff --git a/app/models/concerns/notifiable.rb b/app/models/concerns/notifiable.rb
deleted file mode 100644
index d7dcd97911d..00000000000
--- a/app/models/concerns/notifiable.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-# == Notifiable concern
-#
-# Contains notification functionality
-#
-module Notifiable
- extend ActiveSupport::Concern
-
- included do
- validates :notification_level, inclusion: { in: Notification.project_notification_levels }, presence: true
- end
-
- def notification
- @notification ||= Notification.new(self)
- end
-end
diff --git a/app/models/group.rb b/app/models/group.rb
index b332601c59b..9a04ac70d35 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -27,6 +27,7 @@ class Group < Namespace
has_many :users, through: :group_members
has_many :project_group_links, dependent: :destroy
has_many :shared_projects, through: :project_group_links, source: :project
+ has_many :notification_settings, dependent: :destroy, as: :source
validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? }
validate :visibility_level_allowed_by_projects
diff --git a/app/models/member.rb b/app/models/member.rb
index ca08007b7eb..60efafef211 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -19,7 +19,6 @@
class Member < ActiveRecord::Base
include Sortable
- include Notifiable
include Gitlab::Access
attr_accessor :raw_invite_token
@@ -56,12 +55,15 @@ class Member < ActiveRecord::Base
before_validation :generate_invite_token, on: :create, if: -> (member) { member.invite_email.present? }
after_create :send_invite, if: :invite?
+ after_create :create_notification_setting, unless: :invite?
after_create :post_create_hook, unless: :invite?
after_update :post_update_hook, unless: :invite?
after_destroy :post_destroy_hook, unless: :invite?
delegate :name, :username, :email, to: :user, prefix: true
+ default_value_for :notification_level, NotificationSetting.levels[:global]
+
class << self
def find_by_invite_token(invite_token)
invite_token = Devise.token_generator.digest(self, :invite_token, invite_token)
@@ -160,6 +162,14 @@ class Member < ActiveRecord::Base
send_invite
end
+ def create_notification_setting
+ user.notification_settings.find_or_create_for(source)
+ end
+
+ def notification_setting
+ @notification_setting ||= user.notification_settings_for(source)
+ end
+
private
def send_invite
diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb
index 65d2ea00570..9fb474a1a93 100644
--- a/app/models/members/group_member.rb
+++ b/app/models/members/group_member.rb
@@ -24,7 +24,6 @@ class GroupMember < Member
# Make sure group member points only to group as it source
default_value_for :source_type, SOURCE_TYPE
- default_value_for :notification_level, Notification::N_GLOBAL
validates_format_of :source_type, with: /\ANamespace\z/
default_scope { where(source_type: SOURCE_TYPE) }
diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb
index 560d1690e14..07ddb02ae9d 100644
--- a/app/models/members/project_member.rb
+++ b/app/models/members/project_member.rb
@@ -27,7 +27,6 @@ class ProjectMember < Member
# Make sure project member points only to project as it source
default_value_for :source_type, SOURCE_TYPE
- default_value_for :notification_level, Notification::N_GLOBAL
validates_format_of :source_type, with: /\AProject\z/
default_scope { where(source_type: SOURCE_TYPE) }
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index bf185cb5dd8..e410febdfff 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -128,7 +128,7 @@ class MergeRequest < ActiveRecord::Base
validates :target_project, presence: true
validates :target_branch, presence: true
validates :merge_user, presence: true, if: :merge_when_build_succeeds?
- validate :validate_branches
+ validate :validate_branches, unless: :allow_broken
validate :validate_fork
scope :by_branch, ->(branch_name) { where("(source_branch LIKE :branch) OR (target_branch LIKE :branch)", branch: branch_name) }
@@ -218,7 +218,7 @@ class MergeRequest < ActiveRecord::Base
end
if opened? || reopened?
- similar_mrs = self.target_project.merge_requests.where(source_branch: source_branch, target_branch: target_branch, source_project_id: source_project.id).opened
+ similar_mrs = self.target_project.merge_requests.where(source_branch: source_branch, target_branch: target_branch, source_project_id: source_project.try(:id)).opened
similar_mrs = similar_mrs.where('id not in (?)', self.id) if self.id
if similar_mrs.any?
errors.add :validate_branches,
@@ -345,7 +345,7 @@ class MergeRequest < ActiveRecord::Base
def hook_attrs
attrs = {
- source: source_project.hook_attrs,
+ source: source_project.try(:hook_attrs),
target: target_project.hook_attrs,
last_commit: nil,
work_in_progress: work_in_progress?
diff --git a/app/models/notification.rb b/app/models/notification.rb
deleted file mode 100644
index 171b8df45c2..00000000000
--- a/app/models/notification.rb
+++ /dev/null
@@ -1,77 +0,0 @@
-class Notification
- #
- # Notification levels
- #
- N_DISABLED = 0
- N_PARTICIPATING = 1
- N_WATCH = 2
- N_GLOBAL = 3
- N_MENTION = 4
-
- attr_accessor :target
-
- class << self
- def notification_levels
- [N_DISABLED, N_MENTION, N_PARTICIPATING, N_WATCH]
- end
-
- def options_with_labels
- {
- disabled: N_DISABLED,
- participating: N_PARTICIPATING,
- watch: N_WATCH,
- mention: N_MENTION,
- global: N_GLOBAL
- }
- end
-
- def project_notification_levels
- [N_DISABLED, N_MENTION, N_PARTICIPATING, N_WATCH, N_GLOBAL]
- end
- end
-
- def initialize(target)
- @target = target
- end
-
- def disabled?
- target.notification_level == N_DISABLED
- end
-
- def participating?
- target.notification_level == N_PARTICIPATING
- end
-
- def watch?
- target.notification_level == N_WATCH
- end
-
- def global?
- target.notification_level == N_GLOBAL
- end
-
- def mention?
- target.notification_level == N_MENTION
- end
-
- def level
- target.notification_level
- end
-
- def to_s
- case level
- when N_DISABLED
- 'Disabled'
- when N_PARTICIPATING
- 'Participating'
- when N_WATCH
- 'Watching'
- when N_MENTION
- 'On mention'
- when N_GLOBAL
- 'Global'
- else
- # do nothing
- end
- end
-end
diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb
new file mode 100644
index 00000000000..5001738f411
--- /dev/null
+++ b/app/models/notification_setting.rb
@@ -0,0 +1,28 @@
+class NotificationSetting < ActiveRecord::Base
+ enum level: { disabled: 0, participating: 1, watch: 2, global: 3, mention: 4 }
+
+ default_value_for :level, NotificationSetting.levels[:global]
+
+ belongs_to :user
+ belongs_to :source, polymorphic: true
+
+ validates :user, presence: true
+ validates :source, presence: true
+ validates :level, presence: true
+ validates :user_id, uniqueness: { scope: [:source_type, :source_id],
+ message: "already exists in source",
+ allow_nil: true }
+
+ scope :for_groups, -> { where(source_type: 'Namespace') }
+ scope :for_projects, -> { where(source_type: 'Project') }
+
+ def self.find_or_create_for(source)
+ setting = find_or_initialize_by(source: source)
+
+ unless setting.persisted?
+ setting.save
+ end
+
+ setting
+ end
+end
diff --git a/app/models/project.rb b/app/models/project.rb
index 3e1f04b4158..fadc8bb2c9e 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -154,6 +154,7 @@ class Project < ActiveRecord::Base
has_many :project_group_links, dependent: :destroy
has_many :invited_groups, through: :project_group_links, source: :group
has_many :todos, dependent: :destroy
+ has_many :notification_settings, dependent: :destroy, as: :source
has_one :import_data, dependent: :destroy, class_name: "ProjectImportData"
@@ -388,9 +389,15 @@ class Project < ActiveRecord::Base
def add_import_job
if forked?
- RepositoryForkWorker.perform_async(self.id, forked_from_project.path_with_namespace, self.namespace.path)
+ job_id = RepositoryForkWorker.perform_async(self.id, forked_from_project.path_with_namespace, self.namespace.path)
else
- RepositoryImportWorker.perform_async(self.id)
+ job_id = RepositoryImportWorker.perform_async(self.id)
+ end
+
+ if job_id
+ Rails.logger.info "Import job started for #{path_with_namespace} with job ID #{job_id}"
+ else
+ Rails.logger.error "Import job failed to start for #{path_with_namespace}"
end
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 462b48118ef..0b2289cfa39 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -253,6 +253,8 @@ class Repository
# This ensures this particular cache is flushed after the first commit to a
# new repository.
expire_emptiness_caches if empty?
+ expire_branch_count_cache
+ expire_tag_count_cache
end
def expire_branch_cache(branch_name = nil)
diff --git a/app/models/user.rb b/app/models/user.rb
index 2b0bee2099f..031315debd7 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -143,6 +143,7 @@ class User < ActiveRecord::Base
has_many :spam_logs, dependent: :destroy
has_many :builds, dependent: :nullify, class_name: 'Ci::Build'
has_many :todos, dependent: :destroy
+ has_many :notification_settings, dependent: :destroy
#
# Validations
@@ -157,7 +158,7 @@ class User < ActiveRecord::Base
presence: true,
uniqueness: { case_sensitive: false }
- validates :notification_level, inclusion: { in: Notification.notification_levels }, presence: true
+ validates :notification_level, presence: true
validate :namespace_uniq, if: ->(user) { user.username_changed? }
validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? }
validate :unique_email, if: ->(user) { user.email_changed? }
@@ -190,6 +191,13 @@ class User < ActiveRecord::Base
# Note: When adding an option, it MUST go on the end of the array.
enum project_view: [:readme, :activity, :files]
+ # Notification level
+ # Note: When adding an option, it MUST go on the end of the array.
+ #
+ # TODO: Add '_prefix: :notification' to enum when update to Rails 5. https://github.com/rails/rails/pull/19813
+ # Because user.notification_disabled? is much better than user.disabled?
+ enum notification_level: [:disabled, :participating, :watch, :global, :mention]
+
alias_attribute :private_token, :authentication_token
delegate :path, to: :namespace, allow_nil: true, prefix: true
@@ -349,10 +357,6 @@ class User < ActiveRecord::Base
"#{self.class.reference_prefix}#{username}"
end
- def notification
- @notification ||= Notification.new(self)
- end
-
def generate_password
if self.force_random_password
self.password = self.password_confirmation = Devise.friendly_token.first(8)
@@ -827,6 +831,10 @@ class User < ActiveRecord::Base
end
end
+ def notification_settings_for(source)
+ notification_settings.find_or_initialize_by(source: source)
+ end
+
private
def projects_union
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index eff0d96f93d..42ec1ac9e1a 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -253,8 +253,8 @@ class NotificationService
def project_watchers(project)
project_members = project_member_notification(project)
- users_with_project_level_global = project_member_notification(project, Notification::N_GLOBAL)
- users_with_group_level_global = group_member_notification(project, Notification::N_GLOBAL)
+ users_with_project_level_global = project_member_notification(project, :global)
+ users_with_group_level_global = group_member_notification(project, :global)
users = users_with_global_level_watch([users_with_project_level_global, users_with_group_level_global].flatten.uniq)
users_with_project_setting = select_project_member_setting(project, users_with_project_level_global, users)
@@ -264,18 +264,16 @@ class NotificationService
end
def project_member_notification(project, notification_level=nil)
- project_members = project.project_members
-
if notification_level
- project_members.where(notification_level: notification_level).pluck(:user_id)
+ project.notification_settings.where(level: NotificationSetting.levels[notification_level]).pluck(:user_id)
else
- project_members.pluck(:user_id)
+ project.notification_settings.pluck(:user_id)
end
end
def group_member_notification(project, notification_level)
if project.group
- project.group.group_members.where(notification_level: notification_level).pluck(:user_id)
+ project.group.notification_settings.where(level: NotificationSetting.levels[notification_level]).pluck(:user_id)
else
[]
end
@@ -284,13 +282,13 @@ class NotificationService
def users_with_global_level_watch(ids)
User.where(
id: ids,
- notification_level: Notification::N_WATCH
+ notification_level: NotificationSetting.levels[:watch]
).pluck(:id)
end
# Build a list of users based on project notifcation settings
def select_project_member_setting(project, global_setting, users_global_level_watch)
- users = project_member_notification(project, Notification::N_WATCH)
+ users = project_member_notification(project, :watch)
# If project setting is global, add to watch list if global setting is watch
global_setting.each do |user_id|
@@ -304,7 +302,7 @@ class NotificationService
# Build a list of users based on group notification settings
def select_group_member_setting(project, project_members, global_setting, users_global_level_watch)
- uids = group_member_notification(project, Notification::N_WATCH)
+ uids = group_member_notification(project, :watch)
# Group setting is watch, add to users list if user is not project member
users = []
@@ -331,40 +329,46 @@ class NotificationService
# Remove users with disabled notifications from array
# Also remove duplications and nil recipients
def reject_muted_users(users, project = nil)
- reject_users(users, :disabled?, project)
+ reject_users(users, :disabled, project)
end
# Remove users with notification level 'Mentioned'
def reject_mention_users(users, project = nil)
- reject_users(users, :mention?, project)
+ reject_users(users, :mention, project)
end
- # Reject users which method_name from notification object returns true.
+ # Reject users which has certain notification level
#
# Example:
- # reject_users(users, :watch?, project)
+ # reject_users(users, :watch, project)
#
- def reject_users(users, method_name, project = nil)
+ def reject_users(users, level, project = nil)
+ level = level.to_s
+
+ unless NotificationSetting.levels.keys.include?(level)
+ raise 'Invalid notification level'
+ end
+
users = users.to_a.compact.uniq
users = users.reject(&:blocked?)
users.reject do |user|
- next user.notification.send(method_name) unless project
+ next user.notification_level == level unless project
- member = project.project_members.find_by(user_id: user.id)
+ setting = user.notification_settings_for(project)
- if !member && project.group
- member = project.group.group_members.find_by(user_id: user.id)
+ if !setting && project.group
+ setting = user.notification_settings_for(project.group)
end
- # reject users who globally set mention notification and has no membership
- next user.notification.send(method_name) unless member
+ # reject users who globally set mention notification and has no setting per project/group
+ next user.notification_level == level unless setting
# reject users who set mention notification in project
- next true if member.notification.send(method_name)
+ next true if setting.level == level
- # reject users who have N_MENTION in project and disabled in global settings
- member.notification.global? && user.notification.send(method_name)
+ # reject users who have mention level in project and disabled in global settings
+ setting.global? && user.notification_level == level
end
end
diff --git a/app/services/projects/housekeeping_service.rb b/app/services/projects/housekeeping_service.rb
index a0973c5d260..3b7c36f0908 100644
--- a/app/services/projects/housekeeping_service.rb
+++ b/app/services/projects/housekeeping_service.rb
@@ -26,7 +26,9 @@ module Projects
GitlabShellOneShotWorker.perform_async(:gc, @project.path_with_namespace)
ensure
- @project.update_column(:pushes_since_gc, 0)
+ Gitlab::Metrics.measure(:reset_pushes_since_gc) do
+ @project.update_column(:pushes_since_gc, 0)
+ end
end
def needed?
@@ -34,14 +36,18 @@ module Projects
end
def increment!
- @project.increment!(:pushes_since_gc)
+ Gitlab::Metrics.measure(:increment_pushes_since_gc) do
+ @project.increment!(:pushes_since_gc)
+ end
end
private
def try_obtain_lease
- lease = ::Gitlab::ExclusiveLease.new("project_housekeeping:#{@project.id}", timeout: LEASE_TIMEOUT)
- lease.try_obtain
+ Gitlab::Metrics.measure(:obtain_housekeeping_lease) do
+ lease = ::Gitlab::ExclusiveLease.new("project_housekeeping:#{@project.id}", timeout: LEASE_TIMEOUT)
+ lease.try_obtain
+ end
end
end
end
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 0f3b8119379..44339293095 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -45,6 +45,8 @@
%h1.title= title
+ = yield :header_content
+
= render 'shared/outdated_browser'
- if @project && !@project.empty_repo?
diff --git a/app/views/layouts/project.html.haml b/app/views/layouts/project.html.haml
index a7ef31acd3d..6dfe7fbdae8 100644
--- a/app/views/layouts/project.html.haml
+++ b/app/views/layouts/project.html.haml
@@ -17,4 +17,12 @@
- content_for :scripts_body do
= render "layouts/init_auto_complete" if current_user
+- content_for :header_content do
+ .js-dropdown-menu-projects
+ .dropdown-menu.dropdown-select.dropdown-menu-projects
+ = dropdown_title("Go to a project")
+ = dropdown_filter("Search your projects")
+ = dropdown_content
+ = dropdown_loading
+
= render template: "layouts/application"
diff --git a/app/views/profiles/notifications/_group_settings.html.haml b/app/views/profiles/notifications/_group_settings.html.haml
new file mode 100644
index 00000000000..89ae7ffda2b
--- /dev/null
+++ b/app/views/profiles/notifications/_group_settings.html.haml
@@ -0,0 +1,13 @@
+%li.notification-list-item
+ %span.notification.fa.fa-holder.append-right-5
+ - if setting.global?
+ = notification_icon(current_user.notification_level)
+ - else
+ = notification_icon(setting.level)
+
+ %span.str-truncated
+ = link_to group.name, group_path(group)
+
+ .pull-right
+ = form_for [group, setting], remote: true, html: { class: 'update-notifications' } do |f|
+ = f.select :level, NotificationSetting.levels.keys, {}, class: 'form-control trigger-submit'
diff --git a/app/views/profiles/notifications/_project_settings.html.haml b/app/views/profiles/notifications/_project_settings.html.haml
new file mode 100644
index 00000000000..17c097154da
--- /dev/null
+++ b/app/views/profiles/notifications/_project_settings.html.haml
@@ -0,0 +1,13 @@
+%li.notification-list-item
+ %span.notification.fa.fa-holder.append-right-5
+ - if setting.global?
+ = notification_icon(current_user.notification_level)
+ - else
+ = notification_icon(setting.level)
+
+ %span.str-truncated
+ = link_to_project(project)
+
+ .pull-right
+ = form_for [project.namespace.becomes(Namespace), project, setting], remote: true, html: { class: 'update-notifications' } do |f|
+ = f.select :level, NotificationSetting.levels.keys, {}, class: 'form-control trigger-submit'
diff --git a/app/views/profiles/notifications/_settings.html.haml b/app/views/profiles/notifications/_settings.html.haml
deleted file mode 100644
index d0d044136f6..00000000000
--- a/app/views/profiles/notifications/_settings.html.haml
+++ /dev/null
@@ -1,17 +0,0 @@
-%li.notification-list-item
- %span.notification.fa.fa-holder.append-right-5
- - if notification.global?
- = notification_icon(@notification)
- - else
- = notification_icon(notification)
-
- %span.str-truncated
- - if membership.kind_of? GroupMember
- = link_to membership.group.name, membership.group
- - else
- = link_to_project(membership.project)
- .pull-right
- = form_tag profile_notifications_path, method: :put, remote: true, class: 'update-notifications' do
- = hidden_field_tag :notification_type, type, id: dom_id(membership, 'notification_type')
- = hidden_field_tag :notification_id, membership.id, id: dom_id(membership, 'notification_id')
- = select_tag :notification_level, options_for_select(Notification.options_with_labels, notification.level), class: 'form-control trigger-submit'
diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml
index 6609295a2a5..a2a505c082b 100644
--- a/app/views/profiles/notifications/show.html.haml
+++ b/app/views/profiles/notifications/show.html.haml
@@ -1,8 +1,12 @@
- page_title "Notifications"
- header_title page_title, profile_notifications_path
-= form_for @user, url: profile_notifications_path, method: :put, html: { class: 'update-notifications prepend-top-default' } do |f|
- = form_errors(@user)
+%div
+ - if @user.errors.any?
+ %div.alert.alert-danger
+ %ul
+ - @user.errors.full_messages.each do |msg|
+ %li= msg
= hidden_field_tag :notification_type, 'global'
.row
@@ -16,56 +20,55 @@
.col-lg-9
%h5
Global notification settings
- .form-group
- = f.label :notification_email, class: "label-light"
- = f.select :notification_email, @user.all_emails, { include_blank: false }, class: "select2"
- .form-group
- = f.label :notification_level, class: 'label-light'
- .radio
- = f.label :notification_level, value: Notification::N_DISABLED do
- = f.radio_button :notification_level, Notification::N_DISABLED
- .level-title
- Disabled
- %p You will not get any notifications via email
- .radio
- = f.label :notification_level, value: Notification::N_MENTION do
- = f.radio_button :notification_level, Notification::N_MENTION
- .level-title
- On Mention
- %p You will receive notifications only for comments in which you were @mentioned
+ = form_for @user, url: profile_notifications_path, method: :put, html: { class: 'update-notifications prepend-top-default' } do |f|
+ .form-group
+ = f.label :notification_email, class: "label-light"
+ = f.select :notification_email, @user.all_emails, { include_blank: false }, class: "select2"
+ .form-group
+ = f.label :notification_level, class: 'label-light'
+ .radio
+ = f.label :notification_level, value: :disabled do
+ = f.radio_button :notification_level, :disabled
+ .level-title
+ Disabled
+ %p You will not get any notifications via email
- .radio
- = f.label :notification_level, value: Notification::N_PARTICIPATING do
- = f.radio_button :notification_level, Notification::N_PARTICIPATING
- .level-title
- Participating
- %p You will only receive notifications from related resources (e.g. from your commits or assigned issues)
+ .radio
+ = f.label :notification_level, value: :mention do
+ = f.radio_button :notification_level, :mention
+ .level-title
+ On Mention
+ %p You will receive notifications only for comments in which you were @mentioned
- .radio
- = f.label :notification_level, value: Notification::N_WATCH do
- = f.radio_button :notification_level, Notification::N_WATCH
- .level-title
- Watch
- %p You will receive notifications for any activity
+ .radio
+ = f.label :notification_level, value: :participating do
+ = f.radio_button :notification_level, :participating
+ .level-title
+ Participating
+ %p You will only receive notifications from related resources (e.g. from your commits or assigned issues)
- .prepend-top-default
- = f.submit 'Update settings', class: "btn btn-create"
+ .radio
+ = f.label :notification_level, value: :watch do
+ = f.radio_button :notification_level, :watch
+ .level-title
+ Watch
+ %p You will receive notifications for any activity
+
+ .prepend-top-default
+ = f.submit 'Update settings', class: "btn btn-create"
%hr
-.col-lg-9.col-lg-push-3
- %h5
- Groups (#{@group_members.count})
- %div
- %ul.bordered-list
- - @group_members.each do |group_member|
- - notification = Notification.new(group_member)
- = render 'settings', type: 'group', membership: group_member, notification: notification
- %h5
- Projects (#{@project_members.count})
- %p.account-well
- To specify the notification level per project of a group you belong to, you need to be a member of the project itself, not only its group.
- .append-bottom-default
- %ul.bordered-list
- - @project_members.each do |project_member|
- - notification = Notification.new(project_member)
- = render 'settings', type: 'project', membership: project_member, notification: notification
+ %h5
+ Groups (#{@group_notifications.count})
+ %div
+ %ul.bordered-list
+ - @group_notifications.each do |setting|
+ = render 'group_settings', setting: setting, group: setting.source
+ %h5
+ Projects (#{@project_notifications.count})
+ %p.account-well
+ To specify the notification level per project of a group you belong to, you need to visit project page and change notification level there.
+ .append-bottom-default
+ %ul.bordered-list
+ - @project_notifications.each do |setting|
+ = render 'project_settings', setting: setting, project: setting.source
diff --git a/app/views/profiles/notifications/update.js.haml b/app/views/profiles/notifications/update.js.haml
deleted file mode 100644
index 84c6ab25599..00000000000
--- a/app/views/profiles/notifications/update.js.haml
+++ /dev/null
@@ -1,6 +0,0 @@
-- if @saved
- :plain
- new Flash("Notification settings saved", "notice")
-- else
- :plain
- new Flash("Failed to save new settings", "alert")
diff --git a/app/views/projects/buttons/_notifications.html.haml b/app/views/projects/buttons/_notifications.html.haml
index a3786c35a1f..c1e3e5b73a2 100644
--- a/app/views/projects/buttons/_notifications.html.haml
+++ b/app/views/projects/buttons/_notifications.html.haml
@@ -1,20 +1,11 @@
-- case @membership
-- when ProjectMember
- = form_tag profile_notifications_path, method: :put, remote: true, class: 'inline', id: 'notification-form' do
- = hidden_field_tag :notification_type, 'project'
- = hidden_field_tag :notification_id, @membership.id
- = hidden_field_tag :notification_level
+- if @notification_setting
+ = form_for @notification_setting, url: namespace_project_notification_setting_path(@project.namespace.becomes(Namespace), @project), method: :patch, remote: true, html: { class: 'inline', id: 'notification-form' } do |f|
+ = f.hidden_field :level
%span.dropdown
%a.dropdown-new.btn.notifications-btn#notifications-button{href: '#', "data-toggle" => "dropdown"}
= icon('bell')
- = notification_label(@membership)
+ = notification_title(@notification_setting.level)
= icon('angle-down')
%ul.dropdown-menu.dropdown-menu-right.project-home-dropdown
- - Notification.project_notification_levels.each do |level|
- = notification_list_item(level, @membership)
-
-- when GroupMember
- .btn.disabled.notifications-btn.has-tooltip{title: "To change the notification level, you need to be a member of the project itself, not only its group."}
- = icon('bell')
- = notification_label(@membership)
- = icon('angle-down')
+ - NotificationSetting.levels.each do |level|
+ = notification_list_item(level.first, @notification_setting)
diff --git a/app/views/projects/merge_requests/_form.html.haml b/app/views/projects/merge_requests/_form.html.haml
index 3e4ab09c6d4..1e6724fc92b 100644
--- a/app/views/projects/merge_requests/_form.html.haml
+++ b/app/views/projects/merge_requests/_form.html.haml
@@ -1,4 +1,4 @@
-= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal gfm-form js-requires-input' } do |f|
+= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal gfm-form js-requires-input js-quick-submit' } do |f|
= render 'shared/issuable/form', f: f, issuable: @merge_request
:javascript
diff --git a/app/views/projects/merge_requests/widget/_show.html.haml b/app/views/projects/merge_requests/widget/_show.html.haml
index 92d95358937..3c68d61c4b5 100644
--- a/app/views/projects/merge_requests/widget/_show.html.haml
+++ b/app/views/projects/merge_requests/widget/_show.html.haml
@@ -8,20 +8,22 @@
= render 'projects/merge_requests/widget/locked'
:javascript
- var merge_request_widget;
var opts = {
merge_check_url: "#{merge_check_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
check_enable: #{@merge_request.unchecked? ? "true" : "false"},
ci_status_url: "#{ci_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
gitlab_icon: "#{asset_path 'gitlab_logo.png'}",
ci_status: "",
- ci_message: "Build {{status}} for \"{{title}}\"",
+ ci_message: {
+ normal: "Build {{status}} for \"{{title}}\"",
+ preparing: "{{status}} build for \"{{title}}\""
+ },
ci_enable: #{@project.ci_service ? "true" : "false"},
+ ci_title: {
+ preparing: "{{status}} build",
+ normal: "Build {{status}}"
+ },
builds_path: "#{builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}"
};
- if(typeof merge_request_widget === 'undefined') {
- merge_request_widget = new MergeRequestWidget(opts);
- } else {
- merge_request_widget.setOpts(opts);
- }
+ merge_request_widget = new MergeRequestWidget(opts);
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index 5c42423541e..03a44ca99c0 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -10,12 +10,12 @@
= "#{note.author.to_reference} commented"
%a{ href: "##{dom_id(note)}" }
= time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note-created-ago')
- - if note_editable?(note)
- .note-actions
- - access = note.project.team.human_max_access(note.author.id)
- - if access
- %span.note-role
- = access
+ .note-actions
+ - access = note.project.team.human_max_access(note.author.id)
+ - if access
+ %span.note-role
+ = access
+ - if note_editable?(note)
= link_to '#', title: 'Edit comment', class: 'note-action-button js-note-edit' do
= icon('pencil')
= link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'note-action-button js-note-delete danger' do
diff --git a/app/views/shared/_commit_message_container.html.haml b/app/views/shared/_commit_message_container.html.haml
index 7afbaeddee8..0a38327baa2 100644
--- a/app/views/shared/_commit_message_container.html.haml
+++ b/app/views/shared/_commit_message_container.html.haml
@@ -6,7 +6,7 @@
.commit-message-container
.max-width-marker
= text_area_tag 'commit_message',
- (params[:commit_message] || local_assigns[:text]),
+ (params[:commit_message] || local_assigns[:text] || local_assigns[:placeholder]),
class: 'form-control js-commit-message', placeholder: local_assigns[:placeholder],
required: true, rows: (local_assigns[:rows] || 3),
id: "commit_message-#{nonce}"
diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml
index fd5e58c1f1f..f722e61eeac 100644
--- a/app/views/shared/issuable/_label_dropdown.html.haml
+++ b/app/views/shared/issuable/_label_dropdown.html.haml
@@ -1,7 +1,7 @@
- if params[:label_name].present?
= hidden_field_tag(:label_name, params[:label_name])
.dropdown
- %button.dropdown-menu-toggle.js-label-select.js-filter-submit{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-filter-submit.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"}}
%span.dropdown-toggle-text
= h(params[:label_name].presence || "Label")
= icon('chevron-down')
diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb
index 3cc232ef1ae..9e1215b21a6 100644
--- a/app/workers/post_receive.rb
+++ b/app/workers/post_receive.rb
@@ -40,7 +40,7 @@ class PostReceive
if Gitlab::Git.tag_ref?(ref)
GitTagPushService.new.execute(post_received.project, @user, oldrev, newrev, ref)
- else
+ elsif Gitlab::Git.branch_ref?(ref)
GitPushService.new(post_received.project, @user, oldrev: oldrev, newrev: newrev, ref: ref).execute
end
end