summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/dispatcher.js.coffee3
-rw-r--r--app/assets/javascripts/gl_dropdown.js.coffee6
-rw-r--r--app/assets/javascripts/issue.js.coffee25
-rw-r--r--app/assets/javascripts/issues.js.coffee26
-rw-r--r--app/assets/javascripts/labels_select.js.coffee76
-rw-r--r--app/assets/javascripts/milestone_select.js.coffee51
-rw-r--r--app/assets/javascripts/notes.js.coffee5
-rw-r--r--app/assets/javascripts/project_new.js.coffee13
-rw-r--r--app/assets/javascripts/todos.js.coffee56
-rw-r--r--app/assets/javascripts/users_select.js.coffee35
-rw-r--r--app/assets/stylesheets/framework/blocks.scss4
-rw-r--r--app/assets/stylesheets/framework/buttons.scss10
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss6
-rw-r--r--app/assets/stylesheets/framework/filters.scss17
-rw-r--r--app/assets/stylesheets/framework/lists.scss11
-rw-r--r--app/assets/stylesheets/framework/variables.scss80
-rw-r--r--app/assets/stylesheets/pages/commits.scss2
-rw-r--r--app/assets/stylesheets/pages/diff.scss10
-rw-r--r--app/assets/stylesheets/pages/events.scss9
-rw-r--r--app/assets/stylesheets/pages/issuable.scss149
-rw-r--r--app/assets/stylesheets/pages/issues.scss8
-rw-r--r--app/assets/stylesheets/pages/projects.scss7
-rw-r--r--app/assets/stylesheets/pages/todos.scss17
-rw-r--r--app/assets/stylesheets/pages/tree.scss2
-rw-r--r--app/controllers/dashboard/todos_controller.rb17
-rw-r--r--app/controllers/dashboard_controller.rb25
-rw-r--r--app/controllers/projects/issues_controller.rb8
-rw-r--r--app/controllers/projects/labels_controller.rb7
-rw-r--r--app/controllers/projects/milestones_controller.rb10
-rw-r--r--app/controllers/projects_controller.rb2
-rw-r--r--app/finders/issues_finder.rb6
-rw-r--r--app/helpers/application_helper.rb20
-rw-r--r--app/helpers/dropdowns_helper.rb2
-rw-r--r--app/helpers/events_helper.rb2
-rw-r--r--app/helpers/issuables_helper.rb17
-rw-r--r--app/helpers/issues_helper.rb4
-rw-r--r--app/helpers/labels_helper.rb19
-rw-r--r--app/helpers/milestones_helper.rb24
-rw-r--r--app/helpers/projects_helper.rb4
-rw-r--r--app/helpers/todos_helper.rb11
-rw-r--r--app/models/ability.rb17
-rw-r--r--app/models/commit_status.rb2
-rw-r--r--app/models/concerns/milestoneish.rb20
-rw-r--r--app/models/event.rb6
-rw-r--r--app/models/issue.rb7
-rw-r--r--app/models/merge_request.rb11
-rw-r--r--app/models/milestone.rb4
-rw-r--r--app/models/project.rb5
-rw-r--r--app/models/repository.rb54
-rw-r--r--app/models/todo.rb32
-rw-r--r--app/services/commits/revert_service.rb7
-rw-r--r--app/services/git_push_service.rb2
-rw-r--r--app/services/projects/autocomplete_service.rb6
-rw-r--r--app/services/projects/housekeeping_service.rb2
-rw-r--r--app/services/search/global_service.rb2
-rw-r--r--app/services/search/project_service.rb3
-rw-r--r--app/services/todo_service.rb65
-rw-r--r--app/views/dashboard/todos/_todo.html.haml6
-rw-r--r--app/views/dashboard/todos/index.html.haml14
-rw-r--r--app/views/events/_event.html.haml2
-rw-r--r--app/views/layouts/header/_default.html.haml6
-rw-r--r--app/views/layouts/nav/_project.html.haml2
-rw-r--r--app/views/projects/_builds_settings.html.haml60
-rw-r--r--app/views/projects/diffs/_file.html.haml22
-rw-r--r--app/views/projects/edit.html.haml65
-rw-r--r--app/views/projects/issues/_issue.html.haml7
-rw-r--r--app/views/projects/issues/show.html.haml13
-rw-r--r--app/views/projects/merge_requests/_merge_request.html.haml6
-rw-r--r--app/views/projects/merge_requests/show/_mr_box.html.haml5
-rw-r--r--app/views/projects/merge_requests/show/_mr_title.html.haml13
-rw-r--r--app/views/projects/milestones/show.html.haml2
-rw-r--r--app/views/projects/notes/_note.html.haml10
-rw-r--r--app/views/projects/repositories/_download_archive.html.haml2
-rw-r--r--app/views/search/results/_issue.html.haml1
-rw-r--r--app/views/shared/groups/_group.html.haml13
-rw-r--r--app/views/shared/issuable/_filter.html.haml67
-rw-r--r--app/views/shared/issuable/_form.html.haml9
-rw-r--r--app/views/shared/issuable/_label_dropdown.html.haml39
-rw-r--r--app/views/shared/issuable/_milestone_dropdown.html.haml16
-rw-r--r--app/views/shared/issuable/_participants.html.haml16
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml48
-rw-r--r--app/views/shared/milestones/_issuable.html.haml2
-rw-r--r--app/views/shared/milestones/_milestone.html.haml4
-rw-r--r--app/views/shared/milestones/_summary.html.haml8
-rw-r--r--app/views/shared/milestones/_tabs.html.haml4
-rw-r--r--app/views/shared/milestones/_top.html.haml4
-rw-r--r--app/views/shared/projects/_project.html.haml34
-rw-r--r--app/views/shared/snippets/_snippet.html.haml4
-rw-r--r--app/workers/gitlab_shell_one_shot_worker.rb10
89 files changed, 978 insertions, 587 deletions
diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
index 1be86e3b820..f5e1ca9860d 100644
--- a/app/assets/javascripts/dispatcher.js.coffee
+++ b/app/assets/javascripts/dispatcher.js.coffee
@@ -14,7 +14,6 @@ class Dispatcher
path = page.split(':')
shortcut_handler = null
-
switch page
when 'projects:issues:index'
Issues.init()
@@ -25,6 +24,8 @@ class Dispatcher
new ZenMode()
when 'projects:milestones:show', 'groups:milestones:show', 'dashboard:milestones:show'
new Milestone()
+ when 'dashboard:todos:index'
+ new Todos()
when 'projects:milestones:new', 'projects:milestones:edit'
new ZenMode()
new DropzoneInput($('.milestone-form'))
diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee
index 4f038477755..c81e8bf760a 100644
--- a/app/assets/javascripts/gl_dropdown.js.coffee
+++ b/app/assets/javascripts/gl_dropdown.js.coffee
@@ -246,11 +246,15 @@ class GitLabDropdown
if oldValue
value = "#{oldValue},#{value}"
else
- @dropdown.find(ACTIVE_CLASS).removeClass ACTIVE_CLASS
+ @dropdown.find(".#{ACTIVE_CLASS}").removeClass ACTIVE_CLASS
# Toggle active class for the tick mark
el.toggleClass "is-active"
+ # Toggle the dropdown label
+ if @options.toggleLabel
+ $(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selectedObject)
+
if value?
if !field.length
# Create hidden input for form
diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee
index d663e34871c..f50df1f5ea3 100644
--- a/app/assets/javascripts/issue.js.coffee
+++ b/app/assets/javascripts/issue.js.coffee
@@ -7,6 +7,7 @@ class @Issue
# Prevent duplicate event bindings
@disableTaskList()
@fixAffixScroll()
+ @initParticipants()
if $('a.btn-close').length
@initTaskList()
@initIssueBtnEventListeners()
@@ -84,3 +85,27 @@ class @Issue
type: 'PATCH'
url: $('form.js-issuable-update').attr('action')
data: patchData
+
+ initParticipants: ->
+ _this = @
+ $(document).on "click", ".js-participants-more", @toggleHiddenParticipants
+
+ $(".js-participants-author").each (i) ->
+ if i >= _this.PARTICIPANTS_ROW_COUNT
+ $(@)
+ .addClass "js-participants-hidden"
+ .hide()
+
+ toggleHiddenParticipants: (e) ->
+ e.preventDefault()
+
+ currentText = $(this).text().trim()
+ lessText = $(this).data("less-text")
+ originalText = $(this).data("original-text")
+
+ if currentText is originalText
+ $(this).text(lessText)
+ else
+ $(this).text(originalText)
+
+ $(".js-participants-hidden").toggle()
diff --git a/app/assets/javascripts/issues.js.coffee b/app/assets/javascripts/issues.js.coffee
index a0acf3028bf..1127b289264 100644
--- a/app/assets/javascripts/issues.js.coffee
+++ b/app/assets/javascripts/issues.js.coffee
@@ -41,24 +41,28 @@
@timer = null
$("#issue_search").keyup ->
clearTimeout(@timer)
- @timer = setTimeout(Issues.filterResults, 500)
+ @timer = setTimeout( ->
+ Issues.filterResults $("#issue_search_form")
+ , 500)
- filterResults: =>
- form = $("#issue_search_form")
- search = $("#issue_search").val()
- $('.issues-holder').css("opacity", '0.5')
- issues_url = form.attr('action') + '?' + form.serialize()
+ filterResults: (form) =>
+ $('.issues-holder, .merge-requests-holder').css("opacity", '0.5')
+ formAction = form.attr('action')
+ formData = form.serialize()
+ issuesUrl = formAction
+ issuesUrl += ("#{if formAction.indexOf("?") < 0 then '?' else '&'}")
+ issuesUrl += formData
$.ajax
type: "GET"
- url: form.attr('action')
- data: form.serialize()
+ url: formAction
+ data: formData
complete: ->
- $('.issues-holder').css("opacity", '1.0')
+ $('.issues-holder, .merge-requests-holder').css("opacity", '1.0')
success: (data) ->
- $('.issues-holder').html(data.html)
+ $('.issues-holder, .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: issuesUrl}, document.title, issuesUrl
Issues.reload()
dataType: "json"
diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee
index 5ade2cb66cb..4a0c18a99a6 100644
--- a/app/assets/javascripts/labels_select.js.coffee
+++ b/app/assets/javascripts/labels_select.js.coffee
@@ -1,30 +1,32 @@
class @LabelsSelect
constructor: ->
$('.js-label-select').each (i, dropdown) ->
- projectId = $(dropdown).data('project-id')
- labelUrl = $(dropdown).data("labels")
- selectedLabel = $(dropdown).data('selected')
+ $dropdown = $(dropdown)
+ projectId = $dropdown.data('project-id')
+ labelUrl = $dropdown.data('labels')
+ selectedLabel = $dropdown.data('selected')
if selectedLabel
- selectedLabel = selectedLabel.split(",")
+ selectedLabel = selectedLabel.split(',')
newLabelField = $('#new_label_name')
newColorField = $('#new_label_color')
- showNo = $(dropdown).data('show-no')
- showAny = $(dropdown).data('show-any')
+ showNo = $dropdown.data('show-no')
+ showAny = $dropdown.data('show-any')
+ defaultLabel = $dropdown.data('default-label')
if newLabelField.length
- $('.suggest-colors-dropdown a').on "click", (e) ->
+ $('.suggest-colors-dropdown a').on 'click', (e) ->
e.preventDefault()
e.stopPropagation()
- newColorField.val $(this).data("color")
+ newColorField.val $(this).data('color')
$('.js-dropdown-label-color-preview')
- .css 'background-color', $(this).data("color")
+ .css 'background-color', $(this).data('color')
.addClass 'is-active'
- $('.js-new-label-btn').on "click", (e) ->
+ $('.js-new-label-btn').on 'click', (e) ->
e.preventDefault()
e.stopPropagation()
- if newLabelField.val() isnt "" && newColorField.val() isnt ""
+ if newLabelField.val() isnt '' and newColorField.val() isnt ''
$('.js-new-label-btn').disable()
# Create new label with API
@@ -33,46 +35,38 @@ class @LabelsSelect
color: newColorField.val()
}, (label) ->
$('.js-new-label-btn').enable()
- $('.dropdown-menu-back', $(dropdown).parent()).trigger "click"
+ $('.dropdown-menu-back', $dropdown.parent()).trigger 'click'
- $(dropdown).glDropdown(
+ $dropdown.glDropdown(
data: (term, callback) ->
- # We have to fetch the JS version of the labels list because there is no
- # public facing JSON url for labels
$.ajax(
url: labelUrl
).done (data) ->
- html = $(data)
- data = []
- html.find('.label-row a').each ->
- data.push(
- title: $(@).text().trim()
- )
-
if showNo
data.unshift(
- id: "0"
- title: 'No label'
+ id: 0
+ title: 'No Label'
)
if showAny
data.unshift(
- title: 'Any label'
+ isAny: true
+ title: 'Any Label'
)
if data.length > 2
- data.splice 2, 0, "divider"
+ data.splice 2, 0, 'divider'
callback data
renderRow: (label) ->
if $.isArray(selectedLabel)
- selected = ""
+ selected = ''
$.each selectedLabel, (i, selectedLbl) ->
selectedLbl = selectedLbl.trim()
- if selected is "" && label.title is selectedLbl
- selected = "is-active"
+ if selected is '' and label.title is selectedLbl
+ selected = 'is-active'
else
- selected = if label.title is selectedLabel then "is-active" else ""
+ selected = if label.title is selectedLabel then 'is-active' else ''
"<li>
<a href='#' class='#{selected}'>
@@ -83,10 +77,24 @@ class @LabelsSelect
search:
fields: ['title']
selectable: true
- fieldName: $(dropdown).data('field-name')
+ toggleLabel: (selected) ->
+ if selected and selected.title isnt 'Any Label'
+ selected.title
+ else
+ defaultLabel
+ fieldName: $dropdown.data('field-name')
id: (label) ->
- label.title
+ if label.isAny?
+ ''
+ else
+ label.title
clicked: ->
- if $(dropdown).hasClass "js-filter-submit"
- $(dropdown).parents('form').submit()
+ page = $('body').data 'page'
+ isIssueIndex = page is 'projects:issues:index'
+ isMRIndex = page is page is 'projects:merge_requests:index'
+
+ if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
+ Issues.filterResults $dropdown.closest('form')
+ else if $dropdown.hasClass 'js-filter-submit'
+ $dropdown.closest('form').submit()
)
diff --git a/app/assets/javascripts/milestone_select.js.coffee b/app/assets/javascripts/milestone_select.js.coffee
index 5e884454a65..e17a1adb648 100644
--- a/app/assets/javascripts/milestone_select.js.coffee
+++ b/app/assets/javascripts/milestone_select.js.coffee
@@ -1,60 +1,65 @@
class @MilestoneSelect
constructor: ->
$('.js-milestone-select').each (i, dropdown) ->
- projectId = $(dropdown).data('project-id')
- milestonesUrl = $(dropdown).data('milestones')
- selectedMilestone = $(dropdown).data('selected')
- showNo = $(dropdown).data('show-no')
- showAny = $(dropdown).data('show-any')
- useId = $(dropdown).data('use-id')
+ $dropdown = $(dropdown)
+ projectId = $dropdown.data('project-id')
+ milestonesUrl = $dropdown.data('milestones')
+ selectedMilestone = $dropdown.data('selected')
+ showNo = $dropdown.data('show-no')
+ showAny = $dropdown.data('show-any')
+ useId = $dropdown.data('use-id')
+ defaultLabel = $dropdown.data('default-label')
- $(dropdown).glDropdown(
+ $dropdown.glDropdown(
data: (term, callback) ->
$.ajax(
url: milestonesUrl
).done (data) ->
- html = $(data)
- data = []
- html.find('.milestone strong a').each ->
- link = $(@).attr("href").split("/")
- data.push(
- id: link[link.length - 1]
- title: $(@).text().trim()
- )
-
if showNo
data.unshift(
- id: "0"
+ id: '0'
title: 'No Milestone'
)
if showAny
data.unshift(
+ isAny: true
title: 'Any Milestone'
)
if data.length > 2
- data.splice 2, 0, "divider"
+ data.splice 2, 0, 'divider'
callback(data)
filterable: true
search:
fields: ['title']
selectable: true
- fieldName: $(dropdown).data('field-name')
+ toggleLabel: (selected) ->
+ if selected && 'id' of selected
+ selected.title
+ else
+ defaultLabel
+ fieldName: $dropdown.data('field-name')
text: (milestone) ->
milestone.title
id: (milestone) ->
if !useId
- if milestone.title isnt "Any milestone"
+ if !milestone.isAny?
milestone.title
else
- ""
+ ''
else
milestone.id
isSelected: (milestone) ->
milestone.title is selectedMilestone
clicked: ->
- if $(dropdown).hasClass "js-filter-submit"
- $(dropdown).parents('form').submit()
+ page = $('body').data 'page'
+ isIssueIndex = page is 'projects:issues:index'
+ isMRIndex = page is page is 'projects:merge_requests:index'
+
+ if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
+ Issues.filterResults $dropdown.closest('form')
+ else if $dropdown.hasClass 'js-filter-submit'
+ $dropdown.closest('form').submit()
)
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index 75d7f52bbb6..82532216589 100644
--- a/app/assets/javascripts/notes.js.coffee
+++ b/app/assets/javascripts/notes.js.coffee
@@ -343,6 +343,7 @@ 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()
$html.syntaxHighlight()
$html.find('.js-task-list-container').taskList('enable')
@@ -626,10 +627,10 @@ class @Notes
if closebtn.text() isnt closetext
closebtn.text(closetext)
- if reopenbtn.is(':not(.btn-comment-and-reopen)')
+ if reopenbtn.is('.btn-comment-and-reopen')
reopenbtn.removeClass('btn-comment-and-reopen')
- if closebtn.is(':not(.btn-comment-and-close)')
+ if closebtn.is('.btn-comment-and-close')
closebtn.removeClass('btn-comment-and-close')
if discardbtn.is(':visible')
diff --git a/app/assets/javascripts/project_new.js.coffee b/app/assets/javascripts/project_new.js.coffee
index fecdb9fc2e7..63dee4ed5d7 100644
--- a/app/assets/javascripts/project_new.js.coffee
+++ b/app/assets/javascripts/project_new.js.coffee
@@ -3,3 +3,16 @@ class @ProjectNew
$('.project-edit-container').on 'ajax:before', =>
$('.project-edit-container').hide()
$('.save-project-loader').show()
+ @toggleSettings()
+ @toggleSettingsOnclick()
+
+
+ toggleSettings: ->
+ checked = $("#project_builds_enabled").prop("checked")
+ if checked
+ $('.builds-feature').show()
+ else
+ $('.builds-feature').hide()
+
+ toggleSettingsOnclick: ->
+ $("#project_builds_enabled").on 'click', @toggleSettings
diff --git a/app/assets/javascripts/todos.js.coffee b/app/assets/javascripts/todos.js.coffee
new file mode 100644
index 00000000000..b6b4bd90e6a
--- /dev/null
+++ b/app/assets/javascripts/todos.js.coffee
@@ -0,0 +1,56 @@
+class @Todos
+ constructor: (@name) ->
+ @clearListeners()
+ @initBtnListeners()
+
+ clearListeners: ->
+ $('.done-todo').off('click')
+ $('.js-todos-mark-all').off('click')
+
+ initBtnListeners: ->
+ $('.done-todo').on('click', @doneClicked)
+ $('.js-todos-mark-all').on('click', @allDoneClicked)
+
+ doneClicked: (e) =>
+ e.preventDefault()
+ e.stopImmediatePropagation()
+
+ $this = $(e.currentTarget)
+ $this.disable()
+
+ $.ajax
+ type: 'POST'
+ url: $this.attr('href')
+ dataType: 'json'
+ data: '_method': 'delete'
+ success: (data) =>
+ @clearDone $this.closest('li')
+ @updateBadges data
+
+ allDoneClicked: (e) =>
+ e.preventDefault()
+ e.stopImmediatePropagation()
+
+ $this = $(e.currentTarget)
+ $this.disable()
+
+ $.ajax
+ type: 'POST'
+ url: $this.attr('href')
+ dataType: 'json'
+ data: '_method': 'delete'
+ success: (data) =>
+ $this.remove()
+ $('.js-todos-list').remove()
+ @updateBadges data
+
+ clearDone: ($row) ->
+ $ul = $row.closest('ul')
+ $row.remove()
+
+ if not $ul.find('li').length
+ $ul.parents('.panel').remove()
+
+ updateBadges: (data) ->
+ $('.todos-pending .badge, .todos-pending-count').text data.count
+ $('.todos-done .badge').text data.done_count
diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee
index 987c6f4b8d2..3d6452d2f46 100644
--- a/app/assets/javascripts/users_select.js.coffee
+++ b/app/assets/javascripts/users_select.js.coffee
@@ -4,14 +4,16 @@ class @UsersSelect
@userPath = "/autocomplete/users/:id.json"
$('.js-user-search').each (i, dropdown) =>
- @projectId = $(dropdown).data('project-id')
- @showCurrentUser = $(dropdown).data('current-user')
- showNullUser = $(dropdown).data('null-user')
- showAnyUser = $(dropdown).data('any-user')
- firstUser = $(dropdown).data('first-user')
- selectedId = $(dropdown).data('selected')
-
- $(dropdown).glDropdown(
+ $dropdown = $(dropdown)
+ @projectId = $dropdown.data('project-id')
+ @showCurrentUser = $dropdown.data('current-user')
+ showNullUser = $dropdown.data('null-user')
+ showAnyUser = $dropdown.data('any-user')
+ firstUser = $dropdown.data('first-user')
+ selectedId = $dropdown.data('selected')
+ defaultLabel = $dropdown.data('default-label')
+
+ $dropdown.glDropdown(
data: (term, callback) =>
@users term, (users) =>
if term.length is 0
@@ -52,10 +54,21 @@ class @UsersSelect
search:
fields: ['name', 'username']
selectable: true
- fieldName: $(dropdown).data('field-name')
+ fieldName: $dropdown.data('field-name')
+ toggleLabel: (selected) ->
+ if selected && 'id' of selected
+ selected.name
+ else
+ defaultLabel
clicked: ->
- if $(dropdown).hasClass "js-filter-submit"
- $(dropdown).parents('form').submit()
+ page = $('body').data 'page'
+ isIssueIndex = page is 'projects:issues:index'
+ isMRIndex = page is page is 'projects:merge_requests:index'
+
+ if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
+ Issues.filterResults $dropdown.closest('form')
+ else if $dropdown.hasClass 'js-filter-submit'
+ $dropdown.closest('form').submit()
renderRow: (user) ->
username = if user.username then "@#{user.username}" else ""
avatar = if user.avatar_url then user.avatar_url else false
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index 90c3ce0e84c..c36f29dda0e 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -28,10 +28,6 @@
border-bottom: 1px solid $border-color;
color: $gl-gray;
- a {
- color: $md-link-color;
- }
-
&.oneline-block {
line-height: 42px;
}
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index fa115a4bf56..657c5f033c7 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -208,3 +208,13 @@
background-color: #e4e7ed !important;
}
}
+
+.btn-loading {
+ &:not(.disabled) .fa {
+ display: none;
+ }
+
+ .fa {
+ margin-right: 5px;
+ }
+}
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 3197ea84460..a48b6c17fa0 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -9,6 +9,12 @@
border-left: $caret-width-base solid transparent;
}
+.btn-group {
+ .caret {
+ margin-left: 0;
+ }
+}
+
.dropdown {
position: relative;
}
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index c431e2b0df3..40a508c1ebc 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -3,22 +3,11 @@
vertical-align: top;
}
-@media (min-width: 800px) {
+@media (min-width: $screen-sm-min) {
.issues-filters,
.issues_bulk_update {
- select, .select2-container {
- width: 120px !important;
- display: inline-block;
- }
- }
-}
-
-@media (min-width: 1200px) {
- .issues-filters,
- .issues_bulk_update {
- select, .select2-container {
- width: 150px !important;
- display: inline-block;
+ .dropdown-menu-toggle {
+ width: 132px;
}
}
}
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index bfec0911b3c..b17c8bcbb1e 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -111,14 +111,17 @@ ul.content-list {
> li {
border-color: $table-border-color;
- color: $list-text-color;
font-size: $list-font-size;
+ color: $list-text-color;
.title {
- color: $list-title-color;
font-weight: 600;
}
+ a {
+ color: $gl-dark-link-color;
+ }
+
.description {
p {
@include str-truncated;
@@ -141,6 +144,10 @@ ul.content-list {
}
}
+.panel > .content-list > li {
+ padding: $gl-padding-top $gl-padding;
+}
+
ul.controls {
padding-top: 1px;
float: right;
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 211ead7319d..be626678bd7 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -1,45 +1,75 @@
-$row-hover: #f4f8fe;
-$gl-text-color: #54565b;
-$gl-text-green: #4a2;
-$gl-text-red: #d12f19;
-$gl-text-orange: #d90;
-$gl-header-color: #323232;
-$gl-link-color: #333c48;
-$md-text-color: #444;
-$md-link-color: #3084bb;
-$progress-color: #c0392b;
-$gl-font-size: 15px;
-$list-font-size: 15px;
+/*
+ * Layout
+ */
$sidebar_collapsed_width: 62px;
$sidebar_width: 230px;
$gutter_collapsed_width: 62px;
$gutter_width: 290px;
$gutter_inner_width: 258px;
-$avatar_radius: 50%;
+
+/*
+ * UI elements
+ */
+$border-color: #efeff1;
+$table-border-color: #eef0f2;
+$background-color: #faf9f9;
+
+/*
+ * Text
+ */
+$gl-font-size: 15px;
+$gl-title-color: #333;
+$gl-text-color: #555;
+$gl-text-green: #4a2;
+$gl-text-red: #d12f19;
+$gl-text-orange: #d90;
+$gl-link-color: #3084bb;
+$gl-dark-link-color: #333;
+$gl-placeholder-color: #8f8f8f;
+$gl-gray: $gl-text-color;
+$gl-header-color: $gl-title-color;
+
+/*
+ * Lists
+ */
+$list-font-size: $gl-font-size;
+$list-title-color: $gl-title-color;
+$list-text-color: $gl-text-color;
+
+/*
+ * Markdown
+ */
+$md-text-color: $gl-text-color;
+$md-link-color: $gl-link-color;
+
+/*
+ * Code
+ */
$code_font_size: 13px;
$code_line_height: 1.5;
-$border-color: #efeff1;
-$table-border-color: #eef0f2;
-$background-color: #faf9f9;
-$header-height: 58px;
-$fixed-layout-width: 1280px;
-$gl-gray: #5a5a5a;
+
+/*
+ * Padding
+ */
$gl-padding: 16px;
$gl-btn-padding: 10px;
$gl-vert-padding: 6px;
$gl-padding-top: 10px;
+
+/*
+ * Misc
+ */
+$row-hover: #f4f8fe;
+$progress-color: #c0392b;
+$avatar_radius: 50%;
+$header-height: 58px;
+$fixed-layout-width: 1280px;
$gl-avatar-size: 40px;
-$secondary-text: #7f8fa4;
$error-exclamation-point: #e62958;
$border-radius-default: 3px;
-$list-title-color: #333;
-$list-text-color: #555;
-
$btn-transparent-color: #8f8f8f;
-
$ssh-key-icon-color: #8f8f8f;
$ssh-key-icon-size: 18px;
-
$provider-btn-group-border: #e5e5e5;
$provider-btn-not-active-color: #4688f1;
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index d57be1b2daa..33b3c7558ed 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -55,7 +55,7 @@ li.commit {
}
.commit-row-message {
- color: $gl-link-color;
+ color: $gl-dark-link-color;
&:hover {
text-decoration: underline;
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index db06b8288c2..d5862a11aca 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -1,7 +1,7 @@
// Common
.diff-file {
border: 1px solid $border-color;
- border-top: none;
+ margin-bottom: $gl-padding;
.diff-header {
position: relative;
@@ -361,3 +361,11 @@
border-color: $border;
}
}
+
+.files {
+ margin-top: -1px;
+
+ .diff-file:last-child {
+ margin-bottom: 0;
+ }
+}
diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss
index b39a9abf40f..84eefd01cfe 100644
--- a/app/assets/stylesheets/pages/events.scss
+++ b/app/assets/stylesheets/pages/events.scss
@@ -6,7 +6,7 @@
font-size: $gl-font-size;
padding: $gl-padding-top 0 $gl-padding-top ($gl-avatar-size + $gl-padding-top);
border-bottom: 1px solid $table-border-color;
- color: #7f8fa4;
+ color: $list-text-color;
&.event-inline {
.avatar {
@@ -21,7 +21,7 @@
}
a {
- color: #4c4e54;
+ color: $gl-dark-link-color;
}
.avatar {
@@ -31,10 +31,7 @@
.event-title {
@include str-truncated(calc(100% - 174px));
font-weight: 600;
-
- .author_name {
- color: #333;
- }
+ color: $list-text-color;
}
.event-body {
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index c975ca0ce43..2760af8a48a 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -1,34 +1,3 @@
-@media (max-width: $screen-sm-max) {
- .issuable-affix {
- margin-top: 20px;
- }
-}
-
-@media (max-width: $screen-md-max) {
- .issuable-affix {
- position: static;
- }
-}
-
-@media (min-width: $screen-md-max) {
- .issuable-affix {
- &.affix-top {
- position: static;
- }
-
- &.affix {
- position: fixed;
- top: 70px;
- margin-right: 35px;
-
- &.no-affix {
- position: relative;
- top: 0;
- }
- }
- }
-}
-
.issuable-details {
section {
.issuable-discussion {
@@ -54,6 +23,10 @@
padding: 6px 10px;
}
}
+
+ &.has-labels {
+ margin-bottom: -5px;
+ }
}
.issuable-sidebar {
@@ -66,8 +39,9 @@
width: $gutter_inner_width;
// --
- &:first-child {
- padding-top: 5px;
+ &.issuable-sidebar-header {
+ padding-top: 0;
+ padding-bottom: 10px;
}
&:last-child {
@@ -75,7 +49,6 @@
}
span {
- margin-top: 7px;
display: inline-block;
}
@@ -84,7 +57,7 @@
}
.issuable-count {
-
+ margin-top: 7px;
}
.gutter-toggle {
@@ -99,19 +72,19 @@
.title {
color: $gl-text-color;
- margin-bottom: 8px;
+ margin-bottom: 10px;
+ line-height: 1;
.avatar {
margin-left: 0;
}
- label {
- font-weight: normal;
- margin-right: 4px;
- }
-
.edit-link {
color: $gl-gray;
+
+ &:hover {
+ color: $md-link-color;
+ }
}
}
@@ -144,11 +117,6 @@
.btn-clipboard {
color: $gl-gray;
}
-
- .participants .avatar {
- margin-top: 6px;
- margin-right: 2px;
- }
}
.right-sidebar {
@@ -163,8 +131,12 @@
&.right-sidebar-expanded {
width: $gutter_width;
- hr {
- display: none;
+ .value {
+ line-height: 1;
+ }
+
+ .bold {
+ font-weight: 600;
}
.sidebar-collapsed-icon {
@@ -172,8 +144,23 @@
}
.gutter-toggle {
+ margin-top: 7px;
border-left: 1px solid $border-gray-light;
}
+
+ .assignee .avatar {
+ float: left;
+ margin-right: 10px;
+ margin-bottom: 0;
+ margin-left: 0;
+ }
+
+ .username {
+ display: block;
+ margin-top: 4px;
+ font-size: 13px;
+ font-weight: normal;
+ }
}
.subscribe-button {
@@ -193,14 +180,6 @@
width: $sidebar_collapsed_width;
padding-top: 0;
- hr {
- margin: 0;
- color: $gray-normal;
- border-color: $gray-normal;
- width: 62px;
- margin-left: -20px
- }
-
.block {
width: $sidebar_collapsed_width - 1px;
margin-left: -19px;
@@ -209,12 +188,18 @@
overflow: hidden;
}
+ .participants {
+ border-bottom: 1px solid $border-gray-light;
+ }
+
.hide-collapsed {
display: none;
}
.gutter-toggle {
- margin-left: -36px;
+ width: 100%;
+ margin-left: 0;
+ padding-left: 25px;
}
.sidebar-collapsed-icon {
@@ -229,6 +214,10 @@
margin-top: 0;
}
+ .author {
+ display: none;
+ }
+
.btn-clipboard {
border: none;
@@ -241,6 +230,11 @@
}
}
}
+
+ .sidebar-collapsed-user {
+ padding-bottom: 0;
+ margin-bottom: 10px;
+ }
}
.btn {
@@ -251,6 +245,13 @@
border: 1px solid $border-gray-dark;
}
}
+
+ a:not(.btn) {
+ &:hover {
+ color: $md-link-color;
+ text-decoration: none;
+ }
+ }
}
.btn-default.gutter-toggle {
@@ -262,3 +263,37 @@
color: $gray-darkest;
}
}
+
+.edited-text {
+ color: $gray-darkest;
+
+ .author_link {
+ color: $gray-darkest;
+ }
+}
+
+.participants-list {
+ margin: -5px -5px;
+}
+
+.participants-author {
+ display: inline-block;
+ padding: 5px 5px;
+
+ .author_link {
+ display: block;
+ }
+
+ .avatar.avatar-inline {
+ margin: 0;
+ }
+}
+
+.participants-more {
+ margin-top: 5px;
+ margin-left: 5px;
+
+ a {
+ color: #8c8c8c;
+ }
+}
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index 7ac4bc468d6..6a1d28590c2 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -3,7 +3,7 @@
padding: 10px $gl-padding;
position: relative;
- .issue-title {
+ .title {
margin-bottom: 2px;
}
@@ -130,14 +130,14 @@ form.edit-issue {
}
.issue-closed-by-widget {
- color: $secondary-text;
+ color: $gl-text-color;
margin-left: 52px;
}
.editor-details {
display: block;
-
+
@media (min-width: $screen-sm-min) {
display: inline-block;
}
-} \ No newline at end of file
+}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 6c600c99d51..82c5069638d 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -33,6 +33,13 @@
.project-settings-dropdown {
margin-left: 10px;
display: inline-block;
+
+ .dropdown-menu {
+ left: auto;
+ width: auto;
+ right: 0px;
+ max-width: 240px;
+ }
}
}
diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss
index 27970eba159..f983e9829e6 100644
--- a/app/assets/stylesheets/pages/todos.scss
+++ b/app/assets/stylesheets/pages/todos.scss
@@ -14,25 +14,8 @@
}
.todo-item {
- font-size: $gl-font-size;
- padding-left: $gl-avatar-size + $gl-padding-top;
- color: $secondary-text;
-
- a {
- color: #4c4e54;
- }
-
- .avatar {
- margin-left: -($gl-avatar-size + $gl-padding-top);
- }
-
.todo-title {
@include str-truncated(calc(100% - 174px));
- font-weight: 600;
-
- .author-name {
- color: #333;
- }
}
.todo-body {
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index 73c7c9f687c..25b5e95583e 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -41,7 +41,7 @@
vertical-align: middle;
i, a {
- color: $gl-link-color;
+ color: $gl-dark-link-color;
}
img {
diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb
index 43cf8fa71af..be488483b09 100644
--- a/app/controllers/dashboard/todos_controller.rb
+++ b/app/controllers/dashboard/todos_controller.rb
@@ -1,25 +1,34 @@
class Dashboard::TodosController < Dashboard::ApplicationController
- before_action :find_todos, only: [:index, :destroy_all]
+ before_action :find_todos, only: [:index, :destroy, :destroy_all]
def index
@todos = @todos.page(params[:page]).per(PER_PAGE)
end
def destroy
- todo.done!
+ todo.done
+
+ todo_notice = 'Todo was successfully marked as done.'
respond_to do |format|
- format.html { redirect_to dashboard_todos_path, notice: 'Todo was successfully marked as done.' }
+ format.html { redirect_to dashboard_todos_path, notice: todo_notice }
format.js { render nothing: true }
+ format.json do
+ render json: { count: @todos.size, done_count: current_user.todos.done.count }
+ end
end
end
def destroy_all
- @todos.each(&:done!)
+ @todos.each(&:done)
respond_to do |format|
format.html { redirect_to dashboard_todos_path, notice: 'All todos were marked as done.' }
format.js { render nothing: true }
+ format.json do
+ find_todos
+ render json: { count: @todos.size, done_count: current_user.todos.done.count }
+ end
end
end
diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb
index 139e40db180..b538c7d1608 100644
--- a/app/controllers/dashboard_controller.rb
+++ b/app/controllers/dashboard_controller.rb
@@ -3,7 +3,7 @@ class DashboardController < Dashboard::ApplicationController
include MergeRequestsAction
before_action :event_filter, only: :activity
- before_action :projects, only: [:issues, :merge_requests]
+ before_action :projects, only: [:issues, :merge_requests, :labels, :milestones]
respond_to :html
@@ -20,6 +20,29 @@ class DashboardController < Dashboard::ApplicationController
end
end
+ def labels
+ labels = Label.where(project_id: @projects).select(:title, :color).uniq(:title)
+
+ respond_to do |format|
+ format.json do
+ render json: labels
+ end
+ end
+ end
+
+ def milestones
+ milestones = Milestone.where(project_id: @projects).active
+ epoch = DateTime.parse('1970-01-01')
+ grouped_milestones = GlobalMilestone.build_collection(milestones)
+ grouped_milestones = grouped_milestones.sort_by { |x| x.due_date.nil? ? epoch : x.due_date }
+
+ respond_to do |format|
+ format.json do
+ render json: grouped_milestones
+ end
+ end
+ end
+
protected
def load_events
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index aa7a178dcf4..6603f28a082 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -5,7 +5,7 @@ class Projects::IssuesController < Projects::ApplicationController
before_action :issue, only: [:edit, :update, :show]
# Allow read any issue
- before_action :authorize_read_issue!
+ before_action :authorize_read_issue!, only: [:show]
# Allow write(create) issue
before_action :authorize_create_issue!, only: [:new, :create]
@@ -128,6 +128,10 @@ class Projects::IssuesController < Projects::ApplicationController
end
alias_method :subscribable_resource, :issue
+ def authorize_read_issue!
+ return render_404 unless can?(current_user, :read_issue, @issue)
+ end
+
def authorize_update_issue!
return render_404 unless can?(current_user, :update_issue, @issue)
end
@@ -158,7 +162,7 @@ class Projects::IssuesController < Projects::ApplicationController
def issue_params
params.require(:issue).permit(
- :title, :assignee_id, :position, :description,
+ :title, :assignee_id, :position, :description, :confidential,
:milestone_id, :state_event, :task_num, label_ids: []
)
end
diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb
index 40d8098690a..5f471d405f5 100644
--- a/app/controllers/projects/labels_controller.rb
+++ b/app/controllers/projects/labels_controller.rb
@@ -12,6 +12,13 @@ class Projects::LabelsController < Projects::ApplicationController
def index
@labels = @project.labels.page(params[:page]).per(PER_PAGE)
+
+ respond_to do |format|
+ format.html
+ format.json do
+ render json: @project.labels
+ end
+ end
end
def new
diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb
index da46731d945..0998b191c07 100644
--- a/app/controllers/projects/milestones_controller.rb
+++ b/app/controllers/projects/milestones_controller.rb
@@ -19,7 +19,15 @@ class Projects::MilestonesController < Projects::ApplicationController
end
@milestones = @milestones.includes(:project)
- @milestones = @milestones.page(params[:page]).per(PER_PAGE)
+
+ respond_to do |format|
+ format.html do
+ @milestones = @milestones.page(params[:page]).per(PER_PAGE)
+ end
+ format.json do
+ render json: @milestones
+ end
+ end
end
def new
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 36f37221c58..c9930480770 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -134,7 +134,7 @@ class ProjectsController < ApplicationController
def autocomplete_sources
note_type = params['type']
note_id = params['type_id']
- autocomplete = ::Projects::AutocompleteService.new(@project)
+ autocomplete = ::Projects::AutocompleteService.new(@project, current_user)
participants = ::Projects::ParticipantsService.new(@project, current_user).execute(note_type, note_id)
@suggestions = {
diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb
index 20a2b0ce8f0..c2befa5a5b3 100644
--- a/app/finders/issues_finder.rb
+++ b/app/finders/issues_finder.rb
@@ -19,4 +19,10 @@ class IssuesFinder < IssuableFinder
def klass
Issue
end
+
+ private
+
+ def init_collection
+ Issue.visible_to_user(current_user)
+ end
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index d1b1c61b710..e6ceb213532 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -182,7 +182,7 @@ module ApplicationHelper
# Returns an HTML-safe String
def time_ago_with_tooltip(time, placement: 'top', html_class: 'time_ago', skip_js: false)
element = content_tag :time, time.to_s,
- class: "#{html_class} js-timeago js-timeago-pending",
+ 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),
data: { toggle: 'tooltip', placement: placement, container: 'body' }
@@ -196,6 +196,22 @@ module ApplicationHelper
element
end
+ def edited_time_ago_with_tooltip(object, placement: 'top', html_class: 'time_ago', include_author: false)
+ return if object.updated_at == object.created_at
+
+ content_tag :small, class: "edited-text" do
+ output = content_tag(:span, "Edited ")
+ output << time_ago_with_tooltip(object.updated_at, placement: placement, html_class: html_class)
+
+ if include_author && object.updated_by && object.updated_by != object.author
+ output << content_tag(:span, " by ")
+ output << link_to_member(object.project, object.updated_by, avatar: false, author_class: nil)
+ end
+
+ output
+ end
+ end
+
def render_markup(file_name, file_content)
if gitlab_markdown?(file_name)
Haml::Helpers.preserve(markdown(file_content))
@@ -285,7 +301,7 @@ module ApplicationHelper
if project.nil?
nil
elsif current_controller?(:issues)
- project.issues.send(entity).count
+ project.issues.visible_to_user(current_user).send(entity).count
elsif current_controller?(:merge_requests)
project.merge_requests.send(entity).count
end
diff --git a/app/helpers/dropdowns_helper.rb b/app/helpers/dropdowns_helper.rb
index 74f326e0b83..ceff1fbb161 100644
--- a/app/helpers/dropdowns_helper.rb
+++ b/app/helpers/dropdowns_helper.rb
@@ -24,7 +24,7 @@ module DropdownsHelper
capture(&block) if block && !options.has_key?(:footer_content)
end
- if block && options.has_key?(:footer_content)
+ if block && options[:footer_content]
output << content_tag(:div, class: "dropdown-footer") do
capture(&block)
end
diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb
index 37a888d9c60..a67a6b208e2 100644
--- a/app/helpers/events_helper.rb
+++ b/app/helpers/events_helper.rb
@@ -194,7 +194,7 @@ module EventsHelper
end
def event_to_atom(xml, event)
- if event.proper?
+ if event.proper?(current_user)
xml.entry do
event_link = event_feed_url(event)
event_title = event_feed_title(event)
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index 2dfeddf7368..81df2094392 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -20,6 +20,23 @@ module IssuablesHelper
base_issuable_scope(issuable).where('iid < ?', issuable.iid).first
end
+ def user_dropdown_label(user_id, default_label)
+ return "Unassigned" if user_id == "0"
+
+ if @project
+ member = @project.team.find_member(user_id)
+ user = member.user if member
+ else
+ user = User.find_by(id: user_id)
+ end
+
+ if user
+ user.name
+ else
+ default_label
+ end
+ end
+
private
def sidebar_gutter_collapsed?
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index ae4ebc0854a..e00d3204027 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -98,6 +98,10 @@ module IssuesHelper
end.sort.to_sentence(last_word_connector: ', or ')
end
+ def confidential_icon(issue)
+ icon('eye-slash') if issue.confidential?
+ end
+
def emoji_icon(name, unicode = nil, aliases = [])
unicode ||= Emoji.emoji_filename(name) rescue ""
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index 4455dcd0e20..e238a7b4c26 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -109,19 +109,12 @@ module LabelsHelper
end
end
- def projects_labels_options
- labels =
- if @project
- @project.labels
- else
- Label.where(project_id: @projects)
- end
-
- grouped_labels = GlobalLabel.build_collection(labels)
- grouped_labels.unshift(Label::None)
- grouped_labels.unshift(Label::Any)
-
- options_from_collection_for_select(grouped_labels, 'name', 'title', params[:label_name])
+ def labels_filter_path
+ if @project
+ namespace_project_labels_path(@project.namespace, @project, :json)
+ else
+ labels_dashboard_path(:json)
+ end
end
def label_subscription_status(label)
diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb
index e8ac8788d9d..c9d8787bd19 100644
--- a/app/helpers/milestones_helper.rb
+++ b/app/helpers/milestones_helper.rb
@@ -38,7 +38,7 @@ module MilestonesHelper
def milestone_progress_bar(milestone)
options = {
class: 'progress-bar progress-bar-success',
- style: "width: #{milestone.percent_complete}%;"
+ style: "width: #{milestone.percent_complete(current_user)}%;"
}
content_tag :div, class: 'progress' do
@@ -46,22 +46,12 @@ module MilestonesHelper
end
end
- def projects_milestones_options
- milestones =
- if @project
- @project.milestones
- else
- Milestone.where(project_id: @projects)
- end.active
-
- epoch = DateTime.parse('1970-01-01')
- grouped_milestones = GlobalMilestone.build_collection(milestones)
- grouped_milestones = grouped_milestones.sort_by { |x| x.due_date.nil? ? epoch : x.due_date }
- grouped_milestones.unshift(Milestone::None)
- grouped_milestones.unshift(Milestone::Any)
- grouped_milestones.unshift(Milestone::Upcoming)
-
- options_from_collection_for_select(grouped_milestones, 'name', 'title', params[:milestone_title])
+ def milestones_filter_dropdown_path
+ if @project
+ namespace_project_milestones_path(@project.namespace, @project, :json)
+ else
+ milestones_dashboard_path(:json)
+ end
end
def milestone_remaining_days(milestone)
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index b5acb80b720..5473419ef24 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -26,7 +26,7 @@ module ProjectsHelper
image_tag(avatar_icon(author, opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}", alt:'') if opts[:avatar]
end
- def link_to_member(project, author, opts = {})
+ def link_to_member(project, author, opts = {}, &block)
default_opts = { avatar: true, name: true, size: 16, author_class: 'author', title: ":name" }
opts = default_opts.merge(opts)
@@ -44,6 +44,8 @@ module ProjectsHelper
author_html << content_tag(:span, sanitize(author.name), class: opts[:author_class]) if opts[:name]
end
+ author_html << capture(&block) if block
+
author_html = author_html.html_safe
if opts[:name]
diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb
index 07ddc691d85..edc5686cf08 100644
--- a/app/helpers/todos_helper.rb
+++ b/app/helpers/todos_helper.rb
@@ -16,14 +16,19 @@ module TodosHelper
def todo_target_link(todo)
target = todo.target_type.titleize.downcase
- link_to "#{target} #{todo.target.to_reference}", todo_target_path(todo), { title: h(todo.target.title) }
+ link_to "#{target} #{todo.target_reference}", todo_target_path(todo), { title: todo.target.title }
end
def todo_target_path(todo)
anchor = dom_id(todo.note) if todo.note.present?
- polymorphic_path([todo.project.namespace.becomes(Namespace),
- todo.project, todo.target], anchor: anchor)
+ if todo.for_commit?
+ namespace_project_commit_path(todo.project.namespace.becomes(Namespace), todo.project,
+ todo.target, anchor: anchor)
+ else
+ polymorphic_path([todo.project.namespace.becomes(Namespace),
+ todo.project, todo.target], anchor: anchor)
+ end
end
def todos_filter_params
diff --git a/app/models/ability.rb b/app/models/ability.rb
index ccac08b7d3f..e22da4806e6 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -49,7 +49,6 @@ class Ability
rules = [
:read_project,
:read_wiki,
- :read_issue,
:read_label,
:read_milestone,
:read_project_snippet,
@@ -63,6 +62,9 @@ class Ability
# Allow to read builds by anonymous user if guests are allowed
rules << :read_build if project.public_builds?
+ # Allow to read issues by anonymous user if issue is not confidential
+ rules << :read_issue unless subject.is_a?(Issue) && subject.confidential?
+
rules - project_disabled_features_rules(project)
else
[]
@@ -321,6 +323,7 @@ class Ability
end
rules += project_abilities(user, subject.project)
+ rules = filter_confidential_issues_abilities(user, subject, rules) if subject.is_a?(Issue)
rules
end
end
@@ -439,5 +442,17 @@ class Ability
:"admin_#{name}"
]
end
+
+ def filter_confidential_issues_abilities(user, issue, rules)
+ return rules if user.admin? || !issue.confidential?
+
+ unless issue.author == user || issue.assignee == user || issue.project.team.member?(user.id)
+ rules.delete(:admin_issue)
+ rules.delete(:read_issue)
+ rules.delete(:update_issue)
+ end
+
+ rules
+ end
end
end
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index 3b1aa0f5c80..3377a85a55a 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -114,7 +114,7 @@ class CommitStatus < ActiveRecord::Base
end
def ignored?
- failed? && allow_failure?
+ allow_failure? && (failed? || canceled?)
end
def duration
diff --git a/app/models/concerns/milestoneish.rb b/app/models/concerns/milestoneish.rb
index d67df7c1d9c..5b8e3f654ea 100644
--- a/app/models/concerns/milestoneish.rb
+++ b/app/models/concerns/milestoneish.rb
@@ -1,18 +1,18 @@
module Milestoneish
- def closed_items_count
- issues.closed.size + merge_requests.closed_and_merged.size
+ def closed_items_count(user = nil)
+ issues_visible_to_user(user).closed.size + merge_requests.closed_and_merged.size
end
- def total_items_count
- issues.size + merge_requests.size
+ def total_items_count(user = nil)
+ issues_visible_to_user(user).size + merge_requests.size
end
- def complete?
- total_items_count == closed_items_count
+ def complete?(user = nil)
+ total_items_count(user) == closed_items_count(user)
end
- def percent_complete
- ((closed_items_count * 100) / total_items_count).abs
+ def percent_complete(user = nil)
+ ((closed_items_count(user) * 100) / total_items_count(user)).abs
rescue ZeroDivisionError
0
end
@@ -22,4 +22,8 @@ module Milestoneish
(due_date - Date.today).to_i
end
+
+ def issues_visible_to_user(user = nil)
+ issues.visible_to_user(user)
+ end
end
diff --git a/app/models/event.rb b/app/models/event.rb
index 9a0bbf50f8b..a5cfeaf388e 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -73,15 +73,17 @@ class Event < ActiveRecord::Base
end
end
- def proper?
+ def proper?(user = nil)
if push?
true
elsif membership_changed?
true
elsif created_project?
true
+ elsif issue?
+ Ability.abilities.allowed?(user, :read_issue, issue)
else
- ((issue? || merge_request? || note?) && target) || milestone?
+ ((merge_request? || note?) && target) || milestone?
end
end
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 2447f860c5a..053387cffd7 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -58,6 +58,13 @@ class Issue < ActiveRecord::Base
attributes
end
+ def self.visible_to_user(user)
+ return where(confidential: false) if user.blank?
+ return all if user.admin?
+
+ where('issues.confidential = false OR (issues.confidential = true AND (issues.author_id = :user_id OR issues.assignee_id = :user_id OR issues.project_id IN(:project_ids)))', user_id: user.id, project_ids: user.authorized_projects.select(:id))
+ end
+
def self.reference_prefix
'#'
end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 188325045e2..a6140b5b04c 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -516,11 +516,15 @@ class MergeRequest < ActiveRecord::Base
end
def target_sha
- @target_sha ||= target_project.repository.commit(target_branch).sha
+ @target_sha ||= target_project.repository.commit(target_branch).try(:sha)
end
def source_sha
- last_commit.try(:sha)
+ last_commit.try(:sha) || source_tip.try(:sha)
+ end
+
+ def source_tip
+ source_branch && source_project.repository.commit(source_branch)
end
def fetch_ref
@@ -568,8 +572,11 @@ class MergeRequest < ActiveRecord::Base
end
def compute_diverged_commits_count
+ return 0 unless source_sha && target_sha
+
Gitlab::Git::Commit.between(target_project.repository.raw_repository, source_sha, target_sha).size
end
+ private :compute_diverged_commits_count
def diverged_from_target_branch?
diverged_commits_count > 0
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index 374590ba0c5..de7183bf6b4 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -121,8 +121,8 @@ class Milestone < ActiveRecord::Base
active? && issues.opened.count.zero?
end
- def is_empty?
- total_items_count.zero?
+ def is_empty?(user = nil)
+ total_items_count(user).zero?
end
def author_id
diff --git a/app/models/project.rb b/app/models/project.rb
index ab4913e99a8..412c6c6732d 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -571,10 +571,7 @@ class Project < ActiveRecord::Base
end
def avatar_in_git
- @avatar_file ||= 'logo.png' if repository.blob_at_branch('master', 'logo.png')
- @avatar_file ||= 'logo.jpg' if repository.blob_at_branch('master', 'logo.jpg')
- @avatar_file ||= 'logo.gif' if repository.blob_at_branch('master', 'logo.gif')
- @avatar_file
+ repository.avatar
end
def avatar_url
diff --git a/app/models/repository.rb b/app/models/repository.rb
index e555e97689d..25d24493f6e 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -3,6 +3,10 @@ require 'securerandom'
class Repository
class CommitError < StandardError; end
+ # Files to use as a project avatar in case no avatar was uploaded via the web
+ # UI.
+ AVATAR_FILES = %w{logo.png logo.jpg logo.gif}
+
include Gitlab::ShellAdapter
attr_accessor :path_with_namespace, :project
@@ -223,12 +227,6 @@ class Repository
send(key)
end
end
-
- branches.each do |branch|
- unless cache.exist?(:"diverging_commit_counts_#{branch.name}")
- send(:diverging_commit_counts, branch)
- end
- end
end
def expire_tags_cache
@@ -241,12 +239,13 @@ class Repository
@branches = nil
end
- def expire_cache(branch_name = nil)
+ def expire_cache(branch_name = nil, revision = nil)
cache_keys.each do |key|
cache.expire(key)
end
expire_branch_cache(branch_name)
+ expire_avatar_cache(branch_name, revision)
# This ensures this particular cache is flushed after the first commit to a
# new repository.
@@ -296,18 +295,6 @@ class Repository
@tag_count = nil
end
- def rebuild_cache
- cache_keys.each do |key|
- cache.expire(key)
- send(key)
- end
-
- branches.each do |branch|
- cache.expire(:"diverging_commit_counts_#{branch.name}")
- diverging_commit_counts(branch)
- end
- end
-
def lookup_cache
@lookup_cache ||= {}
end
@@ -316,6 +303,23 @@ class Repository
cache.expire(:branch_names)
end
+ def expire_avatar_cache(branch_name = nil, revision = nil)
+ # Avatars are pulled from the default branch, thus if somebody pushes to a
+ # different branch there's no need to expire anything.
+ return if branch_name && branch_name != root_ref
+
+ # We don't want to flush the cache if the commit didn't actually make any
+ # changes to any of the possible avatar files.
+ if revision && commit = self.commit(revision)
+ return unless commit.diffs.
+ any? { |diff| AVATAR_FILES.include?(diff.new_path) }
+ end
+
+ cache.expire(:avatar)
+
+ @avatar = nil
+ end
+
# Runs code just before a repository is deleted.
def before_delete
expire_cache if exists?
@@ -350,8 +354,8 @@ class Repository
end
# Runs code after a new commit has been pushed.
- def after_push_commit(branch_name)
- expire_cache(branch_name)
+ def after_push_commit(branch_name, revision)
+ expire_cache(branch_name, revision)
end
# Runs code after a new branch has been created.
@@ -857,6 +861,14 @@ class Repository
end
end
+ def avatar
+ @avatar ||= cache.fetch(:avatar) do
+ AVATAR_FILES.find do |file|
+ blob_at_branch('master', file)
+ end
+ end
+ end
+
private
def cache
diff --git a/app/models/todo.rb b/app/models/todo.rb
index 5f91991f781..d85f7bfdf57 100644
--- a/app/models/todo.rb
+++ b/app/models/todo.rb
@@ -5,14 +5,15 @@
# id :integer not null, primary key
# user_id :integer not null
# project_id :integer not null
-# target_id :integer not null
+# target_id :integer
# target_type :string not null
# author_id :integer
-# note_id :integer
# action :integer not null
# state :string not null
# created_at :datetime
# updated_at :datetime
+# note_id :integer
+# commit_id :string
#
class Todo < ActiveRecord::Base
@@ -27,7 +28,9 @@ class Todo < ActiveRecord::Base
delegate :name, :email, to: :author, prefix: true, allow_nil: true
- validates :action, :project, :target, :user, presence: true
+ validates :action, :project, :target_type, :user, presence: true
+ validates :target_id, presence: true, unless: :for_commit?
+ validates :commit_id, presence: true, if: :for_commit?
default_scope { reorder(id: :desc) }
@@ -36,7 +39,7 @@ class Todo < ActiveRecord::Base
state_machine :state, initial: :pending do
event :done do
- transition [:pending, :done] => :done
+ transition [:pending] => :done
end
state :pending
@@ -50,4 +53,25 @@ class Todo < ActiveRecord::Base
target.title
end
end
+
+ def for_commit?
+ target_type == "Commit"
+ end
+
+ # override to return commits, which are not active record
+ def target
+ if for_commit?
+ project.commit(commit_id) rescue nil
+ else
+ super
+ end
+ end
+
+ def target_reference
+ if for_commit?
+ target.short_id
+ else
+ target.to_reference
+ end
+ end
end
diff --git a/app/services/commits/revert_service.rb b/app/services/commits/revert_service.rb
index 9cb918d7a2e..a3c950ede1f 100644
--- a/app/services/commits/revert_service.rb
+++ b/app/services/commits/revert_service.rb
@@ -9,7 +9,8 @@ module Commits
@commit = params[:commit]
@create_merge_request = params[:create_merge_request].present?
- validate and commit
+ check_push_permissions unless @create_merge_request
+ commit
rescue Repository::CommitError, Gitlab::Git::Repository::InvalidBlobName, GitHooksService::PreReceiveError,
ValidationError, ReversionError => ex
error(ex.message)
@@ -45,11 +46,11 @@ module Commits
end
end
- def validate
+ def check_push_permissions
allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(@target_branch)
unless allowed
- raise_error('You are not allowed to push into this branch')
+ raise ValidationError.new('You are not allowed to push into this branch')
end
true
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index d840ab5e340..14e2a2c0699 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -17,7 +17,7 @@ class GitPushService < BaseService
# 6. Checks if the project's main language has changed
#
def execute
- @project.repository.after_push_commit(branch_name)
+ @project.repository.after_push_commit(branch_name, params[:newrev])
if push_remove_branch?
@project.repository.after_remove_branch
diff --git a/app/services/projects/autocomplete_service.rb b/app/services/projects/autocomplete_service.rb
index 7408e09ed1e..ba50305dbd5 100644
--- a/app/services/projects/autocomplete_service.rb
+++ b/app/services/projects/autocomplete_service.rb
@@ -1,11 +1,7 @@
module Projects
class AutocompleteService < BaseService
- def initialize(project)
- @project = project
- end
-
def issues
- @project.issues.opened.select([:iid, :title])
+ @project.issues.visible_to_user(current_user).opened.select([:iid, :title])
end
def merge_requests
diff --git a/app/services/projects/housekeeping_service.rb b/app/services/projects/housekeeping_service.rb
index bccd67d3dbf..a0973c5d260 100644
--- a/app/services/projects/housekeeping_service.rb
+++ b/app/services/projects/housekeeping_service.rb
@@ -24,7 +24,7 @@ module Projects
def execute
raise LeaseTaken if !try_obtain_lease
- GitlabShellWorker.perform_async(:gc, @project.path_with_namespace)
+ GitlabShellOneShotWorker.perform_async(:gc, @project.path_with_namespace)
ensure
@project.update_column(:pushes_since_gc, 0)
end
diff --git a/app/services/search/global_service.rb b/app/services/search/global_service.rb
index e1e94c5cc38..aa9837038a6 100644
--- a/app/services/search/global_service.rb
+++ b/app/services/search/global_service.rb
@@ -11,7 +11,7 @@ module Search
projects = ProjectsFinder.new.execute(current_user)
projects = projects.in_namespace(group.id) if group
- Gitlab::SearchResults.new(projects, params[:search])
+ Gitlab::SearchResults.new(current_user, projects, params[:search])
end
end
end
diff --git a/app/services/search/project_service.rb b/app/services/search/project_service.rb
index c08881dce4b..4b500914cfb 100644
--- a/app/services/search/project_service.rb
+++ b/app/services/search/project_service.rb
@@ -7,7 +7,8 @@ module Search
end
def execute
- Gitlab::ProjectSearchResults.new(project,
+ Gitlab::ProjectSearchResults.new(current_user,
+ project,
params[:search],
params[:repository_ref])
end
diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb
index 4392e2d17fe..f2662922e90 100644
--- a/app/services/todo_service.rb
+++ b/app/services/todo_service.rb
@@ -103,24 +103,16 @@ class TodoService
# * mark all pending todos related to the target for the current user as done
#
def mark_pending_todos_as_done(target, user)
- pending_todos(user, target.project, target).update_all(state: :done)
+ attributes = attributes_for_target(target)
+ pending_todos(user, attributes).update_all(state: :done)
end
private
- def create_todos(project, target, author, users, action, note = nil)
+ def create_todos(users, attributes)
Array(users).each do |user|
- next if pending_todos(user, project, target).exists?
-
- Todo.create(
- project: project,
- user_id: user.id,
- author_id: author.id,
- target_id: target.id,
- target_type: target.class.name,
- action: action,
- note: note
- )
+ next if pending_todos(user, attributes).exists?
+ Todo.create(attributes.merge(user_id: user.id))
end
end
@@ -130,8 +122,8 @@ class TodoService
end
def handle_note(note, author)
- # Skip system notes, notes on commit, and notes on project snippet
- return if note.system? || ['Commit', 'Snippet'].include?(note.noteable_type)
+ # Skip system notes, and notes on project snippet
+ return if note.system? || note.for_project_snippet?
project = note.project
target = note.noteable
@@ -142,13 +134,39 @@ class TodoService
def create_assignment_todo(issuable, author)
if issuable.assignee && issuable.assignee != author
- create_todos(issuable.project, issuable, author, issuable.assignee, Todo::ASSIGNED)
+ attributes = attributes_for_todo(issuable.project, issuable, author, Todo::ASSIGNED)
+ create_todos(issuable.assignee, attributes)
end
end
- def create_mention_todos(project, issuable, author, note = nil)
- mentioned_users = filter_mentioned_users(project, note || issuable, author)
- create_todos(project, issuable, author, mentioned_users, Todo::MENTIONED, note)
+ def create_mention_todos(project, target, author, note = nil)
+ mentioned_users = filter_mentioned_users(project, note || target, author)
+ attributes = attributes_for_todo(project, target, author, Todo::MENTIONED, note)
+ create_todos(mentioned_users, attributes)
+ end
+
+ def attributes_for_target(target)
+ attributes = {
+ project_id: target.project.id,
+ target_id: target.id,
+ target_type: target.class.name,
+ commit_id: nil
+ }
+
+ if target.is_a?(Commit)
+ attributes.merge!(target_id: nil, commit_id: target.id)
+ end
+
+ attributes
+ end
+
+ def attributes_for_todo(project, target, author, action, note = nil)
+ attributes_for_target(target).merge!(
+ project_id: project.id,
+ author_id: author.id,
+ action: action,
+ note: note
+ )
end
def filter_mentioned_users(project, target, author)
@@ -160,11 +178,8 @@ class TodoService
mentioned_users.uniq
end
- def pending_todos(user, project, target)
- user.todos.pending.where(
- project_id: project.id,
- target_id: target.id,
- target_type: target.class.name
- )
+ def pending_todos(user, criteria = {})
+ valid_keys = [:project_id, :target_id, :target_type, :commit_id]
+ user.todos.pending.where(criteria.slice(*valid_keys))
end
end
diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml
index 45cfe3da188..e3a4d64df01 100644
--- a/app/views/dashboard/todos/_todo.html.haml
+++ b/app/views/dashboard/todos/_todo.html.haml
@@ -2,7 +2,7 @@
.todo-item.todo-block
= image_tag avatar_icon(todo.author_email, 40), class: 'avatar s40', alt:''
- .todo-title
+ .todo-title.title
%span.author-name
- if todo.author
= link_to_author(todo)
@@ -16,7 +16,9 @@
- if todo.pending?
.todo-actions.pull-right
- = link_to 'Done', [:dashboard, todo], method: :delete, class: 'btn'
+ = link_to [:dashboard, todo], method: :delete, class: 'btn btn-loading done-todo' do
+ Done
+ = icon('spinner spin')
.todo-body
.todo-note
diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml
index 946d7df3933..f9ec3a89158 100644
--- a/app/views/dashboard/todos/index.html.haml
+++ b/app/views/dashboard/todos/index.html.haml
@@ -3,13 +3,15 @@
.top-area
%ul.nav-links
- %li{class: ('active' if params[:state].blank? || params[:state] == 'pending')}
+ - todo_pending_active = ('active' if params[:state].blank? || params[:state] == 'pending')
+ %li{class: "todos-pending #{todo_pending_active}"}
= link_to todos_filter_path(state: 'pending') do
%span
To do
%span{class: 'badge'}
= todos_pending_count
- %li{class: ('active' if params[:state] == 'done')}
+ - todo_done_active = ('active' if params[:state] == 'done')
+ %li{class: "todos-done #{todo_done_active}"}
= link_to todos_filter_path(state: 'done') do
%span
Done
@@ -18,7 +20,9 @@
.nav-controls
- if @todos.any?(&:pending?)
- = link_to 'Mark all as done', destroy_all_dashboard_todos_path(todos_filter_params), class: 'btn', method: :delete
+ = link_to destroy_all_dashboard_todos_path(todos_filter_params), class: 'btn btn-loading js-todos-mark-all', method: :delete do
+ Mark all as done
+ = icon('spinner spin')
.todos-filters
.gray-content-block.second-block
@@ -42,12 +46,12 @@
.prepend-top-default
- if @todos.any?
- @todos.group_by(&:project).each do |group|
- .panel.panel-default.panel-small
+ .panel.panel-default.panel-small.js-todos-list
- project = group[0]
.panel-heading
= link_to project.name_with_namespace, namespace_project_path(project.namespace, project)
- %ul.well-list.todos-list
+ %ul.content-list.todos-list
= render group[1]
= paginate @todos, theme: "gitlab"
- else
diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml
index 36fb2d51629..2d9d9dd6342 100644
--- a/app/views/events/_event.html.haml
+++ b/app/views/events/_event.html.haml
@@ -1,4 +1,4 @@
-- if event.proper?
+- if event.proper?(current_user)
.event-item{class: "#{event.body? ? "event-block" : "event-inline" }"}
.event-item-timestamp
#{time_ago_with_tooltip(event.created_at)}
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 77d01a7736c..f3090b96702 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -46,6 +46,8 @@
%h1.title= title
= render 'shared/outdated_browser'
+
- if @project && !@project.empty_repo?
- :javascript
- var findFileURL = "#{namespace_project_find_file_path(@project.namespace, @project, @ref || @project.repository.root_ref)}";
+ - if ref = @ref || @project.repository.root_ref
+ :javascript
+ var findFileURL = "#{namespace_project_find_file_path(@project.namespace, @project, ref)}";
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index 0ae83ee01eb..86b46e8c75e 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -67,7 +67,7 @@
%span
Issues
- if @project.default_issues_tracker?
- %span.count.issue_counter= number_with_delimiter(@project.issues.opened.count)
+ %span.count.issue_counter= number_with_delimiter(@project.issues.visible_to_user(current_user).opened.count)
- if project_nav_tab? :merge_requests
= nav_link(controller: :merge_requests) do
diff --git a/app/views/projects/_builds_settings.html.haml b/app/views/projects/_builds_settings.html.haml
new file mode 100644
index 00000000000..95ab9ecf3e8
--- /dev/null
+++ b/app/views/projects/_builds_settings.html.haml
@@ -0,0 +1,60 @@
+%fieldset.builds-feature
+ %legend
+ Builds:
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ %p Get recent application code using the following command:
+ .radio
+ = f.label :build_allow_git_fetch_false do
+ = f.radio_button :build_allow_git_fetch, 'false'
+ %strong git clone
+ %br
+ %span.descr Slower but makes sure you have a clean dir before every build
+ .radio
+ = f.label :build_allow_git_fetch_true do
+ = f.radio_button :build_allow_git_fetch, 'true'
+ %strong git fetch
+ %br
+ %span.descr Faster
+
+ .form-group
+ = f.label :build_timeout_in_minutes, 'Timeout', class: 'control-label'
+ .col-sm-10
+ = f.number_field :build_timeout_in_minutes, class: 'form-control', min: '0'
+ %p.help-block per build in minutes
+ .form-group
+ = f.label :build_coverage_regex, "Test coverage parsing", class: 'control-label'
+ .col-sm-10
+ .input-group
+ %span.input-group-addon /
+ = f.text_field :build_coverage_regex, class: 'form-control', placeholder: '\(\d+.\d+\%\) covered'
+ %span.input-group-addon /
+ %p.help-block
+ We will use this regular expression to find test coverage output in build trace.
+ Leave blank if you want to disable this feature
+ .bs-callout.bs-callout-info
+ %p Below are examples of regex for existing tools:
+ %ul
+ %li
+ Simplecov (Ruby) -
+ %code \(\d+.\d+\%\) covered
+ %li
+ pytest-cov (Python) -
+ %code \d+\%\s*$
+ %li
+ phpunit --coverage-text --colors=never (PHP) -
+ %code ^\s*Lines:\s*\d+.\d+\%
+
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ .checkbox
+ = f.label :public_builds do
+ = f.check_box :public_builds
+ %strong Public builds
+ .help-block Allow everyone to access builds for Public and Internal projects
+
+ .form-group
+ = f.label :runners_token, "Runners token", class: 'control-label'
+ .col-sm-10
+ = f.text_field :runners_token, class: "form-control", placeholder: 'xEeFCaDAB89'
+ %p.help-block The secure token used to checkout project.
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index 3ac058a3bf8..dc34032b1b8 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -42,13 +42,17 @@
.diff-content.diff-wrap-lines
-# Skipp all non non-supported blobs
- return unless blob.respond_to?('text?')
- - if blob_text_viewable?(blob)
- - if diff_view == 'parallel'
- = render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob, index: i
- - else
- = render "projects/diffs/text_file", diff_file: diff_file, index: i
- - elsif blob.image?
- - old_file = project.repository.prev_blob_for_diff(diff_commit, diff_file)
- = render "projects/diffs/image", diff_file: diff_file, old_file: old_file, file: blob, index: i
+ - if diff_file.too_large?
+ .nothing-here-block
+ This diff could not be displayed because it is too large.
- else
- .nothing-here-block No preview for this file type
+ - if blob_text_viewable?(blob)
+ - if diff_view == 'parallel'
+ = render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob, index: i
+ - else
+ = render "projects/diffs/text_file", diff_file: diff_file, index: i
+ - elsif blob.image?
+ - old_file = project.repository.prev_blob_for_diff(diff_commit, diff_file)
+ = render "projects/diffs/image", diff_file: diff_file, old_file: old_file, file: blob, index: i
+ - else
+ .nothing-here-block No preview for this file type
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index f2e56081afe..6d872cd0b21 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -84,6 +84,8 @@
%br
%span.descr Share code pastes with others out of git repository
+ = render 'builds_settings', f: f
+
%fieldset.features
%legend
Project avatar:
@@ -110,69 +112,6 @@
%hr
= link_to 'Remove avatar', namespace_project_avatar_path(@project.namespace, @project), data: { confirm: "Project avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar"
- %fieldset.features
- %legend
- Continuous Integration
- .form-group
- .col-sm-offset-2.col-sm-10
- %p Get recent application code using the following command:
- .radio
- = f.label :build_allow_git_fetch_false do
- = f.radio_button :build_allow_git_fetch, 'false'
- %strong git clone
- %br
- %span.descr Slower but makes sure you have a clean dir before every build
- .radio
- = f.label :build_allow_git_fetch_true do
- = f.radio_button :build_allow_git_fetch, 'true'
- %strong git fetch
- %br
- %span.descr Faster
-
- .form-group
- = f.label :build_timeout_in_minutes, 'Timeout', class: 'control-label'
- .col-sm-10
- = f.number_field :build_timeout_in_minutes, class: 'form-control', min: '0'
- %p.help-block per build in minutes
- .form-group
- = f.label :build_coverage_regex, "Test coverage parsing", class: 'control-label'
- .col-sm-10
- .input-group
- %span.input-group-addon /
- = f.text_field :build_coverage_regex, class: 'form-control', placeholder: '\(\d+.\d+\%\) covered'
- %span.input-group-addon /
- %p.help-block
- We will use this regular expression to find test coverage output in build trace.
- Leave blank if you want to disable this feature
- .bs-callout.bs-callout-info
- %p Below are examples of regex for existing tools:
- %ul
- %li
- Simplecov (Ruby) -
- %code \(\d+.\d+\%\) covered
- %li
- pytest-cov (Python) -
- %code \d+\%\s*$
- %li
- phpunit --coverage-text --colors=never (PHP) -
- %code ^\s*Lines:\s*\d+.\d+\%
-
- .form-group
- .col-sm-offset-2.col-sm-10
- .checkbox
- = f.label :public_builds do
- = f.check_box :public_builds
- %strong Public builds
- .help-block Allow everyone to access builds for Public and Internal projects
-
- %fieldset.features
- %legend
- Advanced settings
- .form-group
- = f.label :runners_token, "CI token", class: 'control-label'
- .col-sm-10
- = f.text_field :runners_token, class: "form-control", placeholder: 'xEeFCaDAB89'
- %p.help-block The secure token used to checkout project.
.form-actions
= f.submit 'Save changes', class: "btn btn-save"
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index a44f34c2a68..4aa92d0b39e 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -3,10 +3,11 @@
.issue-check
= check_box_tag dom_id(issue,"selected"), nil, false, 'data-id' => issue.id, class: "selected_issue"
- .issue-title
+ .issue-title.title
%span.issue-title-text
- = link_to_gfm issue.title, issue_path(issue), class: "title"
- %ul.controls.light
+ = confidential_icon(issue)
+ = link_to_gfm issue.title, issue_path(issue)
+ %ul.controls
- if issue.closed?
%li
CLOSED
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 1e8308277cc..52df3de8a27 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -22,20 +22,20 @@
= icon('angle-double-left')
.issue-meta
+ = confidential_icon(@issue)
%strong.identifier
Issue ##{@issue.iid}
%span.creator
- by
+ opened
.editor-details
.editor-details
+ = time_ago_with_tooltip(@issue.created_at)
+ by
%strong
= link_to_member(@project, @issue.author, size: 24, mobile_classes: "hidden-xs")
- %span.hidden-xs
- = '@' + @issue.author.username
%strong
= link_to_member(@project, @issue.author, size: 24, mobile_classes: "hidden-sm hidden-md hidden-lg",
by_username: true, avatar: false)
- = time_ago_with_tooltip(@issue.created_at)
.pull-right.issue-btn-group
- if can?(current_user, :create_issue, @project)
@@ -63,10 +63,7 @@
= markdown(@issue.description, cache_key: [@issue, "description"])
%textarea.hidden.js-task-list-field
= @issue.description
- - if @issue.updated_at != @issue.created_at
- %small
- Edited
- = time_ago_with_tooltip(@issue.updated_at, placement: 'bottom', html_class: 'issue_edited_ago')
+ = edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue_edited_ago')
.merge-requests
= render 'merge_requests'
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index 18cf3f14f0b..13d0cbdde1d 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -1,8 +1,8 @@
%li{ class: mr_css_classes(merge_request) }
- .merge-request-title
+ .merge-request-title.title
%span.merge-request-title-text
- = link_to_gfm merge_request.title, merge_request_path(merge_request), class: "title"
- %ul.controls.light
+ = link_to_gfm merge_request.title, merge_request_path(merge_request)
+ %ul.controls
- if merge_request.merged?
%li
MERGED
diff --git a/app/views/projects/merge_requests/show/_mr_box.html.haml b/app/views/projects/merge_requests/show/_mr_box.html.haml
index 602f787e6cf..a23bd8d18d0 100644
--- a/app/views/projects/merge_requests/show/_mr_box.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_box.html.haml
@@ -11,7 +11,4 @@
%textarea.hidden.js-task-list-field
= @merge_request.description
- - if @merge_request.updated_at != @merge_request.created_at
- %small
- Edited
- = time_ago_with_tooltip(@merge_request.updated_at, placement: 'bottom')
+ = edited_time_ago_with_tooltip(@merge_request, placement: 'bottom')
diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml
index a75c0d96c57..eeb605e2dc5 100644
--- a/app/views/projects/merge_requests/show/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_title.html.haml
@@ -8,18 +8,21 @@
= icon('angle-double-left')
.issue-meta
%strong.identifier
- Merge Request ##{@merge_request.iid}
+ %span.hidden-sm.hidden-md.hidden-lg
+ MR
+ %span.hidden-xs
+ Merge Request
+ !#{@merge_request.iid}
%span.creator
- by
+ opened
.editor-details
+ = time_ago_with_tooltip(@merge_request.created_at)
+ by
%strong
= link_to_member(@project, @merge_request.author, size: 24, mobile_classes: "hidden-xs")
- %span.hidden-xs
- = '@' + @merge_request.author.username
%strong
= link_to_member(@project, @merge_request.author, size: 24, mobile_classes: "hidden-sm hidden-md hidden-lg",
by_username: true, avatar: false)
- = time_ago_with_tooltip(@merge_request.created_at)
.issue-btn-group.pull-right
- if can?(current_user, :update_merge_request, @merge_request)
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index b4597043a27..be63875ab34 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -42,7 +42,7 @@
= preserve do
= markdown @milestone.description
-- if @milestone.complete? && @milestone.active?
+- if @milestone.complete?(current_user) && @milestone.active?
.alert.alert-success.prepend-top-default
%span All issues for this milestone are closed. You may close milestone now.
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index 52972576aff..2cf32e6093d 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -27,20 +27,13 @@
%span.note-last-update
%a{name: dom_id(note), href: "##{dom_id(note)}", title: 'Link here'}
= time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note_created_ago')
- - if note.updated_at != note.created_at
- %span.note-updated-at
- &middot;
- = icon('edit', title: 'edited')
- = time_ago_with_tooltip(note.updated_at, placement: 'bottom', html_class: 'note_edited_ago')
- - if note.updated_by && note.updated_by != note.author
- by #{link_to_member(note.project, note.updated_by, avatar: false, author_class: nil)}
-
.note-body{class: note_editable?(note) ? 'js-task-list-container' : ''}
.note-text
= preserve do
= markdown(note.note, pipeline: :note, cache_key: [note, "note"])
- if note_editable?(note)
= render 'projects/notes/edit_form', note: note
+ = edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago', include_author: true)
- if note.attachment.url
.note-attachment
@@ -54,4 +47,3 @@
= link_to delete_attachment_namespace_project_note_path(note.project.namespace, note.project, note),
title: 'Delete this attachment', method: :delete, remote: true, data: { confirm: 'Are you sure you want to remove the attachment?' }, class: 'danger js-note-attachment-delete' do
= icon('trash-o', class: 'cred')
- .clear
diff --git a/app/views/projects/repositories/_download_archive.html.haml b/app/views/projects/repositories/_download_archive.html.haml
index b9486a9b492..24658319060 100644
--- a/app/views/projects/repositories/_download_archive.html.haml
+++ b/app/views/projects/repositories/_download_archive.html.haml
@@ -10,7 +10,7 @@
%span.caret
%span.sr-only
Select Archive Format
- %ul.col-xs-10.dropdown-menu{ role: 'menu' }
+ %ul.col-xs-10.dropdown-menu.dropdown-menu-align-right{ role: 'menu' }
%li
= link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), rel: 'nofollow' do
%i.fa.fa-download
diff --git a/app/views/search/results/_issue.html.haml b/app/views/search/results/_issue.html.haml
index 45d700781f3..710f5613c81 100644
--- a/app/views/search/results/_issue.html.haml
+++ b/app/views/search/results/_issue.html.haml
@@ -1,5 +1,6 @@
.search-result-row
%h4
+ = confidential_icon(issue)
= link_to [issue.project.namespace.becomes(Namespace), issue.project, issue] do
%span.term.str-truncated= issue.title
.pull-right ##{issue.iid}
diff --git a/app/views/shared/groups/_group.html.haml b/app/views/shared/groups/_group.html.haml
index fb9a8db0889..f172350f5ff 100644
--- a/app/views/shared/groups/_group.html.haml
+++ b/app/views/shared/groups/_group.html.haml
@@ -10,7 +10,7 @@
%i.fa.fa-cogs
= link_to leave_group_group_members_path(group), data: { confirm: leave_group_message(group.name) }, method: :delete, class: "btn-sm btn btn-grouped", title: 'Leave this group' do
- %i.fa.fa-sign-out
+ = icon('sign-out')
.stats
%span
@@ -22,12 +22,13 @@
= number_with_delimiter(group.users.count)
= image_tag group_icon(group), class: "avatar s40 hidden-xs"
- = link_to group, class: 'group-name title' do
- = group.name
+ .title
+ = link_to group, class: 'group-name' do
+ = group.name
- - if group_member
- as
- %span #{group_member.human_access}
+ - if group_member
+ as
+ %span #{group_member.human_access}
- if group.description.present?
.description
diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml
index 3eb0db276b2..ac20f7d1f7e 100644
--- a/app/views/shared/issuable/_filter.html.haml
+++ b/app/views/shared/issuable/_filter.html.haml
@@ -9,75 +9,20 @@
.filter-item.inline
- if params[:author_id]
= hidden_field_tag(:author_id, params[:author_id])
- = dropdown_tag("Author", options: { toggle_class: "js-user-search js-filter-submit js-author-search", title: "Filter by author", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-author",
- placeholder: "Search authors", data: { any_user: "Any Author", first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), selected: params[:author_id], field_name: "author_id" } })
+ = dropdown_tag(user_dropdown_label(params[:author_id], "Author"), options: { toggle_class: "js-user-search js-filter-submit js-author-search", title: "Filter by author", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-author",
+ placeholder: "Search authors", data: { any_user: "Any Author", first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), selected: params[:author_id], field_name: "author_id", default_label: "Author" } })
.filter-item.inline
- if params[:assignee_id]
= hidden_field_tag(:assignee_id, params[:assignee_id])
- = dropdown_tag("Assignee", options: { toggle_class: "js-user-search js-filter-submit js-assignee-search", title: "Filter by assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee",
- placeholder: "Search assignee", data: { any_user: "Any Assignee", first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: (@project.id if @project), selected: params[:assignee_id], field_name: "assignee_id" } })
+ = dropdown_tag(user_dropdown_label(params[:assignee_id], "Assignee"), options: { toggle_class: "js-user-search js-filter-submit js-assignee-search", title: "Filter by assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee",
+ placeholder: "Search assignee", data: { any_user: "Any Assignee", first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: (@project.id if @project), selected: params[:assignee_id], field_name: "assignee_id", default_label: "Assignee" } })
.filter-item.inline.milestone-filter
- - if params[:milestone_title]
- = hidden_field_tag(:milestone_title, params[:milestone_title])
- = dropdown_tag("Milestone", options: { title: "Filter by milestone", toggle_class: 'js-milestone-select js-filter-submit', filter: true, dropdown_class: "dropdown-menu-selectable",
- placeholder: "Search milestones", footer_content: true, data: { show_no: true, show_any: true, field_name: "milestone_title", selected: params[:milestone_title], project_id: (@project.id if @project), milestones: (namespace_project_milestones_path(@project.namespace, @project, :js) if @project) } }) do
- - if @project
- %ul.dropdown-footer-list
- - if can? current_user, :admin_milestone, @project
- %li
- = link_to new_namespace_project_milestone_path(@project.namespace, @project), title: "New Milestone" do
- Create new
- %li
- = link_to namespace_project_milestones_path(@project.namespace, @project) do
- - if can? current_user, :admin_milestone, @project
- Manage milestones
- - else
- View milestones
+ = render "shared/issuable/milestone_dropdown"
.filter-item.inline.labels-filter
- - if params[:label_name]
- = 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.id if @project), labels: (namespace_project_labels_path(@project.namespace, @project, :js) if @project)}}
- %span.dropdown-toggle-text
- Label
- = icon('chevron-down')
- .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
- .dropdown-page-one
- = dropdown_title("Filter by label")
- = dropdown_filter("Search labels")
- = dropdown_content
- - if @project
- = dropdown_footer do
- %ul.dropdown-footer-list
- - if can? current_user, :admin_label, @project
- %li
- %a.dropdown-toggle-page{href: "#"}
- Create new
- %li
- = link_to namespace_project_labels_path(@project.namespace, @project) do
- - if can? current_user, :admin_label, @project
- Manage labels
- - else
- View labels
- - if can? current_user, :admin_label, @project
- .dropdown-page-two
- = dropdown_title("Create new label", back: true)
- = dropdown_content do
- %input#new_label_color{type: "hidden"}
- %input#new_label_name.dropdown-input-field{type: "text", placeholder: "Name new label"}
- .dropdown-label-color-preview.js-dropdown-label-color-preview
- .suggest-colors.suggest-colors-dropdown
- - suggested_colors.each do |color|
- = link_to '#', style: "background-color: #{color}", data: { color: color } do
- &nbsp
- %button.btn.btn-primary.js-new-label-btn{type: "button"}
- Create
- = dropdown_loading
- .dropdown-loading
- = icon('spinner spin')
+ = render "shared/issuable/label_dropdown"
.pull-right
= render 'shared/sort_dropdown'
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index d5a4aad05d9..9ef729e960c 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -29,6 +29,15 @@
= render 'projects/notes/hints'
.clearfix
.error-alert
+
+- if issuable.is_a?(Issue) && !issuable.project.private?
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ .checkbox
+ = f.label :confidential do
+ = f.check_box :confidential
+ This issue is confidential and should only be visible to team members
+
- if can?(current_user, :"admin_#{issuable.to_ability_name}", issuable.project)
%hr
.form-group
diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml
new file mode 100644
index 00000000000..87617315181
--- /dev/null
+++ b/app/views/shared/issuable/_label_dropdown.html.haml
@@ -0,0 +1,39 @@
+- if params[:label_name]
+ = 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"}}
+ %span.dropdown-toggle-text
+ = h(params[:label_name].presence || "Label")
+ = icon('chevron-down')
+ .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
+ .dropdown-page-one
+ = dropdown_title("Filter by label")
+ = dropdown_filter("Search labels")
+ = dropdown_content
+ - if @project
+ = dropdown_footer do
+ %ul.dropdown-footer-list
+ - if can? current_user, :admin_label, @project
+ %li
+ %a.dropdown-toggle-page{href: "#"}
+ Create new
+ %li
+ = link_to namespace_project_labels_path(@project.namespace, @project) do
+ - if can? current_user, :admin_label, @project
+ Manage labels
+ - else
+ View labels
+ - if can? current_user, :admin_label, @project and @project
+ .dropdown-page-two
+ = dropdown_title("Create new label", back: true)
+ = dropdown_content do
+ %input#new_label_color{type: "hidden"}
+ %input#new_label_name.dropdown-input-field{type: "text", placeholder: "Name new label"}
+ .dropdown-label-color-preview.js-dropdown-label-color-preview
+ .suggest-colors.suggest-colors-dropdown
+ - suggested_colors.each do |color|
+ = link_to '#', style: "background-color: #{color}", data: { color: color } do
+ &nbsp
+ %button.btn.btn-primary.js-new-label-btn{type: "button"}
+ Create
+ = dropdown_loading
diff --git a/app/views/shared/issuable/_milestone_dropdown.html.haml b/app/views/shared/issuable/_milestone_dropdown.html.haml
new file mode 100644
index 00000000000..0434506c8d7
--- /dev/null
+++ b/app/views/shared/issuable/_milestone_dropdown.html.haml
@@ -0,0 +1,16 @@
+- if params[:milestone_title]
+ = hidden_field_tag(:milestone_title, params[:milestone_title])
+= dropdown_tag(h(params[:milestone_title].presence || "Milestone"), options: { title: "Filter by milestone", toggle_class: 'js-milestone-select js-filter-submit', filter: true, dropdown_class: "dropdown-menu-selectable",
+ placeholder: "Search milestones", footer_content: @project.present?, data: { show_no: true, show_any: true, field_name: "milestone_title", selected: params[:milestone_title], project_id: @project.try(:id), milestones: milestones_filter_dropdown_path, default_label: "Milestone" } }) do
+ - if @project
+ %ul.dropdown-footer-list
+ - if can? current_user, :admin_milestone, @project
+ %li
+ = link_to new_namespace_project_milestone_path(@project.namespace, @project), title: "New Milestone" do
+ Create new
+ %li
+ = link_to namespace_project_milestones_path(@project.namespace, @project) do
+ - if can? current_user, :admin_milestone, @project
+ Manage milestones
+ - else
+ View milestones
diff --git a/app/views/shared/issuable/_participants.html.haml b/app/views/shared/issuable/_participants.html.haml
index f1d92ef48b2..3fb409ff727 100644
--- a/app/views/shared/issuable/_participants.html.haml
+++ b/app/views/shared/issuable/_participants.html.haml
@@ -1,3 +1,6 @@
+- participants_row = 7
+- participants_size = participants.size
+- participants_extra = participants_size - participants_row
.block.participants
.sidebar-collapsed-icon
= icon('users')
@@ -5,6 +8,13 @@
= participants.count
.title.hide-collapsed
= pluralize participants.count, "participant"
- - participants.each do |participant|
- %span.hide-collapsed
- = link_to_member(@project, participant, name: false, size: 24)
+ .hide-collapsed.participants-list
+ - participants.each do |participant|
+ .participants-author.js-participants-author
+ = link_to_member(@project, participant, name: false, size: 24)
+ - if participants_extra > 0
+ %div.participants-more
+ %a.js-participants-more{href: "#", data: {original_text: "+ #{participants_size - 7} more", less_text: "- show less"}}
+ + #{participants_extra} more
+:javascript
+ Issue.prototype.PARTICIPANTS_ROW_COUNT = #{participants_row};
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index 23b1ed1e51b..2b95b19facc 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -1,13 +1,12 @@
%aside.right-sidebar{ class: sidebar_gutter_collapsed_class }
.issuable-sidebar
- .block
+ .block.issuable-sidebar-header
%span.issuable-count.hide-collapsed.pull-left
= issuable.iid
of
= issuables_count(issuable)
- %span.pull-right
- %a.gutter-toggle.js-sidebar-toggle{href: '#'}
- = sidebar_gutter_toggle_icon
+ %a.gutter-toggle.pull-right.js-sidebar-toggle{href: '#'}
+ = sidebar_gutter_toggle_icon
.issuable-nav.hide-collapsed.pull-right.btn-group{role: 'group', "aria-label" => '...'}
- if prev_issuable = prev_issuable_for(issuable)
= link_to 'Prev', [@project.namespace.becomes(Namespace), @project, prev_issuable], class: 'btn btn-default prev-btn'
@@ -22,20 +21,20 @@
= form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f|
.block.assignee
- .sidebar-collapsed-icon
+ .sidebar-collapsed-icon.sidebar-collapsed-user{data: {toggle: "tooltip", placement: "left", container: "body"}, title: (issuable.assignee.to_reference if issuable.assignee)}
- if issuable.assignee
- = link_to_member_avatar(issuable.assignee, size: 24)
+ = link_to_member(@project, issuable.assignee, size: 24)
- else
= icon('user')
.title.hide-collapsed
- %label
- Assignee
+ Assignee
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
- .pull-right
- = link_to 'Edit', '#', class: 'edit-link'
- .value.hide-collapsed
+ = link_to 'Edit', '#', class: 'edit-link pull-right'
+ .value.bold.hide-collapsed
- if issuable.assignee
- %strong= link_to_member(@project, issuable.assignee, size: 24)
+ = link_to_member(@project, issuable.assignee, size: 32) do
+ %span.username
+ = issuable.assignee.to_reference
- if issuable.instance_of?(MergeRequest) && !issuable.can_be_merged_by?(issuable.assignee)
%a.pull-right.cannot-be-merged{href: '#', data: {toggle: 'tooltip'}, title: 'Not allowed to merge'}
= icon('exclamation-triangle')
@@ -54,18 +53,13 @@
- else
No
.title.hide-collapsed
- %label
- Milestone
+ Milestone
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
- .pull-right
- = link_to 'Edit', '#', class: 'edit-link'
- .value.hide-collapsed
+ = link_to 'Edit', '#', class: 'edit-link pull-right'
+ .value.bold.hide-collapsed
- if issuable.milestone
- %span.back-to-milestone
- = link_to namespace_project_milestone_path(@project.namespace, @project, issuable.milestone) do
- %strong
- = icon('clock-o')
- = issuable.milestone.title
+ = link_to namespace_project_milestone_path(@project.namespace, @project, issuable.milestone) do
+ = issuable.milestone.title
- else
.light None
.selectbox.hide-collapsed
@@ -80,11 +74,10 @@
%span
= issuable.labels.count
.title.hide-collapsed
- %label Labels
+ Labels
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
- .pull-right
- = link_to 'Edit', '#', class: 'edit-link'
- .value.issuable-show-labels.hide-collapsed
+ = link_to 'Edit', '#', class: 'edit-link pull-right'
+ .value.issuable-show-labels.hide-collapsed{class: ("has-labels" if issuable.labels.any?)}
- if issuable.labels.any?
- issuable.labels.each do |label|
= link_to_label(label, type: issuable.to_ability_name)
@@ -95,14 +88,13 @@
{ selected: issuable.label_ids }, multiple: true, class: 'select2 js-select2', data: { placeholder: "Select labels" }
= render "shared/issuable/participants", participants: issuable.participants(current_user)
- %hr
- if current_user
- subscribed = issuable.subscribed?(current_user)
.block.light.subscription{data: {url: toggle_subscription_path(issuable)}}
.sidebar-collapsed-icon
= icon('rss')
.title.hide-collapsed
- %label.light Notifications
+ Notifications
- subscribtion_status = subscribed ? 'subscribed' : 'unsubscribed'
%button.btn.btn-block.btn-gray.subscribe-button.hide-collapsed{:type => 'button'}
%span= subscribed ? 'Unsubscribe' : 'Subscribe'
diff --git a/app/views/shared/milestones/_issuable.html.haml b/app/views/shared/milestones/_issuable.html.haml
index f7c6fc14adf..85888096722 100644
--- a/app/views/shared/milestones/_issuable.html.haml
+++ b/app/views/shared/milestones/_issuable.html.haml
@@ -10,6 +10,8 @@
%strong #{project.name} &middot;
- elsif show_full_project_name
%strong #{project.name_with_namespace} &middot;
+ - if issuable.is_a?(Issue)
+ = confidential_icon(issuable)
= link_to_gfm issuable.title, [project.namespace.becomes(Namespace), project, issuable], title: issuable.title
%div{class: 'issuable-detail'}
= link_to [project.namespace.becomes(Namespace), project, issuable] do
diff --git a/app/views/shared/milestones/_milestone.html.haml b/app/views/shared/milestones/_milestone.html.haml
index f01138af3f0..6b25745c554 100644
--- a/app/views/shared/milestones/_milestone.html.haml
+++ b/app/views/shared/milestones/_milestone.html.haml
@@ -6,10 +6,10 @@
.col-sm-6
%strong= link_to_gfm truncate(milestone.title, length: 100), milestone_path
.col-sm-6
- .pull-right.light #{milestone.percent_complete}% complete
+ .pull-right.light #{milestone.percent_complete(current_user)}% complete
.row
.col-sm-6
- = link_to pluralize(milestone.issues.size, 'Issue'), issues_path
+ = link_to pluralize(milestone.issues_visible_to_user(current_user).size, 'Issue'), issues_path
&middot;
= link_to pluralize(milestone.merge_requests.size, 'Merge Request'), merge_requests_path
.col-sm-6= milestone_progress_bar(milestone)
diff --git a/app/views/shared/milestones/_summary.html.haml b/app/views/shared/milestones/_summary.html.haml
index 59d4ae29f79..385c6596606 100644
--- a/app/views/shared/milestones/_summary.html.haml
+++ b/app/views/shared/milestones/_summary.html.haml
@@ -3,15 +3,15 @@
.context.prepend-top-default
.milestone-summary
%h4 Progress
- %strong= milestone.issues.size
+ %strong= milestone.issues_visible_to_user(current_user).size
issues:
%span.milestone-stat
- %strong= milestone.issues.opened.size
+ %strong= milestone.issues_visible_to_user(current_user).opened.size
open and
- %strong= milestone.issues.closed.size
+ %strong= milestone.issues_visible_to_user(current_user).closed.size
closed
%span.milestone-stat
- %strong== #{milestone.percent_complete}%
+ %strong== #{milestone.percent_complete(current_user)}%
complete
%span.milestone-stat
diff --git a/app/views/shared/milestones/_tabs.html.haml b/app/views/shared/milestones/_tabs.html.haml
index 57d7ee85a3b..2b6ce2d7e7a 100644
--- a/app/views/shared/milestones/_tabs.html.haml
+++ b/app/views/shared/milestones/_tabs.html.haml
@@ -2,7 +2,7 @@
%li.active
= link_to '#tab-issues', 'data-toggle' => 'tab', 'data-show' => '.tab-issues-buttons' do
Issues
- %span.badge= milestone.issues.size
+ %span.badge= milestone.issues_visible_to_user(current_user).size
%li
= link_to '#tab-merge-requests', 'data-toggle' => 'tab', 'data-show' => '.tab-merge-requests-buttons' do
Merge Requests
@@ -21,7 +21,7 @@
.tab-content.milestone-content
.tab-pane.active#tab-issues
- = render 'shared/milestones/issues_tab', issues: milestone.issues, show_project_name: show_project_name, show_full_project_name: show_full_project_name
+ = render 'shared/milestones/issues_tab', issues: milestone.issues_visible_to_user(current_user), show_project_name: show_project_name, show_full_project_name: show_full_project_name
.tab-pane#tab-merge-requests
= render 'shared/milestones/merge_requests_tab', merge_requests: milestone.merge_requests, show_project_name: show_project_name, show_full_project_name: show_full_project_name
.tab-pane#tab-participants
diff --git a/app/views/shared/milestones/_top.html.haml b/app/views/shared/milestones/_top.html.haml
index 4cf1d948b5b..cab8743a077 100644
--- a/app/views/shared/milestones/_top.html.haml
+++ b/app/views/shared/milestones/_top.html.haml
@@ -28,7 +28,7 @@
%h2.title
= markdown escape_once(milestone.title), pipeline: :single_line
-- if milestone.complete? && milestone.active?
+- if milestone.complete?(current_user) && milestone.active?
.alert.alert-success.prepend-top-default
- close_msg = group ? 'You may close the milestone now.' : 'Navigate to the project to close the milestone.'
%span All issues for this milestone are closed. #{close_msg}
@@ -47,7 +47,7 @@
- project_name = group ? ms.project.name : ms.project.name_with_namespace
= link_to project_name, namespace_project_milestone_path(ms.project.namespace, ms.project, ms)
%td
- = ms.issues.opened.count
+ = ms.issues_visible_to_user(current_user).opened.count
%td
- if ms.closed?
Closed
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index 97cfb76cdb0..872d2bdf46d 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -7,26 +7,11 @@
- show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true && project.commit
- css_class += " no-description" if project.description.blank? && !show_last_commit_as_description
- ci_commit = project.ci_commit(project.commit.sha) if ci && !project.empty_repo? && project.commit
-- cache_key = [project.namespace, project, controller.controller_name, controller.action_name, current_application_settings, 'v2.2']
+- cache_key = [project.namespace, project, controller.controller_name, controller.action_name, current_application_settings, 'v2.3']
- cache_key.push(ci_commit.status) if ci_commit
%li.project-row{ class: css_class }
= cache(cache_key) do
- = link_to project_path(project), class: dom_class(project) do
- - if avatar
- .dash-project-avatar
- - if use_creator_avatar
- = image_tag avatar_icon(project.creator.email, 40), class: "avatar s40", alt:''
- - else
- = project_icon(project, alt: '', class: 'avatar project-avatar s40')
- %span.project-full-name.title
- %span.namespace-name
- - if project.namespace && !skip_namespace
- = project.namespace.human_name
- \/
- %span.project-name.filter-title
- = project.name
-
.controls
- if project.main_language
%span
@@ -45,6 +30,23 @@
%span.visibility-icon.has_tooltip{data: { container: 'body', placement: 'left' },
title: "#{visibility_level_label(project.visibility_level)} - #{project_visibility_level_description(project.visibility_level)}"}
= visibility_level_icon(project.visibility_level, fw: false)
+
+ .title
+ = link_to project_path(project), class: dom_class(project) do
+ - if avatar
+ .dash-project-avatar
+ - if use_creator_avatar
+ = image_tag avatar_icon(project.creator.email, 40), class: "avatar s40", alt:''
+ - else
+ = project_icon(project, alt: '', class: 'avatar project-avatar s40')
+ %span.project-full-name
+ %span.namespace-name
+ - if project.namespace && !skip_namespace
+ = project.namespace.human_name
+ \/
+ %span.project-name.filter-title
+ = project.name
+
- if show_last_commit_as_description
.description
= link_to_gfm project.commit.title, namespace_project_commit_path(project.namespace, project, project.commit),
diff --git a/app/views/shared/snippets/_snippet.html.haml b/app/views/shared/snippets/_snippet.html.haml
index a316a085107..c96dfefe17f 100644
--- a/app/views/shared/snippets/_snippet.html.haml
+++ b/app/views/shared/snippets/_snippet.html.haml
@@ -1,8 +1,8 @@
%li.snippet-row
= image_tag avatar_icon(snippet.author_email), class: "avatar s40 hidden-xs", alt: ''
- .snippet-title
- = link_to reliable_snippet_path(snippet), class: 'title' do
+ .title
+ = link_to reliable_snippet_path(snippet) do
= truncate(snippet.title, length: 60)
- if snippet.private?
%span.label.label-gray
diff --git a/app/workers/gitlab_shell_one_shot_worker.rb b/app/workers/gitlab_shell_one_shot_worker.rb
new file mode 100644
index 00000000000..4ddbcf574d5
--- /dev/null
+++ b/app/workers/gitlab_shell_one_shot_worker.rb
@@ -0,0 +1,10 @@
+class GitlabShellOneShotWorker
+ include Sidekiq::Worker
+ include Gitlab::ShellAdapter
+
+ sidekiq_options queue: :gitlab_shell, retry: false
+
+ def perform(action, *arg)
+ gitlab_shell.send(action, *arg)
+ end
+end