summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/api.js.coffee11
-rw-r--r--app/assets/javascripts/awards_handler.coffee175
-rw-r--r--app/assets/javascripts/blob/blob_license_selector.js.coffee30
-rw-r--r--app/assets/javascripts/blob/edit_blob.js.coffee67
-rw-r--r--app/assets/javascripts/blob/new_blob.js.coffee20
-rw-r--r--app/assets/javascripts/commits.js.coffee2
-rw-r--r--app/assets/javascripts/dispatcher.js.coffee5
-rw-r--r--app/assets/javascripts/due_date_select.js.coffee64
-rw-r--r--app/assets/javascripts/gl_dropdown.js.coffee18
-rw-r--r--app/assets/javascripts/issuable.js.coffee84
-rw-r--r--app/assets/javascripts/issuable_context.js.coffee38
-rw-r--r--app/assets/javascripts/issue.js.coffee23
-rw-r--r--app/assets/javascripts/issues.js.coffee51
-rw-r--r--app/assets/javascripts/labels_select.js.coffee183
-rw-r--r--app/assets/javascripts/lib/animate.js.coffee34
-rw-r--r--app/assets/javascripts/lib/url_utility.js.coffee16
-rw-r--r--app/assets/javascripts/merge_request_tabs.js.coffee12
-rw-r--r--app/assets/javascripts/merge_requests.js.coffee35
-rw-r--r--app/assets/javascripts/milestone_select.js.coffee6
-rw-r--r--app/assets/javascripts/notes.js.coffee8
-rw-r--r--app/assets/javascripts/right_sidebar.js.coffee62
-rw-r--r--app/assets/javascripts/search.js.coffee75
-rw-r--r--app/assets/javascripts/shortcuts.js.coffee41
-rw-r--r--app/assets/javascripts/shortcuts_issuable.coffee20
-rw-r--r--app/assets/javascripts/shortcuts_navigation.coffee1
-rw-r--r--app/assets/javascripts/todos.js.coffee46
-rw-r--r--app/assets/javascripts/user_tabs.js.coffee2
-rw-r--r--app/assets/javascripts/users_select.js.coffee2
-rw-r--r--app/assets/stylesheets/framework.scss1
-rw-r--r--app/assets/stylesheets/framework/blocks.scss2
-rw-r--r--app/assets/stylesheets/framework/buttons.scss8
-rw-r--r--app/assets/stylesheets/framework/calendar.scss4
-rw-r--r--app/assets/stylesheets/framework/common.scss1
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss136
-rw-r--r--app/assets/stylesheets/framework/files.scss11
-rw-r--r--app/assets/stylesheets/framework/forms.scss18
-rw-r--r--app/assets/stylesheets/framework/header.scss6
-rw-r--r--app/assets/stylesheets/framework/markdown_area.scss9
-rw-r--r--app/assets/stylesheets/framework/modal.scss22
-rw-r--r--app/assets/stylesheets/framework/nav.scss19
-rw-r--r--app/assets/stylesheets/framework/selects.scss15
-rw-r--r--app/assets/stylesheets/framework/timeline.scss2
-rw-r--r--app/assets/stylesheets/framework/tw_bootstrap_variables.scss4
-rw-r--r--app/assets/stylesheets/framework/typography.scss10
-rw-r--r--app/assets/stylesheets/framework/variables.scss13
-rw-r--r--app/assets/stylesheets/highlight/monokai.scss2
-rw-r--r--app/assets/stylesheets/highlight/white.scss18
-rw-r--r--app/assets/stylesheets/pages/confirmation.scss18
-rw-r--r--app/assets/stylesheets/pages/detail_page.scss7
-rw-r--r--app/assets/stylesheets/pages/diff.scss16
-rw-r--r--app/assets/stylesheets/pages/editor.scss9
-rw-r--r--app/assets/stylesheets/pages/graph.scss3
-rw-r--r--app/assets/stylesheets/pages/help.scss17
-rw-r--r--app/assets/stylesheets/pages/issuable.scss9
-rw-r--r--app/assets/stylesheets/pages/note_form.scss13
-rw-r--r--app/assets/stylesheets/pages/notes.scss30
-rw-r--r--app/assets/stylesheets/pages/profile.scss17
-rw-r--r--app/assets/stylesheets/pages/projects.scss44
-rw-r--r--app/assets/stylesheets/pages/search.scss91
-rw-r--r--app/assets/stylesheets/pages/settings.scss14
-rw-r--r--app/controllers/admin/application_controller.rb8
-rw-r--r--app/controllers/admin/hooks_controller.rb8
-rw-r--r--app/controllers/admin/impersonation_controller.rb38
-rw-r--r--app/controllers/admin/impersonations_controller.rb24
-rw-r--r--app/controllers/admin/users_controller.rb16
-rw-r--r--app/controllers/application_controller.rb2
-rw-r--r--app/controllers/concerns/filter_projects.rb2
-rw-r--r--app/controllers/confirmations_controller.rb9
-rw-r--r--app/controllers/projects/application_controller.rb3
-rw-r--r--app/controllers/projects/builds_controller.rb10
-rw-r--r--app/controllers/projects/commit_controller.rb15
-rw-r--r--app/controllers/projects/deploy_keys_controller.rb31
-rw-r--r--app/controllers/projects/hooks_controller.rb14
-rw-r--r--app/controllers/projects/issues_controller.rb28
-rw-r--r--app/controllers/projects/merge_requests_controller.rb9
-rw-r--r--app/controllers/projects/project_members_controller.rb2
-rw-r--r--app/controllers/projects/services_controller.rb2
-rw-r--r--app/controllers/projects/wikis_controller.rb21
-rw-r--r--app/controllers/registrations_controller.rb4
-rw-r--r--app/controllers/search_controller.rb6
-rw-r--r--app/finders/issuable_finder.rb42
-rw-r--r--app/finders/snippets_finder.rb2
-rw-r--r--app/helpers/application_helper.rb9
-rw-r--r--app/helpers/blob_helper.rb15
-rw-r--r--app/helpers/ci_badge_helper.rb13
-rw-r--r--app/helpers/ci_status_helper.rb15
-rw-r--r--app/helpers/diff_helper.rb37
-rw-r--r--app/helpers/gitlab_routing_helper.rb4
-rw-r--r--app/helpers/issuables_helper.rb19
-rw-r--r--app/helpers/issues_helper.rb62
-rw-r--r--app/helpers/labels_helper.rb2
-rw-r--r--app/helpers/page_layout_helper.rb8
-rw-r--r--app/helpers/projects_helper.rb69
-rw-r--r--app/helpers/search_helper.rb10
-rw-r--r--app/helpers/selects_helper.rb25
-rw-r--r--app/helpers/sorting_helper.rb18
-rw-r--r--app/helpers/tab_helper.rb8
-rw-r--r--app/mailers/emails/notes.rb8
-rw-r--r--app/models/blob.rb8
-rw-r--r--app/models/ci/build.rb45
-rw-r--r--app/models/ci/commit.rb142
-rw-r--r--app/models/commit.rb7
-rw-r--r--app/models/commit_status.rb56
-rw-r--r--app/models/concerns/issuable.rb9
-rw-r--r--app/models/concerns/milestoneish.rb2
-rw-r--r--app/models/concerns/statuseable.rb81
-rw-r--r--app/models/event.rb2
-rw-r--r--app/models/hooks/project_hook.rb2
-rw-r--r--app/models/hooks/service_hook.rb1
-rw-r--r--app/models/hooks/system_hook.rb1
-rw-r--r--app/models/hooks/web_hook.rb24
-rw-r--r--app/models/issue.rb27
-rw-r--r--app/models/label.rb4
-rw-r--r--app/models/merge_request.rb6
-rw-r--r--app/models/project.rb33
-rw-r--r--app/models/project_services/buildkite_service.rb4
-rw-r--r--app/models/project_services/issue_tracker_service.rb2
-rw-r--r--app/models/project_services/jira_service.rb2
-rw-r--r--app/models/project_services/slack_service.rb7
-rw-r--r--app/models/project_services/slack_service/wiki_page_message.rb53
-rw-r--r--app/models/project_snippet.rb2
-rw-r--r--app/models/repository.rb45
-rw-r--r--app/models/service.rb4
-rw-r--r--app/models/snippet.rb4
-rw-r--r--app/models/user.rb2
-rw-r--r--app/models/wiki_page.rb4
-rw-r--r--app/services/ci/create_builds_service.rb27
-rw-r--r--app/services/ci/create_trigger_request_service.rb4
-rw-r--r--app/services/ci/image_for_build_service.rb11
-rw-r--r--app/services/create_commit_builds_service.rb10
-rw-r--r--app/services/git_push_service.rb9
-rw-r--r--app/services/issuable_base_service.rb28
-rw-r--r--app/services/merge_requests/build_service.rb3
-rw-r--r--app/services/notes/create_service.rb11
-rw-r--r--app/services/projects/transfer_service.rb2
-rw-r--r--app/services/wiki_pages/base_service.rb27
-rw-r--r--app/services/wiki_pages/create_service.rb14
-rw-r--r--app/services/wiki_pages/update_service.rb11
-rw-r--r--app/views/admin/hooks/index.html.haml10
-rw-r--r--app/views/admin/users/_form.html.haml6
-rw-r--r--app/views/dashboard/todos/index.html.haml1
-rw-r--r--app/views/devise/confirmations/almost_there.haml10
-rw-r--r--app/views/devise/sessions/two_factor.html.haml4
-rw-r--r--app/views/doorkeeper/applications/index.html.haml5
-rw-r--r--app/views/events/_event.html.haml7
-rw-r--r--app/views/help/_shortcuts.html.haml14
-rw-r--r--app/views/layouts/_page.html.haml4
-rw-r--r--app/views/layouts/application.html.haml2
-rw-r--r--app/views/layouts/devise_empty.html.haml17
-rw-r--r--app/views/layouts/header/_default.html.haml2
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml3
-rw-r--r--app/views/layouts/nav/_profile.html.haml14
-rw-r--r--app/views/layouts/nav/_project.html.haml7
-rw-r--r--app/views/layouts/profile.html.haml3
-rw-r--r--app/views/notify/note_snippet_email.html.haml1
-rw-r--r--app/views/notify/note_snippet_email.text.erb8
-rw-r--r--app/views/profiles/accounts/show.html.haml1
-rw-r--r--app/views/profiles/audit_log.html.haml1
-rw-r--r--app/views/profiles/emails/index.html.haml3
-rw-r--r--app/views/profiles/keys/_key.html.haml2
-rw-r--r--app/views/profiles/keys/_key_table.html.haml2
-rw-r--r--app/views/profiles/keys/index.html.haml1
-rw-r--r--app/views/profiles/notifications/show.html.haml1
-rw-r--r--app/views/profiles/passwords/edit.html.haml1
-rw-r--r--app/views/profiles/preferences/show.html.haml1
-rw-r--r--app/views/projects/_activity.html.haml5
-rw-r--r--app/views/projects/_builds_settings.html.haml3
-rw-r--r--app/views/projects/_last_commit.html.haml9
-rw-r--r--app/views/projects/_readme.html.haml4
-rw-r--r--app/views/projects/blob/_editor.html.haml6
-rw-r--r--app/views/projects/blob/_text.html.haml25
-rw-r--r--app/views/projects/blob/diff.html.haml8
-rw-r--r--app/views/projects/blob/new.html.haml2
-rw-r--r--app/views/projects/builds/index.html.haml2
-rw-r--r--app/views/projects/builds/show.html.haml7
-rw-r--r--app/views/projects/ci/builds/_build.html.haml13
-rw-r--r--app/views/projects/commit/_builds.html.haml69
-rw-r--r--app/views/projects/commit/_ci_commit.html.haml69
-rw-r--r--app/views/projects/commit/_commit_box.html.haml8
-rw-r--r--app/views/projects/commit/show.html.haml2
-rw-r--r--app/views/projects/commits/_commit.html.haml9
-rw-r--r--app/views/projects/commits/show.html.haml2
-rw-r--r--app/views/projects/deploy_keys/_deploy_key.html.haml45
-rw-r--r--app/views/projects/deploy_keys/_form.html.haml31
-rw-r--r--app/views/projects/deploy_keys/index.html.haml69
-rw-r--r--app/views/projects/diffs/_diffs.html.haml6
-rw-r--r--app/views/projects/diffs/_file.html.haml26
-rw-r--r--app/views/projects/empty.html.haml6
-rw-r--r--app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml13
-rw-r--r--app/views/projects/group_links/index.html.haml79
-rw-r--r--app/views/projects/hooks/_project_hook.html.haml15
-rw-r--r--app/views/projects/hooks/index.html.haml166
-rw-r--r--app/views/projects/issues/_issue.html.haml5
-rw-r--r--app/views/projects/issues/_new_branch.html.haml16
-rw-r--r--app/views/projects/issues/_related_branches.html.haml2
-rw-r--r--app/views/projects/issues/update.js.haml0
-rw-r--r--app/views/projects/merge_requests/_new_submit.html.haml2
-rw-r--r--app/views/projects/merge_requests/_show.html.haml6
-rw-r--r--app/views/projects/merge_requests/invalid.html.haml2
-rw-r--r--app/views/projects/merge_requests/show/_builds.html.haml3
-rw-r--r--app/views/projects/merge_requests/show/_mr_title.html.haml4
-rw-r--r--app/views/projects/merge_requests/update.js.haml0
-rw-r--r--app/views/projects/merge_requests/widget/_merged.html.haml5
-rw-r--r--app/views/projects/merge_requests/widget/_merged_buttons.haml12
-rw-r--r--app/views/projects/milestones/show.html.haml7
-rw-r--r--app/views/projects/notes/_note.html.haml4
-rw-r--r--app/views/projects/project_members/_shared_group_members.html.haml2
-rw-r--r--app/views/projects/protected_branches/_branches_list.html.haml56
-rw-r--r--app/views/projects/protected_branches/index.html.haml58
-rw-r--r--app/views/projects/runners/_shared_runners.html.haml2
-rw-r--r--app/views/projects/show.html.haml12
-rw-r--r--app/views/projects/triggers/_trigger.html.haml8
-rw-r--r--app/views/projects/triggers/index.html.haml119
-rw-r--r--app/views/search/_category.html.haml105
-rw-r--r--app/views/search/_filter.html.haml78
-rw-r--r--app/views/search/_form.html.haml19
-rw-r--r--app/views/search/_results.html.haml15
-rw-r--r--app/views/search/results/_issue.html.haml2
-rw-r--r--app/views/shared/_confirm_modal.html.haml2
-rw-r--r--app/views/shared/_file_highlight.html.haml5
-rw-r--r--app/views/shared/_label_row.html.haml2
-rw-r--r--app/views/shared/_labels_row.html.haml3
-rw-r--r--app/views/shared/_service_settings.html.haml8
-rw-r--r--app/views/shared/_sort_dropdown.html.haml5
-rw-r--r--app/views/shared/issuable/_filter.html.haml8
-rw-r--r--app/views/shared/issuable/_form.html.haml20
-rw-r--r--app/views/shared/issuable/_label_dropdown.html.haml44
-rw-r--r--app/views/shared/issuable/_label_page_create.html.haml17
-rw-r--r--app/views/shared/issuable/_label_page_default.html.haml20
-rw-r--r--app/views/shared/issuable/_nav.html.haml10
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml66
-rw-r--r--app/views/shared/projects/_dropdown.html.haml11
-rw-r--r--app/views/shared/projects/_project.html.haml7
-rw-r--r--app/views/users/show.html.haml6
-rw-r--r--app/views/votes/_votes_block.html.haml22
-rw-r--r--app/workers/repository_check/batch_worker.rb4
236 files changed, 3007 insertions, 1701 deletions
diff --git a/app/assets/javascripts/api.js.coffee b/app/assets/javascripts/api.js.coffee
index f3ed9a66715..dd1bbb37551 100644
--- a/app/assets/javascripts/api.js.coffee
+++ b/app/assets/javascripts/api.js.coffee
@@ -5,6 +5,7 @@
group_projects_path: "/api/:version/groups/:id/projects.json"
projects_path: "/api/:version/projects.json"
labels_path: "/api/:version/projects/:id/labels"
+ license_path: "/api/:version/licenses/:key"
group: (group_id, callback) ->
url = Api.buildUrl(Api.group_path)
@@ -92,6 +93,16 @@
).done (projects) ->
callback(projects)
+ # Return text for a specific license
+ licenseText: (key, data, callback) ->
+ url = Api.buildUrl(Api.license_path).replace(':key', key)
+
+ $.ajax(
+ url: url
+ data: data
+ ).done (license) ->
+ callback(license)
+
buildUrl: (url) ->
url = gon.relative_url_root + url if gon.relative_url_root?
return url.replace(':version', gon.api_version)
diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee
index af4462ece38..bf95e06b4e5 100644
--- a/app/assets/javascripts/awards_handler.coffee
+++ b/app/assets/javascripts/awards_handler.coffee
@@ -1,77 +1,77 @@
class @AwardsHandler
- constructor: (@get_emojis_url, @post_emoji_url, @noteable_type, @noteable_id, @aliases) ->
- $(".js-add-award").on "click", (event) =>
+ constructor: (@getEmojisUrl, @postEmojiUrl, @noteableType, @noteableId, @unicodes) ->
+ $('.js-add-award').on 'click', (event) =>
event.stopPropagation()
event.preventDefault()
@showEmojiMenu()
- $("html").on 'click', (event) ->
- if !$(event.target).closest(".emoji-menu").length
- if $(".emoji-menu").is(":visible")
- $(".emoji-menu").removeClass "is-visible"
+ $('html').on 'click', (event) ->
+ if !$(event.target).closest('.emoji-menu').length
+ if $('.emoji-menu').is(':visible')
+ $('.emoji-menu').removeClass 'is-visible'
- $(".awards")
- .off "click"
- .on "click", ".js-emoji-btn", @handleClick
+ $('.awards')
+ .off 'click'
+ .on 'click', '.js-emoji-btn', @handleClick
@renderFrequentlyUsedBlock()
handleClick: (e) ->
e.preventDefault()
emoji = $(this)
- .find(".icon")
- .data "emoji"
+ .find('.icon')
+ .data 'emoji'
- if emoji is "thumbsup" and awards_handler.didUserClickEmoji $(this), "thumbsdown"
- awards_handler.addAward "thumbsdown"
+ if emoji is 'thumbsup' and awardsHandler.didUserClickEmoji $(this), 'thumbsdown'
+ awardsHandler.addAward 'thumbsdown'
- else if emoji is "thumbsdown" and awards_handler.didUserClickEmoji $(this), "thumbsup"
- awards_handler.addAward "thumbsup"
+ else if emoji is 'thumbsdown' and awardsHandler.didUserClickEmoji $(this), 'thumbsup'
+ awardsHandler.addAward 'thumbsup'
- awards_handler.addAward emoji
+ awardsHandler.addAward emoji
+
+ $(this).trigger 'blur'
didUserClickEmoji: (that, emoji) ->
- if $(that).siblings("button:has([data-emoji=#{emoji}])").attr("data-original-title")
- $(that).siblings("button:has([data-emoji=#{emoji}])").attr("data-original-title").indexOf('me') > -1
+ if $(that).siblings("button:has([data-emoji=#{emoji}])").attr('data-original-title')
+ $(that).siblings("button:has([data-emoji=#{emoji}])").attr('data-original-title').indexOf('me') > -1
showEmojiMenu: ->
- if $(".emoji-menu").length
- if $(".emoji-menu").is ".is-visible"
- $(".emoji-menu").removeClass "is-visible"
- $("#emoji_search").blur()
+ if $('.emoji-menu').length
+ if $('.emoji-menu').is '.is-visible'
+ $('.emoji-menu').removeClass 'is-visible'
+ $('#emoji_search').blur()
else
- $(".emoji-menu").addClass "is-visible"
- $("#emoji_search").focus()
+ $('.emoji-menu').addClass 'is-visible'
+ $('#emoji_search').focus()
else
- $('.js-add-award').addClass "is-loading"
- $.get @get_emojis_url, (response) =>
- $('.js-add-award').removeClass "is-loading"
- $(".js-award-holder").append response
+ $('.js-add-award').addClass 'is-loading'
+ $.get @getEmojisUrl, (response) =>
+ $('.js-add-award').removeClass 'is-loading'
+ $('.js-award-holder').append response
setTimeout =>
- $(".emoji-menu").addClass "is-visible"
- $("#emoji_search").focus()
+ $('.emoji-menu').addClass 'is-visible'
+ $('#emoji_search').focus()
@setupSearch()
, 200
addAward: (emoji) ->
- emoji = @normilizeEmojiName(emoji)
@postEmoji emoji, =>
@addAwardToEmojiBar(emoji)
- $(".emoji-menu").removeClass "is-visible"
+ $('.emoji-menu').removeClass 'is-visible'
addAwardToEmojiBar: (emoji) ->
@addEmojiToFrequentlyUsedList(emoji)
- emoji = @normilizeEmojiName(emoji)
if @exist(emoji)
if @isActive(emoji)
@decrementCounter(emoji)
else
- counter = @findEmojiIcon(emoji).siblings(".js-counter")
+ counter = @findEmojiIcon(emoji).siblings('.js-counter')
counter.text(parseInt(counter.text()) + 1)
- counter.parent().addClass("active")
+ counter.parent().addClass('active')
@addMeToAuthorList(emoji)
else
@createEmoji(emoji)
@@ -80,47 +80,47 @@ class @AwardsHandler
@findEmojiIcon(emoji).length > 0
isActive: (emoji) ->
- @findEmojiIcon(emoji).parent().hasClass("active")
+ @findEmojiIcon(emoji).parent().hasClass('active')
decrementCounter: (emoji) ->
- counter = @findEmojiIcon(emoji).siblings(".js-counter")
+ counter = @findEmojiIcon(emoji).siblings('.js-counter')
emojiIcon = counter.parent()
if parseInt(counter.text()) > 1
counter.text(parseInt(counter.text()) - 1)
- emojiIcon.removeClass("active")
+ emojiIcon.removeClass('active')
@removeMeFromAuthorList(emoji)
- else if emoji == "thumbsup" || emoji == "thumbsdown"
- emojiIcon.tooltip("destroy")
+ else if emoji == 'thumbsup' || emoji == 'thumbsdown'
+ emojiIcon.tooltip('destroy')
counter.text(0)
- emojiIcon.removeClass("active")
+ emojiIcon.removeClass('active')
@removeMeFromAuthorList(emoji)
else
- emojiIcon.tooltip("destroy")
+ emojiIcon.tooltip('destroy')
emojiIcon.remove()
removeMeFromAuthorList: (emoji) ->
- award_block = @findEmojiIcon(emoji).parent()
- authors = award_block
- .attr("data-original-title")
- .split(", ")
- authors.splice(authors.indexOf("me"),1)
- award_block
- .closest(".js-emoji-btn")
- .attr("data-original-title", authors.join(", "))
- @resetTooltip(award_block)
+ awardBlock = @findEmojiIcon(emoji).parent()
+ authors = awardBlock
+ .attr('data-original-title')
+ .split(', ')
+ authors.splice(authors.indexOf('me'),1)
+ awardBlock
+ .closest('.js-emoji-btn')
+ .attr('data-original-title', authors.join(', '))
+ @resetTooltip(awardBlock)
addMeToAuthorList: (emoji) ->
- award_block = @findEmojiIcon(emoji).parent()
- origTitle = award_block.attr("data-original-title").trim()
+ awardBlock = @findEmojiIcon(emoji).parent()
+ origTitle = awardBlock.attr('data-original-title').trim()
authors = []
if origTitle
authors = origTitle.split(', ')
- authors.push("me")
- award_block.attr("data-original-title", authors.join(", "))
- @resetTooltip(award_block)
+ authors.push('me')
+ awardBlock.attr('data-original-title', authors.join(', '))
+ @resetTooltip(awardBlock)
resetTooltip: (award) ->
- award.tooltip("destroy")
+ award.tooltip('destroy')
# "destroy" call is asynchronous and there is no appropriate callback on it, this is why we need to set timeout.
setTimeout (->
@@ -139,28 +139,28 @@ class @AwardsHandler
"</button>"
)
- emoji_node = $(nodes.join("\n"))
- .insertBefore(".js-award-holder")
- .find(".emoji-icon")
- .data("emoji", emoji)
+ $(nodes.join("\n"))
+ .insertBefore('.js-award-holder')
+ .find('.emoji-icon')
+ .data('emoji', emoji)
$('.award-control').tooltip()
resolveNameToCssClass: (emoji) ->
- emoji_icon = $(".emoji-menu-content [data-emoji='#{emoji}']")
+ emojiIcon = $(".emoji-menu-content [data-emoji='#{emoji}']")
- if emoji_icon.length > 0
- unicodeName = emoji_icon.data("unicode-name")
+ if emojiIcon.length > 0
+ unicodeName = emojiIcon.data('unicode-name')
else
# Find by alias
- unicodeName = $(".emoji-menu-content [data-aliases*=':#{emoji}:']").data("unicode-name")
+ unicodeName = $(".emoji-menu-content [data-aliases*=':#{emoji}:']").data('unicode-name')
"emoji-#{unicodeName}"
postEmoji: (emoji, callback) ->
- $.post @post_emoji_url, { note: {
+ $.post @postEmojiUrl, { note: {
note: ":#{emoji}:"
- noteable_type: @noteable_type
- noteable_id: @noteable_id
+ noteable_type: @noteableType
+ noteable_id: @noteableId
}},(data) ->
if data.ok
callback.call()
@@ -173,46 +173,43 @@ class @AwardsHandler
scrollTop: $('.awards').offset().top - 80
}, 200)
- normilizeEmojiName: (emoji) ->
- @aliases[emoji] || emoji
-
addEmojiToFrequentlyUsedList: (emoji) ->
- frequently_used_emojis = @getFrequentlyUsedEmojis()
- frequently_used_emojis.push(emoji)
- $.cookie('frequently_used_emojis', frequently_used_emojis.join(","), { expires: 365 })
+ frequentlyUsedEmojis = @getFrequentlyUsedEmojis()
+ frequentlyUsedEmojis.push(emoji)
+ $.cookie('frequently_used_emojis', frequentlyUsedEmojis.join(','), { expires: 365 })
getFrequentlyUsedEmojis: ->
- frequently_used_emojis = ($.cookie('frequently_used_emojis') || "").split(",")
- _.compact(_.uniq(frequently_used_emojis))
+ frequentlyUsedEmojis = ($.cookie('frequently_used_emojis') || '').split(',')
+ _.compact(_.uniq(frequentlyUsedEmojis))
renderFrequentlyUsedBlock: ->
if $.cookie('frequently_used_emojis')
- frequently_used_emojis = @getFrequentlyUsedEmojis()
+ frequentlyUsedEmojis = @getFrequentlyUsedEmojis()
- ul = $("<ul>")
+ ul = $('<ul>')
- for emoji in frequently_used_emojis
+ for emoji in frequentlyUsedEmojis
do (emoji) ->
- $(".emoji-menu-content [data-emoji='#{emoji}']").closest("li").clone().appendTo(ul)
+ $(".emoji-menu-content [data-emoji='#{emoji}']").closest('li').clone().appendTo(ul)
- $("input.emoji-search").after(ul).after($("<h5>").text("Frequently used"))
+ $('input.emoji-search').after(ul).after($('<h5>').text('Frequently used'))
setupSearch: ->
- $("input.emoji-search").keyup (ev) =>
+ $('input.emoji-search').keyup (ev) =>
term = $(ev.target).val()
# Clean previous search results
- $("ul.emoji-menu-search, h5.emoji-search").remove()
+ $('ul.emoji-menu-search, h5.emoji-search').remove()
if term
# Generate a search result block
- h5 = $("<h5>").text("Search results").addClass("emoji-search")
- found_emojis = @searchEmojis(term).show()
- ul = $("<ul>").addClass("emoji-menu-list emoji-menu-search").append(found_emojis)
- $(".emoji-menu-content ul, .emoji-menu-content h5").hide()
- $(".emoji-menu-content").append(h5).append(ul)
+ h5 = $('<h5>').text('Search results').addClass('emoji-search')
+ foundEmojis = @searchEmojis(term).show()
+ ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(foundEmojis)
+ $('.emoji-menu-content ul, .emoji-menu-content h5').hide()
+ $('.emoji-menu-content').append(h5).append(ul)
else
- $(".emoji-menu-content").children().show()
+ $('.emoji-menu-content').children().show()
searchEmojis: (term)->
$(".emoji-menu-content [data-emoji*='#{term}']").closest("li").clone()
diff --git a/app/assets/javascripts/blob/blob_license_selector.js.coffee b/app/assets/javascripts/blob/blob_license_selector.js.coffee
new file mode 100644
index 00000000000..e17eaa75dc1
--- /dev/null
+++ b/app/assets/javascripts/blob/blob_license_selector.js.coffee
@@ -0,0 +1,30 @@
+class @BlobLicenseSelector
+ licenseRegex: /^(.+\/)?(licen[sc]e|copying)($|\.)/i
+
+ constructor: (editor) ->
+ @$licenseSelector = $('.js-license-selector')
+ $fileNameInput = $('#file_name')
+
+ initialFileNameValue = if $fileNameInput.length
+ $fileNameInput.val()
+ else if $('.editor-file-name').length
+ $('.editor-file-name').text().trim()
+
+ @toggleLicenseSelector(initialFileNameValue)
+
+ if $fileNameInput
+ $fileNameInput.on 'keyup blur', (e) =>
+ @toggleLicenseSelector($(e.target).val())
+
+ $('select.license-select').on 'change', (e) ->
+ data =
+ project: $(this).data('project')
+ fullname: $(this).data('fullname')
+ Api.licenseText $(this).val(), data, (license) ->
+ editor.setValue(license.content, -1)
+
+ toggleLicenseSelector: (fileName) =>
+ if @licenseRegex.test(fileName)
+ @$licenseSelector.show()
+ else
+ @$licenseSelector.hide()
diff --git a/app/assets/javascripts/blob/edit_blob.js.coffee b/app/assets/javascripts/blob/edit_blob.js.coffee
index 390e41ed8d4..eea9aa972ee 100644
--- a/app/assets/javascripts/blob/edit_blob.js.coffee
+++ b/app/assets/javascripts/blob/edit_blob.js.coffee
@@ -1,44 +1,39 @@
class @EditBlob
- constructor: (assets_path, mode)->
- ace.config.set "modePath", assets_path + '/ace'
+ constructor: (assets_path, ace_mode = null) ->
+ ace.config.set "modePath", "#{assets_path}/ace"
ace.config.loadModule "ace/ext/searchbox"
- if mode
- ace_mode = mode
- editor = ace.edit("editor")
- editor.focus()
- @editor = editor
-
- if ace_mode
- editor.getSession().setMode "ace/mode/" + ace_mode
+ @editor = ace.edit("editor")
+ @editor.focus()
+ @editor.getSession().setMode "ace/mode/#{ace_mode}" if ace_mode
# Before a form submission, move the content from the Ace editor into the
# submitted textarea
- $('form').submit ->
- $("#file-content").val(editor.getValue())
+ $('form').submit =>
+ $("#file-content").val(@editor.getValue())
+
+ @initModePanesAndLinks()
+ new BlobLicenseSelector(@editor)
- editModePanes = $(".js-edit-mode-pane")
- editModeLinks = $(".js-edit-mode a")
- editModeLinks.click (event) ->
- event.preventDefault()
- currentLink = $(this)
- paneId = currentLink.attr("href")
- currentPane = editModePanes.filter(paneId)
- editModeLinks.parent().removeClass "active hover"
- currentLink.parent().addClass "active hover"
- editModePanes.hide()
- if paneId is "#preview"
- currentPane.fadeIn 200
- $.post currentLink.data("preview-url"),
- content: editor.getValue()
- , (response) ->
- currentPane.empty().append response
- currentPane.syntaxHighlight()
- return
+ initModePanesAndLinks: ->
+ @$editModePanes = $(".js-edit-mode-pane")
+ @$editModeLinks = $(".js-edit-mode a")
+ @$editModeLinks.click @editModeLinkClickHandler
- else
- currentPane.fadeIn 200
- editor.focus()
- return
+ editModeLinkClickHandler: (event) =>
+ event.preventDefault()
+ currentLink = $(event.target)
+ paneId = currentLink.attr("href")
+ currentPane = @$editModePanes.filter(paneId)
+ @$editModeLinks.parent().removeClass "active hover"
+ currentLink.parent().addClass "active hover"
+ @$editModePanes.hide()
+ currentPane.fadeIn 200
+ if paneId is "#preview"
+ $.post currentLink.data("preview-url"),
+ content: @editor.getValue()
+ , (response) ->
+ currentPane.empty().append response
+ currentPane.syntaxHighlight()
- editor: ->
- return @editor
+ else
+ @editor.focus()
diff --git a/app/assets/javascripts/blob/new_blob.js.coffee b/app/assets/javascripts/blob/new_blob.js.coffee
deleted file mode 100644
index 68c5e5195e3..00000000000
--- a/app/assets/javascripts/blob/new_blob.js.coffee
+++ /dev/null
@@ -1,20 +0,0 @@
-class @NewBlob
- constructor: (assets_path, mode)->
- ace.config.set "modePath", assets_path + '/ace'
- ace.config.loadModule "ace/ext/searchbox"
- if mode
- ace_mode = mode
- editor = ace.edit("editor")
- editor.focus()
- @editor = editor
-
- if ace_mode
- editor.getSession().setMode "ace/mode/" + ace_mode
-
- # Before a form submission, move the content from the Ace editor into the
- # submitted textarea
- $('form').submit ->
- $("#file-content").val(editor.getValue())
-
- editor: ->
- return @editor
diff --git a/app/assets/javascripts/commits.js.coffee b/app/assets/javascripts/commits.js.coffee
index ffd3627b1b0..0acb4c1955e 100644
--- a/app/assets/javascripts/commits.js.coffee
+++ b/app/assets/javascripts/commits.js.coffee
@@ -1,7 +1,7 @@
class @CommitsList
@timer = null
- @init: (ref, limit) ->
+ @init: (limit) ->
$("body").on "click", ".day-commits-table li.commit", (event) ->
if event.target.nodeName != "A"
location.href = $(this).attr("url")
diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
index 0b9110d35fa..f91aa3c5ad7 100644
--- a/app/assets/javascripts/dispatcher.js.coffee
+++ b/app/assets/javascripts/dispatcher.js.coffee
@@ -17,6 +17,7 @@ class Dispatcher
switch page
when 'projects:issues:index'
Issues.init()
+ Issuable.init()
shortcut_handler = new ShortcutsNavigation()
when 'projects:issues:show'
new Issue()
@@ -57,7 +58,7 @@ class Dispatcher
new ZenMode()
when 'projects:merge_requests:index'
shortcut_handler = new ShortcutsNavigation()
- MergeRequests.init()
+ Issuable.init()
when 'dashboard:activity'
new Activities()
when 'dashboard:projects:starred'
@@ -107,6 +108,8 @@ class Dispatcher
new BuildArtifacts()
when 'projects:group_links:index'
new GroupsSelect()
+ when 'search:show'
+ new Search()
switch path.first()
when 'admin'
diff --git a/app/assets/javascripts/due_date_select.js.coffee b/app/assets/javascripts/due_date_select.js.coffee
new file mode 100644
index 00000000000..a4304786cbb
--- /dev/null
+++ b/app/assets/javascripts/due_date_select.js.coffee
@@ -0,0 +1,64 @@
+class @DueDateSelect
+ constructor: ->
+ $loading = $('.js-issuable-update .due_date')
+ .find('.block-loading')
+ .hide()
+
+ $('.js-due-date-select').each (i, dropdown) ->
+ $dropdown = $(dropdown)
+ $dropdownParent = $dropdown.closest('.dropdown')
+ $datePicker = $dropdownParent.find('.js-due-date-calendar')
+ $block = $dropdown.closest('.block')
+ $selectbox = $dropdown.closest('.selectbox')
+ $value = $block.find('.value')
+ $sidebarValue = $('.js-due-date-sidebar-value', $block)
+
+ fieldName = $dropdown.data('field-name')
+ abilityName = $dropdown.data('ability-name')
+ issueUpdateURL = $dropdown.data('issue-update')
+
+ $dropdown.glDropdown(
+ hidden: ->
+ $selectbox.hide()
+ $value.removeAttr('style')
+ )
+
+ addDueDate = ->
+ # Create the post date
+ value = $("input[name='#{fieldName}']").val()
+ date = new Date value.replace(new RegExp('-', 'g'), ',')
+ mediumDate = $.datepicker.formatDate 'M d, yy', date
+
+ data = {}
+ data[abilityName] = {}
+ data[abilityName].due_date = value
+
+ $.ajax(
+ type: 'PUT'
+ url: issueUpdateURL
+ data: data
+ beforeSend: ->
+ $loading.fadeIn()
+ $dropdown.trigger('loading.gl.dropdown')
+ $selectbox.hide()
+ $value.removeAttr('style')
+
+ $value.html(mediumDate)
+ $sidebarValue.html(mediumDate)
+ ).done (data) ->
+ $dropdown.trigger('loaded.gl.dropdown')
+ $dropdown.dropdown('toggle')
+ $loading.fadeOut()
+
+ $datePicker.datepicker(
+ dateFormat: 'yy-mm-dd',
+ defaultDate: $("input[name='#{fieldName}']").val()
+ altField: "input[name='#{fieldName}']"
+ onSelect: ->
+ addDueDate()
+ )
+
+ $(document)
+ .off 'click', '.ui-datepicker-header a'
+ .on 'click', '.ui-datepicker-header a', (e) ->
+ e.stopImmediatePropagation()
diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee
index 641141072f4..1d1bfeb2e77 100644
--- a/app/assets/javascripts/gl_dropdown.js.coffee
+++ b/app/assets/javascripts/gl_dropdown.js.coffee
@@ -184,6 +184,9 @@ class GitLabDropdown
@dropdown.on "shown.bs.dropdown", @opened
@dropdown.on "hidden.bs.dropdown", @hidden
@dropdown.on "click", ".dropdown-menu, .dropdown-menu-close", @shouldPropagate
+ @dropdown.on 'keyup', (e) =>
+ if e.which is 27 # Escape key
+ $('.dropdown-menu-close', @dropdown).trigger 'click'
if @dropdown.find(".dropdown-toggle-page").length
@dropdown.find(".dropdown-toggle-page, .dropdown-menu-back").on "click", (e) =>
@@ -221,6 +224,9 @@ class GitLabDropdown
menu.toggleClass PAGE_TWO_CLASS
+ # Focus first visible input on active page
+ @dropdown.find('[class^="dropdown-page-"]:visible :text:visible:first').focus()
+
parseData: (data) ->
@renderedData = data
@@ -240,7 +246,8 @@ class GitLabDropdown
shouldPropagate: (e) =>
if @options.multiSelect
$target = $(e.target)
- if not $target.hasClass('dropdown-menu-close') and not $target.hasClass('dropdown-menu-close-icon')
+
+ if not $target.hasClass('dropdown-menu-close') and not $target.hasClass('dropdown-menu-close-icon') and not $target.data('is-link')
e.stopPropagation()
return false
else
@@ -375,7 +382,6 @@ class GitLabDropdown
selectedObject = @renderedData[selectedIndex]
value = if @options.id then @options.id(selectedObject, el) else selectedObject.id
field = @dropdown.parent().find("input[name='#{fieldName}'][value='#{value}']")
-
if el.hasClass(ACTIVE_CLASS)
el.removeClass(ACTIVE_CLASS)
field.remove()
@@ -386,13 +392,13 @@ class GitLabDropdown
else
selectedObject
else
- if !value?
- field.remove()
-
- if not @options.multiSelect
+ if not @options.multiSelect or el.hasClass('dropdown-clear-active')
@dropdown.find(".#{ACTIVE_CLASS}").removeClass ACTIVE_CLASS
@dropdown.parent().find("input[name='#{fieldName}']").remove()
+ if !value?
+ field.remove()
+
# Toggle active class for the tick mark
el.addClass ACTIVE_CLASS
diff --git a/app/assets/javascripts/issuable.js.coffee b/app/assets/javascripts/issuable.js.coffee
new file mode 100644
index 00000000000..afffed63ac5
--- /dev/null
+++ b/app/assets/javascripts/issuable.js.coffee
@@ -0,0 +1,84 @@
+@Issuable =
+ init: ->
+ Issuable.initTemplates()
+ Issuable.initSearch()
+
+ initTemplates: ->
+ Issuable.labelRow = _.template(
+ '<% _.each(labels, function(label){ %>
+ <span class="label-row">
+ <a href="#"><span class="label color-label has-tooltip" style="background-color: <%= label.color %>; color: <%= label.text_color %>" title="<%= _.escape(label.description) %>" data-container="body"><%= _.escape(label.title) %></span></a>
+ </span>
+ <% }); %>'
+ )
+
+ initSearch: ->
+ @timer = null
+ $('#issue_search')
+ .off 'keyup'
+ .on 'keyup', ->
+ clearTimeout(@timer)
+ @timer = setTimeout( ->
+ Issuable.filterResults $('#issue_search_form')
+ , 500)
+
+ toggleLabelFilters: ->
+ $filteredLabels = $('.filtered-labels')
+ if $filteredLabels.find('.label-row').length > 0
+ $filteredLabels.removeClass('hidden')
+ else
+ $filteredLabels.addClass('hidden')
+
+ filterResults: (form) =>
+ formData = form.serialize()
+
+ $('.issues-holder, .merge-requests-holder').css('opacity', '0.5')
+ formAction = form.attr('action')
+ issuesUrl = formAction
+ issuesUrl += ("#{if formAction.indexOf('?') < 0 then '?' else '&'}")
+ issuesUrl += formData
+ $.ajax
+ type: 'GET'
+ url: formAction
+ data: formData
+ complete: ->
+ $('.issues-holder, .merge-requests-holder').css('opacity', '1.0')
+ success: (data) ->
+ $('.issues-holder, .merge-requests-holder').html(data.html)
+ # Change url so if user reload a page - search results are saved
+ history.replaceState {page: issuesUrl}, document.title, issuesUrl
+ Issuable.reload()
+ Issuable.updateStateFilters()
+ $filteredLabels = $('.filtered-labels')
+
+ if typeof Issuable.labelRow is 'function'
+ $filteredLabels.html(Issuable.labelRow(data))
+
+ Issuable.toggleLabelFilters()
+
+ dataType: "json"
+
+ reload: ->
+ if Issues.created
+ Issues.initChecks()
+
+ $('#filter_issue_search').val($('#issue_search').val())
+
+ updateStateFilters: ->
+ stateFilters = $('.issues-state-filters')
+ newParams = {}
+ paramKeys = ['author_id', 'milestone_title', 'assignee_id', 'issue_search']
+
+ for paramKey in paramKeys
+ newParams[paramKey] = gl.utils.getParameterValues(paramKey)[0] or ''
+
+ if stateFilters.length
+ stateFilters.find('a').each ->
+ initialUrl = gl.utils.removeParamQueryString($(this).attr('href'), 'label_name[]')
+ labelNameValues = gl.utils.getParameterValues('label_name[]')
+ if labelNameValues
+ labelNameQueryString = ("label_name[]=#{value}" for value in labelNameValues).join('&')
+ newUrl = "#{gl.utils.mergeUrlParams(newParams, initialUrl)}&#{labelNameQueryString}"
+ else
+ newUrl = gl.utils.mergeUrlParams(newParams, initialUrl)
+ $(this).attr 'href', newUrl
diff --git a/app/assets/javascripts/issuable_context.js.coffee b/app/assets/javascripts/issuable_context.js.coffee
index 2f19513a831..3c491ebfc4c 100644
--- a/app/assets/javascripts/issuable_context.js.coffee
+++ b/app/assets/javascripts/issuable_context.js.coffee
@@ -9,21 +9,29 @@ class @IssuableContext
$(".issuable-sidebar .inline-update").on "change", ".js-assignee", ->
$(this).submit()
- $(document).off("click", ".edit-link").on "click",".edit-link", (e) ->
- $block = $(@).parents('.block')
- $selectbox = $block.find('.selectbox')
- if $selectbox.is(':visible')
- $selectbox.hide()
- $block.find('.value').show()
- else
- $selectbox.show()
- $block.find('.value').hide()
-
- if $selectbox.is(':visible')
- setTimeout (->
- $block.find('.dropdown-menu-toggle').trigger 'click'
- ), 0
-
+ $(document)
+ .off 'click', '.issuable-sidebar .dropdown-content a'
+ .on 'click', '.issuable-sidebar .dropdown-content a', (e) ->
+ e.preventDefault()
+
+ $(document)
+ .off 'click', '.edit-link'
+ .on 'click', '.edit-link', (e) ->
+ e.preventDefault()
+
+ $block = $(@).parents('.block')
+ $selectbox = $block.find('.selectbox')
+ if $selectbox.is(':visible')
+ $selectbox.hide()
+ $block.find('.value').show()
+ else
+ $selectbox.show()
+ $block.find('.value').hide()
+
+ if $selectbox.is(':visible')
+ setTimeout ->
+ $block.find('.dropdown-menu-toggle').trigger 'click'
+ , 0
$(".right-sidebar").niceScroll()
diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee
index c7d74a12f99..157361404e0 100644
--- a/app/assets/javascripts/issue.js.coffee
+++ b/app/assets/javascripts/issue.js.coffee
@@ -12,6 +12,7 @@ class @Issue
@initMergeRequests()
@initRelatedBranches()
+ @initCanCreateBranch()
initTaskList: ->
$('.detail-page-description .js-task-list-container').taskList('enable')
@@ -92,3 +93,25 @@ class @Issue
.success (data) ->
if 'html' of data
$container.html(data.html)
+
+ initCanCreateBranch: ->
+ $container = $('div#new-branch')
+
+ # If the user doesn't have the required permissions the container isn't
+ # rendered at all.
+ return unless $container
+
+ $.getJSON($container.data('path'))
+ .error ->
+ $container.find('.checking').hide()
+ $container.find('.unavailable').show()
+
+ new Flash('Failed to check if a new branch can be created.', 'alert')
+ .success (data) ->
+ if data.can_create_branch
+ $container.find('.checking').hide()
+ $container.find('.available').show()
+ $container.find('a').attr('disabled', false)
+ else
+ $container.find('.checking').hide()
+ $container.find('.unavailable').show()
diff --git a/app/assets/javascripts/issues.js.coffee b/app/assets/javascripts/issues.js.coffee
index 0d9f2094c2a..3330e6c68ad 100644
--- a/app/assets/javascripts/issues.js.coffee
+++ b/app/assets/javascripts/issues.js.coffee
@@ -1,6 +1,6 @@
@Issues =
init: ->
- Issues.initSearch()
+ Issues.created = true
Issues.initChecks()
$("body").on "ajax:success", ".close_issue, .reopen_issue", ->
@@ -15,10 +15,6 @@
else
$(this).html totalIssues - 1
- reload: ->
- Issues.initChecks()
- $('#filter_issue_search').val($('#issue_search').val())
-
initChecks: ->
$(".check_all_issues").click ->
$(".selected_issue").prop("checked", @checked)
@@ -26,51 +22,6 @@
$(".selected_issue").bind "change", Issues.checkChanged
- # Update state filters if present in page
- updateStateFilters: ->
- stateFilters = $('.issues-state-filters')
- newParams = {}
- paramKeys = ['author_id', 'label_name', 'milestone_title', 'assignee_id', 'issue_search']
-
- for paramKey in paramKeys
- newParams[paramKey] = gl.utils.getUrlParameter(paramKey) or ''
-
- if stateFilters.length
- stateFilters.find('a').each ->
- initialUrl = $(this).attr 'href'
- $(this).attr 'href', gl.utils.mergeUrlParams(newParams, initialUrl)
-
- # Make sure we trigger ajax request only after user stop typing
- initSearch: ->
- @timer = null
- $("#issue_search").keyup ->
- clearTimeout(@timer)
- @timer = setTimeout( ->
- Issues.filterResults $("#issue_search_form")
- , 500)
-
- 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: formAction
- data: formData
- complete: ->
- $('.issues-holder, .merge-requests-holder').css("opacity", '1.0')
- success: (data) ->
- $('.issues-holder, .merge-requests-holder').html(data.html)
- # Change url so if user reload a page - search results are saved
- history.replaceState {page: issuesUrl}, document.title, issuesUrl
- Issues.reload()
- Issues.updateStateFilters()
- dataType: "json"
-
checkChanged: ->
checked_issues = $(".selected_issue:checked")
if checked_issues.length > 0
diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee
index bc80980acb7..995fd768603 100644
--- a/app/assets/javascripts/labels_select.js.coffee
+++ b/app/assets/javascripts/labels_select.js.coffee
@@ -6,7 +6,7 @@ class @LabelsSelect
labelUrl = $dropdown.data('labels')
issueUpdateURL = $dropdown.data('issueUpdate')
selectedLabel = $dropdown.data('selected')
- if selectedLabel?
+ if selectedLabel? and not $dropdown.hasClass 'js-multiselect'
selectedLabel = selectedLabel.split(',')
newLabelField = $('#new_label_name')
newColorField = $('#new_label_color')
@@ -16,33 +16,32 @@ class @LabelsSelect
abilityName = $dropdown.data('ability-name')
$selectbox = $dropdown.closest('.selectbox')
$block = $selectbox.closest('.block')
+ $form = $dropdown.closest('form')
$sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span')
$value = $block.find('.value')
- $loading = $block.find('.block-loading').fadeOut()
-
- if newLabelField.length
- $newLabelCreateButton = $('.js-new-label-btn')
- $colorPreview = $('.js-dropdown-label-color-preview')
- $newLabelError = $dropdown.parent().find('.js-label-error')
- $newLabelError.hide()
+ $newLabelError = $('.js-label-error')
+ $colorPreview = $('.js-dropdown-label-color-preview')
+ $newLabelCreateButton = $('.js-new-label-btn')
- # Suggested colors in the dropdown to chose from pre-chosen colors
- $('.suggest-colors-dropdown a').on 'click', (e) ->
+ $newLabelError.hide()
+ $loading = $block.find('.block-loading').fadeOut()
issueURLSplit = issueUpdateURL.split('/') if issueUpdateURL?
if issueUpdateURL
labelHTMLTemplate = _.template(
'<% _.each(labels, function(label){ %>
- <a href="<%= ["",issueURLSplit[1], issueURLSplit[2],""].join("/") %>issues?label_name=<%= label.title %>">
- <span class="label has-tooltip color-label" title="<%= label.description %>" style="background-color: <%= label.color %>;">
- <%= label.title %>
+ <a href="<%= ["",issueURLSplit[1], issueURLSplit[2],""].join("/") %>issues?label_name[]=<%= _.escape(label.title) %>">
+ <span class="label has-tooltip color-label" title="<%= _.escape(label.description) %>" style="background-color: <%= label.color %>; color: <%= label.text_color %>;">
+ <%= _.escape(label.title) %>
</span>
</a>
<% }); %>'
- );
+ )
labelNoneHTMLTemplate = _.template('<div class="light">None</div>')
- if newLabelField.length and $dropdown.hasClass 'js-extra-options'
+ if newLabelField.length
+
+ # Suggested colors in the dropdown to chose from pre-chosen colors
$('.suggest-colors-dropdown a').on "click", (e) ->
e.preventDefault()
e.stopPropagation()
@@ -81,26 +80,25 @@ class @LabelsSelect
enableLabelCreateButton = ->
if newLabelField.val() isnt '' and newColorField.val() isnt ''
$newLabelError.hide()
- $('.js-new-label-btn').disable()
-
- # Create new label with API
- Api.newLabel projectId, {
- name: newLabelField.val()
- color: newColorField.val()
- }, (label) ->
- $('.js-new-label-btn').enable()
-
- if label.message?
- $newLabelError
- .text label.message
- .show()
- else
- $('.dropdown-menu-back', $dropdown.parent()).trigger 'click'
-
$newLabelCreateButton.enable()
else
$newLabelCreateButton.disable()
+ saveLabel = ->
+ # Create new label with API
+ Api.newLabel projectId, {
+ name: newLabelField.val()
+ color: newColorField.val()
+ }, (label) ->
+ $newLabelCreateButton.enable()
+
+ if label.message?
+ $newLabelError
+ .text label.message
+ .show()
+ else
+ $('.dropdown-menu-back', $dropdown.parent()).trigger 'click'
+
newLabelField.on 'keyup change', enableLabelCreateButton
newColorField.on 'keyup change', enableLabelCreateButton
@@ -111,24 +109,7 @@ class @LabelsSelect
.on 'click', (e) ->
e.preventDefault()
e.stopPropagation()
-
- if newLabelField.val() isnt '' and newColorField.val() isnt ''
- $newLabelError.hide()
- $('.js-new-label-btn').disable()
-
- # Create new label with API
- Api.newLabel projectId, {
- name: newLabelField.val()
- color: newColorField.val()
- }, (label) ->
- $('.js-new-label-btn').enable()
-
- if label.message?
- $newLabelError
- .text label.message
- .show()
- else
- $('.dropdown-menu-back', $dropdown.parent()).trigger 'click'
+ saveLabel()
saveLabelData = ->
selected = $dropdown
@@ -171,7 +152,7 @@ class @LabelsSelect
.find('a')
.each((i) ->
setTimeout(=>
- glAnimate($(@), 'pulse')
+ gl.animate.animate($(@), 'pulse')
,200 * i
)
)
@@ -182,6 +163,21 @@ class @LabelsSelect
$.ajax(
url: labelUrl
).done (data) ->
+ data = _.chain data
+ .groupBy (label) ->
+ label.title
+ .map (label) ->
+ color = _.map label, (dup) ->
+ dup.color
+
+ return {
+ id: label[0].id
+ title: label[0].title
+ color: color
+ duplicate: color.length > 1
+ }
+ .value()
+
if $dropdown.hasClass 'js-extra-options'
if showNo
data.unshift(
@@ -197,21 +193,47 @@ class @LabelsSelect
if data.length > 2
data.splice 2, 0, 'divider'
+
callback data
renderRow: (label) ->
- selectedClass = ''
- if $selectbox.find("input[type='hidden']\
- [name='#{$dropdown.data('field-name')}']\
- [value='#{label.id}']").length
- selectedClass = 'is-active'
+ removesAll = label.id is 0 or not label.id?
+
+ selectedClass = []
+ if $form.find("input[type='hidden']\
+ [name='#{$dropdown.data('fieldName')}']\
+ [value='#{this.id(label)}']").length
+ selectedClass.push 'is-active'
+
+ if $dropdown.hasClass('js-multiselect') and removesAll
+ selectedClass.push 'dropdown-clear-active'
+
+ if label.duplicate
+ spacing = 100 / label.color.length
+
+ # Reduce the colors to 4
+ label.color = label.color.filter (color, i) ->
+ i < 4
+
+ color = _.map(label.color, (color, i) ->
+ percentFirst = Math.floor(spacing * i)
+ percentSecond = Math.floor(spacing * (i + 1))
+ "#{color} #{percentFirst}%,#{color} #{percentSecond}% "
+ ).join(',')
+ color = "linear-gradient(#{color})"
+ else
+ if label.color?
+ color = label.color[0]
- color = if label.color? then "<span class='dropdown-label-box' style='background-color: #{label.color}'></span>" else ""
+ if color
+ colorEl = "<span class='dropdown-label-box' style='background: #{color}'></span>"
+ else
+ colorEl = ''
"<li>
- <a href='#' class='#{selectedClass}'>
- #{color}
- #{label.title}
+ <a href='#' class='#{selectedClass.join(' ')}'>
+ #{colorEl}
+ #{_.escape(label.title)}
</a>
</li>"
filterable: true
@@ -219,37 +241,56 @@ class @LabelsSelect
fields: ['title']
selectable: true
- toggleLabel: (selected) ->
+ toggleLabel: (selected, el) ->
+ selected_labels = $('.js-label-select').siblings('.dropdown-menu-labels').find('.is-active')
+
if selected and selected.title?
- selected.title
+ if selected_labels.length > 1
+ "#{selected.title} +#{selected_labels.length - 1} more"
+ else
+ selected.title
+ else if not selected and selected_labels.length isnt 0
+ if selected_labels.length > 1
+ "#{$(selected_labels[0]).text()} +#{selected_labels.length - 1} more"
+ else if selected_labels.length is 1
+ $(selected_labels).text()
else
defaultLabel
fieldName: $dropdown.data('field-name')
id: (label) ->
- if label.isAny?
- ''
- else if $dropdown.hasClass "js-filter-submit"
- label.title
+ if $dropdown.hasClass("js-filter-submit") and not label.isAny?
+ _.escape label.title
else
label.id
hidden: ->
+ page = $('body').data 'page'
+ isIssueIndex = page is 'projects:issues:index'
+ isMRIndex = page is 'projects:merge_requests:index'
+
$selectbox.hide()
# display:block overrides the hide-collapse rule
$value.removeAttr('style')
if $dropdown.hasClass 'js-multiselect'
- saveLabelData()
+ if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
+ selectedLabels = $dropdown
+ .closest('form')
+ .find("input:hidden[name='#{$dropdown.data('fieldName')}']")
+ Issuable.filterResults $dropdown.closest('form')
+ else if $dropdown.hasClass('js-filter-submit')
+ $dropdown.closest('form').submit()
+ else
+ saveLabelData()
multiSelect: $dropdown.hasClass 'js-multiselect'
clicked: (label) ->
page = $('body').data 'page'
isIssueIndex = page is 'projects:issues:index'
- isMRIndex = page is page is 'projects:merge_requests:index'
-
+ isMRIndex = page is 'projects:merge_requests:index'
if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
- selectedLabel = label.title
-
- Issues.filterResults $dropdown.closest('form')
+ if not $dropdown.hasClass 'js-multiselect'
+ selectedLabel = label.title
+ Issuable.filterResults $dropdown.closest('form')
else if $dropdown.hasClass 'js-filter-submit'
$dropdown.closest('form').submit()
else
diff --git a/app/assets/javascripts/lib/animate.js.coffee b/app/assets/javascripts/lib/animate.js.coffee
index 8f892b5a2b9..ec3b44d6126 100644
--- a/app/assets/javascripts/lib/animate.js.coffee
+++ b/app/assets/javascripts/lib/animate.js.coffee
@@ -1,13 +1,39 @@
((w) ->
+ if not w.gl? then w.gl = {}
+ if not gl.animate? then gl.animate = {}
- w.glAnimate = ($el, animation, done) ->
+ gl.animate.animate = ($el, animation, options, done) ->
+ if options?.cssStart?
+ $el.css(options.cssStart)
$el
- .removeClass()
+ .removeClass(animation + ' animated')
.addClass(animation + ' animated')
.one 'webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', ->
- $(this).removeClass()
+ $(this).removeClass(animation + ' animated')
+ if done?
+ done()
+ if options?.cssEnd?
+ $el.css(options.cssEnd)
return
return
- return
+ gl.animate.animateEach = ($els, animation, time, options, done) ->
+ dfd = $.Deferred()
+ if not $els.length
+ dfd.resolve()
+ $els.each((i) ->
+ setTimeout(=>
+ $this = $(@)
+ gl.animate.animate($this, animation, options, =>
+ if i is $els.length - 1
+ dfd.resolve()
+ if done?
+ done()
+ )
+ ,time * i
+ )
+ return
+ )
+ return dfd.promise()
+ return
) window \ No newline at end of file
diff --git a/app/assets/javascripts/lib/url_utility.js.coffee b/app/assets/javascripts/lib/url_utility.js.coffee
index abd556e0b4e..6a00932c028 100644
--- a/app/assets/javascripts/lib/url_utility.js.coffee
+++ b/app/assets/javascripts/lib/url_utility.js.coffee
@@ -3,16 +3,20 @@
w.gl ?= {}
w.gl.utils ?= {}
- w.gl.utils.getUrlParameter = (sParam) ->
+ # Returns an array containing the value(s) of the
+ # of the key passed as an argument
+ w.gl.utils.getParameterValues = (sParam) ->
sPageURL = decodeURIComponent(window.location.search.substring(1))
sURLVariables = sPageURL.split('&')
sParameterName = undefined
+ values = []
i = 0
while i < sURLVariables.length
sParameterName = sURLVariables[i].split('=')
if sParameterName[0] is sParam
- return if sParameterName[1] is undefined then true else sParameterName[1]
+ values.push(sParameterName[1])
i++
+ values
# #
# @param {Object} params - url keys and value to merge
@@ -28,4 +32,12 @@
newUrl = "#{newUrl}#{(if newUrl.indexOf('?') > 0 then '&' else '?')}#{paramName}=#{paramValue}"
newUrl
+ # removes parameter query string from url. returns the modified url
+ w.gl.utils.removeParamQueryString = (url, param) ->
+ url = decodeURIComponent(url)
+ urlVariables = url.split('&')
+ (
+ variables for variables in urlVariables when variables.indexOf(param) is -1
+ ).join('&')
+
) window
diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee
index e8613cab72b..372732d0aac 100644
--- a/app/assets/javascripts/merge_request_tabs.js.coffee
+++ b/app/assets/javascripts/merge_request_tabs.js.coffee
@@ -87,8 +87,8 @@ class @MergeRequestTabs
if window.location.hash
navBarHeight = $('.navbar-gitlab').outerHeight()
- $el = $("#{container} #{window.location.hash}")
- $.scrollTo("#{container} #{window.location.hash}", offset: -navBarHeight) if $el.length
+ $el = $("#{container} #{window.location.hash}:not(.match)")
+ $.scrollTo("#{container} #{window.location.hash}:not(.match)", offset: -navBarHeight) if $el.length
# Activate a tab based on the current action
activateTab: (action) ->
@@ -176,12 +176,12 @@ class @MergeRequestTabs
if locationHash isnt ''
hashClassString = ".#{locationHash.replace('#', '')}"
- $diffLine = $(locationHash)
+ $diffLine = $("#{locationHash}:not(.match)", $('#diffs'))
- if $diffLine.is ':not(tr)'
- $diffLine = $("td#{locationHash}, td#{hashClassString}")
+ if not $diffLine.is 'tr'
+ $diffLine = $('#diffs').find("td#{locationHash}, td#{hashClassString}")
else
- $diffLine = $('td', $diffLine)
+ $diffLine = $diffLine.find('td')
if $diffLine.length
$diffLine.addClass 'hll'
diff --git a/app/assets/javascripts/merge_requests.js.coffee b/app/assets/javascripts/merge_requests.js.coffee
deleted file mode 100644
index b3c73ffce5d..00000000000
--- a/app/assets/javascripts/merge_requests.js.coffee
+++ /dev/null
@@ -1,35 +0,0 @@
-#
-# * Filter merge requests
-#
-@MergeRequests =
- init: ->
- MergeRequests.initSearch()
-
- # Make sure we trigger ajax request only after user stop typing
- initSearch: ->
- @timer = null
- $("#issue_search").keyup ->
- clearTimeout(@timer)
- @timer = setTimeout(MergeRequests.filterResults, 500)
-
- filterResults: =>
- form = $("#issue_search_form")
- search = $("#issue_search").val()
- $('.merge-requests-holder').css("opacity", '0.5')
- issues_url = form.attr('action') + '?' + form.serialize()
-
- $.ajax
- type: "GET"
- url: form.attr('action')
- data: form.serialize()
- complete: ->
- $('.merge-requests-holder').css("opacity", '1.0')
- success: (data) ->
- $('.merge-requests-holder').html(data.html)
- # Change url so if user reload a page - search results are saved
- history.replaceState {page: issues_url}, document.title, issues_url
- MergeRequests.reload()
- dataType: "json"
-
- reload: ->
- $('#filter_issue_search').val($('#issue_search').val())
diff --git a/app/assets/javascripts/milestone_select.js.coffee b/app/assets/javascripts/milestone_select.js.coffee
index 6bd4e885a03..345a0e447af 100644
--- a/app/assets/javascripts/milestone_select.js.coffee
+++ b/app/assets/javascripts/milestone_select.js.coffee
@@ -24,7 +24,7 @@ class @MilestoneSelect
if issueUpdateURL
milestoneLinkTemplate = _.template(
- '<a href="/<%= namespace %>/<%= path %>/milestones/<%= iid %>"><%= title %></a>'
+ '<a href="/<%= namespace %>/<%= path %>/milestones/<%= iid %>"><%= _.escape(title) %></a>'
)
milestoneLinkNoneTemplate = '<div class="light">None</div>'
@@ -71,7 +71,7 @@ class @MilestoneSelect
defaultLabel
fieldName: $dropdown.data('field-name')
text: (milestone) ->
- milestone.title
+ _.escape(milestone.title)
id: (milestone) ->
if !useId
milestone.name
@@ -97,7 +97,7 @@ class @MilestoneSelect
selectedMilestone = selected.name
else
selectedMilestone = ''
- Issues.filterResults $dropdown.closest('form')
+ Issuable.filterResults $dropdown.closest('form')
else if $dropdown.hasClass('js-filter-submit')
$dropdown.closest('form').submit()
else
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index 82e210fed7d..efb3e8e2198 100644
--- a/app/assets/javascripts/notes.js.coffee
+++ b/app/assets/javascripts/notes.js.coffee
@@ -167,8 +167,8 @@ class @Notes
return
if note.award
- awards_handler.addAwardToEmojiBar(note.note)
- awards_handler.scrollToAwards()
+ awardsHandler.addAwardToEmojiBar(note.note)
+ awardsHandler.scrollToAwards()
# render note if it not present in loaded list
# or skip if rendered
@@ -373,11 +373,11 @@ class @Notes
new GLForm form
if scrollTo? and myLastNote?
- # scroll to the bottom
+ # scroll to the bottom
# so the open of the last element doesn't make a jump
$('html, body').scrollTop($(document).height());
$('html, body').animate({
- scrollTop: myLastNote.offset().top - 150
+ scrollTop: myLastNote.offset().top - 150
}, 500, ->
$noteText = form.find(".js-note-text")
$noteText.focus()
diff --git a/app/assets/javascripts/right_sidebar.js.coffee b/app/assets/javascripts/right_sidebar.js.coffee
index 67403554340..2d084b76cfe 100644
--- a/app/assets/javascripts/right_sidebar.js.coffee
+++ b/app/assets/javascripts/right_sidebar.js.coffee
@@ -1,10 +1,12 @@
class @Sidebar
constructor: (currentUser) ->
+ @sidebar = $('aside')
+
@addEventListeners()
addEventListeners: ->
- $('aside').on('click', '.sidebar-collapsed-icon', @sidebarCollapseClicked)
- $('.dropdown').on('hidden.gl.dropdown', @sidebarDropdownHidden)
+ @sidebar.on('click', '.sidebar-collapsed-icon', @, @sidebarCollapseClicked)
+ $('.dropdown').on('hidden.gl.dropdown', @, @onSidebarDropdownHidden)
$('.dropdown').on('loading.gl.dropdown', @sidebarDropdownLoading)
$('.dropdown').on('loaded.gl.dropdown', @sidebarDropdownLoaded)
@@ -30,26 +32,56 @@ class @Sidebar
else
i.show()
-
sidebarCollapseClicked: (e) ->
+ sidebar = e.data
e.preventDefault()
$block = $(@).closest('.block')
+ sidebar.openDropdown($block);
- $('aside')
- .find('.gutter-toggle')
- .trigger('click')
- $editLink = $block.find('.edit-link')
+ openDropdown: (blockOrName) ->
+ $block = if _.isString(blockOrName) then @getBlock(blockOrName) else blockOrName
+
+ $block.find('.edit-link').trigger('click')
- if $editLink.length
- $editLink.trigger('click')
- $block.addClass('collapse-after-update')
- $('.page-with-sidebar').addClass('with-overlay')
+ if not @isOpen()
+ @setCollapseAfterUpdate($block)
+ @toggleSidebar('open')
- sidebarDropdownHidden: (e) ->
+ setCollapseAfterUpdate: ($block) ->
+ $block.addClass('collapse-after-update')
+ $('.page-with-sidebar').addClass('with-overlay')
+
+ onSidebarDropdownHidden: (e) ->
+ sidebar = e.data
+ e.preventDefault()
$block = $(@).closest('.block')
+ sidebar.sidebarDropdownHidden($block)
+
+ sidebarDropdownHidden: ($block) ->
if $block.hasClass('collapse-after-update')
$block.removeClass('collapse-after-update')
$('.page-with-sidebar').removeClass('with-overlay')
- $('aside')
- .find('.gutter-toggle')
- .trigger('click') \ No newline at end of file
+ @toggleSidebar('hide')
+
+ triggerOpenSidebar: ->
+ @sidebar
+ .find('.js-sidebar-toggle')
+ .trigger('click')
+
+ toggleSidebar: (action = 'toggle') ->
+ if action is 'toggle'
+ @triggerOpenSidebar()
+
+ if action is 'open'
+ @triggerOpenSidebar() if not @isOpen()
+
+ if action is 'hide'
+ @triggerOpenSidebar() is @isOpen()
+
+ isOpen: ->
+ @sidebar.is('.right-sidebar-expanded')
+
+ getBlock: (name) ->
+ @sidebar.find(".block.#{name}")
+
+
diff --git a/app/assets/javascripts/search.js.coffee b/app/assets/javascripts/search.js.coffee
new file mode 100644
index 00000000000..661e1195f60
--- /dev/null
+++ b/app/assets/javascripts/search.js.coffee
@@ -0,0 +1,75 @@
+class @Search
+ constructor: ->
+ $groupDropdown = $('.js-search-group-dropdown')
+ $projectDropdown = $('.js-search-project-dropdown')
+ @eventListeners()
+
+ $groupDropdown.glDropdown(
+ selectable: true
+ filterable: true
+ fieldName: 'group_id'
+ data: (term, callback) ->
+ Api.groups term, null, (data) ->
+ data.unshift(
+ name: 'Any'
+ )
+ data.splice 1, 0, 'divider'
+
+ callback(data)
+ id: (obj) ->
+ obj.id
+ text: (obj) ->
+ obj.name
+ toggleLabel: (obj) ->
+ "#{$groupDropdown.data('default-label')} #{obj.name}"
+ clicked: =>
+ @submitSearch()
+ )
+
+ $projectDropdown.glDropdown(
+ selectable: true
+ filterable: true
+ fieldName: 'project_id'
+ data: (term, callback) ->
+ Api.projects term, 'id', (data) ->
+ data.unshift(
+ name_with_namespace: 'Any'
+ )
+ data.splice 1, 0, 'divider'
+
+ callback(data)
+ id: (obj) ->
+ obj.id
+ text: (obj) ->
+ obj.name_with_namespace
+ toggleLabel: (obj) ->
+ "#{$projectDropdown.data('default-label')} #{obj.name_with_namespace}"
+ clicked: =>
+ @submitSearch()
+ )
+
+ eventListeners: ->
+ $(document)
+ .off 'keyup', '.js-search-input'
+ .on 'keyup', '.js-search-input', @searchKeyUp
+
+ $(document)
+ .off 'click', '.js-search-clear'
+ .on 'click', '.js-search-clear', @clearSearchField
+
+ submitSearch: ->
+ $('.js-search-form').submit()
+
+ searchKeyUp: ->
+ $input = $(@)
+
+ if $input.val() is ''
+ $('.js-search-clear').addClass 'hidden'
+ else
+ $('.js-search-clear').removeClass 'hidden'
+
+ clearSearchField: ->
+ $('.js-search-input')
+ .val ''
+ .trigger 'keyup'
+ .focus()
diff --git a/app/assets/javascripts/shortcuts.js.coffee b/app/assets/javascripts/shortcuts.js.coffee
index 100e3aac535..f3d66004138 100644
--- a/app/assets/javascripts/shortcuts.js.coffee
+++ b/app/assets/javascripts/shortcuts.js.coffee
@@ -2,34 +2,35 @@ class @Shortcuts
constructor: ->
@enabledHelp = []
Mousetrap.reset()
- Mousetrap.bind('?', @selectiveHelp)
+ Mousetrap.bind('?', @onToggleHelp)
Mousetrap.bind('s', Shortcuts.focusSearch)
Mousetrap.bind(['ctrl+shift+p', 'command+shift+p'], @toggleMarkdownPreview)
Mousetrap.bind('t', -> Turbolinks.visit(findFileURL)) if findFileURL?
- selectiveHelp: (e) =>
- Shortcuts.showHelp(e, @enabledHelp)
+ onToggleHelp: (e) =>
+ e.preventDefault()
+ @toggleHelp(@enabledHelp)
toggleMarkdownPreview: (e) =>
$(document).triggerHandler('markdown-preview:toggle', [e])
- @showHelp: (e, location) ->
- if $('#modal-shortcuts').length > 0
- $('#modal-shortcuts').modal('show')
- else
- url = '/help/shortcuts'
- url = gon.relative_url_root + url if gon.relative_url_root?
- $.ajax(
- url: url,
- dataType: 'script',
- success: (e) ->
- if location and location.length > 0
- $(l).show() for l in location
- else
- $('.hidden-shortcut').show()
- $('.js-more-help-button').remove()
- )
- e.preventDefault()
+ toggleHelp: (location) ->
+ $modal = $('#modal-shortcuts')
+
+ if $modal.length
+ $modal.modal('toggle')
+ return
+
+ $.ajax(
+ url: gon.shortcuts_path,
+ dataType: 'script',
+ success: (e) ->
+ if location and location.length > 0
+ $(l).show() for l in location
+ else
+ $('.hidden-shortcut').show()
+ $('.js-more-help-button').remove()
+ )
@focusSearch: (e) ->
$('#search').focus()
diff --git a/app/assets/javascripts/shortcuts_issuable.coffee b/app/assets/javascripts/shortcuts_issuable.coffee
index bbf02f1db24..ad9b3c1c6bf 100644
--- a/app/assets/javascripts/shortcuts_issuable.coffee
+++ b/app/assets/javascripts/shortcuts_issuable.coffee
@@ -4,18 +4,8 @@
class @ShortcutsIssuable extends ShortcutsNavigation
constructor: (isMergeRequest) ->
super()
- Mousetrap.bind('a', ->
- $('.block.assignee .edit-link').trigger('click')
- return false
- )
- Mousetrap.bind('m', ->
- $('.block.milestone .edit-link').trigger('click')
- return false
- )
- Mousetrap.bind('r', =>
- @replyWithSelectedText()
- return false
- )
+ Mousetrap.bind('a', @openSidebarDropdown.bind(@, 'assignee'))
+ Mousetrap.bind('m', @openSidebarDropdown.bind(@, 'milestone'))
Mousetrap.bind('j', =>
@prevIssue()
return false
@@ -28,7 +18,7 @@ class @ShortcutsIssuable extends ShortcutsNavigation
@editIssue()
return false
)
-
+ Mousetrap.bind('l', @openSidebarDropdown.bind(@, 'labels'))
if isMergeRequest
@enabledHelp.push('.hidden-shortcut.merge_requests')
@@ -71,3 +61,7 @@ class @ShortcutsIssuable extends ShortcutsNavigation
editIssue: ->
$editBtn = $('.issuable-edit')
Turbolinks.visit($editBtn.attr('href'))
+
+ openSidebarDropdown: (name) ->
+ sidebar.openDropdown(name)
+ return false
diff --git a/app/assets/javascripts/shortcuts_navigation.coffee b/app/assets/javascripts/shortcuts_navigation.coffee
index 8decaedd87b..f39504e0645 100644
--- a/app/assets/javascripts/shortcuts_navigation.coffee
+++ b/app/assets/javascripts/shortcuts_navigation.coffee
@@ -14,6 +14,7 @@ class @ShortcutsNavigation extends Shortcuts
Mousetrap.bind('g m', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-merge_requests'))
Mousetrap.bind('g w', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-wiki'))
Mousetrap.bind('g s', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-snippets'))
+ Mousetrap.bind('i', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-new-issue'))
@enabledHelp.push('.hidden-shortcut.project')
@findAndFollowLink: (selector) ->
diff --git a/app/assets/javascripts/todos.js.coffee b/app/assets/javascripts/todos.js.coffee
index 00d2b641723..10bef96f43d 100644
--- a/app/assets/javascripts/todos.js.coffee
+++ b/app/assets/javascripts/todos.js.coffee
@@ -1,5 +1,11 @@
class @Todos
- constructor: (@name) ->
+ constructor: (opts = {}) ->
+ {
+ @el = $('.js-todos-options')
+ } = opts
+
+ @perPage = @el.data('perPage')
+
@clearListeners()
@initBtnListeners()
@@ -26,6 +32,7 @@ class @Todos
dataType: 'json'
data: '_method': 'delete'
success: (data) =>
+ @redirectIfNeeded data.count
@clearDone $this.closest('li')
@updateBadges data
@@ -57,11 +64,46 @@ class @Todos
$('.todos-pending .badge, .todos-pending-count').text data.count
$('.todos-done .badge').text data.done_count
+ getTotalPages: ->
+ @el.data('totalPages')
+
+ getCurrentPage: ->
+ @el.data('currentPage')
+
+ getTodosPerPage: ->
+ @el.data('perPage')
+
+ redirectIfNeeded: (total) ->
+ currPages = @getTotalPages()
+ currPage = @getCurrentPage()
+
+ # Refresh if no remaining Todos
+ if not total
+ location.reload()
+ return
+
+ # Do nothing if no pagination
+ return if not currPages
+
+ newPages = Math.ceil(total / @getTodosPerPage())
+ url = location.href # Includes query strings
+
+ # If new total of pages is different than we have now
+ if newPages isnt currPages
+ # Redirect to previous page if there's one available
+ if currPages > 1 and currPage is currPages
+ pageParams =
+ page: currPages - 1
+ url = gl.utils.mergeUrlParams(pageParams, url)
+
+ Turbolinks.visit(url)
+
goToTodoUrl: (e)->
todoLink = $(this).data('url')
return unless todoLink
- if e.metaKey
+ # Allow Meta-Click or Mouse3-click to open in a new tab
+ if e.metaKey or e.which is 2
e.preventDefault()
window.open(todoLink,'_blank')
else
diff --git a/app/assets/javascripts/user_tabs.js.coffee b/app/assets/javascripts/user_tabs.js.coffee
index 09b7eec9104..c2aeffe2381 100644
--- a/app/assets/javascripts/user_tabs.js.coffee
+++ b/app/assets/javascripts/user_tabs.js.coffee
@@ -92,7 +92,7 @@ class @UserTabs
@setCurrentAction(action)
activateTab: (action) ->
- @parentEl.find(".nav-links .#{action}-tab a").tab('show')
+ @parentEl.find(".nav-links .js-#{action}-tab a").tab('show')
setTab: (source, action) ->
return if @loaded[action] is true
diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee
index a7e934936e9..b80b1b861cc 100644
--- a/app/assets/javascripts/users_select.js.coffee
+++ b/app/assets/javascripts/users_select.js.coffee
@@ -158,7 +158,7 @@ class @UsersSelect
if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
selectedId = user.id
- Issues.filterResults $dropdown.closest('form')
+ Issuable.filterResults $dropdown.closest('form')
else if $dropdown.hasClass 'js-filter-submit'
$dropdown.closest('form').submit()
else
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index c85ab9148d0..560de9fc0bd 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -25,6 +25,7 @@
@import "framework/lists.scss";
@import "framework/markdown_area.scss";
@import "framework/mobile.scss";
+@import "framework/modal.scss";
@import "framework/nav.scss";
@import "framework/pagination.scss";
@import "framework/progress.scss";
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index 62b2af0dbf7..e72e4aa47ef 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -1,5 +1,5 @@
.light-well {
- background-color: #f8fafc;
+ background-color: $background-color;
padding: 15px;
}
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index e8c0172680d..062da397b6b 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -139,11 +139,19 @@
pointer-events: auto !important;
}
+ &[disabled] {
+ pointer-events: none !important;
+ }
+
.caret {
margin-left: 5px;
}
}
+.btn-lg {
+ padding: 12px 20px;
+}
+
.btn-transparent {
color: $btn-transparent-color;
background-color: transparent;
diff --git a/app/assets/stylesheets/framework/calendar.scss b/app/assets/stylesheets/framework/calendar.scss
index 0b3af592d4a..11f39d583bd 100644
--- a/app/assets/stylesheets/framework/calendar.scss
+++ b/app/assets/stylesheets/framework/calendar.scss
@@ -54,6 +54,10 @@
fill: #254e77 !important;
}
+ .future {
+ visibility: hidden;
+ }
+
.domain-background {
fill: none;
shape-rendering: crispedges;
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 2ade341c9dd..3386523dbf7 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -11,6 +11,7 @@
.prepend-top-10 { margin-top: 10px }
.prepend-top-default { margin-top: $gl-padding !important; }
.prepend-top-20 { margin-top: 20px }
+.prepend-left-5 { margin-left: 5px }
.prepend-left-10 { margin-left: 10px }
.prepend-left-default { margin-left: $gl-padding; }
.prepend-left-20 { margin-left: 20px }
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index ba6c7930cdc..4bf3a050403 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -42,7 +42,7 @@
font-size: 15px;
text-align: left;
border: 1px solid $dropdown-toggle-border-color;
- border-radius: $dropdown-border-radius;
+ border-radius: $border-radius-base;
outline: 0;
text-overflow: ellipsis;
white-space: nowrap;
@@ -80,7 +80,7 @@
padding: 10px 0;
background-color: $dropdown-bg;
border: 1px solid $dropdown-border-color;
- border-radius: $dropdown-border-radius;
+ border-radius: $border-radius-base;
box-shadow: 0 2px 4px $dropdown-shadow-color;
&.is-loading {
@@ -248,7 +248,7 @@
.dropdown-title {
position: relative;
- padding: 0 25px 15px;
+ padding: 0 25px 10px;
margin: 0 10px 10px;
font-weight: 600;
line-height: 1;
@@ -278,7 +278,7 @@
right: 5px;
width: 20px;
height: 20px;
- top: -1px;
+ top: -3px;
}
.dropdown-menu-back {
@@ -320,7 +320,7 @@
}
}
-.dropdown-input-field {
+.dropdown-input-field, .default-dropdown-input {
width: 100%;
padding: 0 7px;
color: $dropdown-input-color;
@@ -358,6 +358,13 @@
border-top: 1px solid $dropdown-divider-color;
}
+.dropdown-due-date-footer {
+ padding-top: 0;
+ margin-left: 10px;
+ margin-right: 10px;
+ border-top: 0;
+}
+
.dropdown-footer-list {
font-size: 14px;
@@ -395,3 +402,122 @@
height: 15px;
border-radius: $border-radius-base;
}
+
+.dropdown-menu-due-date {
+ .dropdown-content {
+ max-height: 230px;
+ }
+
+ .ui-widget {
+ table {
+ margin: 0;
+ }
+
+ &.ui-datepicker-inline {
+ padding: 0 10px;
+ border: 0;
+ width: 100%;
+ }
+
+ .ui-datepicker-header {
+ padding: 0 8px 10px;
+ border: 0;
+
+ .ui-icon {
+ background: none;
+ font-size: 20px;
+ text-indent: 0;
+
+ &:before {
+ display: block;
+ position: relative;
+ top: -2px;
+ color: $dropdown-title-btn-color;
+ font: normal normal normal 14px/1 FontAwesome;
+ font-size: inherit;
+ text-rendering: auto;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ }
+ }
+ }
+
+ .ui-state-active,
+ .ui-state-hover {
+ color: $md-link-color;
+ background-color: $calendar-hover-bg;
+ }
+
+ .ui-datepicker-prev,
+ .ui-datepicker-next {
+ top: 0;
+ height: 15px;
+ cursor: pointer;
+
+ &:hover {
+ background-color: transparent;
+ border: 0;
+
+ .ui-icon:before {
+ color: $md-link-color;
+ }
+ }
+ }
+
+ .ui-datepicker-prev {
+ left: 0;
+
+ .ui-icon:before {
+ content: '\f104';
+ text-align: left;
+ }
+ }
+
+ .ui-datepicker-next {
+ right: 0;
+
+ .ui-icon:before {
+ content: '\f105';
+ text-align: right;
+ }
+ }
+
+ td {
+ padding: 0;
+ border: 1px solid $calendar-border-color;
+
+ &:first-child {
+ border-left: 0;
+ }
+
+ &:last-child {
+ border-right: 0;
+ }
+
+ a {
+ line-height: 17px;
+ border: 0;
+ border-radius: 0;
+ }
+ }
+
+ .ui-datepicker-title {
+ color: $gl-gray;
+ font-size: 15px;
+ line-height: 1;
+ font-weight: normal;
+ }
+ }
+
+ th {
+ padding: 2px 0;
+ color: $calendar-header-color;
+ font-weight: normal;
+ text-transform: lowercase;
+ border-top: 1px solid $calendar-border-color;
+ }
+
+ .ui-datepicker-unselectable {
+ background-color: $calendar-unselectable-bg;
+ }
+}
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index 789df42fb66..61d9954c6c8 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -38,12 +38,14 @@
.filename {
&.old {
+ display: inline-block;
span.idiff {
background-color: #f8cbcb;
}
}
&.new {
+ display: inline-block;
span.idiff {
background-color: #a6f3a6;
}
@@ -82,10 +84,6 @@
}
}
- &.blob_file {
-
- }
-
&.blob-no-preview {
background: #eee;
text-shadow: 0 1px 2px #fff;
@@ -129,6 +127,11 @@
td.line-numbers {
float: none;
border-left: 1px solid #ddd;
+
+ i {
+ float: none;
+ margin-right: 0;
+ }
}
td.lines {
padding: 0;
diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss
index 54cb5461113..558b133f593 100644
--- a/app/assets/stylesheets/framework/forms.scss
+++ b/app/assets/stylesheets/framework/forms.scss
@@ -78,6 +78,24 @@ label {
border-radius: 3px;
}
+.select-wrapper {
+ position: relative;
+
+ .caret {
+ position: absolute;
+ right: 10px;
+ top: $gl-padding;
+ color: $gray-darkest;
+ pointer-events: none;
+ }
+}
+
+.select-control {
+ padding-left: 10px;
+ padding-right: 10px;
+ -webkit-appearance: none;
+}
+
.form-control-inline {
display: inline;
}
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index c303380764b..5fa10d29a87 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -26,9 +26,9 @@ header {
z-index: 100;
margin-bottom: 0;
min-height: $header-height;
- background-color: #fff;
+ background-color: $background-color;
border: none;
- border-bottom: 1px solid #eee;
+ border-bottom: 1px solid $border-color;
.container-fluid {
width: 100% !important;
@@ -47,7 +47,7 @@ header {
text-align: center;
&:hover, &:focus, &:active {
- background-color: #fff;
+ background-color: $background-color;
}
}
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index 0f32d36d59c..fd885b38680 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -90,3 +90,12 @@
box-shadow: none;
width: 100%;
}
+
+.md {
+ &.md-preview-holder {
+ code {
+ white-space: pre-wrap;
+ word-break: keep-all;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss
new file mode 100644
index 00000000000..26ad2870aa0
--- /dev/null
+++ b/app/assets/stylesheets/framework/modal.scss
@@ -0,0 +1,22 @@
+.modal-body {
+ position: relative;
+ overflow-y: auto;
+ padding: 15px;
+
+ .form-actions {
+ margin: -$gl-padding+1;
+ margin-top: 15px;
+ }
+
+ .text-danger {
+ font-weight: bold;
+ }
+}
+
+body.modal-open {
+ overflow: hidden;
+}
+
+.modal .modal-dialog {
+ width: 860px;
+}
diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss
index 192d53b048a..5fe687dcec3 100644
--- a/app/assets/stylesheets/framework/nav.scss
+++ b/app/assets/stylesheets/framework/nav.scss
@@ -185,3 +185,22 @@
}
}
}
+
+.layout-nav {
+ background: $background-color;
+ border-bottom: 1px solid $border-color;
+
+ .controls {
+ float: right;
+ position: relative;
+ top: 10px;
+
+ .dropdown {
+ margin-left: 7px;
+ }
+ }
+
+ .nav-links {
+ border-bottom: none;
+ }
+}
diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss
index b2fab387e17..6efc6ec1e4b 100644
--- a/app/assets/stylesheets/framework/selects.scss
+++ b/app/assets/stylesheets/framework/selects.scss
@@ -7,13 +7,11 @@
.select2-choice {
background: #fff;
border-color: $input-border;
- border-color: $border-white-light;
height: 35px;
padding: $gl-vert-padding $gl-btn-padding;
font-size: $gl-font-size;
line-height: 1.42857143;
-
- @include border-radius($border-radius-default);
+ border-radius: $border-radius-base;
.select2-arrow {
background-image: none;
@@ -121,9 +119,6 @@
}
}
-.select2-container-multi .select2-choices .select2-search-choice {
-}
-
.select2-drop-active {
margin-top: 6px;
font-size: 14px;
@@ -202,6 +197,14 @@
}
}
+.select2-highlighted {
+ .group-result {
+ .group-path {
+ color: #fff;
+ }
+ }
+}
+
.group-result {
.group-image {
float: left;
diff --git a/app/assets/stylesheets/framework/timeline.scss b/app/assets/stylesheets/framework/timeline.scss
index f0ec250de2b..29501069d27 100644
--- a/app/assets/stylesheets/framework/timeline.scss
+++ b/app/assets/stylesheets/framework/timeline.scss
@@ -11,7 +11,7 @@
border-bottom: 1px solid $border-white-light;
&:target {
- background: $row-hover;
+ background: $line-target-blue;
}
.avatar {
diff --git a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
index c72af5dad0a..371c1bf17e1 100644
--- a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
+++ b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
@@ -153,8 +153,8 @@ $nav-link-padding: 13px $gl-padding;
//== Code
//
//##
-$pre-bg: #f8fafc !default;
+$pre-bg: $background-color !default;
$pre-color: $gl-gray !default;
-$pre-border-color: #e7e9ed;
+$pre-border-color: $border-color;
$table-bg-accent: $background-color;
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index 0a5b4b8834c..b2535ddf4bd 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -205,6 +205,10 @@ h1, h2, h3, h4, h5, h6 {
font-weight: 600;
}
+.light-header {
+ font-weight: 600;
+}
+
/** CODE **/
pre {
font-family: $monospace_font;
@@ -259,3 +263,9 @@ h1, h2, h3, h4 {
color: $gl-gray;
}
}
+
+.text-right-lg {
+ @media (min-width: $screen-lg-min) {
+ text-align: right;
+ }
+}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index f910cf61817..b8ed7e8a74c 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -71,8 +71,7 @@ $gl-avatar-size: 40px;
$error-exclamation-point: #e62958;
$border-radius-default: 2px;
$btn-transparent-color: #8f8f8f;
-$ssh-key-icon-color: #8f8f8f;
-$ssh-key-icon-size: 18px;
+$settings-icon-size: 18px;
$provider-btn-group-border: #e5e5e5;
$provider-btn-not-active-color: #4688f1;
@@ -168,8 +167,12 @@ $line-removed: #fbe9eb;
$line-removed-dark: #fac5cd;
$line-number-old: #f9d7dc;
$line-number-new: #ddfbe6;
+$line-number-select: #fbf2da;
$match-line: #fafafa;
$table-border-gray: #f0f0f0;
+$line-target-blue: #eaf3fc;
+$line-select-yellow: #fcf8e7;
+$line-select-yellow-dark: #f0e2bd;
/*
* Fonts
*/
@@ -179,7 +182,6 @@ $regular_font: 'Source Sans Pro', "Helvetica Neue", Helvetica, Arial, sans-serif
/*
* Dropdowns
*/
-$dropdown-border-radius: 2px;
$dropdown-width: 300px;
$dropdown-bg: #fff;
$dropdown-link-color: #555;
@@ -241,3 +243,8 @@ $note-form-border-color: #e5e5e5;
$note-toolbar-color: #959494;
$zen-control-hover-color: #111;
+
+$calendar-header-color: #b8b8b8;
+$calendar-hover-bg: #ecf3fe;
+$calendar-border-color: rgba(#000, .1);
+$calendar-unselectable-bg: #faf9f9;
diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss
index 28253d4ccb4..80a509a7c1a 100644
--- a/app/assets/stylesheets/highlight/monokai.scss
+++ b/app/assets/stylesheets/highlight/monokai.scss
@@ -111,8 +111,6 @@
.vg { color: #f8f8f2 } /* Name.Variable.Global */
.vi { color: #f8f8f2 } /* Name.Variable.Instance */
.il { color: #ae81ff } /* Literal.Number.Integer.Long */
-
- .gh { } /* Generic Heading & Diff Header */
.gu { color: #75715e; } /* Generic.Subheading & Diff Unified/Comment? */
.gd { color: #f92672; } /* Generic.Deleted & Diff Deleted */
.gi { color: #a6e22e; } /* Generic.Inserted & Diff Inserted */
diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss
index 1ff6ad75e07..31a4e3deaac 100644
--- a/app/assets/stylesheets/highlight/white.scss
+++ b/app/assets/stylesheets/highlight/white.scss
@@ -21,11 +21,6 @@
// Diff line
.line_holder {
- td.diff-line-num.hll:not(.empty-cell),
- td.line_content.hll:not(.empty-cell) {
- background-color: #f8eec7;
- border-color: darken(#f8eec7, 15%);
- }
.diff-line-num {
&.old {
@@ -37,11 +32,16 @@
background-color: $line-number-new;
border-color: $line-added-dark;
}
+
+ &.hll:not(.empty-cell) {
+ background-color: $line-number-select;
+ border-color: $line-select-yellow-dark;
+ }
}
.line_content {
&.old {
- background: $line-removed;
+ background-color: $line-removed;
span.idiff {
background-color: $line-removed-dark;
@@ -58,7 +58,11 @@
&.match {
color: $black-transparent;
- background: $match-line;
+ background-color: $match-line;
+ }
+
+ &.hll:not(.empty-cell) {
+ background-color: $line-select-yellow;
}
}
}
diff --git a/app/assets/stylesheets/pages/confirmation.scss b/app/assets/stylesheets/pages/confirmation.scss
new file mode 100644
index 00000000000..125f495d6d4
--- /dev/null
+++ b/app/assets/stylesheets/pages/confirmation.scss
@@ -0,0 +1,18 @@
+.well-confirmation {
+ margin-bottom: 20px;
+ border-bottom: 1px solid #eee;
+
+ > h1 {
+ font-weight: 400;
+ }
+
+ .lead {
+ margin-bottom: 20px;
+ }
+}
+
+.confirmation-content {
+ a {
+ color: $md-link-color;
+ }
+}
diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss
index 2c2ac903f29..3438dbe4958 100644
--- a/app/assets/stylesheets/pages/detail_page.scss
+++ b/app/assets/stylesheets/pages/detail_page.scss
@@ -36,4 +36,11 @@
}
}
}
+
+ .wiki {
+ code {
+ white-space: pre-wrap;
+ word-break: keep-all;
+ }
+ }
}
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 183f22a1b24..1a7d5f9666e 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -34,6 +34,7 @@
background: #fff;
color: #333;
border-radius: 0 0 3px 3px;
+ -webkit-overflow-scrolling: auto;
.unfold {
cursor: pointer;
@@ -86,7 +87,7 @@
}
span {
- white-space: pre;
+ white-space: pre-wrap;
}
}
}
@@ -97,7 +98,11 @@
}
td.line_content.parallel {
- width: 50%;
+ width: 46%;
+ }
+
+ .add-diff-note {
+ margin-left: -65px;
}
}
@@ -126,8 +131,13 @@
margin: 0;
padding: 0 0.5em;
border: none;
+
&.parallel {
display: table-cell;
+
+ span {
+ word-break: break-all;
+ }
}
}
@@ -335,7 +345,7 @@
}
.diff-file .line_content {
- white-space: pre;
+ white-space: pre-wrap;
}
.diff-wrap-lines .line_content {
diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss
index 0f0592a0ab8..8981f070a20 100644
--- a/app/assets/stylesheets/pages/editor.scss
+++ b/app/assets/stylesheets/pages/editor.scss
@@ -26,6 +26,10 @@
line-height: 42px;
padding-top: 7px;
padding-bottom: 7px;
+
+ .pull-right {
+ height: 20px;
+ }
}
.editor-ref {
@@ -53,4 +57,9 @@
.select2 {
float: right;
}
+
+ .encoding-selector,
+ .license-selector {
+ display: inline-block;
+ }
}
diff --git a/app/assets/stylesheets/pages/graph.scss b/app/assets/stylesheets/pages/graph.scss
index 4e5c4ed84b6..f7f9a9bb770 100644
--- a/app/assets/stylesheets/pages/graph.scss
+++ b/app/assets/stylesheets/pages/graph.scss
@@ -18,9 +18,6 @@
}
.graphs {
- .graph-author-commits-count {
- }
-
.graph-author-email {
float: right;
color: #777;
diff --git a/app/assets/stylesheets/pages/help.scss b/app/assets/stylesheets/pages/help.scss
index 604f1700cf8..4a95b7b852e 100644
--- a/app/assets/stylesheets/pages/help.scss
+++ b/app/assets/stylesheets/pages/help.scss
@@ -55,23 +55,6 @@
}
}
-.modal-body {
- position: relative;
- overflow-y: auto;
- padding: 15px;
- .form-actions {
- margin: -$gl-padding+1;
- }
-}
-
-body.modal-open {
- overflow: hidden;
-}
-
-.modal .modal-dialog {
- width: 860px;
-}
-
.documentation {
padding: 7px;
}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 5bf44c1cdb6..1cf3023ecc9 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -128,6 +128,7 @@
top: 58px;
bottom: 0;
right: 0;
+ z-index: 10;
transition: width .3s;
background: $gray-light;
padding: 10px 20px;
@@ -241,16 +242,20 @@
}
}
- .btn {
+ .issuable-pager {
background: $gray-normal;
border: 1px solid $border-gray-normal;
&:hover {
background: $gray-dark;
border: 1px solid $border-gray-dark;
}
+
+ &.btn-primary {
+ @extend .btn-primary
+ }
}
- a:not(.btn) {
+ a:not(.issuable-pager) {
&:hover {
color: $md-link-color;
text-decoration: none;
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index c2371d79989..7fa13e66b43 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -42,6 +42,7 @@
.note-textarea {
display: block;
padding: 10px 0;
+ color: $gl-gray;
font-family: $regular_font;
border: 0;
@@ -83,18 +84,6 @@
border-color: $gl-success;
}
}
-
- p {
- code {
- white-space: normal;
- }
-
- pre {
- code {
- white-space: pre;
- }
- }
- }
}
}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index ffcd89a3c9f..9619d65db85 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -81,16 +81,8 @@ ul.notes {
@include md-typography;
// On diffs code should wrap nicely and not overflow
- p {
- code {
- white-space: normal;
- }
-
- pre {
- code {
- white-space: pre;
- }
- }
+ code {
+ white-space: pre-wrap;
}
// Reset ul style types since we're nested inside a ul already
@@ -117,6 +109,10 @@ ul.notes {
border-color: darken(#f5f5f5, 8%);
margin: 10px 0;
}
+
+ code {
+ word-break: keep-all;
+ }
}
a {
@@ -137,7 +133,7 @@ ul.notes {
margin-right: 10px;
}
.line_content {
- white-space: pre;
+ white-space: pre-wrap;
}
}
@@ -171,11 +167,6 @@ ul.notes {
&.parallel {
border-width: 1px;
-
- .code,
- code {
- white-space: pre-wrap;
- }
}
.notes {
@@ -196,6 +187,9 @@ ul.notes {
}
}
+ .author_link {
+ color: $gl-gray;
+ }
}
.note-headline-light,
@@ -221,7 +215,7 @@ ul.notes {
}
.discussion-actions {
- @media (max-width: $screen-sm-max) {
+ @media (max-width: $screen-md-max) {
float: none;
margin-left: 0;
@@ -308,7 +302,7 @@ ul.notes {
padding: 4px;
font-size: 16px;
color: $gl-link-color;
- margin-left: -60px;
+ margin-left: -56px;
position: absolute;
z-index: 10;
width: 32px;
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index a9656e5cae7..01f98479623 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -18,7 +18,8 @@
}
.account-btn-link,
-.profile-settings-sidebar a {
+.profile-settings-sidebar a,
+.settings-sidebar a {
color: $md-link-color;
}
@@ -123,12 +124,6 @@
}
}
-.key-icon {
- color: $ssh-key-icon-color;
- font-size: $ssh-key-icon-size;
- line-height: 42px;
-}
-
.key-created-at {
line-height: 42px;
}
@@ -180,14 +175,6 @@
}
}
-.profile-settings-message {
- line-height: 32px;
- color: $warning-message-color;
- background-color: $warning-message-bg;
- border: 1px solid $warning-message-border;
- border-radius: $border-radius-base;
-}
-
.oauth-applications {
form {
display: inline-block;
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index fcca9d4faf5..99108e9bfc4 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -202,8 +202,31 @@
min-width: 200px;
}
-.deploy-project-label {
- margin: 1px;
+.deploy-key-content {
+ @media (min-width: $screen-sm-min) {
+ float: left;
+
+ &:last-child {
+ float: right;
+ }
+ }
+}
+
+.deploy-key-projects {
+ @media (min-width: $screen-sm-min) {
+ line-height: 42px;
+ }
+}
+
+a.deploy-project-label {
+ padding: 5px;
+ margin-right: 5px;
+ color: $gl-gray;
+ background-color: $row-hover;
+
+ &:hover {
+ color: $gl-link-color;
+ }
}
.vs-public {
@@ -256,12 +279,6 @@
}
}
-table.table.protected-branches-list tr.no-border {
- th, td {
- border: 0;
- }
-}
-
.project-import .btn {
float: left;
margin-right: 10px;
@@ -474,3 +491,14 @@ pre.light-well {
color: #fff;
}
}
+
+.protected-branches-list {
+ a {
+ color: $gl-gray;
+ font-weight: 600;
+
+ &:hover {
+ color: $gl-link-color;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index f0f3744c6fa..2bff70c8c64 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -10,17 +10,6 @@
}
}
-.search-holder {
- max-width: 600px;
- margin: 0 auto;
- margin-bottom: 20px;
-
- input {
- border-color: #bbb;
- font-weight: bold;
- }
-}
-
.search {
margin-right: 10px;
margin-left: 10px;
@@ -159,7 +148,85 @@
&.has-location-badge {
.search-input-wrap {
- width: 78%;
+ width: 68%;
}
}
}
+
+.search-holder {
+ @media (min-width: $screen-sm-min) {
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ }
+
+ .search-field-holder {
+ -webkit-flex: 1 0 auto;
+ -ms-flex: 1 0 auto;
+ flex: 1 0 auto;
+ position: relative;
+ margin-right: 0;
+
+ @media (min-width: $screen-sm-min) {
+ margin-right: 5px;
+ }
+ }
+
+ .search-icon {
+ position: absolute;
+ left: 10px;
+ top: 10px;
+ color: $gray-darkest;
+ pointer-events: none;
+ }
+
+ .search-text-input {
+ padding-left: $gl-padding + 15px;
+ padding-right: $gl-padding + 15px;
+ }
+
+ .btn-search {
+ width: 100%;
+ margin-top: 5px;
+
+ @media (min-width: $screen-sm-min) {
+ width: auto;
+ margin-top: 0;
+ margin-left: 5px;
+ }
+ }
+
+ .dropdown {
+ @media (min-width: $screen-sm-min) {
+ margin-left: 5px;
+ margin-right: 5px;
+ }
+ }
+
+ .dropdown-menu-toggle {
+ width: 100%;
+ margin-top: 5px;
+
+ @media (min-width: $screen-sm-min) {
+ width: 160px;
+ margin-top: 0;
+ }
+ }
+}
+
+.search-clear {
+ position: absolute;
+ right: 10px;
+ top: 10px;
+ padding: 0;
+ color: $gray-darkest;
+ line-height: 0;
+ background: none;
+ border: 0;
+
+ &:hover,
+ &:focus {
+ color: $gl-link-color;
+ outline: none;
+ }
+}
diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss
new file mode 100644
index 00000000000..3fb70085713
--- /dev/null
+++ b/app/assets/stylesheets/pages/settings.scss
@@ -0,0 +1,14 @@
+.settings-list-icon {
+ color: $gl-placeholder-color;
+ font-size: $settings-icon-size;
+ line-height: 42px;
+}
+
+.settings-message {
+ padding: 5px;
+ line-height: 1.3;
+ color: $warning-message-color;
+ background-color: $warning-message-bg;
+ border: 1px solid $warning-message-border;
+ border-radius: $border-radius-base;
+}
diff --git a/app/controllers/admin/application_controller.rb b/app/controllers/admin/application_controller.rb
index 9083bfb41cf..cf795d977ce 100644
--- a/app/controllers/admin/application_controller.rb
+++ b/app/controllers/admin/application_controller.rb
@@ -6,12 +6,6 @@ class Admin::ApplicationController < ApplicationController
layout 'admin'
def authenticate_admin!
- return render_404 unless current_user.is_admin?
- end
-
- def authorize_impersonator!
- if session[:impersonator_id]
- User.find_by!(username: session[:impersonator_id]).admin?
- end
+ render_404 unless current_user.is_admin?
end
end
diff --git a/app/controllers/admin/hooks_controller.rb b/app/controllers/admin/hooks_controller.rb
index 93c4894ea0f..4e85b6b4cf2 100644
--- a/app/controllers/admin/hooks_controller.rb
+++ b/app/controllers/admin/hooks_controller.rb
@@ -39,6 +39,12 @@ class Admin::HooksController < Admin::ApplicationController
end
def hook_params
- params.require(:hook).permit(:url, :enable_ssl_verification, :push_events, :tag_push_events)
+ params.require(:hook).permit(
+ :enable_ssl_verification,
+ :push_events,
+ :tag_push_events,
+ :token,
+ :url
+ )
end
end
diff --git a/app/controllers/admin/impersonation_controller.rb b/app/controllers/admin/impersonation_controller.rb
deleted file mode 100644
index bf98af78615..00000000000
--- a/app/controllers/admin/impersonation_controller.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-class Admin::ImpersonationController < Admin::ApplicationController
- skip_before_action :authenticate_admin!, only: :destroy
-
- before_action :user
- before_action :authorize_impersonator!
-
- def create
- if @user.blocked?
- flash[:alert] = "You cannot impersonate a blocked user"
-
- redirect_to admin_user_path(@user)
- else
- session[:impersonator_id] = current_user.username
- session[:impersonator_return_to] = admin_user_path(@user)
-
- warden.set_user(user, scope: 'user')
-
- flash[:alert] = "You are impersonating #{user.username}."
-
- redirect_to root_path
- end
- end
-
- def destroy
- redirect = session[:impersonator_return_to]
-
- warden.set_user(user, scope: 'user')
-
- session[:impersonator_return_to] = nil
- session[:impersonator_id] = nil
-
- redirect_to redirect || root_path
- end
-
- def user
- @user ||= User.find_by!(username: params[:id] || session[:impersonator_id])
- end
-end
diff --git a/app/controllers/admin/impersonations_controller.rb b/app/controllers/admin/impersonations_controller.rb
new file mode 100644
index 00000000000..2db824c87ef
--- /dev/null
+++ b/app/controllers/admin/impersonations_controller.rb
@@ -0,0 +1,24 @@
+class Admin::ImpersonationsController < Admin::ApplicationController
+ skip_before_action :authenticate_admin!
+ before_action :authenticate_impersonator!
+
+ def destroy
+ original_user = current_user
+
+ warden.set_user(impersonator, scope: :user)
+
+ session[:impersonator_id] = nil
+
+ redirect_to admin_user_path(original_user)
+ end
+
+ private
+
+ def impersonator
+ @impersonator ||= User.find(session[:impersonator_id]) if session[:impersonator_id]
+ end
+
+ def authenticate_impersonator!
+ render_404 unless impersonator && impersonator.is_admin? && !impersonator.blocked?
+ end
+end
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index 9abf08d0e19..b8976fa09a9 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -31,6 +31,22 @@ class Admin::UsersController < Admin::ApplicationController
user
end
+ def impersonate
+ if user.blocked?
+ flash[:alert] = "You cannot impersonate a blocked user"
+
+ redirect_to admin_user_path(user)
+ else
+ session[:impersonator_id] = current_user.id
+
+ warden.set_user(user, scope: :user)
+
+ flash[:alert] = "You are now impersonating #{user.username}"
+
+ redirect_to root_path
+ end
+ end
+
def block
if user.block
redirect_back_or_admin_user(notice: "Successfully blocked")
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 1c53b0b21a3..17b3f49aed1 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -117,7 +117,7 @@ class ApplicationController < ActionController::Base
end
def after_sign_out_path_for(resource)
- current_application_settings.after_sign_out_path || new_user_session_path
+ current_application_settings.after_sign_out_path.presence || new_user_session_path
end
def abilities
diff --git a/app/controllers/concerns/filter_projects.rb b/app/controllers/concerns/filter_projects.rb
index f63b703d101..586f97c5eb4 100644
--- a/app/controllers/concerns/filter_projects.rb
+++ b/app/controllers/concerns/filter_projects.rb
@@ -10,6 +10,8 @@ module FilterProjects
def filter_projects(projects)
projects = projects.search(params[:filter_projects]) if params[:filter_projects].present?
projects = projects.non_archived if params[:archived].blank?
+ projects = projects.personal(current_user) if params[:personal].present? && current_user
+
projects
end
end
diff --git a/app/controllers/confirmations_controller.rb b/app/controllers/confirmations_controller.rb
index af1faca93f6..7b66ad3f92c 100644
--- a/app/controllers/confirmations_controller.rb
+++ b/app/controllers/confirmations_controller.rb
@@ -1,7 +1,16 @@
class ConfirmationsController < Devise::ConfirmationsController
+ def almost_there
+ flash[:notice] = nil
+ render layout: "devise_empty"
+ end
+
protected
+ def after_resending_confirmation_instructions_path_for(resource)
+ users_almost_there_path
+ end
+
def after_confirmation_path_for(resource_name, resource)
if signed_in?(resource_name)
after_sign_in_path_for(resource)
diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb
index 74150ad606b..be872a93fee 100644
--- a/app/controllers/projects/application_controller.rb
+++ b/app/controllers/projects/application_controller.rb
@@ -83,8 +83,7 @@ class Projects::ApplicationController < ApplicationController
end
def apply_diff_view_cookie!
- view = params[:view] || cookies[:diff_view]
- cookies.permanent[:diff_view] = params[:view] = view if view
+ cookies.permanent[:diff_view] = params.delete(:view) if params[:view].present?
end
def builds_enabled
diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb
index f159e169f6d..b8b9e78427d 100644
--- a/app/controllers/projects/builds_controller.rb
+++ b/app/controllers/projects/builds_controller.rb
@@ -1,7 +1,7 @@
class Projects::BuildsController < Projects::ApplicationController
before_action :build, except: [:index, :cancel_all]
before_action :authorize_read_build!, except: [:cancel, :cancel_all, :retry]
- before_action :authorize_update_build!, except: [:index, :show, :status]
+ before_action :authorize_update_build!, except: [:index, :show, :status, :raw]
layout 'project'
def index
@@ -62,6 +62,14 @@ class Projects::BuildsController < Projects::ApplicationController
notice: "Build has been sucessfully erased!"
end
+ def raw
+ if @build.has_trace?
+ send_file @build.path_to_trace, type: 'text/plain; charset=utf-8', disposition: 'inline'
+ else
+ render_404
+ end
+ end
+
private
def build
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index 4d64a2d9884..a202cb38692 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -38,13 +38,13 @@ class Projects::CommitController < Projects::ApplicationController
end
def cancel_builds
- ci_commit.builds.running_or_pending.each(&:cancel)
+ ci_builds.running_or_pending.each(&:cancel)
redirect_back_or_default default: builds_namespace_project_commit_path(project.namespace, project, commit.sha)
end
def retry_builds
- ci_commit.builds.latest.failed.each do |build|
+ ci_builds.latest.failed.each do |build|
if build.retryable?
Ci::Build.retry(build)
end
@@ -99,8 +99,12 @@ class Projects::CommitController < Projects::ApplicationController
@commit ||= @project.commit(params[:id])
end
- def ci_commit
- @ci_commit ||= project.ci_commit(commit.sha)
+ def ci_commits
+ @ci_commits ||= project.ci_commits.where(sha: commit.sha)
+ end
+
+ def ci_builds
+ @ci_builds ||= Ci::Build.where(commit: ci_commits)
end
def define_show_vars
@@ -113,7 +117,8 @@ class Projects::CommitController < Projects::ApplicationController
@diff_refs = [commit.parent || commit, commit]
@notes_count = commit.notes.count
- @statuses = ci_commit.statuses if ci_commit
+ @statuses = CommitStatus.where(commit: ci_commits)
+ @builds = Ci::Build.where(commit: ci_commits)
end
def assign_change_commit_vars(mr_source_branch)
diff --git a/app/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb
index 7d09288bc80..83d5ced9be8 100644
--- a/app/controllers/projects/deploy_keys_controller.rb
+++ b/app/controllers/projects/deploy_keys_controller.rb
@@ -7,31 +7,24 @@ class Projects::DeployKeysController < Projects::ApplicationController
layout "project_settings"
def index
- @enabled_keys = @project.deploy_keys
-
- @available_keys = accessible_keys - @enabled_keys
- @available_project_keys = current_user.project_deploy_keys - @enabled_keys
- @available_public_keys = DeployKey.are_public - @enabled_keys
-
- # Public keys that are already used by another accessible project are already
- # in @available_project_keys.
- @available_public_keys -= @available_project_keys
+ @key = DeployKey.new
+ set_index_vars
end
def new
- @key = @project.deploy_keys.new
-
- respond_with(@key)
+ redirect_to namespace_project_deploy_keys_path(@project.namespace,
+ @project)
end
def create
@key = DeployKey.new(deploy_key_params)
+ set_index_vars
if @key.valid? && @project.deploy_keys << @key
redirect_to namespace_project_deploy_keys_path(@project.namespace,
@project)
else
- render "new"
+ render "index"
end
end
@@ -51,6 +44,18 @@ class Projects::DeployKeysController < Projects::ApplicationController
protected
+ def set_index_vars
+ @enabled_keys ||= @project.deploy_keys
+
+ @available_keys ||= accessible_keys - @enabled_keys
+ @available_project_keys ||= current_user.project_deploy_keys - @enabled_keys
+ @available_public_keys ||= DeployKey.are_public - @enabled_keys
+
+ # Public keys that are already used by another accessible project are already
+ # in @available_project_keys.
+ @available_public_keys -= @available_project_keys
+ end
+
def accessible_keys
@accessible_keys ||= current_user.accessible_deploy_keys
end
diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb
index 5fd4f855dec..dfa9bd259e8 100644
--- a/app/controllers/projects/hooks_controller.rb
+++ b/app/controllers/projects/hooks_controller.rb
@@ -52,8 +52,16 @@ class Projects::HooksController < Projects::ApplicationController
end
def hook_params
- params.require(:hook).permit(:url, :push_events, :issues_events,
- :merge_requests_events, :tag_push_events, :note_events,
- :build_events, :enable_ssl_verification)
+ params.require(:hook).permit(
+ :build_events,
+ :enable_ssl_verification,
+ :issues_events,
+ :merge_requests_events,
+ :note_events,
+ :push_events,
+ :tag_push_events,
+ :token,
+ :url
+ )
end
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 85d5b52fa05..016f5dd0005 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -3,8 +3,8 @@ class Projects::IssuesController < Projects::ApplicationController
include IssuableActions
before_action :module_enabled
- before_action :issue,
- only: [:edit, :update, :show, :referenced_merge_requests, :related_branches]
+ before_action :issue, only: [:edit, :update, :show, :referenced_merge_requests,
+ :related_branches, :can_create_branch]
# Allow read any issue
before_action :authorize_read_issue!, only: [:show]
@@ -33,14 +33,15 @@ class Projects::IssuesController < Projects::ApplicationController
end
@issues = @issues.page(params[:page])
- @label = @project.labels.find_by(title: params[:label_name])
+ @labels = @project.labels.where(title: params[:label_name])
respond_to do |format|
format.html
format.atom { render layout: false }
format.json do
render json: {
- html: view_to_html_string("projects/issues/_issues")
+ html: view_to_html_string("projects/issues/_issues"),
+ labels: @labels.as_json(methods: :text_color)
}
end
end
@@ -95,12 +96,13 @@ class Projects::IssuesController < Projects::ApplicationController
if params[:move_to_project_id].to_i > 0
new_project = Project.find(params[:move_to_project_id])
+ return render_404 unless issue.can_move?(current_user, new_project)
+
move_service = Issues::MoveService.new(project, current_user)
@issue = move_service.execute(@issue, new_project)
end
respond_to do |format|
- format.js
format.html do
if @issue.valid?
redirect_to issue_path(@issue)
@@ -109,7 +111,7 @@ class Projects::IssuesController < Projects::ApplicationController
end
end
format.json do
- render json: @issue.to_json(include: [:milestone, :labels, assignee: { methods: :avatar_url }])
+ render json: @issue.to_json(include: { milestone: {}, assignee: { methods: :avatar_url }, labels: { methods: :text_color } })
end
end
end
@@ -139,6 +141,18 @@ class Projects::IssuesController < Projects::ApplicationController
end
end
+ def can_create_branch
+ can_create = current_user &&
+ can?(current_user, :push_code, @project) &&
+ @issue.can_be_worked_on?(current_user)
+
+ respond_to do |format|
+ format.json do
+ render json: { can_create_branch: can_create }
+ end
+ end
+ end
+
def bulk_update
result = Issues::BulkUpdateService.new(project, current_user, bulk_update_params).execute
redirect_back_or_default(default: { action: 'index' }, options: { notice: "#{result[:count]} issues updated" })
@@ -191,7 +205,7 @@ class Projects::IssuesController < Projects::ApplicationController
def issue_params
params.require(:issue).permit(
:title, :assignee_id, :position, :description, :confidential,
- :milestone_id, :state_event, :task_num, label_ids: []
+ :milestone_id, :due_date, :state_event, :task_num, label_ids: []
)
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 3e0cfc6aa65..9c147b3689e 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -38,13 +38,14 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_requests = @merge_requests.page(params[:page])
@merge_requests = @merge_requests.preload(:target_project)
- @label = @project.labels.find_by(title: params[:label_name])
+ @labels = @project.labels.where(title: params[:label_name])
respond_to do |format|
format.html
format.json do
render json: {
- html: view_to_html_string("projects/merge_requests/_merge_requests")
+ html: view_to_html_string("projects/merge_requests/_merge_requests"),
+ labels: @labels.as_json(methods: :text_color)
}
end
end
@@ -148,13 +149,12 @@ class Projects::MergeRequestsController < Projects::ApplicationController
if @merge_request.valid?
respond_to do |format|
- format.js
format.html do
redirect_to([@merge_request.target_project.namespace.becomes(Namespace),
@merge_request.target_project, @merge_request])
end
format.json do
- render json: @merge_request.to_json(include: [:milestone, :labels, assignee: { methods: :avatar_url }])
+ render json: @merge_request.to_json(include: { milestone: {}, assignee: { methods: :avatar_url }, labels: { methods: :text_color } })
end
end
else
@@ -320,6 +320,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def define_widget_vars
@ci_commit = @merge_request.ci_commit
+ @ci_commits = [@ci_commit].compact
closes_issues
end
diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb
index e457db2f0b7..33b2625c0ac 100644
--- a/app/controllers/projects/project_members_controller.rb
+++ b/app/controllers/projects/project_members_controller.rb
@@ -1,6 +1,6 @@
class Projects::ProjectMembersController < Projects::ApplicationController
# Authorize
- before_action :authorize_admin_project_member!, except: :leave
+ before_action :authorize_admin_project_member!, except: [:leave, :index]
def index
@project_members = @project.project_members
diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb
index 8b2577aebe1..739681f4085 100644
--- a/app/controllers/projects/services_controller.rb
+++ b/app/controllers/projects/services_controller.rb
@@ -6,7 +6,7 @@ class Projects::ServicesController < Projects::ApplicationController
:description, :issues_url, :new_issue_url, :restrict_to_branch, :channel,
:colorize_messages, :channels,
:push_events, :issues_events, :merge_requests_events, :tag_push_events,
- :note_events, :build_events,
+ :note_events, :build_events, :wiki_page_events,
:notify_only_broken_builds, :add_pusher,
:send_from_committer_email, :disable_diffs, :external_wiki_url,
:notify, :color,
diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb
index 9f3a4a69721..0d6c32fabd2 100644
--- a/app/controllers/projects/wikis_controller.rb
+++ b/app/controllers/projects/wikis_controller.rb
@@ -40,11 +40,11 @@ class Projects::WikisController < Projects::ApplicationController
end
def update
- @page = @project_wiki.find_page(params[:id])
-
return render('empty') unless can?(current_user, :create_wiki, @project)
- if @page.update(content, format, message)
+ @page = @project_wiki.find_page(params[:id])
+
+ if @page = WikiPages::UpdateService.new(@project, current_user, wiki_params).execute(@page)
redirect_to(
namespace_project_wiki_path(@project.namespace, @project, @page),
notice: 'Wiki was successfully updated.'
@@ -55,9 +55,9 @@ class Projects::WikisController < Projects::ApplicationController
end
def create
- @page = WikiPage.new(@project_wiki)
+ @page = WikiPages::CreateService.new(@project, current_user, wiki_params).execute
- if @page.create(wiki_params)
+ if @page.persisted?
redirect_to(
namespace_project_wiki_path(@project.namespace, @project, @page),
notice: 'Wiki was successfully updated.'
@@ -122,15 +122,4 @@ class Projects::WikisController < Projects::ApplicationController
params[:wiki].slice(:title, :content, :format, :message)
end
- def content
- params[:wiki][:content]
- end
-
- def format
- params[:wiki][:format]
- end
-
- def message
- params[:wiki][:message]
- end
end
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index b441b34d0be..352bff19383 100644
--- a/app/controllers/registrations_controller.rb
+++ b/app/controllers/registrations_controller.rb
@@ -38,11 +38,11 @@ class RegistrationsController < Devise::RegistrationsController
end
def after_sign_up_path_for(_resource)
- new_user_session_path
+ users_almost_there_path
end
def after_inactive_sign_up_path_for(_resource)
- new_user_session_path
+ users_almost_there_path
end
private
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index e42d2d73947..69c92d2bed2 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -8,8 +8,6 @@ class SearchController < ApplicationController
def show
return if params[:search].nil? || params[:search].blank?
- @search_term = params[:search]
-
if params[:project_id].present?
@project = Project.find_by(id: params[:project_id])
@project = nil unless can?(current_user, :download_code, @project)
@@ -20,6 +18,8 @@ class SearchController < ApplicationController
@group = nil unless can?(current_user, :read_group, @group)
end
+ @search_term = params[:search]
+
@scope = params[:scope]
@show_snippets = params[:snippets].eql? 'true'
@@ -44,7 +44,7 @@ class SearchController < ApplicationController
Search::GlobalService.new(current_user, params).execute
end
- @objects = @search_results.objects(@scope, params[:page])
+ @search_objects = @search_results.objects(@scope, params[:page])
end
def autocomplete
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index f1df6832bf6..f00f3f709e9 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -39,6 +39,7 @@ class IssuableFinder
items = by_assignee(items)
items = by_author(items)
items = by_label(items)
+ items = by_due_date(items)
sort(items)
end
@@ -117,7 +118,7 @@ class IssuableFinder
end
def filter_by_no_label?
- labels? && params[:label_name] == Label::None.title
+ labels? && params[:label_name].include?(Label::None.title)
end
def labels
@@ -271,7 +272,6 @@ class IssuableFinder
items = items.without_label
else
items = items.with_label(label_names)
-
if projects
items = items.where(labels: { project_id: projects })
end
@@ -281,8 +281,44 @@ class IssuableFinder
items
end
+ def by_due_date(items)
+ if due_date?
+ if filter_by_no_due_date?
+ items = items.without_due_date
+ elsif filter_by_overdue?
+ items = items.due_before(Date.today)
+ elsif filter_by_due_this_week?
+ items = items.due_between(Date.today.beginning_of_week, Date.today.end_of_week)
+ elsif filter_by_due_this_month?
+ items = items.due_between(Date.today.beginning_of_month, Date.today.end_of_month)
+ end
+ end
+
+ items
+ end
+
+ def filter_by_no_due_date?
+ due_date? && params[:due_date] == Issue::NoDueDate.name
+ end
+
+ def filter_by_overdue?
+ due_date? && params[:due_date] == Issue::Overdue.name
+ end
+
+ def filter_by_due_this_week?
+ due_date? && params[:due_date] == Issue::DueThisWeek.name
+ end
+
+ def filter_by_due_this_month?
+ due_date? && params[:due_date] == Issue::DueThisMonth.name
+ end
+
+ def due_date?
+ params[:due_date].present? && klass.column_names.include?('due_date')
+ end
+
def label_names
- params[:label_name].split(',')
+ params[:label_name].is_a?(String) ? params[:label_name].split(',') : params[:label_name]
end
def current_user_related?
diff --git a/app/finders/snippets_finder.rb b/app/finders/snippets_finder.rb
index a41172816b8..01cbf91c658 100644
--- a/app/finders/snippets_finder.rb
+++ b/app/finders/snippets_finder.rb
@@ -51,7 +51,7 @@ class SnippetsFinder
snippets = project.snippets.fresh
if current_user
- if project.team.member?(current_user.id)
+ if project.team.member?(current_user.id) || current_user.admin?
snippets
else
snippets.public_and_internal
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 16e5b8ac223..3e0074da394 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -254,11 +254,11 @@ module ApplicationHelper
def page_filter_path(options = {})
without = options.delete(:without)
+ add_label = options.delete(:label)
exist_opts = {
state: params[:state],
scope: params[:scope],
- label_name: params[:label_name],
milestone_title: params[:milestone_title],
assignee_id: params[:assignee_id],
author_id: params[:author_id],
@@ -275,6 +275,13 @@ module ApplicationHelper
path = request.path
path << "?#{options.to_param}"
+ if add_label
+ if params[:label_name].present? and params[:label_name].respond_to?('any?')
+ params[:label_name].each do |label|
+ path << "&label_name[]=#{label}"
+ end
+ end
+ end
path
end
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 9e59a295fc4..474c6f27374 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -3,8 +3,8 @@ module BlobHelper
Gitlab::Highlight.new(blob_name, blob_content, nowrap: nowrap)
end
- def highlight(blob_name, blob_content, nowrap: false)
- Gitlab::Highlight.highlight(blob_name, blob_content, nowrap: nowrap)
+ def highlight(blob_name, blob_content, nowrap: false, plain: false)
+ Gitlab::Highlight.highlight(blob_name, blob_content, nowrap: nowrap, plain: plain)
end
def no_highlight_files
@@ -173,4 +173,15 @@ module BlobHelper
response.etag = @blob.id
!stale
end
+
+ def licenses_for_select
+ return @licenses_for_select if defined?(@licenses_for_select)
+
+ licenses = Licensee::License.all
+
+ @licenses_for_select = {
+ Popular: licenses.select(&:featured).map { |license| [license.name, license.key] },
+ Other: licenses.reject(&:featured).map { |license| [license.name, license.key] }
+ }
+ end
end
diff --git a/app/helpers/ci_badge_helper.rb b/app/helpers/ci_badge_helper.rb
deleted file mode 100644
index 27386133e36..00000000000
--- a/app/helpers/ci_badge_helper.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-module CiBadgeHelper
- def markdown_badge_code(project, ref)
- url = status_ci_project_url(project, ref: ref, format: 'png')
- link = namespace_project_commits_path(project.namespace, project, ref)
- "[![build status](#{url})](#{link})"
- end
-
- def html_badge_code(project, ref)
- url = status_ci_project_url(project, ref: ref, format: 'png')
- link = namespace_project_commits_path(project.namespace, project, ref)
- "<a href='#{link}'><img src='#{url}' /></a>"
- end
-end
diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb
index 8b1575d5e0c..417050b4132 100644
--- a/app/helpers/ci_status_helper.rb
+++ b/app/helpers/ci_status_helper.rb
@@ -4,14 +4,6 @@ module CiStatusHelper
builds_namespace_project_commit_path(project.namespace, project, ci_commit.sha)
end
- def ci_status_icon(ci_commit)
- ci_icon_for_status(ci_commit.status)
- end
-
- def ci_status_label(ci_commit)
- ci_label_for_status(ci_commit.status)
- end
-
def ci_status_with_icon(status, target = nil)
content = ci_icon_for_status(status) + '&nbsp;'.html_safe + ci_label_for_status(status)
klass = "ci-status ci-#{status}"
@@ -47,10 +39,13 @@ module CiStatusHelper
end
def render_ci_status(ci_commit, tooltip_placement: 'auto left')
- link_to ci_status_icon(ci_commit),
+ # TODO: split this method into
+ # - render_commit_status
+ # - render_pipeline_status
+ link_to ci_icon_for_status(ci_commit.status),
ci_status_path(ci_commit),
class: "ci-status-link ci-status-icon-#{ci_commit.status.dasherize}",
- title: "Build #{ci_status_label(ci_commit)}",
+ title: "Build #{ci_label_for_status(ci_commit.status)}",
data: { toggle: 'tooltip', placement: tooltip_placement }
end
diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb
index 6a3ec83b8c0..9f73edb4553 100644
--- a/app/helpers/diff_helper.rb
+++ b/app/helpers/diff_helper.rb
@@ -9,7 +9,13 @@ module DiffHelper
end
def diff_view
- params[:view] == 'parallel' ? 'parallel' : 'inline'
+ diff_views = %w(inline parallel)
+
+ if diff_views.include?(cookies[:diff_view])
+ cookies[:diff_view]
+ else
+ diff_views.first
+ end
end
def diff_hard_limit_enabled?
@@ -17,7 +23,7 @@ module DiffHelper
end
def diff_options
- options = { ignore_whitespace_change: params[:w] == '1' }
+ options = { ignore_whitespace_change: hide_whitespace? }
if diff_hard_limit_enabled?
options.merge!(Commit.max_diff_options)
end
@@ -122,4 +128,31 @@ module DiffHelper
title
end
end
+
+ def commit_diff_whitespace_link(project, commit, options)
+ url = namespace_project_commit_path(project.namespace, project, commit.id, params_with_whitespace)
+ toggle_whitespace_link(url, options)
+ end
+
+ def diff_merge_request_whitespace_link(project, merge_request, options)
+ url = diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, params_with_whitespace)
+ toggle_whitespace_link(url, options)
+ end
+
+ private
+
+ def hide_whitespace?
+ params[:w] == '1'
+ end
+
+ def params_with_whitespace
+ hide_whitespace? ? request.query_parameters.except(:w) : request.query_parameters.merge(w: 1)
+ end
+
+ def toggle_whitespace_link(url, options)
+ options[:class] ||= ''
+ options[:class] << ' btn btn-default'
+
+ link_to "#{hide_whitespace? ? 'Show' : 'Hide'} whitespace changes", url, class: options[:class]
+ end
end
diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb
index f3fddef01cb..f07eff3fb57 100644
--- a/app/helpers/gitlab_routing_helper.rb
+++ b/app/helpers/gitlab_routing_helper.rb
@@ -25,6 +25,10 @@ module GitlabRoutingHelper
namespace_project_commits_path(project.namespace, project, @ref || project.repository.root_ref)
end
+ def project_pipelines_path(project, *args)
+ namespace_project_pipelines_path(project.namespace, project, *args)
+ end
+
def project_builds_path(project, *args)
namespace_project_builds_path(project.namespace, project, *args)
end
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index 49b6b79ce35..39474217286 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -16,6 +16,25 @@ module IssuablesHelper
base_issuable_scope(issuable).where('iid > ?', issuable.iid).last
end
+ def multi_label_name(current_labels, default_label)
+ # current_labels may be a string from before
+ if current_labels.is_a?(Array)
+ if current_labels.count > 1
+ "#{current_labels[0]} +#{current_labels.count - 1} more"
+ else
+ current_labels[0]
+ end
+ elsif current_labels.is_a?(String)
+ if current_labels.nil? || current_labels.empty?
+ default_label
+ else
+ current_labels
+ end
+ else
+ default_label
+ end
+ end
+
def issuable_json_path(issuable)
project = issuable.project
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index 4cb8adcebad..198d39455d7 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -16,31 +16,49 @@ module IssuesHelper
def url_for_project_issues(project = @project, options = {})
return '' if project.nil?
- if options[:only_path]
- project.issues_tracker.project_path
- else
- project.issues_tracker.project_url
- end
+ url =
+ if options[:only_path]
+ project.issues_tracker.project_path
+ else
+ project.issues_tracker.project_url
+ end
+
+ # Ensure we return a valid URL to prevent possible XSS.
+ URI.parse(url).to_s
+ rescue URI::InvalidURIError
+ ''
end
def url_for_new_issue(project = @project, options = {})
return '' if project.nil?
- if options[:only_path]
- project.issues_tracker.new_issue_path
- else
- project.issues_tracker.new_issue_url
- end
+ url =
+ if options[:only_path]
+ project.issues_tracker.new_issue_path
+ else
+ project.issues_tracker.new_issue_url
+ end
+
+ # Ensure we return a valid URL to prevent possible XSS.
+ URI.parse(url).to_s
+ rescue URI::InvalidURIError
+ ''
end
def url_for_issue(issue_iid, project = @project, options = {})
return '' if project.nil?
- if options[:only_path]
- project.issues_tracker.issue_path(issue_iid)
- else
- project.issues_tracker.issue_url(issue_iid)
- end
+ url =
+ if options[:only_path]
+ project.issues_tracker.issue_path(issue_iid)
+ else
+ project.issues_tracker.issue_url(issue_iid)
+ end
+
+ # Ensure we return a valid URL to prevent possible XSS.
+ URI.parse(url).to_s
+ rescue URI::InvalidURIError
+ ''
end
def bulk_update_milestone_options
@@ -131,7 +149,7 @@ module IssuesHelper
class: "icon emoji-icon emoji-#{unicode}",
title: name,
data: data
- else
+ else
# Emoji icons displayed separately, used for the awards already given
# to an issue or merge request.
content_tag :img, "",
@@ -172,6 +190,18 @@ module IssuesHelper
end.to_h
end
+ def due_date_options
+ options = [
+ Issue::AnyDueDate,
+ Issue::NoDueDate,
+ Issue::DueThisWeek,
+ Issue::DueThisMonth,
+ Issue::Overdue
+ ]
+
+ options_from_collection_for_select(options, 'name', 'title', params[:due_date])
+ end
+
# Required for Banzai::Filter::IssueReferenceFilter
module_function :url_for_issue
end
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index 3dded7c2f23..c99b137cdaa 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -37,7 +37,7 @@ module LabelsHelper
link = send("namespace_project_#{type.to_s.pluralize}_path",
project.namespace,
project,
- label_name: label.name)
+ label_name: [label.name])
if block_given?
link_to link, &block
diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb
index 82f805fa444..e4e8b934bc8 100644
--- a/app/helpers/page_layout_helper.rb
+++ b/app/helpers/page_layout_helper.rb
@@ -84,6 +84,14 @@ module PageLayoutHelper
end
end
+ def nav(name = nil)
+ if name
+ @nav = name
+ else
+ @nav
+ end
+ end
+
def fluid_layout(enabled = false)
if @fluid_layout.nil?
@fluid_layout = (current_user && current_user.layout == "fluid") || enabled
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 4470aa16e3f..3d5e61d2c18 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -123,6 +123,18 @@ module ProjectsHelper
end
end
+ def license_short_name(project)
+ no_license_key = project.repository.license_key.nil? ||
+ # Back-compat if cache contains 'no-license', can be removed in a few weeks
+ project.repository.license_key == 'no-license'
+
+ return 'LICENSE' if no_license_key
+
+ license = Licensee::License.new(project.repository.license_key)
+
+ license.nickname || license.name
+ end
+
private
def get_project_nav_tabs(project, current_user)
@@ -144,6 +156,10 @@ module ProjectsHelper
nav_tabs << :settings
end
+ if can?(current_user, :read_project_member, project)
+ nav_tabs << :team
+ end
+
if can?(current_user, :read_issue, project)
nav_tabs << :issues
end
@@ -216,40 +232,14 @@ module ProjectsHelper
end
end
- def add_contribution_guide_path(project)
- if project && !project.repository.contribution_guide
- namespace_project_new_blob_path(
- project.namespace,
- project,
- project.default_branch,
- file_name: "CONTRIBUTING.md",
- commit_message: "Add contribution guide"
- )
- end
- end
-
- def add_changelog_path(project)
- if project && !project.repository.changelog
- namespace_project_new_blob_path(
- project.namespace,
- project,
- project.default_branch,
- file_name: "CHANGELOG",
- commit_message: "Add changelog"
- )
- end
- end
-
- def add_license_path(project)
- if project && !project.repository.license
- namespace_project_new_blob_path(
- project.namespace,
- project,
- project.default_branch,
- file_name: "LICENSE",
- commit_message: "Add license"
- )
- end
+ def add_special_file_path(project, file_name:, commit_message: nil)
+ namespace_project_new_blob_path(
+ project.namespace,
+ project,
+ project.default_branch || 'master',
+ file_name: file_name,
+ commit_message: commit_message || "Add #{file_name.downcase}"
+ )
end
def contribution_guide_path(project)
@@ -272,7 +262,7 @@ module ProjectsHelper
end
def license_path(project)
- filename_path(project, :license)
+ filename_path(project, :license_blob)
end
def version_path(project)
@@ -306,6 +296,13 @@ module ProjectsHelper
namespace_project_new_blob_path(@project.namespace, @project, tree_join(ref), file_name: 'README.md')
end
+ def new_license_path
+ ref = @repository.root_ref if @repository
+ ref ||= 'master'
+
+ namespace_project_new_blob_path(@project.namespace, @project, tree_join(ref), file_name: 'LICENSE')
+ end
+
def last_push_event
if current_user
current_user.recent_push(@project.id)
@@ -335,8 +332,6 @@ module ProjectsHelper
@ref || @repository.try(:root_ref)
end
- private
-
def filename_path(project, filename)
if project && blob = project.repository.send(filename)
namespace_project_blob_path(
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index 8a97a74ad73..24c4c098c65 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -19,6 +19,16 @@ module SearchHelper
end
end
+ def search_entries_info(collection, scope, term)
+ return unless collection.count > 0
+
+ from = collection.offset_value + 1
+ to = collection.offset_value + collection.length
+ count = collection.total_count
+
+ "Showing #{from} - #{to} of #{count} #{scope.humanize(capitalize: false)} for \"#{term}\""
+ end
+
private
# Autocomplete results for various settings pages
diff --git a/app/helpers/selects_helper.rb b/app/helpers/selects_helper.rb
index 4fc6de59a8b..e951a87a212 100644
--- a/app/helpers/selects_helper.rb
+++ b/app/helpers/selects_helper.rb
@@ -2,32 +2,29 @@ module SelectsHelper
def users_select_tag(id, opts = {})
css_class = "ajax-users-select "
css_class << "multiselect " if opts[:multiple]
+ css_class << "skip_ldap " if opts[:skip_ldap]
css_class << (opts[:class] || '')
value = opts[:selected] || ''
- placeholder = opts[:placeholder] || 'Search for a user'
- null_user = opts[:null_user] || false
- any_user = opts[:any_user] || false
- email_user = opts[:email_user] || false
- first_user = opts[:first_user] && current_user ? current_user.username : false
- current_user = opts[:current_user] || false
- author_id = opts[:author_id] || ''
- project = opts[:project] || @project
+ first_user = opts[:first_user] && current_user ? current_user.username : false
html = {
class: css_class,
data: {
- placeholder: placeholder,
- null_user: null_user,
- any_user: any_user,
- email_user: email_user,
+ placeholder: opts[:placeholder] || 'Search for a user',
+ null_user: opts[:null_user] || false,
+ any_user: opts[:any_user] || false,
+ email_user: opts[:email_user] || false,
first_user: first_user,
- current_user: current_user,
- author_id: author_id
+ current_user: opts[:current_user] || false,
+ "push-code-to-protected-branches" => opts[:push_code_to_protected_branches],
+ author_id: opts[:author_id] || ''
}
}
unless opts[:scope] == :all
+ project = opts[:project] || @project
+
if project
html['data-project-id'] = project.id
elsif @group
diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb
index 2f2d2721d6d..630e10ea892 100644
--- a/app/helpers/sorting_helper.rb
+++ b/app/helpers/sorting_helper.rb
@@ -8,6 +8,8 @@ module SortingHelper
sort_value_oldest_created => sort_title_oldest_created,
sort_value_milestone_soon => sort_title_milestone_soon,
sort_value_milestone_later => sort_title_milestone_later,
+ sort_value_due_date_soon => sort_title_due_date_soon,
+ sort_value_due_date_later => sort_title_due_date_later,
sort_value_largest_repo => sort_title_largest_repo,
sort_value_recently_signin => sort_title_recently_signin,
sort_value_oldest_signin => sort_title_oldest_signin,
@@ -50,6 +52,14 @@ module SortingHelper
'Milestone due later'
end
+ def sort_title_due_date_soon
+ 'Due soon'
+ end
+
+ def sort_title_due_date_later
+ 'Due later'
+ end
+
def sort_title_name
'Name'
end
@@ -98,6 +108,14 @@ module SortingHelper
'milestone_due_desc'
end
+ def sort_value_due_date_soon
+ 'due_date_asc'
+ end
+
+ def sort_value_due_date_later
+ 'due_date_desc'
+ end
+
def sort_value_name
'name_asc'
end
diff --git a/app/helpers/tab_helper.rb b/app/helpers/tab_helper.rb
index 04e53fe7c61..96a83671009 100644
--- a/app/helpers/tab_helper.rb
+++ b/app/helpers/tab_helper.rb
@@ -110,4 +110,12 @@ module TabHelper
'active'
end
end
+
+ def profile_tab_class
+ if controller.controller_path =~ /\Aprofiles/
+ return 'active'
+ end
+
+ 'active' if current_controller?('oauth/applications')
+ end
end
diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb
index cdc40b81ee1..96116e916dd 100644
--- a/app/mailers/emails/notes.rb
+++ b/app/mailers/emails/notes.rb
@@ -28,6 +28,14 @@ module Emails
mail_answer_thread(@merge_request, note_thread_options(recipient_id))
end
+ def note_snippet_email(recipient_id, note_id)
+ setup_note_mail(note_id, recipient_id)
+
+ @snippet = @note.noteable
+ @target_url = namespace_project_snippet_url(*note_target_url_options)
+ mail_answer_thread(@snippet, note_thread_options(recipient_id))
+ end
+
private
def note_target_url_options
diff --git a/app/models/blob.rb b/app/models/blob.rb
index 72e6c5fa3fd..0fea6b7f576 100644
--- a/app/models/blob.rb
+++ b/app/models/blob.rb
@@ -19,6 +19,14 @@ class Blob < SimpleDelegator
new(blob)
end
+ def no_highlighting?
+ size && size > 1.megabyte
+ end
+
+ def only_display_raw?
+ size && size > 5.megabytes
+ end
+
def svg?
text? && language && language.name == 'SVG'
end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 85ef0523b31..553cd447971 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -37,8 +37,6 @@
module Ci
class Build < CommitStatus
- LAZY_ATTRIBUTES = ['trace']
-
belongs_to :runner, class_name: 'Ci::Runner'
belongs_to :trigger_request, class_name: 'Ci::TriggerRequest'
belongs_to :erased_by, class_name: 'User'
@@ -50,25 +48,17 @@ module Ci
scope :unstarted, ->() { where(runner_id: nil) }
scope :ignore_failures, ->() { where(allow_failure: false) }
- scope :similar, ->(build) { where(ref: build.ref, tag: build.tag, trigger_request_id: build.trigger_request_id) }
mount_uploader :artifacts_file, ArtifactUploader
mount_uploader :artifacts_metadata, ArtifactUploader
acts_as_taggable
- # To prevent db load megabytes of data from trace
- default_scope -> { select(Ci::Build.columns_without_lazy) }
-
before_destroy { project }
- class << self
- def columns_without_lazy
- (column_names - LAZY_ATTRIBUTES).map do |column_name|
- "#{table_name}.#{column_name}"
- end
- end
+ after_create :execute_hooks
+ class << self
def last_month
where('created_at > ?', Date.today - 1.month)
end
@@ -126,12 +116,16 @@ module Ci
end
def retried?
- !self.commit.latest_statuses_for_ref(self.ref).include?(self)
+ !self.commit.statuses.latest.include?(self)
+ end
+
+ def retry
+ Ci::Build.retry(self)
end
def depends_on_builds
# Get builds of the same type
- latest_builds = self.commit.builds.similar(self).latest
+ latest_builds = self.commit.builds.latest
# Return builds from previous stages
latest_builds.where('stage_idx < ?', stage_idx)
@@ -230,12 +224,33 @@ module Ci
end
end
+ def trace_length
+ if raw_trace
+ raw_trace.length
+ else
+ 0
+ end
+ end
+
def trace=(trace)
+ recreate_trace_dir
+ File.write(path_to_trace, trace)
+ end
+
+ def recreate_trace_dir
unless Dir.exists?(dir_to_trace)
FileUtils.mkdir_p(dir_to_trace)
end
+ end
+ private :recreate_trace_dir
- File.write(path_to_trace, trace)
+ def append_trace(trace_part, offset)
+ recreate_trace_dir
+
+ File.truncate(path_to_trace, offset) if File.exist?(path_to_trace)
+ File.open(path_to_trace, 'a') do |f|
+ f.write(trace_part)
+ end
end
def dir_to_trace
diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb
index f4cf7034b14..f2667e5476b 100644
--- a/app/models/ci/commit.rb
+++ b/app/models/ci/commit.rb
@@ -19,21 +19,28 @@
module Ci
class Commit < ActiveRecord::Base
extend Ci::Model
+ include Statuseable
belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id
has_many :statuses, class_name: 'CommitStatus'
has_many :builds, class_name: 'Ci::Build'
has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest'
+ delegate :stages, to: :statuses
+
validates_presence_of :sha
+ validates_presence_of :status
validate :valid_commit_sha
+ # Invalidate object and save if when touched
+ after_touch :update_state
+
def self.truncate_sha(sha)
sha[0...8]
end
- def to_param
- sha
+ def self.stages
+ CommitStatus.where(commit: all).stages
end
def project_id
@@ -68,15 +75,20 @@ module Ci
nil
end
- def stage
- running_or_pending = statuses.latest.running_or_pending.ordered
- running_or_pending.first.try(:stage)
+ def branch?
+ !tag?
+ end
+
+ def retryable?
+ builds.latest.any? do |build|
+ build.failed? && build.retryable?
+ end
end
- def create_builds(ref, tag, user, trigger_request = nil)
+ def create_builds(user, trigger_request = nil)
return unless config_processor
config_processor.stages.any? do |stage|
- CreateBuildsService.new.execute(self, stage, ref, tag, user, trigger_request, 'success').present?
+ CreateBuildsService.new(self).execute(stage, user, 'success', trigger_request).present?
end
end
@@ -84,7 +96,7 @@ module Ci
return unless config_processor
# don't create other builds if this one is retried
- latest_builds = builds.similar(build).latest
+ latest_builds = builds.latest
return unless latest_builds.exists?(build.id)
# get list of stages after this build
@@ -92,88 +104,21 @@ module Ci
next_stages.delete(build.stage)
# get status for all prior builds
- prior_builds = latest_builds.reject { |other_build| next_stages.include?(other_build.stage) }
- status = Ci::Status.get_status(prior_builds)
+ prior_builds = latest_builds.where.not(stage: next_stages)
+ prior_status = prior_builds.status
# create builds for next stages based
next_stages.any? do |stage|
- CreateBuildsService.new.execute(self, stage, build.ref, build.tag, build.user, build.trigger_request, status).present?
+ CreateBuildsService.new(self).execute(stage, build.user, prior_status, build.trigger_request).present?
end
end
- def refs
- statuses.order(:ref).pluck(:ref).uniq
- end
-
- def latest_statuses
- @latest_statuses ||= statuses.latest.to_a
- end
-
- def latest_statuses_for_ref(ref)
- latest_statuses.select { |status| status.ref == ref }
- end
-
- def matrix_builds(build = nil)
- matrix_builds = builds.latest.ordered
- matrix_builds = matrix_builds.similar(build) if build
- matrix_builds.to_a
- end
-
def retried
@retried ||= (statuses.order(id: :desc) - statuses.latest)
end
- def status
- if yaml_errors.present?
- return 'failed'
- end
-
- @status ||= Ci::Status.get_status(latest_statuses)
- end
-
- def pending?
- status == 'pending'
- end
-
- def running?
- status == 'running'
- end
-
- def success?
- status == 'success'
- end
-
- def failed?
- status == 'failed'
- end
-
- def canceled?
- status == 'canceled'
- end
-
- def active?
- running? || pending?
- end
-
- def complete?
- canceled? || success? || failed?
- end
-
- def duration
- duration_array = statuses.map(&:duration).compact
- duration_array.reduce(:+).to_i
- end
-
- def started_at
- @started_at ||= statuses.order('started_at ASC').first.try(:started_at)
- end
-
- def finished_at
- @finished_at ||= statuses.order('finished_at DESC').first.try(:finished_at)
- end
-
def coverage
- coverage_array = latest_statuses.map(&:coverage).compact
+ coverage_array = statuses.latest.map(&:coverage).compact
if coverage_array.size >= 1
'%.2f' % (coverage_array.reduce(:+) / coverage_array.size)
end
@@ -181,23 +126,29 @@ module Ci
def config_processor
return nil unless ci_yaml_file
- @config_processor ||= Ci::GitlabCiYamlProcessor.new(ci_yaml_file, project.path_with_namespace)
- rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e
- save_yaml_error(e.message)
- nil
- rescue
- save_yaml_error("Undefined error")
- nil
+ return @config_processor if defined?(@config_processor)
+
+ @config_processor ||= begin
+ Ci::GitlabCiYamlProcessor.new(ci_yaml_file, project.path_with_namespace)
+ rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e
+ save_yaml_error(e.message)
+ nil
+ rescue
+ save_yaml_error("Undefined error")
+ nil
+ end
end
def ci_yaml_file
+ return @ci_yaml_file if defined?(@ci_yaml_file)
+
@ci_yaml_file ||= begin
blob = project.repository.blob_at(sha, '.gitlab-ci.yml')
blob.load_all_data!(project.repository)
blob.data
+ rescue
+ nil
end
- rescue
- nil
end
def skip_ci?
@@ -206,10 +157,23 @@ module Ci
private
+ def update_state
+ statuses.reload
+ self.status = if yaml_errors.blank?
+ statuses.latest.status || 'skipped'
+ else
+ 'failed'
+ end
+ self.started_at = statuses.started_at
+ self.finished_at = statuses.finished_at
+ self.duration = statuses.latest.duration
+ save
+ end
+
def save_yaml_error(error)
return if self.yaml_errors?
self.yaml_errors = error
- save
+ update_state
end
end
end
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 6bb018b086f..562c3ed15b2 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -207,12 +207,13 @@ class Commit
@raw.short_id(7)
end
- def ci_commit
- project.ci_commit(sha)
+ def ci_commits
+ @ci_commits ||= project.ci_commits.where(sha: sha)
end
def status
- ci_commit.try(:status) || :not_found
+ return @status if defined?(@status)
+ @status ||= ci_commits.status
end
def revert_branch_name
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index 3377a85a55a..aa56314aa16 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -33,30 +33,23 @@
#
class CommitStatus < ActiveRecord::Base
+ include Statuseable
+
self.table_name = 'ci_builds'
belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id
- belongs_to :commit, class_name: 'Ci::Commit'
+ belongs_to :commit, class_name: 'Ci::Commit', touch: true
belongs_to :user
validates :commit, presence: true
- validates :status, inclusion: { in: %w(pending running failed success canceled) }
validates_presence_of :name
alias_attribute :author, :user
- scope :running, -> { where(status: 'running') }
- scope :pending, -> { where(status: 'pending') }
- scope :success, -> { where(status: 'success') }
- scope :failed, -> { where(status: 'failed') }
- scope :running_or_pending, -> { where(status: [:running, :pending]) }
- scope :finished, -> { where(status: [:success, :failed, :canceled]) }
- scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name, :ref)) }
+ scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name, :commit_id)) }
scope :ordered, -> { order(:ref, :stage_idx, :name) }
- scope :for_ref, ->(ref) { where(ref: ref) }
-
- AVAILABLE_STATUSES = ['pending', 'running', 'success', 'failed', 'canceled']
+ scope :ignored, -> { where(allow_failure: true, status: [:failed, :canceled]) }
state_machine :status, initial: :pending do
event :run do
@@ -86,31 +79,24 @@ class CommitStatus < ActiveRecord::Base
after_transition [:pending, :running] => :success do |commit_status|
MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.commit.project, nil).trigger(commit_status)
end
-
- state :pending, value: 'pending'
- state :running, value: 'running'
- state :failed, value: 'failed'
- state :success, value: 'success'
- state :canceled, value: 'canceled'
end
- delegate :sha, :short_sha, to: :commit, prefix: false
+ delegate :sha, :short_sha, to: :commit
- # TODO: this should be removed with all references
def before_sha
- Gitlab::Git::BLANK_SHA
+ commit.before_sha || Gitlab::Git::BLANK_SHA
end
- def started?
- !pending? && !canceled? && started_at
+ def self.stages
+ order_by = 'max(stage_idx)'
+ group('stage').order(order_by).pluck(:stage, order_by).map(&:first).compact
end
- def active?
- running? || pending?
- end
-
- def complete?
- canceled? || success? || failed?
+ def self.stages_status
+ all.stages.inject({}) do |h, stage|
+ h[stage] = all.where(stage: stage).status
+ h
+ end
end
def ignored?
@@ -118,11 +104,13 @@ class CommitStatus < ActiveRecord::Base
end
def duration
- if started_at && finished_at
- finished_at - started_at
- elsif started_at
- Time.now - started_at
- end
+ duration =
+ if started_at && finished_at
+ finished_at - started_at
+ elsif started_at
+ Time.now - started_at
+ end
+ duration
end
def stuck?
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index afa2ca039ae..d5166e81474 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -37,7 +37,6 @@ module Issuable
scope :closed, -> { with_state(:closed) }
scope :order_milestone_due_desc, -> { joins(:milestone).reorder('milestones.due_date DESC, milestones.id DESC') }
scope :order_milestone_due_asc, -> { joins(:milestone).reorder('milestones.due_date ASC, milestones.id ASC') }
- scope :with_label, ->(title) { joins(:labels).where(labels: { title: title }) }
scope :without_label, -> { joins("LEFT OUTER JOIN label_links ON label_links.target_type = '#{name}' AND label_links.target_id = #{table_name}.id").where(label_links: { id: nil }) }
scope :join_project, -> { joins(:project) }
@@ -122,6 +121,14 @@ module Issuable
joins(join_clause).group(issuable_table[:id]).reorder("COUNT(notes.id) DESC")
end
+
+ def with_label(title)
+ if title.is_a?(Array) && title.count > 1
+ joins(:labels).where(labels: { title: title }).group('issues.id').having("count(distinct labels.title) = #{title.count}")
+ else
+ joins(:labels).where(labels: { title: title })
+ end
+ end
end
def today?
diff --git a/app/models/concerns/milestoneish.rb b/app/models/concerns/milestoneish.rb
index 5b8e3f654ea..7bcc78247ba 100644
--- a/app/models/concerns/milestoneish.rb
+++ b/app/models/concerns/milestoneish.rb
@@ -8,7 +8,7 @@ module Milestoneish
end
def complete?(user = nil)
- total_items_count(user) == closed_items_count(user)
+ total_items_count(user) > 0 && total_items_count(user) == closed_items_count(user)
end
def percent_complete(user = nil)
diff --git a/app/models/concerns/statuseable.rb b/app/models/concerns/statuseable.rb
new file mode 100644
index 00000000000..3ef91caad47
--- /dev/null
+++ b/app/models/concerns/statuseable.rb
@@ -0,0 +1,81 @@
+module Statuseable
+ extend ActiveSupport::Concern
+
+ AVAILABLE_STATUSES = %w(pending running success failed canceled skipped)
+
+ class_methods do
+ def status_sql
+ builds = all.select('count(*)').to_sql
+ success = all.success.select('count(*)').to_sql
+ ignored = all.ignored.select('count(*)').to_sql if all.respond_to?(:ignored)
+ ignored ||= '0'
+ pending = all.pending.select('count(*)').to_sql
+ running = all.running.select('count(*)').to_sql
+ canceled = all.canceled.select('count(*)').to_sql
+ skipped = all.skipped.select('count(*)').to_sql
+
+ deduce_status = "(CASE
+ WHEN (#{builds})=0 THEN NULL
+ WHEN (#{builds})=(#{success})+(#{ignored}) THEN 'success'
+ WHEN (#{builds})=(#{pending}) THEN 'pending'
+ WHEN (#{builds})=(#{canceled})+(#{success})+(#{ignored}) THEN 'canceled'
+ WHEN (#{builds})=(#{skipped}) THEN 'skipped'
+ WHEN (#{running})+(#{pending})>0 THEN 'running'
+ ELSE 'failed'
+ END)"
+
+ deduce_status
+ end
+
+ def status
+ all.pluck(self.status_sql).first
+ end
+
+ def duration
+ duration_array = all.map(&:duration).compact
+ duration_array.reduce(:+)
+ end
+
+ def started_at
+ all.minimum(:started_at)
+ end
+
+ def finished_at
+ all.maximum(:finished_at)
+ end
+ end
+
+ included do
+ validates :status, inclusion: { in: AVAILABLE_STATUSES }
+
+ state_machine :status, initial: :pending do
+ state :pending, value: 'pending'
+ state :running, value: 'running'
+ state :failed, value: 'failed'
+ state :success, value: 'success'
+ state :canceled, value: 'canceled'
+ state :skipped, value: 'skipped'
+ end
+
+ scope :running, -> { where(status: 'running') }
+ scope :pending, -> { where(status: 'pending') }
+ scope :success, -> { where(status: 'success') }
+ scope :failed, -> { where(status: 'failed') }
+ scope :canceled, -> { where(status: 'canceled') }
+ scope :skipped, -> { where(status: 'skipped') }
+ scope :running_or_pending, -> { where(status: [:running, :pending]) }
+ scope :finished, -> { where(status: [:success, :failed, :canceled]) }
+ end
+
+ def started?
+ !pending? && !canceled? && started_at
+ end
+
+ def active?
+ running? || pending?
+ end
+
+ def complete?
+ canceled? || success? || failed?
+ end
+end
diff --git a/app/models/event.rb b/app/models/event.rb
index 12183524b79..897518aadc7 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -345,7 +345,7 @@ class Event < ActiveRecord::Base
end
def reset_project_activity
- if project
+ if project && Gitlab::ExclusiveLease.new("project:update_last_activity_at:#{project.id}", timeout: 60).try_obtain
project.update_column(:last_activity_at, self.created_at)
end
end
diff --git a/app/models/hooks/project_hook.rb b/app/models/hooks/project_hook.rb
index 7365e360de2..d149511b868 100644
--- a/app/models/hooks/project_hook.rb
+++ b/app/models/hooks/project_hook.rb
@@ -16,6 +16,7 @@
# note_events :boolean default(FALSE), not null
# enable_ssl_verification :boolean default(TRUE)
# build_events :boolean default(FALSE), not null
+# token :string
#
class ProjectHook < WebHook
@@ -25,4 +26,5 @@ class ProjectHook < WebHook
scope :note_hooks, -> { where(note_events: true) }
scope :merge_request_hooks, -> { where(merge_requests_events: true) }
scope :build_hooks, -> { where(build_events: true) }
+ scope :wiki_page_hooks, -> { where(wiki_page_events: true) }
end
diff --git a/app/models/hooks/service_hook.rb b/app/models/hooks/service_hook.rb
index 80962264ba2..f45145eeb3a 100644
--- a/app/models/hooks/service_hook.rb
+++ b/app/models/hooks/service_hook.rb
@@ -16,6 +16,7 @@
# note_events :boolean default(FALSE), not null
# enable_ssl_verification :boolean default(TRUE)
# build_events :boolean default(FALSE), not null
+# token :string
#
class ServiceHook < WebHook
diff --git a/app/models/hooks/system_hook.rb b/app/models/hooks/system_hook.rb
index 15dddcc2447..012cc8ec005 100644
--- a/app/models/hooks/system_hook.rb
+++ b/app/models/hooks/system_hook.rb
@@ -16,6 +16,7 @@
# note_events :boolean default(FALSE), not null
# enable_ssl_verification :boolean default(TRUE)
# build_events :boolean default(FALSE), not null
+# token :string
#
class SystemHook < WebHook
diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb
index 3a2e4f546f7..1e3b4815596 100644
--- a/app/models/hooks/web_hook.rb
+++ b/app/models/hooks/web_hook.rb
@@ -16,6 +16,7 @@
# note_events :boolean default(FALSE), not null
# enable_ssl_verification :boolean default(TRUE)
# build_events :boolean default(FALSE), not null
+# token :string
#
class WebHook < ActiveRecord::Base
@@ -43,23 +44,17 @@ class WebHook < ActiveRecord::Base
if parsed_url.userinfo.blank?
response = WebHook.post(url,
body: data.to_json,
- headers: {
- "Content-Type" => "application/json",
- "X-Gitlab-Event" => hook_name.singularize.titleize
- },
+ headers: build_headers(hook_name),
verify: enable_ssl_verification)
else
- post_url = url.gsub("#{parsed_url.userinfo}@", "")
+ post_url = url.gsub("#{parsed_url.userinfo}@", '')
auth = {
username: CGI.unescape(parsed_url.user),
password: CGI.unescape(parsed_url.password),
}
response = WebHook.post(post_url,
body: data.to_json,
- headers: {
- "Content-Type" => "application/json",
- "X-Gitlab-Event" => hook_name.singularize.titleize
- },
+ headers: build_headers(hook_name),
verify: enable_ssl_verification,
basic_auth: auth)
end
@@ -73,4 +68,15 @@ class WebHook < ActiveRecord::Base
def async_execute(data, hook_name)
Sidekiq::Client.enqueue(ProjectWebHookWorker, id, data, hook_name)
end
+
+ private
+
+ def build_headers(hook_name)
+ headers = {
+ 'Content-Type' => 'application/json',
+ 'X-Gitlab-Event' => hook_name.singularize.titleize
+ }
+ headers['X-Gitlab-Token'] = token if token.present?
+ headers
+ end
end
diff --git a/app/models/issue.rb b/app/models/issue.rb
index a009e235b37..ea1bfb776ee 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -28,6 +28,13 @@ class Issue < ActiveRecord::Base
include Sortable
include Taskable
+ DueDateStruct = Struct.new(:title, :name).freeze
+ NoDueDate = DueDateStruct.new('No Due Date', '0').freeze
+ AnyDueDate = DueDateStruct.new('Any Due Date', '').freeze
+ Overdue = DueDateStruct.new('Overdue', 'overdue').freeze
+ DueThisWeek = DueDateStruct.new('Due This Week', 'week').freeze
+ DueThisMonth = DueDateStruct.new('Due This Month', 'month').freeze
+
ActsAsTaggableOn.strict_case_match = true
belongs_to :project
@@ -39,6 +46,13 @@ class Issue < ActiveRecord::Base
scope :open_for, ->(user) { opened.assigned_to(user) }
scope :in_projects, ->(project_ids) { where(project_id: project_ids) }
+ scope :without_due_date, -> { where(due_date: nil) }
+ scope :due_before, ->(date) { where('issues.due_date < ?', date) }
+ scope :due_between, ->(from_date, to_date) { where('issues.due_date >= ?', from_date).where('issues.due_date <= ?', to_date) }
+
+ scope :order_due_date_asc, -> { reorder('issues.due_date IS NULL, issues.due_date ASC') }
+ scope :order_due_date_desc, -> { reorder('issues.due_date IS NULL, issues.due_date DESC') }
+
state_machine :state, initial: :opened do
event :close do
transition [:reopened, :opened] => :closed
@@ -82,6 +96,15 @@ class Issue < ActiveRecord::Base
@link_reference_pattern ||= super("issues", /(?<issue>\d+)/)
end
+ def self.sort(method)
+ case method.to_s
+ when 'due_date_asc' then order_due_date_asc
+ when 'due_date_desc' then order_due_date_desc
+ else
+ super
+ end
+ end
+
def to_reference(from_project = nil)
reference = "#{self.class.reference_prefix}#{iid}"
@@ -169,4 +192,8 @@ class Issue < ActiveRecord::Base
self.related_branches(current_user).empty? &&
self.closed_by_merge_requests(current_user).empty?
end
+
+ def overdue?
+ due_date.try(:past?) || false
+ end
end
diff --git a/app/models/label.rb b/app/models/label.rb
index 55c01cae762..60bdce32952 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -113,6 +113,10 @@ class Label < ActiveRecord::Base
template
end
+ def text_color
+ LabelsHelper::text_color_for_bg(self.color)
+ end
+
private
def label_format_reference(format = :id)
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 47da154dd81..d00919c3b0c 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -586,7 +586,7 @@ class MergeRequest < ActiveRecord::Base
end
def ci_commit
- @ci_commit ||= source_project.ci_commit(last_commit.id) if last_commit && source_project
+ @ci_commit ||= source_project.ci_commit(last_commit.id, source_branch) if last_commit && source_project
end
def diff_refs
@@ -602,4 +602,8 @@ class MergeRequest < ActiveRecord::Base
def can_be_reverted?(current_user = nil)
merge_commit && !merge_commit.has_been_reverted?(current_user, self)
end
+
+ def can_be_cherry_picked?
+ merge_commit
+ end
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 8f0272d2ce0..af62e8ecd90 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -735,19 +735,17 @@ class Project < ActiveRecord::Base
end
def open_branches
- all_branches = repository.branches
+ # We're using a Set here as checking values in a large Set is faster than
+ # checking values in a large Array.
+ protected_set = Set.new(protected_branch_names)
- if protected_branches.present?
- all_branches.reject! do |branch|
- protected_branches_names.include?(branch.name)
- end
+ repository.branches.reject do |branch|
+ protected_set.include?(branch.name)
end
-
- all_branches
end
- def protected_branches_names
- @protected_branches_names ||= protected_branches.map(&:name)
+ def protected_branch_names
+ @protected_branch_names ||= protected_branches.pluck(:name)
end
def root_ref?(branch)
@@ -764,7 +762,7 @@ class Project < ActiveRecord::Base
# Check if current branch name is marked as protected in the system
def protected_branch?(branch_name)
- protected_branches_names.include?(branch_name)
+ protected_branches.where(name: branch_name).any?
end
def developers_can_push_to_protected_branch?(branch_name)
@@ -820,13 +818,11 @@ class Project < ActiveRecord::Base
wiki = Repository.new("#{old_path}.wiki", self)
if repo.exists?
- repo.expire_cache
- repo.expire_emptiness_caches
+ repo.before_delete
end
if wiki.exists?
- wiki.expire_cache
- wiki.expire_emptiness_caches
+ wiki.before_delete
end
end
@@ -903,6 +899,7 @@ class Project < ActiveRecord::Base
repository.rugged.references.create('HEAD',
"refs/heads/#{branch}",
force: true)
+ repository.copy_gitattributes(branch)
reload_default_branch
end
@@ -963,12 +960,12 @@ class Project < ActiveRecord::Base
!namespace.share_with_group_lock
end
- def ci_commit(sha)
- ci_commits.find_by(sha: sha)
+ def ci_commit(sha, ref)
+ ci_commits.order(id: :desc).find_by(sha: sha, ref: ref)
end
- def ensure_ci_commit(sha)
- ci_commit(sha) || ci_commits.create(sha: sha)
+ def ensure_ci_commit(sha, ref)
+ ci_commit(sha, ref) || ci_commits.create(sha: sha, ref: ref)
end
def enable_ci
diff --git a/app/models/project_services/buildkite_service.rb b/app/models/project_services/buildkite_service.rb
index 3efbfd2eec3..861cc974ec4 100644
--- a/app/models/project_services/buildkite_service.rb
+++ b/app/models/project_services/buildkite_service.rb
@@ -26,7 +26,7 @@ class BuildkiteService < CiService
prop_accessor :project_url, :token, :enable_ssl_verification
- validates :project_url, presence: true, if: :activated?
+ validates :project_url, presence: true, url: true, if: :activated?
validates :token, presence: true, if: :activated?
after_save :compose_service_hook, if: :activated?
@@ -91,7 +91,7 @@ class BuildkiteService < CiService
{ type: 'text',
name: 'project_url',
placeholder: "#{ENDPOINT}/example/project" },
-
+
{ type: 'checkbox',
name: 'enable_ssl_verification',
title: "Enable SSL verification" }
diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb
index 25045224ce5..c5501e06411 100644
--- a/app/models/project_services/issue_tracker_service.rb
+++ b/app/models/project_services/issue_tracker_service.rb
@@ -21,7 +21,7 @@
class IssueTrackerService < Service
- validates :project_url, :issues_url, :new_issue_url, presence: true, if: :activated?
+ validates :project_url, :issues_url, :new_issue_url, presence: true, url: true, if: :activated?
default_value_for :category, 'issue_tracker'
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index 1ed42c4f3e7..b4418ba9284 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -28,6 +28,8 @@ class JiraService < IssueTrackerService
prop_accessor :username, :password, :api_url, :jira_issue_transition_id,
:title, :description, :project_url, :issues_url, :new_issue_url
+ validates :api_url, presence: true, url: true, if: :activated?
+
before_validation :set_api_url, :set_jira_issue_transition_id
before_update :reset_password
diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb
index d89cf6d17b2..7092b757549 100644
--- a/app/models/project_services/slack_service.rb
+++ b/app/models/project_services/slack_service.rb
@@ -22,7 +22,7 @@
class SlackService < Service
prop_accessor :webhook, :username, :channel
boolean_accessor :notify_only_broken_builds
- validates :webhook, presence: true, if: :activated?
+ validates :webhook, presence: true, url: true, if: :activated?
def initialize_properties
if properties.nil?
@@ -60,7 +60,7 @@ class SlackService < Service
end
def supported_events
- %w(push issue merge_request note tag_push build)
+ %w(push issue merge_request note tag_push build wiki_page)
end
def execute(data)
@@ -90,6 +90,8 @@ class SlackService < Service
NoteMessage.new(data)
when "build"
BuildMessage.new(data) if should_build_be_notified?(data)
+ when "wiki_page"
+ WikiPageMessage.new(data)
end
opt = {}
@@ -133,3 +135,4 @@ require "slack_service/push_message"
require "slack_service/merge_message"
require "slack_service/note_message"
require "slack_service/build_message"
+require "slack_service/wiki_page_message"
diff --git a/app/models/project_services/slack_service/wiki_page_message.rb b/app/models/project_services/slack_service/wiki_page_message.rb
new file mode 100644
index 00000000000..f336d9e7691
--- /dev/null
+++ b/app/models/project_services/slack_service/wiki_page_message.rb
@@ -0,0 +1,53 @@
+class SlackService
+ class WikiPageMessage < BaseMessage
+ attr_reader :user_name
+ attr_reader :title
+ attr_reader :project_name
+ attr_reader :project_url
+ attr_reader :wiki_page_url
+ attr_reader :action
+ attr_reader :description
+
+ def initialize(params)
+ @user_name = params[:user][:name]
+ @project_name = params[:project_name]
+ @project_url = params[:project_url]
+
+ obj_attr = params[:object_attributes]
+ obj_attr = HashWithIndifferentAccess.new(obj_attr)
+ @title = obj_attr[:title]
+ @wiki_page_url = obj_attr[:url]
+ @description = obj_attr[:content]
+
+ @action =
+ case obj_attr[:action]
+ when "create"
+ "created"
+ when "update"
+ "edited"
+ end
+ end
+
+ def attachments
+ description_message
+ end
+
+ private
+
+ def message
+ "#{user_name} #{action} #{wiki_page_link} in #{project_link}: *#{title}*"
+ end
+
+ def description_message
+ [{ text: format(@description), color: attachment_color }]
+ end
+
+ def project_link
+ "[#{project_name}](#{project_url})"
+ end
+
+ def wiki_page_link
+ "[wiki page](#{wiki_page_url})"
+ end
+ end
+end
diff --git a/app/models/project_snippet.rb b/app/models/project_snippet.rb
index 1f7d85a5f3d..d48f0546159 100644
--- a/app/models/project_snippet.rb
+++ b/app/models/project_snippet.rb
@@ -22,4 +22,6 @@ class ProjectSnippet < Snippet
# Scopes
scope :fresh, -> { order("created_at DESC") }
+
+ participant :author, :notes
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index c6d7a439c05..b4319297e43 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -228,7 +228,8 @@ class Repository
def cache_keys
%i(size branch_names tag_names commit_count
- readme version contribution_guide changelog license)
+ readme version contribution_guide changelog
+ license_blob license_key)
end
def build_cache
@@ -456,32 +457,26 @@ class Repository
def changelog
cache.fetch(:changelog) do
tree(:head).blobs.find do |file|
- file.name =~ /\A(changelog|history)/i
+ file.name =~ /\A(changelog|history|changes|news)/i
end
end
end
- def license
- cache.fetch(:license) do
- licenses = tree(:head).blobs.find_all do |file|
- file.name =~ /\A(copying|license|licence)/i
- end
-
- preferences = [
- /\Alicen[sc]e\z/i, # LICENSE, LICENCE
- /\Alicen[sc]e\./i, # LICENSE.md, LICENSE.txt
- /\Acopying\z/i, # COPYING
- /\Acopying\.(?!lesser)/i, # COPYING.txt
- /Acopying.lesser/i # COPYING.LESSER
- ]
+ def license_blob
+ return nil if !exists? || empty?
- license = nil
- preferences.each do |r|
- license = licenses.find { |l| l.name =~ r }
- break if license
+ cache.fetch(:license_blob) do
+ tree(:head).blobs.find do |file|
+ file.name =~ /\A(licen[sc]e|copying)(\..+|\z)/i
end
+ end
+ end
+
+ def license_key
+ return nil if !exists? || empty?
- license
+ cache.fetch(:license_key) do
+ Licensee.license(path).try(:key)
end
end
@@ -943,6 +938,16 @@ class Repository
raw_repository.ls_files(actual_ref)
end
+ def copy_gitattributes(ref)
+ actual_ref = ref || root_ref
+ begin
+ raw_repository.copy_gitattributes(actual_ref)
+ true
+ rescue Gitlab::Git::Repository::InvalidRef
+ false
+ end
+ end
+
def main_language
return if empty? || rugged.head_unborn?
diff --git a/app/models/service.rb b/app/models/service.rb
index 721273250ea..2645b8321d7 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -32,6 +32,7 @@ class Service < ActiveRecord::Base
default_value_for :tag_push_events, true
default_value_for :note_events, true
default_value_for :build_events, true
+ default_value_for :wiki_page_events, true
after_initialize :initialize_properties
@@ -53,6 +54,7 @@ class Service < ActiveRecord::Base
scope :merge_request_hooks, -> { where(merge_requests_events: true, active: true) }
scope :note_hooks, -> { where(note_events: true, active: true) }
scope :build_hooks, -> { where(build_events: true, active: true) }
+ scope :wiki_page_hooks, -> { where(wiki_page_events: true, active: true) }
default_value_for :category, 'common'
@@ -94,7 +96,7 @@ class Service < ActiveRecord::Base
end
def supported_events
- %w(push tag_push issue merge_request)
+ %w(push tag_push issue merge_request wiki_page)
end
def execute(data)
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index b96e3937281..0fd08061925 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -112,6 +112,10 @@ class Snippet < ActiveRecord::Base
visibility_level
end
+ def no_highlighting?
+ content.lines.count > 1000
+ end
+
class << self
# Searches for snippets with a matching title or file name.
#
diff --git a/app/models/user.rb b/app/models/user.rb
index ab48f8f1960..b6f405c6981 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -91,7 +91,7 @@ class User < ActiveRecord::Base
devise :two_factor_backupable, otp_number_of_backup_codes: 10
serialize :otp_backup_codes, JSON
- devise :lockable, :async, :recoverable, :rememberable, :trackable,
+ devise :lockable, :recoverable, :rememberable, :trackable,
:validatable, :omniauthable, :confirmable, :registerable
attr_accessor :force_random_password
diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb
index 526760779a4..3d5fd9d3ee9 100644
--- a/app/models/wiki_page.rb
+++ b/app/models/wiki_page.rb
@@ -29,6 +29,10 @@ class WikiPage
# new Page values before writing to the Gollum repository.
attr_accessor :attributes
+ def hook_attrs
+ attributes
+ end
+
def initialize(wiki, page = nil, persisted = false)
@wiki = wiki
@page = page
diff --git a/app/services/ci/create_builds_service.rb b/app/services/ci/create_builds_service.rb
index 2cd51a7610f..18274ce24e2 100644
--- a/app/services/ci/create_builds_service.rb
+++ b/app/services/ci/create_builds_service.rb
@@ -1,7 +1,11 @@
module Ci
class CreateBuildsService
- def execute(commit, stage, ref, tag, user, trigger_request, status)
- builds_attrs = commit.config_processor.builds_for_stage_and_ref(stage, ref, tag, trigger_request)
+ def initialize(commit)
+ @commit = commit
+ end
+
+ def execute(stage, user, status, trigger_request = nil)
+ builds_attrs = config_processor.builds_for_stage_and_ref(stage, @commit.ref, @commit.tag, trigger_request)
# check when to create next build
builds_attrs = builds_attrs.select do |build_attrs|
@@ -17,7 +21,8 @@ module Ci
builds_attrs.map do |build_attrs|
# don't create the same build twice
- unless commit.builds.find_by(ref: ref, tag: tag, trigger_request: trigger_request, name: build_attrs[:name])
+ unless @commit.builds.find_by(ref: @commit.ref, tag: @commit.tag,
+ trigger_request: trigger_request, name: build_attrs[:name])
build_attrs.slice!(:name,
:commands,
:tag_list,
@@ -26,17 +31,21 @@ module Ci
:stage,
:stage_idx)
- build_attrs.merge!(ref: ref,
- tag: tag,
+ build_attrs.merge!(ref: @commit.ref,
+ tag: @commit.tag,
trigger_request: trigger_request,
user: user,
- project: commit.project)
+ project: @commit.project)
- build = commit.builds.create!(build_attrs)
- build.execute_hooks
- build
+ @commit.builds.create!(build_attrs)
end
end
end
+
+ private
+
+ def config_processor
+ @config_processor ||= @commit.config_processor
+ end
end
end
diff --git a/app/services/ci/create_trigger_request_service.rb b/app/services/ci/create_trigger_request_service.rb
index b3dfc707221..993acf11db9 100644
--- a/app/services/ci/create_trigger_request_service.rb
+++ b/app/services/ci/create_trigger_request_service.rb
@@ -7,14 +7,14 @@ module Ci
# check if ref is tag
tag = project.repository.find_tag(ref).present?
- ci_commit = project.ensure_ci_commit(commit.sha)
+ ci_commit = project.ci_commits.create(sha: commit.sha, ref: ref, tag: tag)
trigger_request = trigger.trigger_requests.create!(
variables: variables,
commit: ci_commit,
)
- if ci_commit.create_builds(ref, tag, nil, trigger_request)
+ if ci_commit.create_builds(nil, trigger_request)
trigger_request
end
end
diff --git a/app/services/ci/image_for_build_service.rb b/app/services/ci/image_for_build_service.rb
index 50c95ced8a7..3018f27ec05 100644
--- a/app/services/ci/image_for_build_service.rb
+++ b/app/services/ci/image_for_build_service.rb
@@ -3,8 +3,9 @@ module Ci
def execute(project, opts)
sha = opts[:sha] || ref_sha(project, opts[:ref])
- commit = project.ci_commits.find_by(sha: sha)
- image_name = image_for_commit(commit)
+ ci_commits = project.ci_commits.where(sha: sha)
+ ci_commits = ci_commits.where(ref: opts[:ref]) if opts[:ref]
+ image_name = image_for_status(ci_commits.status)
image_path = Rails.root.join('public/ci', image_name)
OpenStruct.new(path: image_path, name: image_name)
@@ -16,9 +17,9 @@ module Ci
project.commit(ref).try(:sha) if ref
end
- def image_for_commit(commit)
- return 'build-unknown.svg' unless commit
- 'build-' + commit.status + ".svg"
+ def image_for_status(status)
+ status ||= 'unknown'
+ 'build-' + status + ".svg"
end
end
end
diff --git a/app/services/create_commit_builds_service.rb b/app/services/create_commit_builds_service.rb
index 69d5c42a877..0d2aa1ff03d 100644
--- a/app/services/create_commit_builds_service.rb
+++ b/app/services/create_commit_builds_service.rb
@@ -2,6 +2,7 @@ class CreateCommitBuildsService
def execute(project, user, params)
return false unless project.builds_enabled?
+ before_sha = params[:checkout_sha] || params[:before]
sha = params[:checkout_sha] || params[:after]
origin_ref = params[:ref]
@@ -10,15 +11,16 @@ class CreateCommitBuildsService
end
ref = Gitlab::Git.ref_name(origin_ref)
+ tag = Gitlab::Git.tag_ref?(origin_ref)
# Skip branch removal
if sha == Gitlab::Git::BLANK_SHA
return false
end
- commit = project.ci_commit(sha)
+ commit = project.ci_commit(sha, ref)
unless commit
- commit = project.ci_commits.new(sha: sha)
+ commit = project.ci_commits.new(sha: sha, ref: ref, before_sha: before_sha, tag: tag)
# Skip creating ci_commit when no gitlab-ci.yml is found
unless commit.ci_yaml_file
@@ -32,10 +34,10 @@ class CreateCommitBuildsService
# Skip creating builds for commits that have [ci skip]
unless commit.skip_ci?
# Create builds for commit
- tag = Gitlab::Git.tag_ref?(origin_ref)
- commit.create_builds(ref, tag, user)
+ commit.create_builds(user)
end
+ commit.touch
commit
end
end
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index 1e1be8cd04b..b7af80055bf 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -42,7 +42,12 @@ class GitPushService < BaseService
# Collect data for this git push
@push_commits = @project.repository.commits_between(params[:oldrev], params[:newrev])
process_commit_messages
+
+ # Update the bare repositories info/attributes file using the contents of the default branches
+ # .gitattributes file
+ update_gitattributes if is_default_branch?
end
+
# Update merge requests that may be affected by this push. A new branch
# could cause the last commit of a merge request to change.
update_merge_requests
@@ -54,6 +59,10 @@ class GitPushService < BaseService
perform_housekeeping
end
+ def update_gitattributes
+ @project.repository.copy_gitattributes(params[:ref])
+ end
+
def update_main_language
# Performance can be bad so for now only check main_language once
# See https://gitlab.com/gitlab-org/gitlab-ce/issues/14937
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 18f76d3f650..2b16089df1b 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -37,8 +37,9 @@ class IssuableBaseService < BaseService
end
def filter_params(issuable_ability_name = :issue)
- params[:assignee_id] = "" if params[:assignee_id] == IssuableFinder::NONE
- params[:milestone_id] = "" if params[:milestone_id] == IssuableFinder::NONE
+ filter_assignee
+ filter_milestone
+ filter_labels
ability = :"admin_#{issuable_ability_name}"
@@ -49,6 +50,29 @@ class IssuableBaseService < BaseService
end
end
+ def filter_assignee
+ if params[:assignee_id] == IssuableFinder::NONE
+ params[:assignee_id] = ''
+ end
+ end
+
+ def filter_milestone
+ milestone_id = params[:milestone_id]
+ return unless milestone_id
+
+ if milestone_id == IssuableFinder::NONE ||
+ project.milestones.find_by(id: milestone_id).nil?
+ params[:milestone_id] = ''
+ end
+ end
+
+ def filter_labels
+ return if params[:label_ids].to_a.empty?
+
+ params[:label_ids] =
+ project.labels.where(id: params[:label_ids]).pluck(:id)
+ end
+
def update(issuable)
change_state(issuable)
filter_params
diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb
index fa34753c4fd..3544752d47a 100644
--- a/app/services/merge_requests/build_service.rb
+++ b/app/services/merge_requests/build_service.rb
@@ -7,6 +7,9 @@ module MergeRequests
merge_request.can_be_created = false
merge_request.compare_commits = []
merge_request.source_project = project unless merge_request.source_project
+
+ merge_request.target_project = nil unless can?(current_user, :read_project, merge_request.target_project)
+
merge_request.target_project ||= (project.forked_from_project || project)
merge_request.target_branch ||= merge_request.target_project.default_branch
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index 2bb312bb252..01586994813 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -5,6 +5,8 @@ module Notes
note.author = current_user
note.system = false
+ return unless valid_project?(note)
+
if note.save
# Finish the harder work in the background
NewNoteWorker.perform_in(2.seconds, note.id, params)
@@ -13,5 +15,14 @@ module Notes
note
end
+
+ private
+
+ def valid_project?(note)
+ return false unless project
+ return true if note.for_commit?
+
+ note.noteable.try(:project) == project
+ end
end
end
diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb
index 79a27f4af7e..111b3ec05ea 100644
--- a/app/services/projects/transfer_service.rb
+++ b/app/services/projects/transfer_service.rb
@@ -34,6 +34,8 @@ module Projects
raise TransferError.new("Project with same path in target namespace already exists")
end
+ project.expire_caches_before_rename(old_path)
+
# Apply new namespace id and visibility level
project.namespace = new_namespace
project.visibility_level = new_namespace.visibility_level unless project.visibility_level_allowed_by_group?
diff --git a/app/services/wiki_pages/base_service.rb b/app/services/wiki_pages/base_service.rb
new file mode 100644
index 00000000000..9162f128602
--- /dev/null
+++ b/app/services/wiki_pages/base_service.rb
@@ -0,0 +1,27 @@
+module WikiPages
+ class BaseService < ::BaseService
+
+ def hook_data(page, action)
+ hook_data = {
+ object_kind: page.class.name.underscore,
+ user: current_user.hook_attrs,
+ project: @project.hook_attrs,
+ object_attributes: page.hook_attrs,
+ # DEPRECATED
+ repository: @project.hook_attrs.slice(:name, :url, :description, :homepage)
+ }
+
+ page_url = Gitlab::UrlBuilder.build(page)
+ hook_data[:object_attributes].merge!(url: page_url, action: action)
+ hook_data
+ end
+
+ private
+
+ def execute_hooks(page, action = 'create')
+ page_data = hook_data(page, action)
+ @project.execute_hooks(page_data, :wiki_page_hooks)
+ @project.execute_services(page_data, :wiki_page_hooks)
+ end
+ end
+end
diff --git a/app/services/wiki_pages/create_service.rb b/app/services/wiki_pages/create_service.rb
new file mode 100644
index 00000000000..24a817c06c9
--- /dev/null
+++ b/app/services/wiki_pages/create_service.rb
@@ -0,0 +1,14 @@
+module WikiPages
+ class CreateService < WikiPages::BaseService
+ def execute
+ project_wiki = ProjectWiki.new(@project, current_user)
+ page = WikiPage.new(project_wiki)
+
+ if page.create(@params)
+ execute_hooks(page, 'create')
+ end
+
+ page
+ end
+ end
+end
diff --git a/app/services/wiki_pages/update_service.rb b/app/services/wiki_pages/update_service.rb
new file mode 100644
index 00000000000..8f6a50da838
--- /dev/null
+++ b/app/services/wiki_pages/update_service.rb
@@ -0,0 +1,11 @@
+module WikiPages
+ class UpdateService < WikiPages::BaseService
+ def execute(page)
+ if page.update(@params[:content], @params[:format], @params[:message])
+ execute_hooks(page, 'update')
+ end
+
+ page
+ end
+ end
+end
diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml
index 67d23c80233..7b388cf7862 100644
--- a/app/views/admin/hooks/index.html.haml
+++ b/app/views/admin/hooks/index.html.haml
@@ -13,9 +13,15 @@
= form_errors(@hook)
.form-group
- = f.label :url, "URL:", class: 'control-label'
+ = f.label :url, 'URL', class: 'control-label'
.col-sm-10
- = f.text_field :url, class: "form-control"
+ = f.text_field :url, class: 'form-control'
+ .form-group
+ = f.label :token, 'Secret Token', class: 'control-label'
+ .col-sm-10
+ = f.text_field :token, class: 'form-control'
+ %p.help-block
+ Use this token to validate received payloads
.form-group
= f.label :url, "Trigger", class: 'control-label'
.col-sm-10.prepend-top-10
diff --git a/app/views/admin/users/_form.html.haml b/app/views/admin/users/_form.html.haml
index b05fdbd5552..fe0b9d3a491 100644
--- a/app/views/admin/users/_form.html.haml
+++ b/app/views/admin/users/_form.html.haml
@@ -7,17 +7,17 @@
.form-group
= f.label :name, class: 'control-label'
.col-sm-10
- = f.text_field :name, required: true, autocomplete: "off", class: 'form-control'
+ = f.text_field :name, required: true, autocomplete: 'off', class: 'form-control'
%span.help-inline * required
.form-group
= f.label :username, class: 'control-label'
.col-sm-10
- = f.text_field :username, required: true, autocomplete: "off", class: 'form-control'
+ = f.text_field :username, required: true, autocomplete: 'off', autocorrect: 'off', autocapitalize: 'off', spellcheck: false, class: 'form-control'
%span.help-inline * required
.form-group
= f.label :email, class: 'control-label'
.col-sm-10
- = f.text_field :email, required: true, autocomplete: "off", class: 'form-control'
+ = f.text_field :email, required: true, autocomplete: 'off', class: 'form-control'
%span.help-inline * required
- if @user.new_record?
diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml
index f9ec3a89158..49ab8aad1d5 100644
--- a/app/views/dashboard/todos/index.html.haml
+++ b/app/views/dashboard/todos/index.html.haml
@@ -45,6 +45,7 @@
.prepend-top-default
- if @todos.any?
+ .js-todos-options{ data: {per_page: @todos.limit_value, current_page: @todos.current_page, total_pages: @todos.total_pages} }
- @todos.group_by(&:project).each do |group|
.panel.panel-default.panel-small.js-todos-list
- project = group[0]
diff --git a/app/views/devise/confirmations/almost_there.haml b/app/views/devise/confirmations/almost_there.haml
new file mode 100644
index 00000000000..3c3830a3f10
--- /dev/null
+++ b/app/views/devise/confirmations/almost_there.haml
@@ -0,0 +1,10 @@
+.well-confirmation.text-center
+ %h1.prepend-top-0
+ Almost there...
+ %p.lead
+ Please check your email to confirm your account
+%p.confirmation-content.text-center
+ No confirmation email received? Please check your spam folder or
+.append-bottom-20.prepend-top-20.text-center
+ %a.btn.btn-lg.btn-success{ href: new_user_confirmation_path }
+ Request new confirmation email
diff --git a/app/views/devise/sessions/two_factor.html.haml b/app/views/devise/sessions/two_factor.html.haml
index 22b2c1a186b..c9d1e454a5e 100644
--- a/app/views/devise/sessions/two_factor.html.haml
+++ b/app/views/devise/sessions/two_factor.html.haml
@@ -4,7 +4,7 @@
%h3 Two-factor Authentication
.login-body
= form_for(resource, as: resource_name, url: session_path(resource_name), method: :post) do |f|
- = f.text_field :otp_attempt, class: 'form-control', placeholder: 'Two-factor authentication code', required: true, autofocus: true
- %p.help-block.hint If you've lost your phone, you may enter one of your recovery codes.
+ = f.text_field :otp_attempt, class: 'form-control', placeholder: 'Two-factor Authentication code', required: true, autofocus: true
+ %p.help-block.hint Enter the code from the two-factor app on your mobile device. If you've lost your device, you may enter one of your recovery codes.
.prepend-top-20
= f.submit "Verify code", class: "btn btn-save"
diff --git a/app/views/doorkeeper/applications/index.html.haml b/app/views/doorkeeper/applications/index.html.haml
index 0aff79749ef..3998e66f40d 100644
--- a/app/views/doorkeeper/applications/index.html.haml
+++ b/app/views/doorkeeper/applications/index.html.haml
@@ -1,5 +1,4 @@
- page_title "Applications"
-- header_title page_title, applications_profile_path
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
@@ -45,7 +44,7 @@
= icon('pencil')
= render 'delete_form', application: application, small: true
- else
- .profile-settings-message.text-center
+ .settings-message.text-center
You don't have any applications
.oauth-authorized-applications.prepend-top-20.append-bottom-default
- if user_oauth_applications?
@@ -79,5 +78,5 @@
%td= token.scopes
%td= render 'doorkeeper/authorized_applications/delete_form', token: token
- else
- .profile-settings-message.text-center
+ .settings-message.text-center
You don't have any authorized applications
diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml
index 4d20dd5830e..5d622582088 100644
--- a/app/views/events/_event.html.haml
+++ b/app/views/events/_event.html.haml
@@ -4,7 +4,12 @@
#{time_ago_with_tooltip(event.created_at)}
= cache [event, current_application_settings, "v2.2"] do
- = image_tag avatar_icon(event.author_email, 40), class: "avatar s40", alt:''
+ - if event.author
+ = link_to user_path(event.author.username) do
+ = image_tag avatar_icon(event.author_email, 40), class: "avatar s40", alt:''
+ - else
+ = image_tag avatar_icon(event.author_email, 40), class: "avatar s40", alt:''
+
- if event.created_project?
= render "events/event/created_project", event: event
- elsif event.push?
diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml
index da3c3711cdd..70e88da7aae 100644
--- a/app/views/help/_shortcuts.html.haml
+++ b/app/views/help/_shortcuts.html.haml
@@ -21,7 +21,7 @@
%tr
%td.shortcut
.key ?
- %td Show this dialog
+ %td Show/hide this dialog
%tr
%td.shortcut
- if browser.mac?
@@ -169,6 +169,10 @@
%td.shortcut
.key t
%td Go to finding file
+ %tr
+ %td.shortcut
+ .key i
+ %td New issue
.col-lg-4
%table.shortcut-mappings
%tbody{ class: 'hidden-shortcut network', style: 'display:none' }
@@ -241,6 +245,10 @@
%td.shortcut
.key e
%td Edit issue
+ %tr
+ %td.shortcut
+ .key l
+ %td Change Label
%tbody{ class: 'hidden-shortcut merge_requests', style: 'display:none' }
%tr
%th
@@ -261,3 +269,7 @@
%td.shortcut
.key e
%td Edit merge request
+ %tr
+ %td.shortcut
+ .key l
+ %td Change Label
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index c799e9c588d..ad8a2e1e6c7 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -22,6 +22,10 @@
= image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s36'
.username
= current_user.username
+ - if defined?(nav) && nav
+ .layout-nav
+ .container-fluid
+ = render "layouts/nav/#{nav}"
.content-wrapper
= render "layouts/flash"
= yield :flash_message
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index babfb032236..e4d1c773d03 100644
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -6,6 +6,6 @@
= yield :scripts_body_top
= render "layouts/header/default", title: header_title
- = render 'layouts/page', sidebar: sidebar
+ = render 'layouts/page', sidebar: sidebar, nav: nav
= yield :scripts_body
diff --git a/app/views/layouts/devise_empty.html.haml b/app/views/layouts/devise_empty.html.haml
new file mode 100644
index 00000000000..7c061dd531f
--- /dev/null
+++ b/app/views/layouts/devise_empty.html.haml
@@ -0,0 +1,17 @@
+!!! 5
+%html{ lang: "en"}
+ = render "layouts/head"
+ %body.ui_charcoal.login-page.application.navless
+ = render "layouts/header/empty"
+ = render "layouts/broadcast"
+ .container.navless-container
+ .content
+ = render "layouts/flash"
+ = yield
+
+ %hr
+ .container
+ .footer-links
+ = link_to "Explore", explore_root_path
+ = link_to "Help", help_path
+ = link_to "About GitLab", "https://about.gitlab.com/"
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 3beb8ff7c0d..cde9e1b918b 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -15,7 +15,7 @@
- if current_user
- if session[:impersonator_id]
%li.impersonation
- = link_to stop_impersonation_admin_users_path, method: :delete, title: 'Stop Impersonation', data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
+ = link_to admin_impersonation_path, method: :delete, title: 'Stop Impersonation', data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
= icon('user-secret fw')
- if current_user.is_admin?
%li
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index 5cef652da14..ca49c313ff7 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -48,8 +48,7 @@
%span
Help
- %li.separate-item
- = nav_link(controller: :profile) do
+ = nav_link(html_options: {class: profile_tab_class}) do
= link_to profile_path, title: 'Profile Settings', data: {placement: 'bottom'} do
= icon('user fw')
%span
diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml
index 3b9d31a6fc5..d730840d63a 100644
--- a/app/views/layouts/nav/_profile.html.haml
+++ b/app/views/layouts/nav/_profile.html.haml
@@ -1,17 +1,9 @@
-%ul.nav.nav-sidebar
- = nav_link do
- = link_to root_path, title: 'Go to dashboard', class: 'back-link' do
- = icon('caret-square-o-left fw')
- %span
- Go to dashboard
-
- %li.separate-item
-
+%ul.nav-links
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
= link_to profile_path, title: 'Profile Settings' do
= icon('user fw')
%span
- Profile Settings
+ Profile
= nav_link(controller: [:accounts, :two_factor_auths]) do
= link_to profile_account_path, title: 'Account' do
= icon('gear fw')
@@ -27,7 +19,6 @@
= icon('envelope-o fw')
%span
Emails
- %span.count= number_with_delimiter(current_user.emails.count + 1)
- unless current_user.ldap_user?
= nav_link(controller: :passwords) do
= link_to edit_profile_password_path, title: 'Password' do
@@ -45,7 +36,6 @@
= icon('key fw')
%span
SSH Keys
- %span.count= number_with_delimiter(current_user.keys.count)
= nav_link(controller: :preferences) do
= link_to profile_preferences_path, title: 'Preferences' do
-# TODO (rspeicher): Better icon?
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index 86b46e8c75e..479bde33719 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -77,7 +77,7 @@
Merge Requests
%span.count.merge_counter= number_with_delimiter(@project.merge_requests.opened.count)
- - if project_nav_tab? :settings
+ - if project_nav_tab? :team
= nav_link(controller: [:project_members, :teams]) do
= link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab' do
= icon('users fw')
@@ -124,3 +124,8 @@
%li.hidden
= link_to namespace_project_network_path(@project.namespace, @project, current_ref), title: 'Network', class: 'shortcuts-network' do
Network
+
+ -# Shortcut to create a new issue
+ %li.hidden
+ = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'shortcuts-new-issue' do
+ Create a new issue
diff --git a/app/views/layouts/profile.html.haml b/app/views/layouts/profile.html.haml
index dfa6cc5702e..b77d3402a2e 100644
--- a/app/views/layouts/profile.html.haml
+++ b/app/views/layouts/profile.html.haml
@@ -1,5 +1,6 @@
- page_title "Profile Settings"
- header_title "Profile Settings", profile_path unless header_title
-- sidebar "profile"
+- sidebar "dashboard"
+- nav "profile"
= render template: "layouts/application"
diff --git a/app/views/notify/note_snippet_email.html.haml b/app/views/notify/note_snippet_email.html.haml
new file mode 100644
index 00000000000..2fa2f784661
--- /dev/null
+++ b/app/views/notify/note_snippet_email.html.haml
@@ -0,0 +1 @@
+= render 'note_message'
diff --git a/app/views/notify/note_snippet_email.text.erb b/app/views/notify/note_snippet_email.text.erb
new file mode 100644
index 00000000000..4d5a406f4b0
--- /dev/null
+++ b/app/views/notify/note_snippet_email.text.erb
@@ -0,0 +1,8 @@
+New comment for Snippet <%= @snippet.id %>
+
+<%= url_for(namespace_project_snippet_url(@snippet.project.namespace, @snippet.project, @snippet, anchor: "note_#{@note.id}")) %>
+
+
+Author: <%= @note.author_name %>
+
+<%= @note.note %>
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index 6efd119f260..afd3d79321f 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -1,5 +1,4 @@
- page_title "Account"
-- header_title page_title, profile_account_path
- if current_user.ldap_user?
.alert.alert-info
diff --git a/app/views/profiles/audit_log.html.haml b/app/views/profiles/audit_log.html.haml
index f630c03e5f6..9c404b6935f 100644
--- a/app/views/profiles/audit_log.html.haml
+++ b/app/views/profiles/audit_log.html.haml
@@ -1,5 +1,4 @@
- page_title "Audit Log"
-- header_title page_title, audit_log_profile_path
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml
index 3f328f96cea..6f7fefdb46d 100644
--- a/app/views/profiles/emails/index.html.haml
+++ b/app/views/profiles/emails/index.html.haml
@@ -1,5 +1,4 @@
- page_title "Emails"
-- header_title page_title, profile_emails_path
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
@@ -46,4 +45,4 @@
%span.label.label-info Public Email
- if email.email === current_user.notification_email
%span.label.label-info Notification Email
- = link_to 'Remove', profile_email_path(email), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-sm btn-remove pull-right'
+ = link_to 'Remove', profile_email_path(email), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-sm btn-warning prepend-left-10'
diff --git a/app/views/profiles/keys/_key.html.haml b/app/views/profiles/keys/_key.html.haml
index 4dbaa662b66..3276db6692c 100644
--- a/app/views/profiles/keys/_key.html.haml
+++ b/app/views/profiles/keys/_key.html.haml
@@ -1,6 +1,6 @@
%li.key-list-item
.pull-left.append-right-10
- = icon 'key', class: "key-icon hidden-xs"
+ = icon 'key', class: "settings-list-icon hidden-xs"
.key-list-item-info
= link_to path_to_key(key, is_admin), class: "title" do
= key.title
diff --git a/app/views/profiles/keys/_key_table.html.haml b/app/views/profiles/keys/_key_table.html.haml
index 296cafa6e31..e78763bdcb2 100644
--- a/app/views/profiles/keys/_key_table.html.haml
+++ b/app/views/profiles/keys/_key_table.html.haml
@@ -4,7 +4,7 @@
%ul.well-list
= render partial: 'profiles/keys/key', collection: @keys, locals: { is_admin: is_admin }
- else
- %p.profile-settings-message.text-center
+ %p.settings-message.text-center
- if is_admin
There are no SSH keys associated with this account.
- else
diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml
index e0f8c9a5733..6a067a03535 100644
--- a/app/views/profiles/keys/index.html.haml
+++ b/app/views/profiles/keys/index.html.haml
@@ -1,5 +1,4 @@
- page_title "SSH Keys"
-- header_title page_title, profile_keys_path
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml
index a2a505c082b..7696f112bb3 100644
--- a/app/views/profiles/notifications/show.html.haml
+++ b/app/views/profiles/notifications/show.html.haml
@@ -1,5 +1,4 @@
- page_title "Notifications"
-- header_title page_title, profile_notifications_path
%div
- if @user.errors.any?
diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml
index 5ac8a8b9d09..243428b690e 100644
--- a/app/views/profiles/passwords/edit.html.haml
+++ b/app/views/profiles/passwords/edit.html.haml
@@ -1,5 +1,4 @@
- page_title "Password"
-- header_title page_title, edit_profile_password_path
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index f80211669fb..bfe53be6854 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -1,5 +1,4 @@
- page_title 'Preferences'
-- header_title page_title, profile_preferences_path
= form_for @user, url: profile_preferences_path, remote: true, method: :put, html: {class: 'row prepend-top-default js-preferences-form'} do |f|
.col-lg-3.profile-settings-sidebar
diff --git a/app/views/projects/_activity.html.haml b/app/views/projects/_activity.html.haml
index 961b61d2e76..48b0dd6b121 100644
--- a/app/views/projects/_activity.html.haml
+++ b/app/views/projects/_activity.html.haml
@@ -9,4 +9,7 @@
= spinner
:javascript
- new Activities();
+ var activity = new Activities();
+ $(document).on('page:restore', function (event) {
+ activity.reloadActivities()
+ })
diff --git a/app/views/projects/_builds_settings.html.haml b/app/views/projects/_builds_settings.html.haml
index b6074373e2b..0de019983ca 100644
--- a/app/views/projects/_builds_settings.html.haml
+++ b/app/views/projects/_builds_settings.html.haml
@@ -55,6 +55,9 @@
%li
gcovr (C/C++) -
%code ^TOTAL.*\s+(\d+\%)$
+ %li
+ tap --coverage-report=text-summary (Node.js) -
+ %code ^Statements\s*:\s*([^%]+)
.form-group
.col-sm-offset-2.col-sm-10
diff --git a/app/views/projects/_last_commit.html.haml b/app/views/projects/_last_commit.html.haml
index 386d72e7787..66c30283c7a 100644
--- a/app/views/projects/_last_commit.html.haml
+++ b/app/views/projects/_last_commit.html.haml
@@ -1,9 +1,8 @@
.project-last-commit
- - ci_commit = project.ci_commit(commit.sha)
- - if ci_commit
- = link_to ci_status_path(ci_commit), class: "ci-status ci-#{ci_commit.status}" do
- = ci_status_icon(ci_commit)
- = ci_status_label(ci_commit)
+ - if commit.status
+ = link_to builds_namespace_project_commit_path(commit.project.namespace, commit.project, commit), class: "ci-status ci-#{commit.status}" do
+ = ci_icon_for_status(commit.status)
+ = ci_label_for_status(commit.status)
= link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
= link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit), class: "commit-row-message"
diff --git a/app/views/projects/_readme.html.haml b/app/views/projects/_readme.html.haml
index d1191928d4f..a9908eaecca 100644
--- a/app/views/projects/_readme.html.haml
+++ b/app/views/projects/_readme.html.haml
@@ -9,7 +9,7 @@
- else
.gray-content-block.second-block.center
%h3.page-title
- This project does not have README yet
+ This project does not have a README yet
- if can?(current_user, :push_code, @project)
%p
A
@@ -18,5 +18,5 @@
distributed with computer software, forming part of its documentation.
%p
We recommend you to
- = link_to "add README", new_readme_path, class: 'underlined-link'
+ = link_to "add a README", new_readme_path, class: 'underlined-link'
file to the repository and GitLab will render it here instead of this message.
diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml
index f8b6fa253c4..fefa652a3da 100644
--- a/app/views/projects/blob/_editor.html.haml
+++ b/app/views/projects/blob/_editor.html.haml
@@ -13,7 +13,11 @@
required: true, class: 'form-control new-file-name'
.pull-right
- = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2'
+ .license-selector.js-license-selector.hide
+ = select_tag :license_type, grouped_options_for_select(licenses_for_select, @project.repository.license_key), include_blank: true, class: 'select2 license-select', data: {placeholder: 'Choose a license template', project: @project.name, fullname: @project.namespace.human_name}
+
+ .encoding-selector
+ = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2'
.file-content.code
%pre.js-edit-mode-pane#editor #{params[:content] || local_assigns[:blob_data]}
diff --git a/app/views/projects/blob/_text.html.haml b/app/views/projects/blob/_text.html.haml
index d09cd73558c..b1769759dce 100644
--- a/app/views/projects/blob/_text.html.haml
+++ b/app/views/projects/blob/_text.html.haml
@@ -1,10 +1,19 @@
-- blob.load_all_data!(@repository)
-- if markup?(blob.name)
- .file-content.wiki
- = render_markup(blob.name, blob.data)
+- if blob.only_display_raw?
+ .file-content.code
+ .nothing-here-block
+ File too large, you can
+ = succeed '.' do
+ = link_to 'view the raw file', namespace_project_raw_path(@project.namespace, @project, @id), target: '_blank'
+
- else
- - unless blob.empty?
- = render 'shared/file_highlight', blob: blob
+ - blob.load_all_data!(@repository)
+
+ - if markup?(blob.name)
+ .file-content.wiki
+ = render_markup(blob.name, blob.data)
- else
- .file-content.code
- .nothing-here-block Empty file
+ - if blob.empty?
+ .file-content.code
+ .nothing-here-block Empty file
+ - else
+ = render 'shared/file_highlight', blob: blob
diff --git a/app/views/projects/blob/diff.html.haml b/app/views/projects/blob/diff.html.haml
index 38e62c81fed..5926d181ba3 100644
--- a/app/views/projects/blob/diff.html.haml
+++ b/app/views/projects/blob/diff.html.haml
@@ -1,17 +1,17 @@
- if @lines.present?
- if @form.unfold? && @form.since != 1 && !@form.bottom?
- %tr.line_holder{ id: @form.since }
+ %tr.line_holder
= render "projects/diffs/match_line", { line: @match_line,
line_old: @form.since, line_new: @form.since, bottom: false, new_file: false }
- @lines.each_with_index do |line, index|
- line_new = index + @form.since
- line_old = line_new - @form.offset
- %tr.line_holder
+ %tr.line_holder{ id: line_old }
%td.old_line.diff-line-num{ data: { linenumber: line_old } }
- = link_to raw(line_old), "#"
+ = link_to raw(line_old), "##{line_old}"
%td.new_line.diff-line-num{ data: { linenumber: line_old } }
- = link_to raw(line_new) , "#"
+ = link_to raw(line_new) , "##{line_old}"
%td.line_content.noteable_line==#{' ' * @form.indent}#{line}
- if @form.unfold? && @form.bottom? && @form.to < @blob.loc
diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml
index 1dd2b5c0af7..0459699432e 100644
--- a/app/views/projects/blob/new.html.haml
+++ b/app/views/projects/blob/new.html.haml
@@ -14,5 +14,5 @@
cancel_path: namespace_project_tree_path(@project.namespace, @project, @id)
:javascript
- blob = new NewBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}", null)
+ blob = new EditBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}")
new NewCommitForm($('.js-new-blob-form'))
diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml
index aa85f495e39..0406fc21d77 100644
--- a/app/views/projects/builds/index.html.haml
+++ b/app/views/projects/builds/index.html.haml
@@ -58,6 +58,6 @@
%th Coverage
%th
- = render @builds, commit_sha: true, stage: true, allow_retry: true, coverage: @project.build_coverage_enabled?
+ = render @builds, commit_sha: true, ref: true, stage: true, allow_retry: true, coverage: @project.build_coverage_enabled?
= paginate @builds, theme: 'gitlab'
diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml
index aa8827b5ac8..99d72aa7935 100644
--- a/app/views/projects/builds/show.html.haml
+++ b/app/views/projects/builds/show.html.haml
@@ -13,7 +13,7 @@
= link_to "merge request #{merge_request.to_reference}", merge_request_path(merge_request)
#up-build-trace
- - builds = @build.commit.matrix_builds(@build)
+ - builds = @build.commit.builds.latest.to_a
- if builds.size > 1
%ul.nav-links.no-top.no-bottom
- builds.each do |build|
@@ -110,7 +110,7 @@
= icon('folder-open')
Browse
- .build-widget
+ .build-widget.build-controls
%h4.title
Build ##{@build.id}
- if can?(current_user, :update_build, @project)
@@ -127,6 +127,9 @@
data: { confirm: 'Are you sure you want to erase this build?' } do
= icon('eraser')
Erase
+ - if @build.has_trace?
+ = link_to 'Raw', raw_namespace_project_build_path(@project.namespace, @project, @build),
+ class: 'btn btn-sm btn-success', target: '_blank'
.clearfix
- if @build.duration
diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml
index 2cf9115e4dd..e123eb1cc7a 100644
--- a/app/views/projects/ci/builds/_build.html.haml
+++ b/app/views/projects/ci/builds/_build.html.haml
@@ -19,11 +19,12 @@
%td
= link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "monospace"
- %td
- - if build.ref
- = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref)
- - else
- .light none
+ - if defined?(ref) && ref
+ %td
+ - if build.ref
+ = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref)
+ - else
+ .light none
- if defined?(runner) && runner
%td
@@ -48,6 +49,8 @@
%span.label.label-info triggered
- if build.try(:allow_failure)
%span.label.label-danger allowed to fail
+ - if defined?(retried) && retried
+ %span.label.label-warning retried
%td.duration
- if build.duration
diff --git a/app/views/projects/commit/_builds.html.haml b/app/views/projects/commit/_builds.html.haml
index 003b7c18d0e..5c9a319edeb 100644
--- a/app/views/projects/commit/_builds.html.haml
+++ b/app/views/projects/commit/_builds.html.haml
@@ -1,67 +1,2 @@
-.gray-content-block.middle-block
- .pull-right
- - if can?(current_user, :update_build, @ci_commit.project)
- - if @ci_commit.builds.latest.failed.any?(&:retryable?)
- = link_to "Retry failed", retry_builds_namespace_project_commit_path(@ci_commit.project.namespace, @ci_commit.project, @ci_commit.sha), class: 'btn btn-grouped btn-primary', method: :post
-
- - if @ci_commit.builds.running_or_pending.any?
- = link_to "Cancel running", cancel_builds_namespace_project_commit_path(@ci_commit.project.namespace, @ci_commit.project, @ci_commit.sha), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post
-
- .oneline
- = pluralize @statuses.count(:id), "build"
- - if defined?(link_to_commit) && link_to_commit
- for commit
- = link_to @ci_commit.short_sha, namespace_project_commit_path(@ci_commit.project.namespace, @ci_commit.project, @ci_commit.sha), class: "monospace"
- - if @ci_commit.duration > 0
- in
- = time_interval_in_words @ci_commit.duration
-
-- if @ci_commit.yaml_errors.present?
- .bs-callout.bs-callout-danger
- %h4 Found errors in your .gitlab-ci.yml:
- %ul
- - @ci_commit.yaml_errors.split(",").each do |error|
- %li= error
- You can also test your .gitlab-ci.yml in the #{link_to "Lint", ci_lint_path}
-
-- if @ci_commit.project.builds_enabled? && !@ci_commit.ci_yaml_file
- .bs-callout.bs-callout-warning
- \.gitlab-ci.yml not found in this commit
-
-.table-holder
- %table.table.builds
- %thead
- %tr
- %th Status
- %th Build ID
- %th Ref
- %th Stage
- %th Name
- %th Duration
- %th Finished at
- - if @ci_commit.project.build_coverage_enabled?
- %th Coverage
- %th
- - @ci_commit.refs.each do |ref|
- - builds = @ci_commit.statuses.for_ref(ref).latest.ordered
- = render builds, coverage: @ci_commit.project.build_coverage_enabled?, stage: true, allow_retry: true
-
-- if @ci_commit.retried.any?
- .gray-content-block.second-block
- Retried builds
-
- .table-holder
- %table.table.builds
- %thead
- %tr
- %th Status
- %th Build ID
- %th Ref
- %th Stage
- %th Name
- %th Duration
- %th Finished at
- - if @ci_commit.project.build_coverage_enabled?
- %th Coverage
- %th
- = render @ci_commit.retried, coverage: @ci_commit.project.build_coverage_enabled?, stage: true
+- @ci_commits.each do |ci_commit|
+ = render "ci_commit", ci_commit: ci_commit
diff --git a/app/views/projects/commit/_ci_commit.html.haml b/app/views/projects/commit/_ci_commit.html.haml
new file mode 100644
index 00000000000..d3acd33116c
--- /dev/null
+++ b/app/views/projects/commit/_ci_commit.html.haml
@@ -0,0 +1,69 @@
+.gray-content-block.middle-block
+ .pull-right
+ - if can?(current_user, :update_build, @project)
+ - if ci_commit.builds.latest.failed.any?(&:retryable?)
+ = link_to "Retry failed", retry_builds_namespace_project_commit_path(@project.namespace, @project, ci_commit.sha), class: 'btn btn-grouped btn-primary', method: :post
+
+ - if ci_commit.builds.running_or_pending.any?
+ = link_to "Cancel running", cancel_builds_namespace_project_commit_path(@project.namespace, @project, ci_commit.sha), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post
+
+ .oneline
+ = pluralize ci_commit.statuses.count(:id), "build"
+ - if ci_commit.ref
+ for
+ %span.label.label-info
+ = ci_commit.ref
+ - if defined?(link_to_commit) && link_to_commit
+ for commit
+ = link_to ci_commit.short_sha, namespace_project_commit_path(@project.namespace, @project, ci_commit.sha), class: "monospace"
+ - if ci_commit.duration
+ in
+ = time_interval_in_words ci_commit.duration
+
+- if ci_commit.yaml_errors.present?
+ .bs-callout.bs-callout-danger
+ %h4 Found errors in your .gitlab-ci.yml:
+ %ul
+ - ci_commit.yaml_errors.split(",").each do |error|
+ %li= error
+ You can also test your .gitlab-ci.yml in the #{link_to "Lint", ci_lint_path}
+
+- if @project.builds_enabled? && !ci_commit.ci_yaml_file
+ .bs-callout.bs-callout-warning
+ \.gitlab-ci.yml not found in this commit
+
+.table-holder
+ %table.table.builds
+ %thead
+ %tr
+ %th Status
+ %th Build ID
+ %th Stage
+ %th Name
+ %th Duration
+ %th Finished at
+ - if @project.build_coverage_enabled?
+ %th Coverage
+ %th
+ - builds = ci_commit.statuses.latest.ordered
+ = render builds, coverage: @project.build_coverage_enabled?, stage: true, ref: false, allow_retry: true
+
+- if ci_commit.retried.any?
+ .gray-content-block.second-block
+ Retried builds
+
+ .table-holder
+ %table.table.builds
+ %thead
+ %tr
+ %th Status
+ %th Build ID
+ %th Ref
+ %th Stage
+ %th Name
+ %th Duration
+ %th Finished at
+ - if @project.build_coverage_enabled?
+ %th Coverage
+ %th
+ = render ci_commit.retried, coverage: @project.build_coverage_enabled?, stage: true, ref: false
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index d6c9e54e657..3d7c18a5f58 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -43,12 +43,12 @@
- @commit.parents.each do |parent|
= link_to parent.short_id, namespace_project_commit_path(@project.namespace, @project, parent), class: "monospace"
-- if @ci_commit
+- if @commit.status
.pull-right
- = link_to ci_status_path(@ci_commit), class: "ci-status ci-#{@ci_commit.status}" do
- = ci_status_icon(@ci_commit)
+ = link_to builds_namespace_project_commit_path(@project.namespace, @project, @commit.id), class: "ci-status ci-#{@commit.status}" do
+ = ci_icon_for_status(@commit.status)
build:
- = ci_status_label(@ci_commit)
+ = ci_label_for_status(@commit.status)
.commit-info-row.branches
%i.fa.fa-spinner.fa-spin
diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml
index e550af7888a..e5e3d696035 100644
--- a/app/views/projects/commit/show.html.haml
+++ b/app/views/projects/commit/show.html.haml
@@ -5,7 +5,7 @@
.prepend-top-default
= render "commit_box"
-- if @ci_commit
+- if @commit.status
= render "ci_menu"
- else
%div.block-connector
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index 34f27f1e793..c7d8c9a0d15 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -4,9 +4,8 @@
- notes = commit.notes
- note_count = notes.user.count
-- ci_commit = project.ci_commit(commit.sha)
- cache_key = [project.path_with_namespace, commit.id, current_application_settings, note_count]
-- cache_key.push(ci_commit.status) if ci_commit
+- cache_key.push(commit.status) if commit.status
= cache(cache_key) do
%li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" }
@@ -17,8 +16,8 @@
%a.text-expander.js-toggle-button ...
.pull-right
- - if ci_commit
- = render_ci_status(ci_commit)
+ - if commit.status
+ = render_ci_status(commit)
= clipboard_button(clipboard_text: commit.id)
= link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
@@ -31,5 +30,5 @@
by
= commit_author_link(commit, avatar: true, size: 24)
.committed_ago
- #{time_ago_with_tooltip(commit.committed_date, skip_js: true)} &nbsp;
+ #{time_ago_with_tooltip(commit.committed_date)} &nbsp;
= link_to_browse_code(project, commit)
diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml
index c52cf25d40a..bcdb09208aa 100644
--- a/app/views/projects/commits/show.html.haml
+++ b/app/views/projects/commits/show.html.haml
@@ -39,4 +39,4 @@
= spinner
:javascript
- CommitsList.init("#{@ref}", #{@limit});
+ CommitsList.init(#{@limit});
diff --git a/app/views/projects/deploy_keys/_deploy_key.html.haml b/app/views/projects/deploy_keys/_deploy_key.html.haml
index 8d66bae8cdf..450aaeb367c 100644
--- a/app/views/projects/deploy_keys/_deploy_key.html.haml
+++ b/app/views/projects/deploy_keys/_deploy_key.html.haml
@@ -1,32 +1,27 @@
%li
- .pull-right
+ .pull-left.append-right-10.hidden-xs
+ = icon "key", class: "key-icon"
+ .deploy-key-content.key-list-item-info
+ %strong.title
+ = deploy_key.title
+ .description
+ = deploy_key.fingerprint
+ .deploy-key-content.prepend-left-default.deploy-key-projects
+ - deploy_key.projects.each do |project|
+ - if can?(current_user, :read_project, project)
+ = link_to namespace_project_path(project.namespace, project), class: "label deploy-project-label" do
+ = project.name_with_namespace
+ .deploy-key-content
+ %span.key-created-at
+ created #{time_ago_with_tooltip(deploy_key.created_at)}
+ .visible-xs-block.visible-sm-block
- if @available_keys.include?(deploy_key)
- = link_to enable_namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), class: 'btn btn-sm', method: :put do
- = icon('plus')
+ = link_to enable_namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), class: "btn btn-sm prepend-left-10", method: :put do
Enable
- else
- if deploy_key.destroyed_when_orphaned? && deploy_key.almost_orphaned?
- = link_to 'Remove', disable_namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), data: { confirm: 'You are going to remove deploy key. Are you sure?'}, method: :put, class: "btn btn-remove delete-key btn-sm pull-right"
+ = link_to disable_namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), data: { confirm: "You are going to remove deploy key. Are you sure?" }, method: :put, class: "btn btn-warning btn-sm prepend-left-10" do
+ Remove
- else
- = link_to disable_namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), class: 'btn btn-sm', method: :put do
- = icon('power-off')
+ = link_to disable_namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), class: "btn btn-warning btn-sm prepend-left-10", method: :put do
Disable
-
- = icon('key')
- %strong= deploy_key.title
- %br
- %code.key-fingerprint= deploy_key.fingerprint
-
- %p.light.prepend-top-10
- - if deploy_key.public?
- %span.label.label-info.deploy-project-label
- Public deploy key
-
- - deploy_key.projects.each do |project|
- - if can?(current_user, :read_project, project)
- %span.label.label-gray.deploy-project-label
- = link_to namespace_project_path(project.namespace, project) do
- = project.name_with_namespace
-
- %small.pull-right
- Created #{time_ago_with_tooltip(deploy_key.created_at)}
diff --git a/app/views/projects/deploy_keys/_form.html.haml b/app/views/projects/deploy_keys/_form.html.haml
index f6565f85836..894c36a96df 100644
--- a/app/views/projects/deploy_keys/_form.html.haml
+++ b/app/views/projects/deploy_keys/_form.html.haml
@@ -1,18 +1,13 @@
-%div
- = form_for [@project.namespace.becomes(Namespace), @project, @key], url: namespace_project_deploy_keys_path, html: { class: 'deploy-key-form form-horizontal js-requires-input' } do |f|
- = form_errors(@key)
-
- .form-group
- = f.label :title, class: "control-label"
- .col-sm-10= f.text_field :title, class: 'form-control', autofocus: true, required: true
- .form-group
- = f.label :key, class: "control-label"
- .col-sm-10
- %p.light
- Paste a machine public key here. Read more about how to generate it
- = link_to "here", help_page_path("ssh", "README")
- = f.text_area :key, class: "form-control thin_area", rows: 5, required: true
-
- .form-actions
- = f.submit 'Create Deploy Key', class: "btn-create btn"
- = link_to "Cancel", namespace_project_deploy_keys_path(@project.namespace, @project), class: "btn btn-cancel"
+= form_for [@project.namespace.becomes(Namespace), @project, @key], url: namespace_project_deploy_keys_path, html: { class: "js-requires-input" } do |f|
+ = form_errors(@key)
+ .form-group
+ = f.label :title, class: "label-light"
+ = f.text_field :title, class: 'form-control', autofocus: true, required: true
+ .form-group
+ = f.label :key, class: "label-light"
+ = f.text_area :key, class: "form-control", rows: 5, required: true
+ .form-group
+ %p.light.append-bottom-0
+ Paste a machine public key here. Read more about how to generate it
+ = link_to "here", help_page_path("ssh", "README")
+ = f.submit "Add key", class: "btn-create btn"
diff --git a/app/views/projects/deploy_keys/index.html.haml b/app/views/projects/deploy_keys/index.html.haml
index 8e24c778b7c..e230834e8ba 100644
--- a/app/views/projects/deploy_keys/index.html.haml
+++ b/app/views/projects/deploy_keys/index.html.haml
@@ -1,43 +1,36 @@
- page_title "Deploy Keys"
-%h3.page-title
- Deploy keys allow read-only access to the repository
-
- = link_to new_namespace_project_deploy_key_path(@project.namespace, @project), class: "btn btn-new pull-right", title: "New Deploy Key" do
- %i.fa.fa-plus
- New Deploy Key
-
-%p.light
- Deploy keys can be used for CI, staging or production servers.
- You can create a deploy key or add an existing one
-
-%hr.clearfix
-
-.row
- .col-md-6.enabled-keys
- %h5
- %strong.cgreen Enabled deploy keys
- for this project
- %ul.bordered-list
- = render @enabled_keys
- - if @enabled_keys.blank?
- .light-well
- .nothing-here-block Create a #{link_to 'new deploy key', new_namespace_project_deploy_key_path(@project.namespace, @project)} or add an existing one
- .col-md-6.available-keys
- - # If there are available public deploy keys but no available project deploy keys, only public deploy keys are shown.
- - if @available_project_keys.any? || @available_public_keys.blank?
- %h5
- %strong Deploy keys
- from projects you have access to
- %ul.bordered-list
+.row.prepend-top-default
+ .col-lg-3.profile-settings-sidebar
+ %h4.prepend-top-0
+ = page_title
+ %p
+ Deploy keys allow read-only access to your repository. Deploy keys can be used for CI, staging or production servers. You can create a deploy key or add an existing one.
+ .col-lg-9
+ %h5.prepend-top-0
+ Create a new deploy key for this project
+ = render "form"
+ .col-lg-9.col-lg-offset-3
+ %hr
+ .col-lg-9.col-lg-offset-3.append-bottom-default.deploy-keys
+ %h5.prepend-top-0
+ Enabled deploy keys for this project (#{@enabled_keys.size})
+ - if @enabled_keys.any?
+ %ul.well-list
+ = render @enabled_keys
+ - else
+ .profile-settings-message.text-center
+ No deploy keys found. Create one with the form above or add existing one below.
+ %h5.prepend-top-default
+ Deploy keys from projects you have access to (#{@available_project_keys.size})
+ - if @available_project_keys.any?
+ %ul.well-list
= render @available_project_keys
- - if @available_project_keys.blank?
- .light-well
- .nothing-here-block Deploy keys from projects you have access to will be displayed here
-
+ - else
+ .profile-settings-message.text-center
+ No deploy keys from your projects could be found. Create one with the form above or add existing one below.
- if @available_public_keys.any?
- %h5
- %strong Public deploy keys
- available to any project
- %ul.bordered-list
+ %h5.prepend-top-default
+ Public deploy keys available to any project (#{@available_public_keys.size})
+ %ul.well-list
= render @available_public_keys
diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml
index eaab99973a4..d9c4b410d32 100644
--- a/app/views/projects/diffs/_diffs.html.haml
+++ b/app/views/projects/diffs/_diffs.html.haml
@@ -1,3 +1,4 @@
+- show_whitespace_toggle = local_assigns.fetch(:show_whitespace_toggle, true)
- if diff_view == 'parallel'
- fluid_layout true
@@ -5,6 +6,11 @@
.content-block.oneline-block.files-changed
.inline-parallel-buttons
+ - if show_whitespace_toggle
+ - if current_controller?(:commit)
+ = commit_diff_whitespace_link(@project, @commit, class: 'hidden-xs')
+ - elsif current_controller?(:merge_requests)
+ = diff_merge_request_whitespace_link(@project, @merge_request, class: 'hidden-xs')
.btn-group
= inline_diff_btn
= parallel_diff_btn
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index 83a8d7ae9bf..0f04fc5d33c 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -40,19 +40,19 @@
= view_file_btn(diff_commit.id, diff_file, project)
.diff-content.diff-wrap-lines
- -# Skipp all non non-supported blobs
+ - # Skip all non non-supported blobs
- return unless blob.respond_to?('text?')
- if diff_file.too_large?
- .nothing-here-block
- This diff could not be displayed because it is too large.
- - else
- - 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, diff_refs: diff_refs
+ .nothing-here-block This diff could not be displayed because it is too large.
+ - elsif blob_text_viewable?(blob) && !project.repository.diffable?(blob)
+ .nothing-here-block This diff was suppressed by a .gitattributes entry.
+ - elsif blob_text_viewable?(blob)
+ - if diff_view == 'parallel'
+ = render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob, index: i
- else
- .nothing-here-block No preview for this file type
+ = 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, diff_refs: diff_refs
+ - else
+ .nothing-here-block No preview for this file type
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index 6ad7b05155a..52d093871b4 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -14,8 +14,10 @@
%p
If you already have files you can push them using command line instructions below.
%p
- Otherwise you can start with
- = link_to "adding README", new_readme_path, class: 'underlined-link'
+ Otherwise you can start with adding a
+ = link_to "README", new_readme_path, class: 'underlined-link'
+ or a
+ = link_to "LICENSE", add_special_file_path(@project, file_name: 'LICENSE'), class: 'underlined-link'
file to this project.
- if can?(current_user, :push_code, @project)
diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
index c15386b4883..f21c864e35c 100644
--- a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
+++ b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
@@ -15,12 +15,13 @@
- if defined?(commit_sha) && commit_sha
%td
= link_to generic_commit_status.short_sha, namespace_project_commit_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.sha), class: "monospace"
-
- %td
- - if generic_commit_status.ref
- = link_to generic_commit_status.ref, namespace_project_commits_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.ref)
- - else
- .light none
+
+ - if defined?(ref) && ref
+ %td
+ - if generic_commit_status.ref
+ = link_to generic_commit_status.ref, namespace_project_commits_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.ref)
+ - else
+ .light none
- if defined?(runner) && runner
%td
diff --git a/app/views/projects/group_links/index.html.haml b/app/views/projects/group_links/index.html.haml
index 13f5fc141fa..2b904544f28 100644
--- a/app/views/projects/group_links/index.html.haml
+++ b/app/views/projects/group_links/index.html.haml
@@ -1,41 +1,44 @@
- page_title "Groups"
-%h3.page_title Share project with other groups
-%p.light
- Projects can be stored in only one group at once. However you can share a project with other groups here.
-%hr
-- if @group_links.present?
- .enabled-groups.panel.panel-default
- .panel-heading
- Already shared with
- %ul.well-list
- - @group_links.each do |group_link|
- - group = group_link.group
- %li
- .pull-right
- = link_to namespace_project_group_link_path(@project.namespace, @project, group_link), method: :delete, class: 'btn btn-sm' do
- %i.icon-remove
- disable sharing
- = link_to group do
- %strong
- %i.icon-folder-open
- = group.name
- %br
- .light up to #{group_link.human_access}
-
-
-.available-groups
- %h4
- Can be shared with
- %div
- = form_tag namespace_project_group_links_path(@project.namespace, @project), method: :post, class: 'form-horizontal' do
+.row.prepend-top-default
+ .col-lg-3.settings-sidebar
+ %h4.prepend-top-0
+ Share project with other groups
+ %p
+ Projects can be stored in only one group at once. However you can share a project with other groups here.
+ .col-lg-9
+ %h5.prepend-top-0
+ Set a group to share
+ = form_tag namespace_project_group_links_path(@project.namespace, @project), method: :post do
.form-group
- = label_tag :link_group_id, 'Group', class: 'control-label'
- .col-sm-10
- = groups_select_tag(:link_group_id, skip_group: @project.group.try(:path))
+ = label_tag :link_group_id, "Group", class: "label-light"
+ = groups_select_tag(:link_group_id, skip_group: @project.group.try(:path))
.form-group
- = label_tag :link_group_access, 'Max access level', class: 'control-label'
- .col-sm-10
- = select_tag :link_group_access, options_for_select(ProjectGroupLink.access_options, ProjectGroupLink.default_access), class: "form-control"
- .form-actions
- = submit_tag "Share", class: "btn btn-create"
-
+ = label_tag :link_group_access, "Max access level", class: "label-light"
+ .select-wrapper
+ = select_tag :link_group_access, options_for_select(ProjectGroupLink.access_options, ProjectGroupLink.default_access), class: "form-control select-control"
+ %span.caret
+ = submit_tag "Share", class: "btn btn-create"
+ .col-lg-9.col-lg-offset-3
+ %hr
+ .col-lg-9.col-lg-offset-3.append-bottom-default.enabled-groups
+ %h5.prepend-top-0
+ Groups you share with (#{@group_links.size})
+ - if @group_links.present?
+ %ul.well-list
+ - @group_links.each do |group_link|
+ - group = group_link.group
+ %li
+ .pull-left.append-right-10.hidden-xs
+ = icon("folder-open-o", class: "settings-list-icon")
+ .pull-left
+ = link_to group do
+ = group.name
+ %br
+ up to #{group_link.human_access}
+ .pull-right
+ = link_to namespace_project_group_link_path(@project.namespace, @project, group_link), method: :delete, class: "btn btn-transparent" do
+ %span.sr-only disable sharing
+ = icon("trash")
+ - else
+ .settings-message.text-center
+ There are no groups with access to your project, add one in the form above
diff --git a/app/views/projects/hooks/_project_hook.html.haml b/app/views/projects/hooks/_project_hook.html.haml
new file mode 100644
index 00000000000..62eba5888a4
--- /dev/null
+++ b/app/views/projects/hooks/_project_hook.html.haml
@@ -0,0 +1,15 @@
+%li
+ .row
+ .col-md-8.col-lg-7
+ %strong.light-header= hook.url
+ %div
+ - %w(push_events tag_push_events issues_events note_events merge_requests_events build_events).each do |trigger|
+ - if hook.send(trigger)
+ %span.label.label-gray.deploy-project-label= trigger.titleize
+ .col-md-4.col-lg-5.text-right-lg.prepend-top-5
+ %span.append-right-10.inline
+ SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"}
+ = link_to "Test", test_namespace_project_hook_path(@project.namespace, @project, hook), class: "btn btn-sm"
+ = link_to namespace_project_hook_path(@project.namespace, @project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-transparent" do
+ %span.sr-only Remove
+ = icon('trash')
diff --git a/app/views/projects/hooks/index.html.haml b/app/views/projects/hooks/index.html.haml
index aae3abcad4b..36c1d69f060 100644
--- a/app/views/projects/hooks/index.html.haml
+++ b/app/views/projects/hooks/index.html.haml
@@ -1,88 +1,84 @@
- page_title "Webhooks"
-%h3.page-title
- Webhooks
+.row.prepend-top-default
+ .col-lg-3.profile-settings-sidebar
+ %h4.prepend-top-0
+ = page_title
+ %p
+ #{link_to "Webhooks", help_page_path("web_hooks", "web_hooks")} can be
+ used for binding events when something is happening within the project.
+ .col-lg-9.append-bottom-default
+ %h5.prepend-top-0
+ Add new webhook
+ = form_for [@project.namespace.becomes(Namespace), @project, @hook], as: :hook, url: namespace_project_hooks_path(@project.namespace, @project) do |f|
+ = form_errors(@hook)
-%p.light
- #{link_to "Webhooks ", help_page_path("web_hooks", "web_hooks"), class: "vlink"} can be
- used for binding events when something is happening within the project.
-
-%hr.clearfix
-
-= form_for [@project.namespace.becomes(Namespace), @project, @hook], as: :hook, url: namespace_project_hooks_path(@project.namespace, @project), html: { class: 'form-horizontal' } do |f|
- = form_errors(@hook)
-
- .form-group
- = f.label :url, "URL", class: 'control-label'
- .col-sm-10
- = f.text_field :url, class: "form-control", placeholder: 'http://example.com/trigger-ci.json'
- .form-group
- = f.label :url, "Trigger", class: 'control-label'
- .col-sm-10.prepend-top-10
- %div
- = f.check_box :push_events, class: 'pull-left'
- .prepend-left-20
- = f.label :push_events, class: 'list-label' do
- %strong Push events
- %p.light
- This url will be triggered by a push to the repository
- %div
- = f.check_box :tag_push_events, class: 'pull-left'
- .prepend-left-20
- = f.label :tag_push_events, class: 'list-label' do
- %strong Tag push events
- %p.light
- This url will be triggered when a new tag is pushed to the repository
- %div
- = f.check_box :note_events, class: 'pull-left'
- .prepend-left-20
- = f.label :note_events, class: 'list-label' do
- %strong Comments
- %p.light
- This url will be triggered when someone adds a comment
- %div
- = f.check_box :issues_events, class: 'pull-left'
- .prepend-left-20
- = f.label :issues_events, class: 'list-label' do
- %strong Issues events
- %p.light
- This url will be triggered when an issue is created/updated/merged
- %div
- = f.check_box :merge_requests_events, class: 'pull-left'
- .prepend-left-20
- = f.label :merge_requests_events, class: 'list-label' do
- %strong Merge Request events
- %p.light
- This url will be triggered when a merge request is created/updated/merged
- %div
- = f.check_box :build_events, class: 'pull-left'
- .prepend-left-20
- = f.label :build_events, class: 'list-label' do
- %strong Build events
- %p.light
- This url will be triggered when the build status changes
- .form-group
- = f.label :enable_ssl_verification, "SSL verification", class: 'control-label checkbox'
- .col-sm-10
- .checkbox
- = f.label :enable_ssl_verification do
- = f.check_box :enable_ssl_verification
- %strong Enable SSL verification
- .form-actions
- = f.submit "Add Webhook", class: "btn btn-create"
-
--if @hooks.any?
- .panel.panel-default
- .panel-heading
+ .form-group
+ = f.label :url, "URL", class: "label-light"
+ = f.text_field :url, class: "form-control", placeholder: "http://example.com/trigger-ci.json"
+ .form-group
+ = f.label :token, "Secret Token", class: 'label-light'
+ = f.text_field :token, class: "form-control", placeholder: ''
+ %p.help-block
+ Use this token to validate received payloads
+ .form-group
+ = f.label :url, "Trigger", class: "label-light"
+ %div
+ = f.check_box :push_events, class: "pull-left"
+ .prepend-left-20
+ = f.label :push_events, class: "label-light append-bottom-0" do
+ Push events
+ %p.light
+ This url will be triggered by a push to the repository
+ %div
+ = f.check_box :tag_push_events, class: "pull-left"
+ .prepend-left-20
+ = f.label :tag_push_events, class: "label-light append-bottom-0" do
+ Tag push events
+ %p.light
+ This url will be triggered when a new tag is pushed to the repository
+ %div
+ = f.check_box :note_events, class: "pull-left"
+ .prepend-left-20
+ = f.label :note_events, class: "label-light append-bottom-0" do
+ Comments
+ %p.light
+ This url will be triggered when someone adds a comment
+ %div
+ = f.check_box :issues_events, class: "pull-left"
+ .prepend-left-20
+ = f.label :issues_events, class: "label-light append-bottom-0" do
+ Issues events
+ %p.light
+ This url will be triggered when an issue is created/updated/merged
+ %div
+ = f.check_box :merge_requests_events, class: "pull-left"
+ .prepend-left-20
+ = f.label :merge_requests_events, class: "label-light append-bottom-0" do
+ Merge Request events
+ %p.light
+ This url will be triggered when a merge request is created/updated/merged
+ %div
+ = f.check_box :build_events, class: "pull-left"
+ .prepend-left-20
+ = f.label :build_events, class: "label-light append-bottom-0" do
+ Build events
+ %p.light
+ This url will be triggered when the build status changes
+ .form-group
+ = f.label :enable_ssl_verification, "SSL verification", class: "label-light"
+ %div
+ = f.check_box :enable_ssl_verification, class: "pull-left"
+ .prepend-left-20
+ = f.label :enable_ssl_verification, class: "label-light append-bottom-0" do
+ Enable SSL verification
+ = f.submit "Add Webhook", class: "btn btn-create"
+ %hr
+ %h5.prepend-top-default
Webhooks (#{@hooks.count})
- %ul.content-list
- - @hooks.each do |hook|
- %li
- .controls
- = link_to 'Test Hook', test_namespace_project_hook_path(@project.namespace, @project, hook), class: "btn btn-sm btn-grouped"
- = link_to 'Remove', namespace_project_hook_path(@project.namespace, @project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-sm btn-grouped"
- .monospace= hook.url
- %div
- - %w(push_events tag_push_events issues_events note_events merge_requests_events build_events).each do |trigger|
- - if hook.send(trigger)
- %span.label.label-gray= trigger.titleize
- %span.label.label-gray SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"}
+ - if @hooks.any?
+ %ul.well-list
+ - @hooks.each do |hook|
+ = render "project_hook", hook: hook
+ - else
+ %p.profile-settings-message.text-center.append-bottom-0
+ No webhooks found, add one in the form above.
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index 7a8009f6da4..9ad86ed71c9 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -48,6 +48,11 @@
= link_to namespace_project_issues_path(issue.project.namespace, issue.project, milestone_title: issue.milestone.title) do
= icon('clock-o')
= issue.milestone.title
+ - if issue.due_date
+ %span{class: "#{'cred' if issue.overdue?}"}
+ &nbsp;
+ = icon('calendar')
+ = issue.due_date.to_s(:medium)
- if issue.labels.any?
&nbsp;
- issue.labels.each do |label|
diff --git a/app/views/projects/issues/_new_branch.html.haml b/app/views/projects/issues/_new_branch.html.haml
index 6da8e4f33a9..469429ccf3c 100644
--- a/app/views/projects/issues/_new_branch.html.haml
+++ b/app/views/projects/issues/_new_branch.html.haml
@@ -1,5 +1,13 @@
-- if current_user && can?(current_user, :push_code, @project) && @issue.can_be_worked_on?(current_user)
+- if can?(current_user, :push_code, @project)
.pull-right
- = link_to namespace_project_branches_path(@project.namespace, @project, branch_name: @issue.to_branch_name, issue_iid: @issue.iid), method: :post, class: 'btn has-tooltip', title: @issue.to_branch_name do
- = icon('code-fork')
- New Branch
+ #new-branch{'data-path' => can_create_branch_namespace_project_issue_path(@project.namespace, @project, @issue)}
+ = link_to namespace_project_branches_path(@project.namespace, @project, branch_name: @issue.to_branch_name, issue_iid: @issue.iid), method: :post, class: 'btn has-tooltip', title: @issue.to_branch_name, disabled: 'disabled' do
+ .checking
+ %i.fa.fa-spinner.fa-spin
+ Checking branches
+ .available(style="display: none")
+ %i.fa.fa-code-fork
+ New branch
+ .unavailable(style="display: none")
+ %i.fa.fa-exclamation-triangle
+ New branch unavailable
diff --git a/app/views/projects/issues/_related_branches.html.haml b/app/views/projects/issues/_related_branches.html.haml
index b10cd03515f..bdfa0c7009e 100644
--- a/app/views/projects/issues/_related_branches.html.haml
+++ b/app/views/projects/issues/_related_branches.html.haml
@@ -5,7 +5,7 @@
- @related_branches.each do |branch|
%li
- sha = @project.repository.find_branch(branch).target
- - ci_commit = @project.ci_commit(sha) if sha
+ - ci_commit = @project.ci_commit(sha, branch) if sha
- if ci_commit
%span.related-branch-ci-status
= render_ci_status(ci_commit)
diff --git a/app/views/projects/issues/update.js.haml b/app/views/projects/issues/update.js.haml
deleted file mode 100644
index e69de29bb2d..00000000000
--- a/app/views/projects/issues/update.js.haml
+++ /dev/null
diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml
index 2f14a91e64f..18b3f9e1549 100644
--- a/app/views/projects/merge_requests/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/_new_submit.html.haml
@@ -42,7 +42,7 @@
%h4 This comparison includes more than #{MergeRequestDiff::COMMITS_SAFE_SIZE} commits.
%p To preserve performance the line changes are not shown.
- else
- = render "projects/diffs/diffs", diffs: @diffs, project: @project, diff_refs: @merge_request.diff_refs
+ = render "projects/diffs/diffs", diffs: @diffs, project: @project, diff_refs: @merge_request.diff_refs, show_whitespace_toggle: false
- if @ci_commit
#builds.builds.tab-pane
= render "projects/merge_requests/show/builds"
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index 65da3712a24..290753d57c6 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -3,7 +3,7 @@
- page_card_attributes @merge_request.card_attributes
- header_title project_title(@project, "Merge Requests", namespace_project_merge_requests_path(@project.namespace, @project))
-- if params[:view] == 'parallel'
+- if diff_view == 'parallel'
- fluid_layout true
.merge-request{'data-url' => merge_request_path(@merge_request)}
@@ -86,9 +86,9 @@
= spinner
= render 'shared/issuable/sidebar', issuable: @merge_request
-- if @merge_request.can_be_reverted?
+- if @merge_request.can_be_reverted?(current_user)
= render "projects/commit/change", type: 'revert', commit: @merge_request.merge_commit, title: @merge_request.title
-- if @merge_request.merge_commit
+- if @merge_request.can_be_cherry_picked?
= render "projects/commit/change", type: 'cherry-pick', commit: @merge_request.merge_commit, title: @merge_request.title
:javascript
diff --git a/app/views/projects/merge_requests/invalid.html.haml b/app/views/projects/merge_requests/invalid.html.haml
index 8ac653427c9..f5bf16ef3ad 100644
--- a/app/views/projects/merge_requests/invalid.html.haml
+++ b/app/views/projects/merge_requests/invalid.html.haml
@@ -1,4 +1,4 @@
-- page_title "#{@merge_request.title} (#{merge_request.to_reference}", "Merge Requests"
+- page_title "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests"
= render "header_title"
.merge-request
diff --git a/app/views/projects/merge_requests/show/_builds.html.haml b/app/views/projects/merge_requests/show/_builds.html.haml
index 307a75d02ca..a116ffe2e15 100644
--- a/app/views/projects/merge_requests/show/_builds.html.haml
+++ b/app/views/projects/merge_requests/show/_builds.html.haml
@@ -1 +1,2 @@
-= render "projects/commit/builds", link_to_commit: true
+= render "projects/commit/ci_commit", ci_commit: @ci_commit, link_to_commit: true
+
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 0a99e8c9591..36c275e8be1 100644
--- a/app/views/projects/merge_requests/show/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_title.html.haml
@@ -24,9 +24,9 @@
%li{ class: issue_button_visibility(@merge_request, false) }
= link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'reopen-mr-link', title: 'Reopen merge request'
%li
- = link_to 'Edit', edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'issuable-edit', id: 'edit_merge_request'
+ = link_to 'Edit', edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'issuable-edit'
= link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: "hidden-xs hidden-sm btn btn-nr btn-grouped btn-close #{issue_button_visibility(@merge_request, true)}", title: 'Close merge request'
= link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "hidden-xs hidden-sm btn btn-nr btn-grouped btn-reopen reopen-mr-link #{issue_button_visibility(@merge_request, false)}", title: 'Reopen merge request'
- = link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "hidden-xs hidden-sm btn btn-nr btn-grouped issuable-edit", id: 'edit_merge_request' do
+ = link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "hidden-xs hidden-sm btn btn-nr btn-grouped issuable-edit" do
= icon('pencil-square-o')
Edit
diff --git a/app/views/projects/merge_requests/update.js.haml b/app/views/projects/merge_requests/update.js.haml
deleted file mode 100644
index e69de29bb2d..00000000000
--- a/app/views/projects/merge_requests/update.js.haml
+++ /dev/null
diff --git a/app/views/projects/merge_requests/widget/_merged.html.haml b/app/views/projects/merge_requests/widget/_merged.html.haml
index 3abae9f0bf6..ec4beae9727 100644
--- a/app/views/projects/merge_requests/widget/_merged.html.haml
+++ b/app/views/projects/merge_requests/widget/_merged.html.haml
@@ -44,3 +44,8 @@
$('.remove_source_branch_in_progress').hide();
$('.remove_source_branch_widget.failed').show();
});
+ - else
+ %p
+ The changes were merged into
+ #{link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch"}.
+ = render 'projects/merge_requests/widget/merged_buttons'
diff --git a/app/views/projects/merge_requests/widget/_merged_buttons.haml b/app/views/projects/merge_requests/widget/_merged_buttons.haml
index 361acf7d27f..56167509af9 100644
--- a/app/views/projects/merge_requests/widget/_merged_buttons.haml
+++ b/app/views/projects/merge_requests/widget/_merged_buttons.haml
@@ -1,12 +1,14 @@
-- source_branch_exists = local_assigns.fetch(:source_branch_exists, false)
-- mr_can_be_reverted = @merge_request.can_be_reverted?
+- can_remove_source_branch = local_assigns.fetch(:source_branch_exists, false) && @merge_request.can_remove_source_branch?(current_user)
+- mr_can_be_reverted = @merge_request.can_be_reverted?(current_user)
+- mr_can_be_cherry_picked = @merge_request.can_be_cherry_picked?
-- if source_branch_exists || mr_can_be_reverted
+- if can_remove_source_branch || mr_can_be_reverted || mr_can_be_cherry_picked
.btn-group
- - if source_branch_exists
+ - if can_remove_source_branch
= link_to namespace_project_branch_path(@merge_request.source_project.namespace, @merge_request.source_project, @merge_request.source_branch), remote: true, method: :delete, class: "btn btn-default btn-grouped btn-sm remove_source_branch" do
= icon('trash-o')
Remove Source Branch
- if mr_can_be_reverted
= revert_commit_link(@merge_request.merge_commit, namespace_project_merge_request_path(@project.namespace, @project, @merge_request), btn_class: 'sm')
- = cherry_pick_commit_link(@merge_request.merge_commit, namespace_project_merge_request_path(@project.namespace, @project, @merge_request), btn_class: 'sm')
+ - if mr_can_be_cherry_picked
+ = cherry_pick_commit_link(@merge_request.merge_commit, namespace_project_merge_request_path(@project.namespace, @project, @merge_request), btn_class: 'sm')
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index be63875ab34..56543ccd062 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -42,9 +42,12 @@
= preserve do
= markdown @milestone.description
-- if @milestone.complete?(current_user) && @milestone.active?
+- if @milestone.total_items_count(current_user).zero?
.alert.alert-success.prepend-top-default
- %span All issues for this milestone are closed. You may close milestone now.
+ %span Assign some issues to this milestone.
+- elsif @milestone.complete?(current_user) && @milestone.active?
+ .alert.alert-success.prepend-top-default
+ %span All issues for this milestone are closed. You may close this milestone now.
= render 'shared/milestones/summary', milestone: @milestone, project: @project
= render 'shared/milestones/tabs', milestone: @milestone
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index 6e9ecdf7649..aeb7c1d5ee4 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -8,7 +8,9 @@
.note-header
= link_to_member(note.project, note.author, avatar: false)
.inline.note-headline-light
- = "#{note.author.to_reference} commented"
+ = "#{note.author.to_reference}"
+ - if !note.system
+ commented
%a{ href: "##{dom_id(note)}" }
= time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note-created-ago')
.note-actions
diff --git a/app/views/projects/project_members/_shared_group_members.html.haml b/app/views/projects/project_members/_shared_group_members.html.haml
index 62888e41935..ae13f8428f0 100644
--- a/app/views/projects/project_members/_shared_group_members.html.haml
+++ b/app/views/projects/project_members/_shared_group_members.html.haml
@@ -8,7 +8,7 @@
group, members with
%strong #{group_links.human_access}
role (#{shared_group_users_count})
- - if current_user.can?(:admin_group, shared_group)
+ - if can?(current_user, :admin_group, shared_group)
.panel-head-actions
= link_to group_group_members_path(shared_group), class: 'btn btn-sm' do
%i.fa.fa-pencil-square-o
diff --git a/app/views/projects/protected_branches/_branches_list.html.haml b/app/views/projects/protected_branches/_branches_list.html.haml
index f68449b1863..b9e9dd8aaea 100644
--- a/app/views/projects/protected_branches/_branches_list.html.haml
+++ b/app/views/projects/protected_branches/_branches_list.html.haml
@@ -1,35 +1,41 @@
-- unless @branches.empty?
- %br
- %h4 Already Protected:
- .table-holder
+%h5.prepend-top-0
+ Already Protected (#{@branches.size})
+- if @branches.empty?
+ %p.profile-settings-message.text-center
+ No branches are protected, protect a branch with the form above.
+- else
+ - can_admin_project = can?(current_user, :admin_project, @project)
+ .table-responsive
%table.table.protected-branches-list
+ %colgroup
+ %col{ width: "30%" }
+ %col{ width: "30%" }
+ %col{ width: "25%" }
+ - if can_admin_project
+ %col
%thead
- %tr.no-border
+ %tr
%th Branch
- %th Developers can push
%th Last commit
- %th
-
+ %th Developers can push
+ - if can_admin_project
+ %th
%tbody
- @branches.each do |branch|
- @url = namespace_project_protected_branch_path(@project.namespace, @project, branch)
%tr
%td
- = link_to namespace_project_commits_path(@project.namespace, @project, branch.name) do
- %strong= branch.name
- - if @project.root_ref?(branch.name)
- %span.label.label-info default
- %td
- = check_box_tag "developers_can_push", branch.id, branch.developers_can_push, "data-url" => @url
- %td
- - if commit = branch.commit
- = link_to namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit_short_id' do
- = commit.short_id
- &middot;
- #{time_ago_with_tooltip(commit.committed_date)}
- - else
- (branch was removed from repository)
+ = link_to(branch.name, namespace_project_commits_path(@project.namespace, @project, branch.name))
+ - if @project.root_ref?(branch.name)
+ %span.label.label-info.prepend-left-5 default
+ %td
+ - if commit = branch.commit
+ = link_to(commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit_short_id')
+ #{time_ago_with_tooltip(commit.committed_date)}
+ - else
+ (branch was removed from repository)
+ %td
+ = check_box_tag("developers_can_push", branch.id, branch.developers_can_push, data: { url: @url })
+ - if can_admin_project
%td
- .pull-right
- - if can? current_user, :admin_project, @project
- = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-remove btn-sm"
+ = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-warning btn-sm"
diff --git a/app/views/projects/protected_branches/index.html.haml b/app/views/projects/protected_branches/index.html.haml
index 653b02da4db..c7d317dbaee 100644
--- a/app/views/projects/protected_branches/index.html.haml
+++ b/app/views/projects/protected_branches/index.html.haml
@@ -1,31 +1,33 @@
- page_title "Protected branches"
-%h3.page-title Protected branches
-%p.light Keep stable branches secure and force developers to use Merge Requests
-%hr
-.well
- %p Protected branches are designed to
- %ul
- %li prevent pushes from everybody except #{link_to "masters", help_page_path("permissions", "permissions"), class: "vlink"}
- %li prevent anyone from force pushing to the branch
- %li prevent anyone from deleting the branch
- %p Read more about #{link_to "project permissions", help_page_path("permissions", "permissions"), class: "underlined-link"}
+.row.prepend-top-default.append-bottom-default
+ .col-lg-3
+ %h4.prepend-top-0
+ = page_title
+ %p Keep stable branches secure and force developers to use Merge Requests
+ .col-lg-9
+ %h5.prepend-top-0
+ Protect a branch
+ .account-well.append-bottom-default
+ %p.light-header.append-bottom-0 Protected branches are designed to
+ %ul
+ %li prevent pushes from everybody except #{link_to "masters", help_page_path("permissions", "permissions"), class: "vlink"}
+ %li prevent anyone from force pushing to the branch
+ %li prevent anyone from deleting the branch
+ %p.append-bottom-0 Read more about #{link_to "project permissions", help_page_path("permissions", "permissions"), class: "underlined-link"}
+ - if can? current_user, :admin_project, @project
+ = form_for [@project.namespace.becomes(Namespace), @project, @protected_branch] do |f|
+ = form_errors(@protected_branch)
-- if can? current_user, :admin_project, @project
- = form_for [@project.namespace.becomes(Namespace), @project, @protected_branch], html: { class: 'form-horizontal' } do |f|
- = form_errors(@protected_branch)
-
- .form-group
- = f.label :name, "Branch", class: 'control-label'
- .col-sm-10
- = f.select(:name, @project.open_branches.map { |br| [br.name, br.name] } , {include_blank: true}, {class: "select2", data: {placeholder: "Select branch"}})
- .form-group
- .col-sm-offset-2.col-sm-10
- .checkbox
- = f.label :developers_can_push do
- = f.check_box :developers_can_push
- %strong Developers can push
- .help-block Allow developers to push to this branch
- .form-actions
- = f.submit 'Protect', class: "btn-create btn"
-= render 'branches_list'
+ .form-group
+ = f.label :name, "Branch", class: "label-light"
+ = f.select(:name, @project.open_branches.map { |br| [br.name, br.name] } , {include_blank: true}, {class: "select2", data: {placeholder: "Select branch"}})
+ .form-group
+ = f.check_box :developers_can_push, class: "pull-left"
+ .prepend-left-20
+ = f.label :developers_can_push, "Developers can push", class: "label-light append-bottom-0"
+ %p.light.append-bottom-0
+ Allow developers to push to this branch
+ = f.submit "Protect", class: "btn-create btn"
+ %hr
+ = render "branches_list"
diff --git a/app/views/projects/runners/_shared_runners.html.haml b/app/views/projects/runners/_shared_runners.html.haml
index b31fcfea763..9fa4127c948 100644
--- a/app/views/projects/runners/_shared_runners.html.haml
+++ b/app/views/projects/runners/_shared_runners.html.haml
@@ -4,7 +4,7 @@
- if shared_runners_text.present?
= markdown(shared_runners_text, pipeline: 'plain_markdown')
- else
- GitLab Runners do not offer secure isolation between projects that they do builds for. You are TRUSTING all GitLab users who can push code to project A, B or C to run shell scripts on the machine hosting runner X.
+ Shared runners execute code of different projects on the same Runner unless you configure GitLab Runner Autoscale with MaxBuilds 1 (which it is on GitLab.com).
%hr
- if @project.shared_runners_enabled?
= link_to toggle_shared_runners_namespace_project_runners_path(@project.namespace, @project), class: 'btn btn-warning', method: :post do
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index 4310f038fc9..d854ac21725 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -36,9 +36,9 @@
%li
= link_to 'Changelog', changelog_path(@project)
- - if @repository.license
+ - if @repository.license_blob
%li
- = link_to 'License', license_path(@project)
+ = link_to license_short_name(@project), license_path(@project)
- if @repository.contribution_guide
%li
@@ -47,15 +47,15 @@
- if current_user && can_push_branch?(@project, @project.default_branch)
- unless @repository.changelog
%li.missing
- = link_to add_changelog_path(@project) do
+ = link_to add_special_file_path(@project, file_name: 'CHANGELOG') do
Add Changelog
- - unless @repository.license
+ - unless @repository.license_blob
%li.missing
- = link_to add_license_path(@project) do
+ = link_to add_special_file_path(@project, file_name: 'LICENSE') do
Add License
- unless @repository.contribution_guide
%li.missing
- = link_to add_contribution_guide_path(@project) do
+ = link_to add_special_file_path(@project, file_name: 'CONTRIBUTING.md', commit_message: 'Add contribution guide') do
Add Contribution guide
- if @repository.commit
diff --git a/app/views/projects/triggers/_trigger.html.haml b/app/views/projects/triggers/_trigger.html.haml
index 48b3b5c9920..112b51712ef 100644
--- a/app/views/projects/triggers/_trigger.html.haml
+++ b/app/views/projects/triggers/_trigger.html.haml
@@ -1,7 +1,6 @@
%tr
%td
- .clearfix
- %span.monospace= trigger.token
+ %span.monospace= trigger.token
%td
- if trigger.last_trigger_request
@@ -9,6 +8,5 @@
- else
Never
- %td
- .pull-right
- = link_to 'Revoke', namespace_project_trigger_path(@project.namespace, @project, trigger), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-danger btn-sm btn-grouped"
+ %td.text-right
+ = link_to 'Revoke', namespace_project_trigger_path(@project.namespace, @project, trigger), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-warning btn-sm"
diff --git a/app/views/projects/triggers/index.html.haml b/app/views/projects/triggers/index.html.haml
index bd346c4b8e6..f91885b216d 100644
--- a/app/views/projects/triggers/index.html.haml
+++ b/app/views/projects/triggers/index.html.haml
@@ -1,71 +1,70 @@
- page_title "Triggers"
-%h3.page-title
- Triggers
-%p.light
- Triggers can be used to force a rebuild of a specific branch or tag with an API call.
+.row.prepend-top-default.append-bottom-default
+ .col-lg-3
+ %h4.prepend-top-0
+ = page_title
+ %p
+ Triggers can be used to force a rebuild of a specific branch or tag with an API call.
+ .col-lg-9
+ %h5.prepend-top-0
+ Your triggers
+ - if @triggers.any?
+ .table-responsive
+ %table.table
+ %thead
+ %th Token
+ %th Last used
+ %th
+ = render partial: 'trigger', collection: @triggers, as: :trigger
+ - else
+ %p.profile-settings-message.text-center.append-bottom-default
+ There are no triggers to use, add one by the button below.
-%hr.clearfix
+ = form_for @trigger, url: url_for(controller: 'projects/triggers', action: 'create') do |f|
+ = f.submit "Add Trigger", class: 'btn btn-success'
--if @triggers.any?
- .table-holder
- %table.table
- %thead
- %th Token
- %th Last used
- %th
- = render partial: 'trigger', collection: @triggers, as: :trigger
-- else
- %h4 No triggers
+ %h5.prepend-top-default
+ Use CURL
-= form_for @trigger, url: url_for(controller: 'projects/triggers', action: 'create'), html: { class: 'form-horizontal' } do |f|
- .clearfix
- = f.submit "Add Trigger", class: 'btn btn-success pull-right'
+ %p.light
+ Copy the token above and set your branch or tag name. This is the reference that will be rebuild.
-%hr.clearfix
--if @triggers.any?
- %h3
- Use CURL
+ %pre
+ :plain
+ curl -X POST \
+ -F token=TOKEN \
+ -F ref=REF_NAME \
+ #{builds_trigger_url(@project.id)}
+ %h5.prepend-top-default
+ Use .gitlab-ci.yml
- %p.light
- Copy the token above and set your branch or tag name. This is the reference that will be rebuild.
+ %p.light
+ Copy the snippet to
+ %i .gitlab-ci.yml
+ of dependent project.
+ At the end of your build it will trigger this project to rebuilt.
+ %pre
+ :plain
+ trigger:
+ type: deploy
+ script:
+ - "curl -X POST -F token=TOKEN -F ref=REF_NAME #{builds_trigger_url(@project.id)}"
+ %h5.prepend-top-default
+ Pass build variables
- %pre
- :plain
- curl -X POST \
- -F token=TOKEN \
- -F ref=REF_NAME \
- #{builds_trigger_url(@project.id)}
- %h3
- Use .gitlab-ci.yml
+ %p.light
+ Add
+ %strong variables[VARIABLE]=VALUE
+ to API request.
+ The value of variable could then be used to distinguish triggered build from normal one.
- %p.light
- Copy the snippet to
- %i .gitlab-ci.yml
- of dependent project.
- At the end of your build it will trigger this project to rebuilt.
-
- %pre
- :plain
- trigger:
- type: deploy
- script:
- - "curl -X POST -F token=TOKEN -F ref=REF_NAME #{builds_trigger_url(@project.id)}"
- %h3
- Pass build variables
-
- %p.light
- Add
- %strong variables[VARIABLE]=VALUE
- to API request.
- The value of variable could then be used to distinguish triggered build from normal one.
-
- %pre
- :plain
- curl -X POST \
- -F token=TOKEN \
- -F "ref=REF_NAME" \
- -F "variables[RUN_NIGHTLY_BUILD]=true" \
- #{builds_trigger_url(@project.id)}
+ %pre.append-bottom-0
+ :plain
+ curl -X POST \
+ -F token=TOKEN \
+ -F "ref=REF_NAME" \
+ -F "variables[RUN_NIGHTLY_BUILD]=true" \
+ #{builds_trigger_url(@project.id)}
diff --git a/app/views/search/_category.html.haml b/app/views/search/_category.html.haml
index 2c3fca439f3..2c378231237 100644
--- a/app/views/search/_category.html.haml
+++ b/app/views/search/_category.html.haml
@@ -2,97 +2,70 @@
- if @project
%li{class: ("active" if @scope == 'blobs')}
= link_to search_filter_path(scope: 'blobs') do
- = icon('code fw')
- %span
- Code
- %span.badge
- = @search_results.blobs_count
+ Code
+ %span.badge
+ = @search_results.blobs_count
%li{class: ("active" if @scope == 'issues')}
= link_to search_filter_path(scope: 'issues') do
- = icon('exclamation-circle fw')
- %span
- Issues
- %span.badge
- = @search_results.issues_count
+ Issues
+ %span.badge
+ = @search_results.issues_count
%li{class: ("active" if @scope == 'merge_requests')}
= link_to search_filter_path(scope: 'merge_requests') do
- = icon('tasks fw')
- %span
- Merge requests
- %span.badge
- = @search_results.merge_requests_count
+ Merge requests
+ %span.badge
+ = @search_results.merge_requests_count
%li{class: ("active" if @scope == 'milestones')}
= link_to search_filter_path(scope: 'milestones') do
- = icon('clock-o fw')
- %span
- Milestones
- %span.badge
- = @search_results.milestones_count
+ Milestones
+ %span.badge
+ = @search_results.milestones_count
%li{class: ("active" if @scope == 'notes')}
= link_to search_filter_path(scope: 'notes') do
- = icon('comments fw')
- %span
- Comments
- %span.badge
- = @search_results.notes_count
+ Comments
+ %span.badge
+ = @search_results.notes_count
%li{class: ("active" if @scope == 'wiki_blobs')}
= link_to search_filter_path(scope: 'wiki_blobs') do
- = icon('book fw')
- %span
- Wiki
- %span.badge
- = @search_results.wiki_blobs_count
+ Wiki
+ %span.badge
+ = @search_results.wiki_blobs_count
%li{class: ("active" if @scope == 'commits')}
= link_to search_filter_path(scope: 'commits') do
- = icon('history fw')
- %span
- Commits
- %span.badge
- = @search_results.commits_count
+ Commits
+ %span.badge
+ = @search_results.commits_count
- elsif @show_snippets
%li{class: ("active" if @scope == 'snippet_blobs')}
= link_to search_filter_path(scope: 'snippet_blobs', snippets: true, group_id: nil, project_id: nil) do
- = icon('code fw')
- %span
- Snippet Contents
- %span.badge
- = @search_results.snippet_blobs_count
+ Snippet Contents
+ %span.badge
+ = @search_results.snippet_blobs_count
%li{class: ("active" if @scope == 'snippet_titles')}
= link_to search_filter_path(scope: 'snippet_titles', snippets: true, group_id: nil, project_id: nil) do
- = icon('book fw')
- %span
- Titles and Filenames
- %span.badge
- = @search_results.snippet_titles_count
+ Titles and Filenames
+ %span.badge
+ = @search_results.snippet_titles_count
- else
%li{class: ("active" if @scope == 'projects')}
= link_to search_filter_path(scope: 'projects') do
- = icon('bookmark fw')
- %span
- Projects
- %span.badge
- = @search_results.projects_count
+ Projects
+ %span.badge
+ = @search_results.projects_count
%li{class: ("active" if @scope == 'issues')}
= link_to search_filter_path(scope: 'issues') do
- = icon('exclamation-circle fw')
- %span
- Issues
- %span.badge
- = @search_results.issues_count
+ Issues
+ %span.badge
+ = @search_results.issues_count
%li{class: ("active" if @scope == 'merge_requests')}
= link_to search_filter_path(scope: 'merge_requests') do
- = icon('tasks fw')
- %span
- Merge requests
- %span.badge
- = @search_results.merge_requests_count
+ Merge requests
+ %span.badge
+ = @search_results.merge_requests_count
%li{class: ("active" if @scope == 'milestones')}
= link_to search_filter_path(scope: 'milestones') do
- = icon('clock-o fw')
- %span
- Milestones
- %span.badge
- = @search_results.milestones_count
-
+ Milestones
+ %span.badge
+ = @search_results.milestones_count
diff --git a/app/views/search/_filter.html.haml b/app/views/search/_filter.html.haml
index 4ef544136a8..ef1c0296d49 100644
--- a/app/views/search/_filter.html.haml
+++ b/app/views/search/_filter.html.haml
@@ -1,47 +1,33 @@
-.dropdown.inline
- %button.dropdown-toggle.btn.btn-sm{type: 'button', 'data-toggle' => 'dropdown'}
- %span.light Group:
- - if @group.present?
- %strong= @group.name
- - else
- Any
- %b.caret
- .dropdown-menu.dropdown-select.dropdown-menu-selectable
- .dropdown-title
- %span Filter results by group
- %button.dropdown-title-button.dropdown-menu-close{aria: {label: "Close"}}
- = icon('times')
- .dropdown-content
- %ul
- %li
- = link_to search_filter_path(group_id: nil), class: ("is-active" if !params[:group_id].present?) do
- Any
- %li.divider
- - current_user.authorized_groups.sort_by(&:name).each do |group|
- %li
- = link_to search_filter_path(group_id: group.id, project_id: nil), class: ("is-active" if params[:group_id] == group.id.to_s) do
- = group.name
+- if params[:group_id].present?
+ = hidden_field_tag :group_id, params[:group_id]
+- if params[:project_id].present?
+ = hidden_field_tag :project_id, params[:project_id]
+.dropdown
+ %button.dropdown-menu-toggle.btn.js-search-group-dropdown{ type: "button", data: { toggle: "dropdown", default_label: "Group:" } }
+ %span.dropdown-toggle-text
+ Group:
+ - if @group.present?
+ = @group.name
+ - else
+ Any
+ = icon("chevron-down")
+ .dropdown-menu.dropdown-select.dropdown-menu-selectable.dropdown-menu-align-right
+ = dropdown_title("Filter results by group")
+ = dropdown_filter("Search groups")
+ = dropdown_content
+ = dropdown_loading
-.dropdown.inline.prepend-left-10.project-filter
- %button.dropdown-toggle.btn.btn-sm{type: 'button', 'data-toggle' => 'dropdown'}
- %span.light Project:
- - if @project.present?
- %strong= @project.name_with_namespace
- - else
- Any
- %b.caret
- .dropdown-menu.dropdown-select.dropdown-menu-selectable
- .dropdown-title
- %span Filter results by project
- %button.dropdown-title-button.dropdown-menu-close{aria: {label: "Close"}}
- = icon('times')
- .dropdown-content
- %ul
- %li
- = link_to search_filter_path(project_id: nil), class: ("is-active" if !params[:project_id].present?) do
- Any
- %li.divider
- - current_user.authorized_projects.sort_by(&:name_with_namespace).each do |project|
- %li
- = link_to search_filter_path(project_id: project.id, group_id: nil), class: ("is-active" if params[:project_id] == project.id.to_s) do
- = project.name_with_namespace
+.dropdown.project-filter
+ %button.dropdown-menu-toggle.btn.js-search-project-dropdown{ type: "button", data: { toggle: "dropdown", default_label: "Project:" } }
+ %span.dropdown-toggle-text
+ Project:
+ - if @project.present?
+ = @project.name_with_namespace
+ - else
+ Any
+ = icon("chevron-down")
+ .dropdown-menu.dropdown-select.dropdown-menu-selectable.dropdown-menu-align-right
+ = dropdown_title("Filter results by project")
+ = dropdown_filter("Search projects")
+ = dropdown_content
+ = dropdown_loading
diff --git a/app/views/search/_form.html.haml b/app/views/search/_form.html.haml
index a9dbc84da29..3139be1cd37 100644
--- a/app/views/search/_form.html.haml
+++ b/app/views/search/_form.html.haml
@@ -1,14 +1,15 @@
-= form_tag search_path, method: :get do |f|
- = hidden_field_tag :project_id, params[:project_id]
- = hidden_field_tag :group_id, params[:group_id]
+= form_tag search_path, method: :get, class: 'js-search-form' do |f|
= hidden_field_tag :snippets, params[:snippets]
= hidden_field_tag :scope, params[:scope]
- .search-holder.clearfix
- .input-group
- = search_field_tag :search, params[:search], placeholder: "Search for projects, issues etc", class: "form-control search-text-input", id: "dashboard_search", autofocus: true, spellcheck: false
- %span.input-group-btn
- = button_tag 'Search', class: "btn btn-primary"
+ .search-holder
+ .search-field-holder
+ = search_field_tag :search, params[:search], placeholder: "Search for projects, issues etc", class: "form-control search-text-input js-search-input", id: "dashboard_search", autofocus: true, spellcheck: false
+ = icon("search", class: "search-icon")
+ %button.search-clear.js-search-clear{ class: ("hidden" if !params[:search].present?), type: "button", tabindex: "-1" }
+ = icon("times-circle")
+ %span.sr-only
+ Clear search
- unless params[:snippets].eql? 'true'
- %br
= render 'filter' if current_user
+ = button_tag "Search", class: "btn btn-success btn-search"
diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml
index 60df348891c..711337f308e 100644
--- a/app/views/search/_results.html.haml
+++ b/app/views/search/_results.html.haml
@@ -1,10 +1,8 @@
-- if @search_results.empty?
+- if @search_objects.empty?
= render partial: "search/results/empty"
- else
.gray-content-block
- Search results for
- %code
- = @search_term
+ = search_entries_info(@search_objects, @scope, @search_term)
- unless @show_snippets
- if @project
in project #{link_to @project.name_with_namespace, [@project.namespace.becomes(Namespace), @project]}
@@ -15,12 +13,9 @@
.search-results
- if @scope == 'projects'
.term
- = render 'shared/projects/list', projects: @objects
+ = render 'shared/projects/list', projects: @search_objects
- else
- = render partial: "search/results/#{@scope.singularize}", collection: @objects
+ = render partial: "search/results/#{@scope.singularize}", collection: @search_objects
- if @scope != 'projects'
- = paginate @objects, theme: 'gitlab'
-
-:javascript
- $(".search-results .term").highlight("#{escape_javascript(params[:search])}");
+ = paginate(@search_objects, theme: 'gitlab')
diff --git a/app/views/search/results/_issue.html.haml b/app/views/search/results/_issue.html.haml
index 710f5613c81..640890fbe92 100644
--- a/app/views/search/results/_issue.html.haml
+++ b/app/views/search/results/_issue.html.haml
@@ -7,7 +7,7 @@
- if issue.description.present?
.description.term
= preserve do
- = search_md_sanitize(markdown(issue.description, { project: issue.project }))
+ = search_md_sanitize(markdown(truncate(issue.description, length: 200, separator: " "), { project: issue.project }))
%span.light
#{issue.project.name_with_namespace}
- if issue.closed?
diff --git a/app/views/shared/_confirm_modal.html.haml b/app/views/shared/_confirm_modal.html.haml
index 34241cd8aad..b0fc60573f7 100644
--- a/app/views/shared/_confirm_modal.html.haml
+++ b/app/views/shared/_confirm_modal.html.haml
@@ -7,7 +7,7 @@
Confirmation required
.modal-body
- %p.cred.lead.js-confirm-text
+ %p.text-danger.js-confirm-text
%p
This action can lead to data loss.
diff --git a/app/views/shared/_file_highlight.html.haml b/app/views/shared/_file_highlight.html.haml
index 57856031d6e..37dcf39c062 100644
--- a/app/views/shared/_file_highlight.html.haml
+++ b/app/views/shared/_file_highlight.html.haml
@@ -1,12 +1,13 @@
.file-content.code.js-syntax-highlight
.line-numbers
- if blob.data.present?
+ - link_icon = icon('link')
- blob.data.each_line.each_with_index do |_, index|
- offset = defined?(first_line_number) ? first_line_number : 1
- i = index + offset
-# We're not using `link_to` because it is too slow once we get to thousands of lines.
%a.diff-line-num{href: "#L#{i}", id: "L#{i}", 'data-line-number' => i}
- %i.fa.fa-link
+ = link_icon
= i
.blob-content{data: {blob_id: blob.id}}
- = highlight(blob.name, blob.data)
+ = highlight(blob.name, blob.data, plain: blob.no_highlighting?)
diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml
index b38c5e18efb..9ce5562e667 100644
--- a/app/views/shared/_label_row.html.haml
+++ b/app/views/shared/_label_row.html.haml
@@ -2,4 +2,4 @@
%span.label-name
= link_to_label(label, tooltip: false)
%span.prepend-left-10
- = markdown(label.description, pipeline: :single_line)
+ = markdown(label.description, pipeline: :single_line) \ No newline at end of file
diff --git a/app/views/shared/_labels_row.html.haml b/app/views/shared/_labels_row.html.haml
new file mode 100644
index 00000000000..dc89e36419c
--- /dev/null
+++ b/app/views/shared/_labels_row.html.haml
@@ -0,0 +1,3 @@
+- labels.each do |label|
+ %span.label-row
+ = link_to_label(label, tooltip: false)
diff --git a/app/views/shared/_service_settings.html.haml b/app/views/shared/_service_settings.html.haml
index fc935166bf6..4eaf7c2a025 100644
--- a/app/views/shared/_service_settings.html.haml
+++ b/app/views/shared/_service_settings.html.haml
@@ -62,6 +62,14 @@
%strong Build events
%p.light
This url will be triggered when a build status changes
+ - if @service.supported_events.include?("wiki_page")
+ %div
+ = form.check_box :wiki_page_events, class: 'pull-left'
+ .prepend-left-20
+ = form.label :wiki_page_events, class: 'list-label' do
+ %strong Wiki Page events
+ %p.light
+ This url will be triggered when a wiki page is created/updated
- @service.fields.each do |field|
diff --git a/app/views/shared/_sort_dropdown.html.haml b/app/views/shared/_sort_dropdown.html.haml
index e3a6a5a68b6..d327bd0a96f 100644
--- a/app/views/shared/_sort_dropdown.html.haml
+++ b/app/views/shared/_sort_dropdown.html.haml
@@ -20,6 +20,11 @@
= sort_title_milestone_soon
= link_to page_filter_path(sort: sort_value_milestone_later) do
= sort_title_milestone_later
+ - if controller.controller_name == 'issues' || controller.action_name == 'issues'
+ = link_to page_filter_path(sort: sort_value_due_date_soon) do
+ = sort_title_due_date_soon
+ = link_to page_filter_path(sort: sort_value_due_date_later) do
+ = sort_title_due_date_later
= link_to page_filter_path(sort: sort_value_upvotes) do
= sort_title_upvotes
= link_to page_filter_path(sort: sort_value_downvotes) do
diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml
index 921eaefd79a..fc1101646fb 100644
--- a/app/views/shared/issuable/_filter.html.haml
+++ b/app/views/shared/issuable/_filter.html.haml
@@ -23,6 +23,7 @@
.filter-item.inline.labels-filter
= render "shared/issuable/label_dropdown"
+
.pull-right
= render 'shared/sort_dropdown'
@@ -46,9 +47,10 @@
.filter-item.inline
= button_tag "Update issues", class: "btn update_selected_issues btn-save"
-- if @label
- .gray-content-block.second-block
- = render "shared/label_row", label: @label
+ - if !@labels.nil?
+ .gray-content-block.second-block.filtered-labels{ class: ("hidden" if !@labels.any?) }
+ - if @labels.any?
+ = render "shared/labels_row", labels: @labels
:javascript
new UsersSelect();
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index bae15b7f844..18b091df39b 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -75,8 +75,9 @@
= f.label :label_ids, "Labels", class: 'control-label'
.col-sm-10{ class: ('issuable-form-padding-top' if !has_labels) }
- if has_labels
- = f.collection_select :label_ids, issuable.project.labels.all, :id, :name,
- { selected: issuable.label_ids }, multiple: true, class: 'select2', data: { placeholder: "Select labels" }
+ .issuable-form-select-holder
+ = f.collection_select :label_ids, issuable.project.labels.all, :id, :name,
+ { selected: issuable.label_ids }, multiple: true, class: 'select2', data: { placeholder: "Select labels" }
- else
%span.light No labels yet.
&nbsp;
@@ -88,9 +89,10 @@
.form-group
= label_tag :move_to_project_id, 'Move', class: 'control-label'
.col-sm-10
- - projects = project_options(issuable, current_user, ability: :admin_issue)
- = select_tag(:move_to_project_id, projects, include_blank: true,
- class: 'select2', data: { placeholder: 'Select project' })
+ .issuable-form-select-holder
+ - projects = project_options(issuable, current_user, ability: :admin_issue)
+ = select_tag(:move_to_project_id, projects, include_blank: true,
+ class: 'select2', data: { placeholder: 'Select project' })
&nbsp;
%span{ data: { toggle: 'tooltip', placement: 'auto top' }, style: 'cursor: default',
title: 'Moving an issue will copy the discussion to a different project and close it here. All participants will be notified of the new location.' }
@@ -102,13 +104,15 @@
.form-group
= f.label :source_branch, class: 'control-label'
.col-sm-10
- = f.select(:source_branch, [@merge_request.source_branch], { }, { class: 'source_branch select2 span2', disabled: true })
+ .issuable-form-select-holder
+ = f.select(:source_branch, [@merge_request.source_branch], { }, { class: 'source_branch select2 span2', disabled: true })
.form-group
= f.label :target_branch, class: 'control-label'
.col-sm-10
- = f.select(:target_branch, @merge_request.target_branches, { include_blank: true }, { class: 'target_branch select2 span2', disabled: @merge_request.new_record?, data: {placeholder: "Select branch"} })
+ .issuable-form-select-holder
+ = f.select(:target_branch, @merge_request.target_branches, { include_blank: true }, { class: 'target_branch select2 span2', disabled: @merge_request.new_record?, data: {placeholder: "Select branch"} })
- if @merge_request.new_record?
- %p.help-block
+ &nbsp;
= link_to 'Change branches', mr_change_branches_path(@merge_request)
- is_footer = !(issuable.is_a?(MergeRequest) && issuable.new_record?)
diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml
index f722e61eeac..61fd1e9c335 100644
--- a/app/views/shared/issuable/_label_dropdown.html.haml
+++ b/app/views/shared/issuable/_label_dropdown.html.haml
@@ -1,44 +1,14 @@
- if params[:label_name].present?
- = hidden_field_tag(:label_name, params[:label_name])
+ - if params[:label_name].respond_to?('any?')
+ - params[:label_name].each do |label|
+ = hidden_field_tag "label_name[]", label, id: nil
.dropdown
- %button.dropdown-menu-toggle.js-label-select.js-filter-submit.js-extra-options{type: "button", data: {toggle: "dropdown", field_name: "label_name", show_no: "true", show_any: "true", selected: params[:label_name], project_id: @project.try(:id), labels: labels_filter_path, default_label: "Label"}}
+ %button.dropdown-menu-toggle.js-label-select.js-filter-submit.js-multiselect.js-extra-options{type: "button", data: {toggle: "dropdown", field_name: "label_name[]", show_no: "true", show_any: "true", selected: params[:label_name], project_id: @project.try(:id), labels: labels_filter_path, default_label: "Label"}}
%span.dropdown-toggle-text
- = h(params[:label_name].presence || "Label")
+ = h(multi_label_name(params[:label_name], "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
+ = render partial: "shared/issuable/label_page_default", locals: { title: "Filter by label" }
- if can? current_user, :admin_label, @project and @project
- .dropdown-page-two.dropdown-new-label
- = dropdown_title("Create new label", back: true)
- = dropdown_content do
- .dropdown-labels-error.js-label-error
- %input#new_label_name.dropdown-input-field{type: "text", placeholder: "Name new label"}
- .suggest-colors.suggest-colors-dropdown
- - suggested_colors.each do |color|
- = link_to '#', style: "background-color: #{color}", data: { color: color } do
- &nbsp
- .dropdown-label-color-input
- .dropdown-label-color-preview.js-dropdown-label-color-preview
- %input#new_label_color.dropdown-input-field{ type: "text" }
- .clearfix
- %button.btn.btn-primary.pull-left.js-new-label-btn{type: "button"}
- Create
- %button.btn.btn-default.pull-right.js-cancel-label-btn{type: "button"}
- Cancel
+ = render partial: "shared/issuable/label_page_create"
= dropdown_loading
diff --git a/app/views/shared/issuable/_label_page_create.html.haml b/app/views/shared/issuable/_label_page_create.html.haml
new file mode 100644
index 00000000000..3bc57d3d2ac
--- /dev/null
+++ b/app/views/shared/issuable/_label_page_create.html.haml
@@ -0,0 +1,17 @@
+.dropdown-page-two.dropdown-new-label
+ = dropdown_title("Create new label", back: true)
+ = dropdown_content do
+ .dropdown-labels-error.js-label-error
+ %input#new_label_name.default-dropdown-input{ type: "text", placeholder: "Name new label" }
+ .suggest-colors.suggest-colors-dropdown
+ - suggested_colors.each do |color|
+ = link_to '#', style: "background-color: #{color}", data: { color: color } do
+ &nbsp
+ .dropdown-label-color-input
+ .dropdown-label-color-preview.js-dropdown-label-color-preview
+ %input#new_label_color.default-dropdown-input{ type: "text" }
+ .clearfix
+ %button.btn.btn-primary.pull-left.js-new-label-btn{ type: "button" }
+ Create
+ %button.btn.btn-default.pull-right.js-cancel-label-btn{ type: "button" }
+ Cancel
diff --git a/app/views/shared/issuable/_label_page_default.html.haml b/app/views/shared/issuable/_label_page_default.html.haml
new file mode 100644
index 00000000000..7f4867417f7
--- /dev/null
+++ b/app/views/shared/issuable/_label_page_default.html.haml
@@ -0,0 +1,20 @@
+- title = local_assigns.fetch(:title, 'Assign labels')
+- filter_placeholder = local_assigns.fetch(:filter_placeholder, 'Search labels')
+.dropdown-page-one
+ = dropdown_title(title)
+ = dropdown_filter(filter_placeholder)
+ = 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), :"data-is-link" => true do
+ - if can? current_user, :admin_label, @project
+ Manage labels
+ - else
+ View labels
+ = dropdown_loading \ No newline at end of file
diff --git a/app/views/shared/issuable/_nav.html.haml b/app/views/shared/issuable/_nav.html.haml
index a6970b7eebb..1d9b09a5ef1 100644
--- a/app/views/shared/issuable/_nav.html.haml
+++ b/app/views/shared/issuable/_nav.html.haml
@@ -4,22 +4,22 @@
- else
- page_context_word = 'issues'
%li{class: ("active" if params[:state] == 'opened')}
- = link_to page_filter_path(state: 'opened'), title: "Filter by #{page_context_word} that are currently opened." do
+ = link_to page_filter_path(state: 'opened', label: true), title: "Filter by #{page_context_word} that are currently opened." do
#{state_filters_text_for(:opened, @project)}
- if defined?(type) && type == :merge_requests
%li{class: ("active" if params[:state] == 'merged')}
- = link_to page_filter_path(state: 'merged'), title: 'Filter by merge requests that are currently merged.' do
+ = link_to page_filter_path(state: 'merged', label: true), title: 'Filter by merge requests that are currently merged.' do
#{state_filters_text_for(:merged, @project)}
%li{class: ("active" if params[:state] == 'closed')}
- = link_to page_filter_path(state: 'closed'), title: 'Filter by merge requests that are currently closed and unmerged.' do
+ = link_to page_filter_path(state: 'closed', label: true), title: 'Filter by merge requests that are currently closed and unmerged.' do
#{state_filters_text_for(:closed, @project)}
- else
%li{class: ("active" if params[:state] == 'closed')}
- = link_to page_filter_path(state: 'closed'), title: 'Filter by issues that are currently closed.' do
+ = link_to page_filter_path(state: 'closed', label: true), title: 'Filter by issues that are currently closed.' do
#{state_filters_text_for(:closed, @project)}
%li{class: ("active" if params[:state] == 'all')}
- = link_to page_filter_path(state: 'all'), title: "Show all #{page_context_word}." do
+ = link_to page_filter_path(state: 'all', label: true), title: "Show all #{page_context_word}." do
#{state_filters_text_for(:all, @project)}
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index 55d7a38cce2..ed1b8a8da2a 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -10,17 +10,17 @@
= 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'
+ = link_to 'Prev', [@project.namespace.becomes(Namespace), @project, prev_issuable], class: 'btn btn-default prev-btn issuable-pager'
- else
- %a.btn.btn-default.disabled{href: '#'}
+ %a.btn.btn-default.issuable-pager.disabled{href: '#'}
Prev
- if next_issuable = next_issuable_for(issuable)
- = link_to 'Next', [@project.namespace.becomes(Namespace), @project, next_issuable], class: 'btn btn-default next-btn'
+ = link_to 'Next', [@project.namespace.becomes(Namespace), @project, next_issuable], class: 'btn btn-default next-btn issuable-pager'
- else
- %a.btn.btn-default.disabled{href: '#'}
+ %a.btn.btn-default.issuable-pager.disabled{href: '#'}
Next
- = form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f|
+ = form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, format: :json, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f|
.block.assignee
.sidebar-collapsed-icon.sidebar-collapsed-user{data: {toggle: "tooltip", placement: "left", container: "body"}, title: (issuable.assignee.to_reference if issuable.assignee)}
- if issuable.assignee
@@ -58,7 +58,7 @@
- if issuable.milestone
= issuable.milestone.title
- else
- No
+ None
.title.hide-collapsed
Milestone
= icon('spinner spin', class: 'block-loading')
@@ -75,6 +75,34 @@
= f.hidden_field 'milestone_id', value: issuable.milestone_id, id: nil
= dropdown_tag('Milestone', options: { title: 'Assign milestone', toggle_class: 'js-milestone-select js-extra-options', filter: true, dropdown_class: 'dropdown-menu-selectable', placeholder: 'Search milestones', data: { show_no: true, field_name: "#{issuable.to_ability_name}[milestone_id]", project_id: @project.id, issuable_id: issuable.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), ability_name: issuable.to_ability_name, issue_update: issuable_json_path(issuable), use_id: true }})
+ - if issuable.has_attribute?(:due_date)
+ .block.due_date
+ .sidebar-collapsed-icon
+ = icon('calendar')
+ %span.js-due-date-sidebar-value
+ = issuable.due_date.try(:to_s, :medium) || 'None'
+ .title.hide-collapsed
+ Due date
+ = icon('spinner spin', class: 'block-loading')
+ - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
+ = link_to 'Edit', '#', class: 'edit-link pull-right'
+ .value.bold.hide-collapsed
+ - if issuable.due_date
+ = issuable.due_date.to_s(:medium)
+ - else
+ .light None
+ - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
+ .selectbox.hide-collapsed
+ = f.hidden_field :due_date, value: issuable.due_date
+ .dropdown
+ %button.dropdown-menu-toggle.js-due-date-select{ type: 'button', data: { toggle: 'dropdown', field_name: "#{issuable.to_ability_name}[due_date]", ability_name: issuable.to_ability_name, issue_update: issuable_json_path(issuable) } }
+ %span.dropdown-toggle-text Due date
+ = icon('chevron-down')
+ .dropdown-menu.dropdown-menu-due-date
+ = dropdown_title('Due date')
+ = dropdown_content do
+ .js-due-date-calendar
+
- if issuable.project.labels.any?
.block.labels
.sidebar-collapsed-icon
@@ -101,24 +129,9 @@
Label
= icon('chevron-down')
.dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
- .dropdown-page-one
- = dropdown_title("Assign labels")
- = 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
- = dropdown_loading
+ = render partial: "shared/issuable/label_page_default"
+ - if can? current_user, :admin_label, @project and @project
+ = render partial: "shared/issuable/label_page_create"
= render "shared/issuable/participants", participants: issuable.participants(current_user)
- if current_user
@@ -151,6 +164,7 @@
:javascript
new MilestoneSelect('{"namespace":"#{@project.namespace.path}","path":"#{@project.path}"}');
new LabelsSelect();
- new IssuableContext('#{current_user.to_json(only: [:username, :id, :name])}');
+ new IssuableContext('#{escape_javascript(current_user.to_json(only: [:username, :id, :name]))}');
new Subscription('.subscription')
- new Sidebar();
+ new DueDateSelect();
+ sidebar = new Sidebar();
diff --git a/app/views/shared/projects/_dropdown.html.haml b/app/views/shared/projects/_dropdown.html.haml
index e7e04621ff4..1169bed0382 100644
--- a/app/views/shared/projects/_dropdown.html.haml
+++ b/app/views/shared/projects/_dropdown.html.haml
@@ -1,4 +1,5 @@
- @sort ||= sort_value_recently_updated
+- personal = params[:personal]
- archived = params[:archived]
.dropdown.inline
%button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
@@ -10,7 +11,7 @@
Sort by
- projects_sort_options_hash.each do |value, title|
%li
- = link_to filter_projects_path(sort: value, archived: archived), class: ("is-active" if @sort == value) do
+ = link_to filter_projects_path(sort: value, archived: archived, personal: personal), class: ("is-active" if @sort == value) do
= title
%li.divider
@@ -20,3 +21,11 @@
%li
= link_to filter_projects_path(sort: @sort, archived: true), class: ("is-active" if params[:archived].present?) do
Show archived projects
+ - if current_user
+ %li.divider
+ %li
+ = link_to filter_projects_path(sort: @sort, personal: nil), class: ("is-active" unless personal) do
+ Owned by anyone
+ %li
+ = link_to filter_projects_path(sort: @sort, personal: true), class: ("is-active" if personal) do
+ Owned by me
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index 53ff8959bc8..ab8b022411d 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -6,9 +6,8 @@
- css_class = '' unless local_assigns[:css_class]
- 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.3']
-- cache_key.push(ci_commit.status) if ci_commit
+- cache_key.push(project.commit.status) if project.commit.try(:status)
%li.project-row{ class: css_class }
= cache(cache_key) do
@@ -16,9 +15,9 @@
- if project.main_language
%span
= project.main_language
- - if ci_commit
+ - if project.commit.try(:status)
%span
- = render_ci_status(ci_commit)
+ = render_ci_status(project.commit)
- if forks
%span
= icon('code-fork')
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index 3028491e5b6..0dff27f9654 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -69,13 +69,13 @@
= @user.location
%ul.nav-links.center.user-profile-nav
- %li.activity-tab
+ %li.js-activity-tab
= link_to user_calendar_activities_path, data: {target: 'div#activity', action: 'activity', toggle: 'tab'} do
Activity
- %li.groups-tab
+ %li.js-groups-tab
= link_to user_groups_path, data: {target: 'div#groups', action: 'groups', toggle: 'tab'} do
Groups
- %li.contributed-tab
+ %li.js-contributed-tab
= link_to user_contributed_projects_path, data: {target: 'div#contributed', action: 'contributed', toggle: 'tab'} do
Contributed projects
%li.projects-tab
diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml
index dc249155b92..4beb8746444 100644
--- a/app/views/votes/_votes_block.html.haml
+++ b/app/views/votes/_votes_block.html.haml
@@ -15,16 +15,16 @@
- if current_user
:javascript
- var get_emojis_url = "#{emojis_path}";
- var post_emoji_url = "#{award_toggle_namespace_project_notes_path(@project.namespace, @project)}";
- var noteable_type = "#{votable.class.name.underscore}";
- var noteable_id = "#{votable.id}";
- var aliases = #{AwardEmoji.aliases.to_json};
+ var getEmojisUrl = "#{emojis_path}";
+ var postEmojiUrl = "#{award_toggle_namespace_project_notes_path(@project.namespace, @project)}";
+ var noteableType = "#{votable.class.name.underscore}";
+ var noteableId = "#{votable.id}";
+ var unicodes = #{AwardEmoji.unicode.to_json};
- window.awards_handler = new AwardsHandler(
- get_emojis_url,
- post_emoji_url,
- noteable_type,
- noteable_id,
- aliases
+ window.awardsHandler = new AwardsHandler(
+ getEmojisUrl,
+ postEmojiUrl,
+ noteableType,
+ noteableId,
+ unicodes
);
diff --git a/app/workers/repository_check/batch_worker.rb b/app/workers/repository_check/batch_worker.rb
index 44b3145d50f..a3e16fa5212 100644
--- a/app/workers/repository_check/batch_worker.rb
+++ b/app/workers/repository_check/batch_worker.rb
@@ -33,8 +33,8 @@ module RepositoryCheck
# has to sit and wait for this query to finish.
def project_ids
limit = 10_000
- never_checked_projects = Project.where('last_repository_check_at IS NULL').limit(limit).
- pluck(:id)
+ never_checked_projects = Project.where('last_repository_check_at IS NULL AND created_at < ?', 24.hours.ago).
+ limit(limit).pluck(:id)
old_check_projects = Project.where('last_repository_check_at < ?', 1.month.ago).
reorder('last_repository_check_at ASC').limit(limit).pluck(:id)
never_checked_projects + old_check_projects