Commit d754d991 authored by Timothy Andrew's avatar Timothy Andrew

Merge remote-tracking branch 'origin/master' into 2979-personal-access-tokens

parents e18a08fd cea3cf17
...@@ -12,6 +12,7 @@ v 8.9.0 (unreleased) ...@@ -12,6 +12,7 @@ v 8.9.0 (unreleased)
- Allow customisable text on the 'nearly there' page after a user signs up - Allow customisable text on the 'nearly there' page after a user signs up
- Bump recaptcha gem to 3.0.0 to remove deprecated stoken support - Bump recaptcha gem to 3.0.0 to remove deprecated stoken support
- Allow forking projects with restricted visibility level - Allow forking projects with restricted visibility level
- Added descriptions to notification settings dropdown
- Improve note validation to prevent errors when creating invalid note via API - Improve note validation to prevent errors when creating invalid note via API
- Reduce number of fog gem dependencies - Reduce number of fog gem dependencies
- Remove project notification settings associated with deleted projects - Remove project notification settings associated with deleted projects
...@@ -22,6 +23,7 @@ v 8.9.0 (unreleased) ...@@ -22,6 +23,7 @@ v 8.9.0 (unreleased)
- `git clone https://host/namespace/project` now works, in addition to using the `.git` suffix - `git clone https://host/namespace/project` now works, in addition to using the `.git` suffix
- Bump nokogiri to 1.6.8 - Bump nokogiri to 1.6.8
- Use gitlab-shell v3.0.0 - Use gitlab-shell v3.0.0
- Upgrade to jQuery 2
- Use Knapsack to evenly distribute tests across multiple nodes - Use Knapsack to evenly distribute tests across multiple nodes
- Add `sha` parameter to MR merge API, to ensure only reviewed changes are merged - Add `sha` parameter to MR merge API, to ensure only reviewed changes are merged
- Don't allow MRs to be merged when commits were added since the last review / page load - Don't allow MRs to be merged when commits were added since the last review / page load
...@@ -40,11 +42,13 @@ v 8.9.0 (unreleased) ...@@ -40,11 +42,13 @@ v 8.9.0 (unreleased)
- Use downcased path to container repository as this is expected path by Docker - Use downcased path to container repository as this is expected path by Docker
- Projects pending deletion will render a 404 page - Projects pending deletion will render a 404 page
- Measure queue duration between gitlab-workhorse and Rails - Measure queue duration between gitlab-workhorse and Rails
- Make Omniauth providers specs to not modify global configuration
- Make authentication service for Container Registry to be compatible with < Docker 1.11 - Make authentication service for Container Registry to be compatible with < Docker 1.11
- Add Application Setting to configure Container Registry token expire delay (default 5min) - Add Application Setting to configure Container Registry token expire delay (default 5min)
- Cache assigned issue and merge request counts in sidebar nav - Cache assigned issue and merge request counts in sidebar nav
- Use Knapsack only in CI environment - Use Knapsack only in CI environment
- Cache project build count in sidebar nav - Cache project build count in sidebar nav
- Add milestone expire date to the right sidebar
- Fix markdown_spec to use before instead of before(:all) to properly cleanup database after testing - Fix markdown_spec to use before instead of before(:all) to properly cleanup database after testing
- Reduce number of queries needed to render issue labels in the sidebar - Reduce number of queries needed to render issue labels in the sidebar
- Improve error handling importing projects - Improve error handling importing projects
...@@ -55,19 +59,21 @@ v 8.9.0 (unreleased) ...@@ -55,19 +59,21 @@ v 8.9.0 (unreleased)
- RepositoryCheck::SingleRepositoryWorker public and private methods are now instrumented - RepositoryCheck::SingleRepositoryWorker public and private methods are now instrumented
- Improve issuables APIs performance when accessing notes !4471 - Improve issuables APIs performance when accessing notes !4471
- External links now open in a new tab - External links now open in a new tab
- Markdown editor now correctly resets the input value on edit cancellation !4175
- Toggling a task list item in a issue/mr description does not creates a Todo for mentions
- Improved UX of date pickers on issue & milestone forms
v 8.8.4 (unreleased) v 8.8.5 (unreleased)
- Ensure branch cleanup regardless of whether the GitHub import process succeeds - Ensure branch cleanup regardless of whether the GitHub import process succeeds
- Fix issue with arrow keys not working in search autocomplete dropdown - Fix issue with arrow keys not working in search autocomplete dropdown
- Fix todos page throwing errors when you have a project pending deletion - Fix todos page throwing errors when you have a project pending deletion
- Reduce number of SQL queries when rendering user references - Reduce number of SQL queries when rendering user references
- Upgrade to jQuery 2
- Remove prev/next buttons on issues and merge requests
- Import GitHub repositories respecting the API rate limit - Import GitHub repositories respecting the API rate limit
- Fix importer for GitHub comments on diff - Fix importer for GitHub comments on diff
- Disable Webhooks before proceeding with the GitHub import - Disable Webhooks before proceeding with the GitHub import
- Added descriptions to notification settings dropdown
- Markdown editor now correctly resets the input value on edit cancellation !4175 v 8.8.4
- Fix LDAP-based login for users with 2FA enabled. !4493
v 8.8.3 v 8.8.3
- Fix 404 page when viewing TODOs that contain milestones or labels in different projects. !4312 - Fix 404 page when viewing TODOs that contain milestones or labels in different projects. !4312
...@@ -179,6 +185,7 @@ v 8.8.0 ...@@ -179,6 +185,7 @@ v 8.8.0
- Fixed advice on invalid permissions on upload path !2948 (Ludovic Perrine) - Fixed advice on invalid permissions on upload path !2948 (Ludovic Perrine)
- Allows MR authors to have the source branch removed when merging the MR. !2801 (Jeroen Jacobs) - Allows MR authors to have the source branch removed when merging the MR. !2801 (Jeroen Jacobs)
- When creating a .gitignore file a dropdown with templates will be provided - When creating a .gitignore file a dropdown with templates will be provided
- Shows the issue/MR list search/filter form and corrects the mobile styling for guest users. #17562
v 8.7.7 v 8.7.7
- Fix import by `Any Git URL` broken if the URL contains a space - Fix import by `Any Git URL` broken if the URL contains a space
......
class @Activities class @Activities
constructor: -> constructor: ->
Pager.init 20, true Pager.init 20, true, false, @updateTooltips
$(".event-filter-link").on "click", (event) => $(".event-filter-link").on "click", (event) =>
event.preventDefault() event.preventDefault()
@toggleFilter($(event.currentTarget)) @toggleFilter($(event.currentTarget))
@reloadActivities() @reloadActivities()
updateTooltips: ->
gl.utils.localTimeAgo($('.js-timeago', '#activity'))
reloadActivities: -> reloadActivities: ->
$(".content_list").html '' $(".content_list").html ''
Pager.init 20, true Pager.init 20, true
......
...@@ -35,7 +35,6 @@ ...@@ -35,7 +35,6 @@
#= require raphael #= require raphael
#= require g.raphael #= require g.raphael
#= require g.bar #= require g.bar
#= require Chart
#= require branch-graph #= require branch-graph
#= require ace/ace #= require ace/ace
#= require ace/ext-searchbox #= require ace/ext-searchbox
...@@ -226,6 +225,10 @@ $ -> ...@@ -226,6 +225,10 @@ $ ->
form = btn.closest("form") form = btn.closest("form")
new ConfirmDangerModal(form, text) new ConfirmDangerModal(form, text)
$(document).on 'click', 'button', ->
$(this).blur()
$('input[type="search"]').each -> $('input[type="search"]').each ->
$this = $(this) $this = $(this)
$this.attr 'value', $this.val() $this.attr 'value', $this.val()
...@@ -268,5 +271,6 @@ $ -> ...@@ -268,5 +271,6 @@ $ ->
.on "resize", (e) -> .on "resize", (e) ->
fitSidebarForSize() fitSidebarForSize()
gl.awardsHandler = new AwardsHandler()
checkInitialSidebarSize() checkInitialSidebarSize()
new Aside() new Aside()
...@@ -65,7 +65,7 @@ class @AwardsHandler ...@@ -65,7 +65,7 @@ class @AwardsHandler
$addBtn.removeClass 'is-loading' $addBtn.removeClass 'is-loading'
$menu = $('.emoji-menu') $menu = $('.emoji-menu')
@positionMenu($menu, $addBtn) @positionMenu($menu, $addBtn)
@renderFrequentlyUsedBlock() @renderFrequentlyUsedBlock() unless @frequentEmojiBlockRendered
setTimeout => setTimeout =>
$menu.addClass 'is-visible' $menu.addClass 'is-visible'
...@@ -100,7 +100,7 @@ class @AwardsHandler ...@@ -100,7 +100,7 @@ class @AwardsHandler
$menu.css(css) $menu.css(css)
addAward: (votesBlock, awardUrl, emoji, checkMutuality = yes, callback) -> addAward: (votesBlock, awardUrl, emoji, checkMutuality = true, callback) ->
emoji = @normilizeEmojiName emoji emoji = @normilizeEmojiName emoji
...@@ -111,7 +111,7 @@ class @AwardsHandler ...@@ -111,7 +111,7 @@ class @AwardsHandler
$('.emoji-menu').removeClass 'is-visible' $('.emoji-menu').removeClass 'is-visible'
addAwardToEmojiBar: (votesBlock, emoji, checkForMutuality = yes) -> addAwardToEmojiBar: (votesBlock, emoji, checkForMutuality = true) ->
@checkMutuality votesBlock, emoji if checkForMutuality @checkMutuality votesBlock, emoji if checkForMutuality
@addEmojiToFrequentlyUsedList emoji @addEmojiToFrequentlyUsedList emoji
...@@ -153,7 +153,7 @@ class @AwardsHandler ...@@ -153,7 +153,7 @@ class @AwardsHandler
if isAlreadyVoted if isAlreadyVoted
@showEmojiLoader $emojiButton @showEmojiLoader $emojiButton
@addAward votesBlock, awardUrl, mutualVote, no, -> @addAward votesBlock, awardUrl, mutualVote, false, ->
$emojiButton.removeClass 'is-loading' $emojiButton.removeClass 'is-loading'
...@@ -282,7 +282,7 @@ class @AwardsHandler ...@@ -282,7 +282,7 @@ class @AwardsHandler
@createEmojiMenu @getAwardMenuUrl(), => @createEmoji_ votesBlock, emoji @createEmojiMenu @getAwardMenuUrl(), => @createEmoji_ votesBlock, emoji
getAwardMenuUrl: -> return gl.awardMenuUrl getAwardMenuUrl: -> return gon.award_menu_url
resolveNameToCssClass: (emoji) -> resolveNameToCssClass: (emoji) ->
...@@ -336,13 +336,15 @@ class @AwardsHandler ...@@ -336,13 +336,15 @@ class @AwardsHandler
if $.cookie 'frequently_used_emojis' if $.cookie 'frequently_used_emojis'
frequentlyUsedEmojis = @getFrequentlyUsedEmojis() frequentlyUsedEmojis = @getFrequentlyUsedEmojis()
ul = $("<ul class='clearfix emoji-menu-list'>") ul = $("<ul class='clearfix emoji-menu-list frequent-emojis'>")
for emoji in frequentlyUsedEmojis for emoji in frequentlyUsedEmojis
$(".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'))
@frequentEmojiBlockRendered = true
setupSearch: -> setupSearch: ->
...@@ -365,4 +367,4 @@ class @AwardsHandler ...@@ -365,4 +367,4 @@ class @AwardsHandler
searchEmojis: (term) -> searchEmojis: (term) ->
$(".emoji-menu-content [data-emoji*='#{term}']").closest('li').clone() $(".emoji-menu-list:not(.frequent-emojis) [data-emoji*='#{term}']").closest('li').clone()
...@@ -23,7 +23,6 @@ class Dispatcher ...@@ -23,7 +23,6 @@ class Dispatcher
new Issue() new Issue()
shortcut_handler = new ShortcutsIssuable() shortcut_handler = new ShortcutsIssuable()
new ZenMode() new ZenMode()
gl.awardsHandler = new AwardsHandler()
when 'projects:milestones:show', 'groups:milestones:show', 'dashboard:milestones:show' when 'projects:milestones:show', 'groups:milestones:show', 'dashboard:milestones:show'
new Milestone() new Milestone()
when 'dashboard:todos:index' when 'dashboard:todos:index'
...@@ -54,7 +53,6 @@ class Dispatcher ...@@ -54,7 +53,6 @@ class Dispatcher
new Diff() new Diff()
shortcut_handler = new ShortcutsIssuable(true) shortcut_handler = new ShortcutsIssuable(true)
new ZenMode() new ZenMode()
gl.awardsHandler = new AwardsHandler()
when "projects:merge_requests:diffs" when "projects:merge_requests:diffs"
new Diff() new Diff()
new ZenMode() new ZenMode()
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
window.GitLab ?= {} window.GitLab ?= {}
GitLab.GfmAutoComplete = GitLab.GfmAutoComplete =
dataLoading: false dataLoading: false
dataLoaded: false
dataSource: '' dataSource: ''
...@@ -35,7 +36,7 @@ GitLab.GfmAutoComplete = ...@@ -35,7 +36,7 @@ GitLab.GfmAutoComplete =
$.fn.atwho.default.callbacks.filter(query, data, searchKey) $.fn.atwho.default.callbacks.filter(query, data, searchKey)
beforeInsert: (value) -> beforeInsert: (value) ->
if value.indexOf('undefined') if not GitLab.GfmAutoComplete.dataLoaded
@at @at
else else
value value
...@@ -182,6 +183,8 @@ GitLab.GfmAutoComplete = ...@@ -182,6 +183,8 @@ GitLab.GfmAutoComplete =
$.getJSON(dataSource) $.getJSON(dataSource)
loadData: (data) -> loadData: (data) ->
@dataLoaded = true
# load members # load members
@input.atwho 'load', '@', data.members @input.atwho 'load', '@', data.members
# load issues # load issues
......
...@@ -4,4 +4,5 @@ ...@@ -4,4 +4,5 @@
# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the # It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
# the compiled file. # the compiled file.
# #
#= require Chart
#= require_tree . #= require_tree .
((w) ->
jQuery.timefor = (time, suffix, expiredLabel) ->
return '' unless time
suffix or= 'remaining'
expiredLabel or= 'Past due'
jQuery.timeago.settings.allowFuture = yes
{ suffixFromNow } = jQuery.timeago.settings.strings
jQuery.timeago.settings.strings.suffixFromNow = suffix
timefor = $.timeago time
if timefor.indexOf('ago') > -1
timefor = expiredLabel
jQuery.timeago.settings.strings.suffixFromNow = suffixFromNow
return timefor
) window
...@@ -12,6 +12,13 @@ ...@@ -12,6 +12,13 @@
$el.attr('title', gl.utils.formatDate($el.attr('datetime'))) $el.attr('title', gl.utils.formatDate($el.attr('datetime')))
) )
$timeagoEls.timeago() if setTimeago if setTimeago
$timeagoEls.timeago()
$timeagoEls.tooltip('destroy')
# Recreate with custom template
$timeagoEls.tooltip(
template: '<div class="tooltip local-timeago" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
)
) window ) window
...@@ -24,11 +24,21 @@ class @MilestoneSelect ...@@ -24,11 +24,21 @@ class @MilestoneSelect
if issueUpdateURL if issueUpdateURL
milestoneLinkTemplate = _.template( milestoneLinkTemplate = _.template(
'<a href="/<%= namespace %>/<%= path %>/milestones/<%= iid %>"><%= _.escape(title) %></a>' '<a href="/<%= namespace %>/<%= path %>/milestones/<%= iid %>">
<span class="has-tooltip" data-container="body" title="<%= remaining %>">
<%= _.escape(title) %>
</span>
</a>'
) )
milestoneLinkNoneTemplate = '<div class="light">None</div>' milestoneLinkNoneTemplate = '<div class="light">None</div>'
collapsedSidebarLabelTemplate = _.template(
'<span class="has-tooltip" data-container="body" title="<%= remaining %>" data-placement="left">
<%= _.escape(title) %>
</span>'
)
$dropdown.glDropdown( $dropdown.glDropdown(
data: (term, callback) -> data: (term, callback) ->
$.ajax( $.ajax(
...@@ -122,8 +132,9 @@ class @MilestoneSelect ...@@ -122,8 +132,9 @@ class @MilestoneSelect
if data.milestone? if data.milestone?
data.milestone.namespace = _this.currentProject.namespace data.milestone.namespace = _this.currentProject.namespace
data.milestone.path = _this.currentProject.path data.milestone.path = _this.currentProject.path
data.milestone.remaining = $.timefor data.milestone.due_date
$value.html(milestoneLinkTemplate(data.milestone)) $value.html(milestoneLinkTemplate(data.milestone))
$sidebarCollapsedValue.find('span').text(data.milestone.title) $sidebarCollapsedValue.find('span').html(collapsedSidebarLabelTemplate(data.milestone))
else else
$value.html(milestoneLinkNoneTemplate) $value.html(milestoneLinkNoneTemplate)
$sidebarCollapsedValue.find('span').text('No') $sidebarCollapsedValue.find('span').text('No')
......
@Pager = @Pager =
init: (@limit = 0, preload, @disable = false) -> init: (@limit = 0, preload, @disable = false, @callback = $.noop) ->
@loading = $('.loading').first() @loading = $('.loading').first()
if preload if preload
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
@loading.hide() @loading.hide()
success: (data) -> success: (data) ->
Pager.append(data.count, data.html) Pager.append(data.count, data.html)
Pager.callback()
dataType: "json" dataType: "json"
append: (count, html) -> append: (count, html) ->
......
...@@ -19,3 +19,8 @@ class @Subscription ...@@ -19,3 +19,8 @@ class @Subscription
action = if status == 'subscribed' then 'Unsubscribe' else 'Subscribe' action = if status == 'subscribed' then 'Unsubscribe' else 'Subscribe'
btn.find('span').text(action) btn.find('span').text(action)
@subscription_status.find('>div').toggleClass('hidden') @subscription_status.find('>div').toggleClass('hidden')
if btn.attr('data-original-title')
btn.tooltip('hide')
.attr('data-original-title', action)
.tooltip('fixTitle')
...@@ -122,6 +122,9 @@ class @UserTabs ...@@ -122,6 +122,9 @@ class @UserTabs
@parentEl.find(tabSelector).html(data.html) @parentEl.find(tabSelector).html(data.html)
@loaded[action] = true @loaded[action] = true
# Fix tooltips
gl.utils.localTimeAgo($('.js-timeago', tabSelector))
loadActivities: (source) -> loadActivities: (source) ->
return if @loaded['activity'] is true return if @loaded['activity'] is true
......
...@@ -79,6 +79,23 @@ ...@@ -79,6 +79,23 @@
@include btn-color($white-light, $border-color, $white-normal, $border-white-normal, $white-dark, $border-white-dark, $btn-white-active); @include btn-color($white-light, $border-color, $white-normal, $border-white-normal, $white-dark, $border-white-dark, $btn-white-active);
} }
@mixin btn-with-margin {
margin-left: $btn-side-margin;
float: left;
&.inline {
float: none;
}
&.btn-sm {
margin-left: $btn-sm-side-margin;
}
&.btn-xs {
margin-left: $btn-xs-side-margin;
}
}
.btn { .btn {
@include btn-default; @include btn-default;
@include btn-white; @include btn-white;
...@@ -142,24 +159,7 @@ ...@@ -142,24 +159,7 @@
} }
&.btn-grouped { &.btn-grouped {
margin-right: $btn-side-margin; @include btn-with-margin;
float: left;
&.inline {
float: none;
}
&:last-child {
margin-right: 0;
}
&.btn-sm {
margin-right: $btn-sm-side-margin;
}
&.btn-xs {
margin-right: $btn-xs-side-margin;
}
} }
&.disabled { &.disabled {
...@@ -203,11 +203,7 @@ ...@@ -203,11 +203,7 @@
.btn-group { .btn-group {
&.btn-grouped { &.btn-grouped {
margin-right: 7px; @include btn-with-margin;
float: left;
&:last-child {
margin-right: 0;
}
} }
} }
......
...@@ -76,6 +76,7 @@ label { ...@@ -76,6 +76,7 @@ label {
.form-control { .form-control {
@include box-shadow(none); @include box-shadow(none);
border-radius: 3px; border-radius: 3px;
padding: $gl-vert-padding $gl-input-padding;
} }
.select-wrapper { .select-wrapper {
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
font-family: $regular_font; font-family: $regular_font;
font-size: $font-size-base; font-size: $font-size-base;
&.ui-datepicker,
&.ui-datepicker-inline { &.ui-datepicker-inline {
border: 1px solid #ddd; border: 1px solid #ddd;
padding: 10px; padding: 10px;
...@@ -10,6 +11,25 @@ ...@@ -10,6 +11,25 @@
.ui-datepicker-header { .ui-datepicker-header {
background: #fff; background: #fff;
border-color: #ddd; border-color: #ddd;
.ui-datepicker-prev,
.ui-datepicker-next {
top: 4px;
}
.ui-datepicker-prev {
left: 2px;
}
.ui-datepicker-next {
right: 2px;
}
.ui-state-hover {
background: transparent;
border: 0;
cursor: pointer;
}
} }
.ui-datepicker-calendar td a { .ui-datepicker-calendar td a {
...@@ -36,21 +56,18 @@ ...@@ -36,21 +56,18 @@
} }
.ui-state-highlight { .ui-state-highlight {
border: 1px solid #eee; border: 0;
background: #eee; background: transparent;
} }
.ui-state-active { .ui-datepicker-calendar {
border: 1px solid $gl-primary; .ui-state-active,
background: $gl-primary; .ui-state-hover,
color: #fff; .ui-state-focus {
} border: 1px solid $gl-primary;
background: $gl-primary;
.ui-state-hover, color: #fff;
.ui-state-focus { }
border: 1px solid $row-hover;
background: $row-hover;
color: #333;
} }
} }
......
...@@ -137,8 +137,16 @@ ul.content-list { ...@@ -137,8 +137,16 @@ ul.content-list {
padding-top: 1px; padding-top: 1px;
float: right; float: right;
.btn { > .btn,
padding: 10px 14px; > .btn-group {
margin-right: $gl-padding-top;
display: inline-block;
margin-top: 4px;
margin-bottom: 4px;
&:last-child {
margin-right: 0;
}
} }
} }
......
...@@ -171,6 +171,7 @@ ...@@ -171,6 +171,7 @@
> form { > form {
display: inline-block; display: inline-block;
margin-top: -1px; margin-top: -1px;
margin-bottom: 12px;
} }
.icon-label { .icon-label {
...@@ -207,7 +208,7 @@ ...@@ -207,7 +208,7 @@
@media (max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
padding-bottom: 0; padding-bottom: 0;
width: 100%;
.btn, form, .dropdown, .dropdown-menu-toggle, .form-control { .btn, form, .dropdown, .dropdown-menu-toggle, .form-control {
margin: 0 0 10px; margin: 0 0 10px;
display: block; display: block;
...@@ -238,16 +239,6 @@ ...@@ -238,16 +239,6 @@
margin: 0; margin: 0;
} }
} }
/* Small devices (tablets, 768px and lower) */
@media (max-width: $screen-sm-max) {
width: 100%;
text-align: left;
input {
width: 300px;
}
}
} }
} }
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
background: #fff; background: #fff;
border-color: $input-border; border-color: $input-border;
height: 35px; height: 35px;
padding: $gl-vert-padding $gl-btn-padding; padding: $gl-vert-padding $gl-input-padding;
font-size: $gl-font-size; font-size: $gl-font-size;
line-height: 1.42857143; line-height: 1.42857143;
border-radius: $border-radius-base; border-radius: $border-radius-base;
......
...@@ -192,3 +192,8 @@ ...@@ -192,3 +192,8 @@
.text-info:hover { .text-info:hover {
color: $brand-info; color: $brand-info;
} }
// Prevent datetimes on tooltips to break into two lines
.local-timeago {
white-space: nowrap;
}
...@@ -57,6 +57,7 @@ $code_line_height: 1.5; ...@@ -57,6 +57,7 @@ $code_line_height: 1.5;
*/ */
$gl-padding: 16px; $gl-padding: 16px;
$gl-btn-padding: 10px; $gl-btn-padding: 10px;
$gl-input-padding: 10px;
$gl-vert-padding: 6px; $gl-vert-padding: 6px;
$gl-padding-top: 10px; $gl-padding-top: 10px;
...@@ -79,8 +80,8 @@ $provider-btn-not-active-color: #4688f1; ...@@ -79,8 +80,8 @@ $provider-btn-not-active-color: #4688f1;
$link-underline-blue: #4a8bee; $link-underline-blue: #4a8bee;
$layout-link-gray: #7e7c7c; $layout-link-gray: #7e7c7c;
$todo-alert-blue: #428bca; $todo-alert-blue: #428bca;
$btn-side-margin: 7px; $btn-side-margin: 10px;
$btn-sm-side-margin: 5px; $btn-sm-side-margin: 7px;
$btn-xs-side-margin: 5px; $btn-xs-side-margin: 5px;
/* /*
......
...@@ -50,11 +50,26 @@ ...@@ -50,11 +50,26 @@
.label-row { .label-row {
.label-name { .label-name {
display: inline-block; display: block;
width: 170px; margin-bottom: 10px;
@media (max-width: $screen-xs-min) { @media (min-width: $screen-sm-min) {
display: block; display: inline-block;
width: 200px;
margin-bottom: 0;
}
}
.label-description {
display: block;
margin-bottom: 10px;
@media (min-width: $screen-sm-min) {
display: inline-block;
width: 40%;
margin-left: 10px;
margin-bottom: 0;
vertical-align: middle;
} }
} }
...@@ -68,10 +83,6 @@ ...@@ -68,10 +83,6 @@
padding: 3px 4px; padding: 3px 4px;
} }
.label-subscription {
display: inline-block;
}
.dropdown-labels-error { .dropdown-labels-error {
padding: 5px 10px; padding: 5px 10px;
margin-bottom: 10px; margin-bottom: 10px;
...@@ -79,62 +90,27 @@ ...@@ -79,62 +90,27 @@
color: $white-light; color: $white-light;
} }
@mixin labels-mobile {
@media (max-width: $screen-xs-min) {
display: block;
width: 100%;
margin-left: 0;
padding: 10px 0;
}
}
.manage-labels-list { .manage-labels-list {
.btn-action {
color: $gl-dark-link-color;
.prepend-left-10, .prepend-description-left { .fa {
display: inline-block; font-size: 18px;
width: 40%; vertical-align: middle;
vertical-align: middle;
@include labels-mobile;
}
.prepend-description-left {
width: 57%;
@include labels-mobile;
}
.pull-info-right {
float: right;
@media (max-width: $screen-xs-min) {
float: none;
} }
.action-buttons { &:hover {
border-color: transparent; color: $gl-link-color;
padding: 6px;
color: $gl-text-color;
&.label-subscribe-button { &.remove-row {
padding-left: 0; color: $gl-danger;
} }
} }
}
i { .dropdown {
color: $gl-text-color; @media (min-width: $screen-sm-min) {
} float: right;
.append-right-20 {
a {
color: $gl-text-color;
}
@media (max-width: $screen-xs-min) {
display: block;
margin-bottom: 10px;
}
} }
} }
} }
...@@ -186,3 +162,23 @@ ...@@ -186,3 +162,23 @@
color: inherit; color: inherit;
} }
} }
.label-options-toggle {
width: 100%;
}
.label-subscribe-button {
.label-subscribe-button-loading {
display: none;
}
&.disabled {
.label-subscribe-button-icon {
display: none;
}
.label-subscribe-button-loading {
display: block;
}
}
}
...@@ -108,6 +108,11 @@ ...@@ -108,6 +108,11 @@
font-size: 17px; font-size: 17px;
margin: 5px 0; margin: 5px 0;
color: $gl-gray-dark; color: $gl-gray-dark;
&.has-conflicts .fa-exclamation-triangle {
color: $gl-warning;
}
} }
p:last-child { p:last-child {
......
...@@ -129,17 +129,8 @@ ...@@ -129,17 +129,8 @@
display: none; display: none;
font-size: 15px; font-size: 15px;
.form-actions { .md-area {
padding-left: 20px; background-color: #fff;
.btn-save {
float: left;
}
.note-form-option {
float: left;
padding: 2px 0 0 25px;
}
} }
} }
......
...@@ -32,7 +32,7 @@ module LabelsHelper ...@@ -32,7 +32,7 @@ module LabelsHelper
# link_to_label(label) { "My Custom Label Text" } # link_to_label(label) { "My Custom Label Text" }
# #
# Returns a String # Returns a String
def link_to_label(label, project: nil, type: :issue, tooltip: true, &block) def link_to_label(label, project: nil, type: :issue, tooltip: true, css_class: nil, &block)
project ||= @project || label.project project ||= @project || label.project
link = send("namespace_project_#{type.to_s.pluralize}_path", link = send("namespace_project_#{type.to_s.pluralize}_path",
project.namespace, project.namespace,
...@@ -40,9 +40,9 @@ module LabelsHelper ...@@ -40,9 +40,9 @@ module LabelsHelper
label_name: [label.name]) label_name: [label.name])
if block_given? if block_given?
link_to link, &block link_to link, class: css_class, &block
else else
link_to render_colored_label(label, tooltip: tooltip), link link_to render_colored_label(label, tooltip: tooltip), link, class: css_class
end end
end end
......
...@@ -56,7 +56,7 @@ module MilestonesHelper ...@@ -56,7 +56,7 @@ module MilestonesHelper
def milestone_remaining_days(milestone) def milestone_remaining_days(milestone)
if milestone.expired? if milestone.expired?
content_tag(:strong, 'expired') content_tag(:strong, 'Past due')
elsif milestone.due_date elsif milestone.due_date
days = milestone.remaining_days days = milestone.remaining_days
content = content_tag(:strong, days) content = content_tag(:strong, days)
......
...@@ -20,7 +20,7 @@ class TodoService ...@@ -20,7 +20,7 @@ class TodoService
# * mark all pending todos related to the issue for the current user as done # * mark all pending todos related to the issue for the current user as done
# #
def update_issue(issue, current_user) def update_issue(issue, current_user)
create_mention_todos(issue.project, issue, current_user) update_issuable(issue, current_user)
end end
# When close an issue we should: # When close an issue we should:
...@@ -53,7 +53,7 @@ class TodoService ...@@ -53,7 +53,7 @@ class TodoService
# * create a todo for each mentioned user on merge request # * create a todo for each mentioned user on merge request
# #
def update_merge_request(merge_request, current_user) def update_merge_request(merge_request, current_user)
create_mention_todos(merge_request.project, merge_request, current_user) update_issuable(merge_request, current_user)
end end
# When close a merge request we should: # When close a merge request we should:
...@@ -153,6 +153,13 @@ class TodoService ...@@ -153,6 +153,13 @@ class TodoService
create_mention_todos(issuable.project, issuable, author) create_mention_todos(issuable.project, issuable, author)
end end
def update_issuable(issuable, author)
# Skip toggling a task list item in a description
return if issuable.tasks? && issuable.updated_tasks.any?
create_mention_todos(issuable.project, issuable, author)
end
def handle_note(note, author) def handle_note(note, author)
# Skip system notes, and notes on project snippet # Skip system notes, and notes on project snippet
return if note.system? || note.for_snippet? return if note.system? || note.for_snippet?
......
...@@ -7,9 +7,6 @@ ...@@ -7,9 +7,6 @@
= awards.count = awards.count
- if current_user - if current_user
:javascript
gl.awardMenuUrl = "#{emojis_path}"
.award-menu-holder.js-award-holder .award-menu-holder.js-award-holder
%button.btn.award-control.js-add-award{ type: "button" } %button.btn.award-control.js-add-award{ type: "button" }
= icon('smile-o', class: "award-control-icon award-control-icon-normal") = icon('smile-o', class: "award-control-icon award-control-icon-normal")
......
...@@ -6,7 +6,8 @@ ...@@ -6,7 +6,8 @@
- if @user.two_factor_otp_enabled? - if @user.two_factor_otp_enabled?
%h5 Authenticate via Two-Factor App %h5 Authenticate via Two-Factor App
= form_for(resource, as: resource_name, url: session_path(resource_name), method: :post) do |f| = form_for(resource, as: resource_name, url: session_path(resource_name), method: :post) do |f|
= f.hidden_field :remember_me, value: params[resource_name][:remember_me] - resource_params = params[resource_name].presence || params
= f.hidden_field :remember_me, value: resource_params.fetch(:remember_me, 0)
= f.text_field :otp_attempt, class: 'form-control', placeholder: 'Two-Factor Authentication code', required: true, autofocus: true, autocomplete: 'off' = f.text_field :otp_attempt, class: 'form-control', placeholder: 'Two-Factor Authentication code', required: true, autofocus: true, autocomplete: 'off'
%p.help-block.hint Enter the code from the two-factor app on your mobile device. If you've lost your device, you may enter one of your recovery codes. %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 .prepend-top-20
......
...@@ -34,7 +34,7 @@ ...@@ -34,7 +34,7 @@
%strong.member-access-level= member.human_access %strong.member-access-level= member.human_access
- if show_controls - if show_controls
- if can?(current_user, :update_group_member, member) - if can?(current_user, :update_group_member, member)
= button_tag class: "btn-xs btn js-toggle-button", = button_tag class: "btn-xs btn btn-grouped inline js-toggle-button",
title: 'Edit access level', type: 'button' do title: 'Edit access level', type: 'button' do
= icon('pencil') = icon('pencil')
......
...@@ -39,9 +39,8 @@ ...@@ -39,9 +39,8 @@
.col-md-6 .col-md-6
.form-group .form-group
= f.label :due_date, "Due Date", class: "control-label" = f.label :due_date, "Due Date", class: "control-label"
.col-sm-10= f.hidden_field :due_date
.col-sm-10 .col-sm-10
.datepicker = f.text_field :due_date, class: "datepicker form-control", placeholder: "Select due date"
.form-actions .form-actions
= f.submit 'Create Milestone', class: "btn-create btn" = f.submit 'Create Milestone', class: "btn-create btn"
......
...@@ -3,31 +3,30 @@ ...@@ -3,31 +3,30 @@
= render "projects/commits/head" = render "projects/commits/head"
%div{ class: (container_class) } %div{ class: (container_class) }
.row-content-block.second-block.content-component-block .top-area
.pull-right .nav-text
- if can? current_user, :push_code, @project Protected branches can be managed in project settings
- if can? current_user, :push_code, @project
.nav-controls
= link_to new_namespace_project_branch_path(@project.namespace, @project), class: 'btn btn-create' do = link_to new_namespace_project_branch_path(@project.namespace, @project), class: 'btn btn-create' do
= icon('plus')
New branch New branch
&nbsp; .dropdown.inline
.dropdown.inline %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
%button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'} %span.light
%span.light - if @sort.present?
- if @sort.present? = @sort.humanize
= @sort.humanize - else
- else
Name
%b.caret
%ul.dropdown-menu.dropdown-menu-align-right
%li
= link_to namespace_project_branches_path(sort: nil) do
Name Name
= link_to namespace_project_branches_path(sort: 'recently_updated') do %b.caret
= sort_title_recently_updated %ul.dropdown-menu.dropdown-menu-align-right
= link_to namespace_project_branches_path(sort: 'last_updated') do %li
= sort_title_oldest_updated = link_to namespace_project_branches_path(sort: nil) do
.oneline Name
Protected branches can be managed in project settings = link_to namespace_project_branches_path(sort: 'recently_updated') do
= sort_title_recently_updated
= link_to namespace_project_branches_path(sort: 'last_updated') do
= sort_title_oldest_updated
- unless @branches.empty? - unless @branches.empty?
%ul.content-list.all-branches %ul.content-list.all-branches
- @branches.each do |branch| - @branches.each do |branch|
......
...@@ -34,7 +34,6 @@ ...@@ -34,7 +34,6 @@
= link_to 'Get started with Builds', help_page_path('ci/quick_start', 'README'), class: 'btn btn-info' = link_to 'Get started with Builds', help_page_path('ci/quick_start', 'README'), class: 'btn btn-info'
= link_to ci_lint_path, class: 'btn btn-default' do = link_to ci_lint_path, class: 'btn btn-default' do
= icon('wrench')
%span CI Lint %span CI Lint
%ul.content-list %ul.content-list
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
= icon('rss') = icon('rss')
%span.icon-label %span.icon-label
Subscribe Subscribe
= render 'shared/issuable/search_form', path: namespace_project_issues_path(@project.namespace, @project) = render 'shared/issuable/search_form', path: namespace_project_issues_path(@project.namespace, @project)
- if can? current_user, :create_issue, @project - if can? current_user, :create_issue, @project
= link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { assignee_id: @issuable_finder.assignee.try(:id), milestone_id: @issuable_finder.milestones.try(:first).try(:id) }), class: "btn btn-new", title: "New Issue", id: "new_issue_link" do = link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { assignee_id: @issuable_finder.assignee.try(:id), milestone_id: @issuable_finder.milestones.try(:first).try(:id) }), class: "btn btn-new", title: "New Issue", id: "new_issue_link" do
New Issue New Issue
......
- label_css_id = dom_id(label) - label_css_id = dom_id(label)
%li{id: label_css_id, data: { id: label.id } } %li{id: label_css_id, data: { id: label.id } }
= render "shared/label_row", label: label = render "shared/label_row", label: label
.pull-info-right
%span.append-right-20
= link_to_label(label, type: :merge_request) do
= pluralize label.open_merge_requests_count, 'merge request'
%span.append-right-20 .visible-xs.visible-sm-inline-block.visible-md-inline-block.dropdown
= link_to_label(label) do %button.btn.btn-default.label-options-toggle{ data: { toggle: "dropdown" } }
= pluralize label.open_issues_count(current_user), 'open issue' Options
%span.caret
.dropdown-menu.dropdown-menu-align-right
%ul
%li
= link_to_label(label, type: :merge_request) do
= pluralize label.open_merge_requests_count, 'merge request'
%li
= link_to_label(label) do
= pluralize label.open_issues_count(current_user), 'open issue'
- if current_user
%li.label-subscription{ data: { url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label) } }
%a.js-subscribe-button.label-subscribe-button.subscription-status{ role: "button", href: "#", data: { toggle: "tooltip", status: label_subscription_status(label) } }
%span= label_subscription_toggle_button_text(label)
- if can? current_user, :admin_label, @project
%li
= link_to "Edit", edit_namespace_project_label_path(@project.namespace, @project, label)
%li
= link_to "Delete", namespace_project_label_path(@project.namespace, @project, label), title: "Delete", method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?"}
- if current_user .pull-right.hidden-xs.hidden-sm.hidden-md
.label-subscription{ data: { url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label) } } = link_to_label(label, type: :merge_request, css_class: 'btn btn-transparent btn-action') do
.subscription-status{ data: { status: label_subscription_status(label) } } = pluralize label.open_merge_requests_count, 'merge request'
= link_to_label(label, css_class: 'btn btn-transparent btn-action') do
= pluralize label.open_issues_count(current_user), 'open issue'
%button.js-subscribe-button.label-subscribe-button.btn.action-buttons{ type: "button", data: { toggle: "tooltip" } } - if current_user
%span= label_subscription_toggle_button_text(label) .label-subscription.inline{ data: { url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label) } }
%button.js-subscribe-button.label-subscribe-button.btn.btn-transparent.btn-action.subscription-status{ type: "button", title: label_subscription_toggle_button_text(label), data: { toggle: "tooltip", status: label_subscription_status(label) } }
%span.sr-only= label_subscription_toggle_button_text(label)
= icon('eye', class: 'label-subscribe-button-icon')
= icon('spinner spin', class: 'label-subscribe-button-loading')
- if can?(current_user, :admin_label, @project) - if can? current_user, :admin_label, @project
= link_to edit_namespace_project_label_path(@project.namespace, @project, label), title: "Edit", class: 'btn action-buttons', data: { toggle: 'tooltip' } do = link_to edit_namespace_project_label_path(@project.namespace, @project, label), title: "Edit", class: 'btn btn-transparent btn-action', data: {toggle: "tooltip"} do
%i.fa.fa-pencil-square-o %span.sr-only Edit
= link_to namespace_project_label_path(@project.namespace, @project, label), title: "Delete", class: 'btn action-buttons remove-row', method: :delete, remote: true, data: { confirm: 'Remove this label? Are you sure?', toggle: 'tooltip' } do = icon('pencil-square-o')
%i.fa.fa-trash-o = link_to namespace_project_label_path(@project.namespace, @project, label), title: "Delete", class: 'btn btn-transparent btn-action remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?", toggle: "tooltip"} do
%span.sr-only Delete
= icon('trash-o')
- if current_user - if current_user
:javascript :javascript
new Subscription('##{label_css_id} .label-subscription'); new Subscription('##{dom_id(label)} .label-subscription');
%h4 %h4.has-conflicts
= icon("exclamation-triangle") = icon("exclamation-triangle")
This merge request contains merge conflicts This merge request contains merge conflicts
%p %p
Please resolve these conflicts or Please resolve these conflicts or
- if @merge_request.can_be_merged_by?(current_user) - if @merge_request.can_be_merged_by?(current_user)
#{link_to "merge this request manually", "#modal_merge_info", class: "how_to_merge_link vlink", "data-toggle" => "modal"}. #{link_to "merge this request manually", "#modal_merge_info", class: "how_to_merge_link vlink", "data-toggle" => "modal"}.
- else - else
......
...@@ -17,9 +17,8 @@ ...@@ -17,9 +17,8 @@
.col-md-6 .col-md-6
.form-group .form-group
= f.label :due_date, "Due Date", class: "control-label" = f.label :due_date, "Due Date", class: "control-label"
.col-sm-10= f.hidden_field :due_date
.col-sm-10 .col-sm-10
.datepicker = f.text_field :due_date, class: "datepicker form-control", placeholder: "Select due date"
.form-actions .form-actions
- if @milestone.new_record? - if @milestone.new_record?
......
...@@ -6,7 +6,6 @@ ...@@ -6,7 +6,6 @@
.nav-controls .nav-controls
- if can?(current_user, :admin_milestone, @project) - if can?(current_user, :admin_milestone, @project)
= link_to new_namespace_project_milestone_path(@project.namespace, @project), class: "btn btn-new", title: "New Milestone" do = link_to new_namespace_project_milestone_path(@project.namespace, @project), class: "btn btn-new", title: "New Milestone" do
= icon('plus')
New Milestone New Milestone
.milestones .milestones
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
- if @milestone.closed? - if @milestone.closed?
Closed Closed
- elsif @milestone.expired? - elsif @milestone.expired?
Expired Past due
- else - else
Open Open
%span.identifier %span.identifier
...@@ -23,11 +23,9 @@ ...@@ -23,11 +23,9 @@
= link_to 'Reopen Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-nr btn-grouped" = link_to 'Reopen Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-nr btn-grouped"
= link_to edit_namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-grouped btn-nr" do = link_to edit_namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-grouped btn-nr" do
= icon('pencil-square-o')
Edit Edit
= link_to namespace_project_milestone_path(@project.namespace, @project, @milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-grouped btn-danger" do = link_to namespace_project_milestone_path(@project.namespace, @project, @milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-grouped btn-danger" do
= icon('trash-o')
Delete Delete
.detail-page-description.milestone-detail .detail-page-description.milestone-detail
......
...@@ -28,14 +28,12 @@ ...@@ -28,14 +28,12 @@
.nav-controls .nav-controls
- if can? current_user, :create_pipeline, @project - if can? current_user, :create_pipeline, @project
= link_to new_namespace_project_pipeline_path(@project.namespace, @project), class: 'btn btn-create' do = link_to new_namespace_project_pipeline_path(@project.namespace, @project), class: 'btn btn-create' do
= icon('plus')
New pipeline New pipeline
- unless @repository.gitlab_ci_yml - unless @repository.gitlab_ci_yml
= link_to 'Get started with Pipelines', help_page_path('ci/quick_start', 'README'), class: 'btn btn-info' = link_to 'Get started with Pipelines', help_page_path('ci/quick_start', 'README'), class: 'btn btn-info'
= link_to ci_lint_path, class: 'btn btn-default' do = link_to ci_lint_path, class: 'btn btn-default' do
= icon('wrench')
%span CI Lint %span CI Lint
%ul.content-list.pipelines %ul.content-list.pipelines
......
...@@ -32,7 +32,7 @@ ...@@ -32,7 +32,7 @@
.pull-right .pull-right
%strong= member.human_access %strong= member.human_access
- if can?(current_user, :update_project_member, member) - if can?(current_user, :update_project_member, member)
= button_tag class: "btn-xs btn js-toggle-button", = button_tag class: "btn-xs btn-grouped inline btn js-toggle-button",
title: 'Edit access level', type: 'button' do title: 'Edit access level', type: 'button' do
= icon('pencil') = icon('pencil')
......
%span.btn-group.btn-grouped %span.btn-group
= link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'zip'), class: 'btn btn-default', rel: 'nofollow' do = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'zip'), class: 'btn btn-default', rel: 'nofollow' do
%i.fa.fa-download %span Source code
%span source code
%a.btn.btn-default.dropdown-toggle{ 'data-toggle' => 'dropdown' } %a.btn.btn-default.dropdown-toggle{ 'data-toggle' => 'dropdown' }
%span.caret %span.caret
%span.sr-only %span.sr-only
...@@ -9,9 +8,7 @@ ...@@ -9,9 +8,7 @@
%ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' } %ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' }
%li %li
= link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'zip'), rel: 'nofollow' do = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'zip'), rel: 'nofollow' do
%i.fa.fa-download
%span Download zip %span Download zip
%li %li
= link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do
%i.fa.fa-download
%span Download tar.gz %span Download tar.gz
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
= render 'projects/tags/download', ref: tag.name, project: @project = render 'projects/tags/download', ref: tag.name, project: @project
- if can?(current_user, :push_code, @project) - if can?(current_user, :push_code, @project)
= link_to edit_namespace_project_tag_release_path(@project.namespace, @project, tag.name), class: 'btn-grouped btn has-tooltip', title: "Edit release notes" do = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, tag.name), class: 'btn has-tooltip', title: "Edit release notes" do
= icon("pencil") = icon("pencil")
- if can?(current_user, :admin_project, @project) - if can?(current_user, :admin_project, @project)
= link_to namespace_project_tag_path(@project.namespace, @project, tag.name), class: 'btn btn-grouped btn-xs btn-remove remove-row has-tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{tag.name}' tag cannot be undone. Are you sure?", container: 'body' }, remote: true do = link_to namespace_project_tag_path(@project.namespace, @project, tag.name), class: 'btn btn-remove remove-row has-tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{tag.name}' tag cannot be undone. Are you sure?", container: 'body' }, remote: true do
= icon("trash-o") = icon("trash-o")
- if commit - if commit
......
...@@ -3,14 +3,14 @@ ...@@ -3,14 +3,14 @@
= render "projects/commits/head" = render "projects/commits/head"
%div{ class: (container_class) } %div{ class: (container_class) }
.row-content-block.second-block.content-component-block .top-area
.nav-text
Tags give the ability to mark specific points in history as being important
- if can? current_user, :push_code, @project - if can? current_user, :push_code, @project
.pull-right .nav-controls
= link_to new_namespace_project_tag_path(@project.namespace, @project), class: 'btn btn-create new-tag-btn' do = link_to new_namespace_project_tag_path(@project.namespace, @project), class: 'btn btn-create new-tag-btn' do
= icon('plus')
New tag New tag
.oneline
Tags give the ability to mark specific points in history as being important
.tags .tags
- unless @tags.empty? - unless @tags.empty?
......
- if (@page && @page.persisted?) - if (@page && @page.persisted?)
= link_to namespace_project_wiki_history_path(@project.namespace, @project, @page), class: "btn btn-grouped" do = link_to namespace_project_wiki_history_path(@project.namespace, @project, @page), class: "btn" do
Page History Page History
- if can?(current_user, :create_wiki, @project) - if can?(current_user, :create_wiki, @project)
= link_to namespace_project_wiki_edit_path(@project.namespace, @project, @page), class: "btn btn-grouped" do = link_to namespace_project_wiki_edit_path(@project.namespace, @project, @page), class: "btn" do
%i.fa.fa-pencil-square-o
Edit Edit
- if can?(current_user, :admin_wiki, @project) - if can?(current_user, :admin_wiki, @project)
= link_to namespace_project_wiki_path(@project.namespace, @project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-remove" do = link_to namespace_project_wiki_path(@project.namespace, @project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-remove" do
= icon('trash')
Delete Delete
...@@ -13,7 +13,6 @@ ...@@ -13,7 +13,6 @@
.nav-controls .nav-controls
- if can?(current_user, :create_wiki, @project) - if can?(current_user, :create_wiki, @project)
= link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do
= icon('plus')
New Page New Page
= render 'projects/wikis/new' = render 'projects/wikis/new'
...@@ -8,5 +8,6 @@ ...@@ -8,5 +8,6 @@
= icon('star') = icon('star')
%span.label-name %span.label-name
= link_to_label(label, tooltip: false) = link_to_label(label, tooltip: false)
%span.prepend-left-10 - if label.description
= markdown(label.description, pipeline: :single_line) %span.label-description
= markdown(label.description, pipeline: :single_line)
...@@ -6,10 +6,10 @@ ...@@ -6,10 +6,10 @@
- if group_member - if group_member
.controls.hidden-xs .controls.hidden-xs
- if can?(current_user, :admin_group, group) - if can?(current_user, :admin_group, group)
= link_to edit_group_path(group), class: "btn-sm btn btn-grouped" do = link_to edit_group_path(group), class: "btn" do
%i.fa.fa-cogs = icon('cogs')
= link_to leave_group_group_members_path(group), data: { confirm: leave_group_message(group.name) }, method: :delete, class: "btn-sm btn btn-grouped", title: 'Leave this group' do = link_to leave_group_group_members_path(group), data: { confirm: leave_group_message(group.name) }, method: :delete, class: "btn", title: 'Leave this group' do
= icon('sign-out') = icon('sign-out')
.stats .stats
......
...@@ -88,9 +88,9 @@ ...@@ -88,9 +88,9 @@
.col-lg-6 .col-lg-6
.form-group .form-group
= f.label :due_date, "Due date", class: "control-label" = f.label :due_date, "Due date", class: "control-label"
= f.hidden_field :due_date, id: "issuable-due-date"
.col-sm-10 .col-sm-10
.datepicker .issuable-form-select-holder
= f.text_field :due_date, id: "issuable-due-date", class: "datepicker form-control", placeholder: "Select due date"
- if issuable.can_move?(current_user) - if issuable.can_move?(current_user)
%hr %hr
......
...@@ -41,7 +41,8 @@ ...@@ -41,7 +41,8 @@
= icon('clock-o') = icon('clock-o')
%span %span
- if issuable.milestone - if issuable.milestone
= issuable.milestone.title %span.has-tooltip{title: milestone_remaining_days(issuable.milestone), data: {container: 'body', html: 1, placement: 'left'}}
= issuable.milestone.title
- else - else
None None
.title.hide-collapsed .title.hide-collapsed
...@@ -52,7 +53,8 @@ ...@@ -52,7 +53,8 @@
.value.bold.hide-collapsed .value.bold.hide-collapsed
- if issuable.milestone - if issuable.milestone
= link_to namespace_project_milestone_path(@project.namespace, @project, issuable.milestone) do = link_to namespace_project_milestone_path(@project.namespace, @project, issuable.milestone) do
= issuable.milestone.title %span.has-tooltip{title: milestone_remaining_days(issuable.milestone), data: {container: 'body', html: 1}}
= issuable.milestone.title
- else - else
.light None .light None
......
...@@ -35,11 +35,9 @@ ...@@ -35,11 +35,9 @@
.col-sm-6= render('shared/milestone_expired', milestone: milestone) .col-sm-6= render('shared/milestone_expired', milestone: milestone)
.col-sm-6 .col-sm-6
- if can?(current_user, :admin_milestone, milestone.project) and milestone.active? - if can?(current_user, :admin_milestone, milestone.project) and milestone.active?
= link_to edit_namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), class: "btn btn-xs" do = link_to edit_namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), class: "btn btn-xs btn-grouped" do
= icon('pencil-square-o')
Edit Edit
\ \
= link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-xs btn-close" = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-xs btn-close btn-grouped"
= link_to namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-xs btn-remove" do = link_to namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-xs btn-remove btn-grouped" do
= icon('trash-o')
Delete Delete
...@@ -79,10 +79,10 @@ ...@@ -79,10 +79,10 @@
%li.js-contributed-tab %li.js-contributed-tab
= link_to user_contributed_projects_path, data: {target: 'div#contributed', action: 'contributed', toggle: 'tab'} do = link_to user_contributed_projects_path, data: {target: 'div#contributed', action: 'contributed', toggle: 'tab'} do
Contributed projects Contributed projects
%li.projects-tab %li.js-projects-tab
= link_to user_projects_path, data: {target: 'div#projects', action: 'projects', toggle: 'tab'} do = link_to user_projects_path, data: {target: 'div#projects', action: 'projects', toggle: 'tab'} do
Personal projects Personal projects
%li.snippets-tab %li.js-snippets-tab
= link_to user_snippets_path, data: {target: 'div#snippets', action: 'snippets', toggle: 'tab'} do = link_to user_snippets_path, data: {target: 'div#snippets', action: 'snippets', toggle: 'tab'} do
Snippets Snippets
......
...@@ -449,22 +449,6 @@ Rails.application.routes.draw do ...@@ -449,22 +449,6 @@ Rails.application.routes.draw do
resources(:projects, constraints: { id: /[a-zA-Z.0-9_\-]+(?<!\.atom)/ }, except: resources(:projects, constraints: { id: /[a-zA-Z.0-9_\-]+(?<!\.atom)/ }, except:
[:new, :create, :index], path: "/") do [:new, :create, :index], path: "/") do
# Allow /info/refs, /info/refs?service=git-upload-pack, and
# /info/refs?service=git-receive-pack, but nothing else.
#
git_http_handshake = lambda do |request|
request.query_string.blank? ||
request.query_string.match(/\Aservice=git-(upload|receive)-pack\z/)
end
ref_redirect = redirect do |params, request|
path = "#{params[:namespace_id]}/#{params[:project_id]}.git/info/refs"
path << "?#{request.query_string}" unless request.query_string.blank?
path
end
get '/info/refs', constraints: git_http_handshake, to: ref_redirect
member do member do
put :transfer put :transfer
delete :remove_fork delete :remove_fork
...@@ -479,12 +463,28 @@ Rails.application.routes.draw do ...@@ -479,12 +463,28 @@ Rails.application.routes.draw do
scope module: :projects do scope module: :projects do
# Git HTTP clients ('git clone' etc.) # Git HTTP clients ('git clone' etc.)
scope constraints: { format: /(git|wiki\.git)/ } do scope constraints: { id: /.+\.git/, format: nil } do
get '/info/refs', to: 'git_http#info_refs' get '/info/refs', to: 'git_http#info_refs'
post '/git-upload-pack', to: 'git_http#git_upload_pack' post '/git-upload-pack', to: 'git_http#git_upload_pack'
post '/git-receive-pack', to: 'git_http#git_receive_pack' post '/git-receive-pack', to: 'git_http#git_receive_pack'
end end
# Allow /info/refs, /info/refs?service=git-upload-pack, and
# /info/refs?service=git-receive-pack, but nothing else.
#
git_http_handshake = lambda do |request|
request.query_string.blank? ||
request.query_string.match(/\Aservice=git-(upload|receive)-pack\z/)
end
ref_redirect = redirect do |params, request|
path = "#{params[:namespace_id]}/#{params[:project_id]}.git/info/refs"
path << "?#{request.query_string}" unless request.query_string.blank?
path
end
get '/info/refs', constraints: git_http_handshake, to: ref_redirect
# Blob routes: # Blob routes:
get '/new/*id', to: 'blob#new', constraints: { id: /.+/ }, as: 'new_blob' get '/new/*id', to: 'blob#new', constraints: { id: /.+/ }, as: 'new_blob'
post '/create/*id', to: 'blob#create', constraints: { id: /.+/ }, as: 'create_blob' post '/create/*id', to: 'blob#create', constraints: { id: /.+/ }, as: 'create_blob'
......
...@@ -9,7 +9,7 @@ class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps ...@@ -9,7 +9,7 @@ class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps
step 'I remove label \'bug\'' do step 'I remove label \'bug\'' do
page.within "#label_#{bug_label.id}" do page.within "#label_#{bug_label.id}" do
click_link 'Delete' first(:link, 'Delete').click
end end
end end
......
...@@ -29,6 +29,6 @@ class Spinach::Features::Labels < Spinach::FeatureSteps ...@@ -29,6 +29,6 @@ class Spinach::Features::Labels < Spinach::FeatureSteps
private private
def subscribe_button def subscribe_button
first('.label-subscribe-button span') first('.js-subscribe-button', visible: true)
end end
end end
...@@ -8,6 +8,7 @@ module Gitlab ...@@ -8,6 +8,7 @@ module Gitlab
gon.relative_url_root = Gitlab.config.gitlab.relative_url_root gon.relative_url_root = Gitlab.config.gitlab.relative_url_root
gon.shortcuts_path = help_shortcuts_path gon.shortcuts_path = help_shortcuts_path
gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class
gon.award_menu_url = emojis_path
if current_user if current_user
gon.current_user_id = current_user.id gon.current_user_id = current_user.id
......
...@@ -69,13 +69,20 @@ module Gitlab ...@@ -69,13 +69,20 @@ module Gitlab
return unless ldap_person return unless ldap_person
# If a corresponding person exists with same uid in a LDAP server, # If a corresponding person exists with same uid in a LDAP server,
# set up a Gitlab user with dual LDAP and Omniauth identities. # check if the user already has a GitLab account.
if user = Gitlab::LDAP::User.find_by_uid_and_provider(ldap_person.dn, ldap_person.provider) user = Gitlab::LDAP::User.find_by_uid_and_provider(ldap_person.dn, ldap_person.provider)
# Case when a LDAP user already exists in Gitlab. Add the Omniauth identity to existing account. if user
# Case when a LDAP user already exists in Gitlab. Add the OAuth identity to existing account.
log.info "LDAP account found for user #{user.username}. Building new #{auth_hash.provider} identity."
user.identities.build(extern_uid: auth_hash.uid, provider: auth_hash.provider) user.identities.build(extern_uid: auth_hash.uid, provider: auth_hash.provider)
else else
# No account in Gitlab yet: create it and add the LDAP identity log.info "No existing LDAP account was found in GitLab. Checking for #{auth_hash.provider} account."
user = build_new_user user = find_by_uid_and_provider
if user.nil?
log.info "No user found using #{auth_hash.provider} provider. Creating a new one."
user = build_new_user
end
log.info "Correct account has been found. Adding LDAP identity to user: #{user.username}."
user.identities.new(provider: ldap_person.provider, extern_uid: ldap_person.dn) user.identities.new(provider: ldap_person.provider, extern_uid: ldap_person.dn)
end end
......
...@@ -12,12 +12,12 @@ module Gitlab ...@@ -12,12 +12,12 @@ module Gitlab
end end
def gl_user def gl_user
@user ||= find_by_uid_and_provider
if auto_link_ldap_user? if auto_link_ldap_user?
@user ||= find_or_create_ldap_user @user ||= find_or_create_ldap_user
end end
@user ||= find_by_uid_and_provider
if auto_link_saml_user? if auto_link_saml_user?
@user ||= find_by_email @user ||= find_by_email
end end
......
...@@ -34,7 +34,7 @@ namespace :gitlab do ...@@ -34,7 +34,7 @@ namespace :gitlab do
# PG: http://www.postgresql.org/docs/current/static/ddl-depend.html # PG: http://www.postgresql.org/docs/current/static/ddl-depend.html
# MySQL: http://dev.mysql.com/doc/refman/5.7/en/drop-table.html # MySQL: http://dev.mysql.com/doc/refman/5.7/en/drop-table.html
# Add `IF EXISTS` because cascade could have already deleted a table. # Add `IF EXISTS` because cascade could have already deleted a table.
tables.each { |t| connection.execute("DROP TABLE IF EXISTS #{t} CASCADE") } tables.each { |t| connection.execute("DROP TABLE IF EXISTS #{connection.quote_table_name(t)} CASCADE") }
end end
desc 'Configures the database by running migrate, or by loading the schema and seeding if needed' desc 'Configures the database by running migrate, or by loading the schema and seeding if needed'
......
require 'spec_helper' require 'spec_helper'
require_relative 'import_spec_helper'
describe Import::BitbucketController do describe Import::BitbucketController do
include ImportSpecHelper include ImportSpecHelper
......
require 'spec_helper' require 'spec_helper'
require_relative 'import_spec_helper'
describe Import::FogbugzController do describe Import::FogbugzController do
include ImportSpecHelper include ImportSpecHelper
......
require 'spec_helper' require 'spec_helper'
require_relative 'import_spec_helper'
describe Import::GithubController do describe Import::GithubController do
include ImportSpecHelper include ImportSpecHelper
......
require 'spec_helper' require 'spec_helper'
require_relative 'import_spec_helper'
describe Import::GitlabController do describe Import::GitlabController do
include ImportSpecHelper include ImportSpecHelper
......
require 'spec_helper' require 'spec_helper'
require_relative 'import_spec_helper'
describe Import::GitoriousController do describe Import::GitoriousController do
include ImportSpecHelper include ImportSpecHelper
......
require 'spec_helper' require 'spec_helper'
require_relative 'import_spec_helper'
describe Import::GoogleCodeController do describe Import::GoogleCodeController do
include ImportSpecHelper include ImportSpecHelper
......
require 'spec_helper'
feature 'Tooltips on .timeago dates', feature: true, js: true do
include WaitForAjax
let(:user) { create(:user) }
let(:project) { create(:project, name: 'test', namespace: user.namespace) }
let(:created_date) { Date.yesterday.to_time }
let(:expected_format) { created_date.strftime('%b %-d, %Y %l:%M%P UTC') }
context 'on the activity tab' do
before do
project.team << [user, :master]
Event.create( project: project, author_id: user.id, action: Event::JOINED,
updated_at: created_date, created_at: created_date)
login_as user
visit user_path(user)
wait_for_ajax()
page.find('.js-timeago').hover
end
it 'has the datetime formated correctly' do
expect(page).to have_selector('.local-timeago', text: expected_format)
end
end
context 'on the snippets tab' do
before do
project.team << [user, :master]
create(:snippet, author: user, updated_at: created_date, created_at: created_date)
login_as user
visit user_snippets_path(user)
wait_for_ajax()
page.find('.js-timeago').hover
end
it 'has the datetime formated correctly' do
expect(page).to have_selector('.local-timeago', text: expected_format)
end
end
end
...@@ -75,12 +75,13 @@ describe 'Issues', feature: true do ...@@ -75,12 +75,13 @@ describe 'Issues', feature: true do
fill_in 'issue_title', with: 'bug 345' fill_in 'issue_title', with: 'bug 345'
fill_in 'issue_description', with: 'bug description' fill_in 'issue_description', with: 'bug description'
find('#issuable-due-date').click
page.within '.datepicker' do page.within '.ui-datepicker' do
click_link date.day click_link date.day
end end
expect(find('#issuable-due-date', visible: false).value).to eq date.to_s expect(find('#issuable-due-date').value).to eq date.to_s
click_button 'Submit issue' click_button 'Submit issue'
...@@ -100,18 +101,19 @@ describe 'Issues', feature: true do ...@@ -100,18 +101,19 @@ describe 'Issues', feature: true do
it 'should save with due date' do it 'should save with due date' do
date = Date.today.at_beginning_of_month date = Date.today.at_beginning_of_month
expect(find('#issuable-due-date', visible: false).value).to eq date.to_s expect(find('#issuable-due-date').value).to eq date.to_s
date = date.tomorrow date = date.tomorrow
fill_in 'issue_title', with: 'bug 345' fill_in 'issue_title', with: 'bug 345'
fill_in 'issue_description', with: 'bug description' fill_in 'issue_description', with: 'bug description'
find('#issuable-due-date').click
page.within '.datepicker' do page.within '.ui-datepicker' do
click_link date.day click_link date.day
end end
expect(find('#issuable-due-date', visible: false).value).to eq date.to_s expect(find('#issuable-due-date').value).to eq date.to_s
click_button 'Save changes' click_button 'Save changes'
......
...@@ -3,10 +3,11 @@ ...@@ -3,10 +3,11 @@
#= require jquery.cookie #= require jquery.cookie
#= require ./fixtures/emoji_menu #= require ./fixtures/emoji_menu
awardsHandler = null awardsHandler = null
window.gl or= {} window.gl or= {}
gl.emojiAliases = -> return { '+1': 'thumbsup', '-1': 'thumbsdown' } window.gon or= {}
gl.awardMenuUrl = '/emojis' gl.emojiAliases = -> return { '+1': 'thumbsup', '-1': 'thumbsdown' }
gon.award_menu_url = '/emojis'
lazyAssert = (done, assertFn) -> lazyAssert = (done, assertFn) ->
...@@ -25,9 +26,7 @@ describe 'AwardsHandler', -> ...@@ -25,9 +26,7 @@ describe 'AwardsHandler', ->
fixture.load 'awards_handler.html' fixture.load 'awards_handler.html'
awardsHandler = new AwardsHandler awardsHandler = new AwardsHandler
spyOn(awardsHandler, 'postEmoji').and.callFake (url, emoji, cb) => cb() spyOn(awardsHandler, 'postEmoji').and.callFake (url, emoji, cb) => cb()
spyOn(jQuery, 'get').and.callFake (req, cb) -> spyOn(jQuery, 'get').and.callFake (req, cb) -> cb window.emojiMenu
expect(req).toBe '/emojis'
cb window.emojiMenu
describe '::showEmojiMenu', -> describe '::showEmojiMenu', ->
......
require 'spec_helper' require 'spec_helper'
describe Gitlab::BitbucketImport::Client, lib: true do describe Gitlab::BitbucketImport::Client, lib: true do
include ImportSpecHelper
let(:token) { '123456' } let(:token) { '123456' }
let(:secret) { 'secret' } let(:secret) { 'secret' }
let(:client) { Gitlab::BitbucketImport::Client.new(token, secret) } let(:client) { Gitlab::BitbucketImport::Client.new(token, secret) }
before do before do
Gitlab.config.omniauth.providers << OpenStruct.new(app_id: "asd123", app_secret: "asd123", name: "bitbucket") stub_omniauth_provider('bitbucket')
end end
it 'all OAuth client options are symbols' do it 'all OAuth client options are symbols' do
......
require 'spec_helper' require 'spec_helper'
describe Gitlab::BitbucketImport::Importer, lib: true do describe Gitlab::BitbucketImport::Importer, lib: true do
include ImportSpecHelper
before do before do
Gitlab.config.omniauth.providers << OpenStruct.new(app_id: "asd123", app_secret: "asd123", name: "bitbucket") stub_omniauth_provider('bitbucket')
end end
let(:statuses) do let(:statuses) do
......
require 'spec_helper' require 'spec_helper'
describe Gitlab::GitlabImport::Client, lib: true do describe Gitlab::GitlabImport::Client, lib: true do
include ImportSpecHelper
let(:token) { '123456' } let(:token) { '123456' }
let(:client) { Gitlab::GitlabImport::Client.new(token) } let(:client) { Gitlab::GitlabImport::Client.new(token) }
before do before do
Gitlab.config.omniauth.providers << OpenStruct.new(app_id: "asd123", app_secret: "asd123", name: "gitlab") stub_omniauth_provider('gitlab')
end end
it 'all OAuth2 client options are symbols' do it 'all OAuth2 client options are symbols' do
......
...@@ -145,6 +145,7 @@ describe Gitlab::Saml::User, lib: true do ...@@ -145,6 +145,7 @@ describe Gitlab::Saml::User, lib: true do
allow(ldap_user).to receive(:email) { %w(john@mail.com john2@example.com) } allow(ldap_user).to receive(:email) { %w(john@mail.com john2@example.com) }
allow(ldap_user).to receive(:dn) { 'uid=user1,ou=People,dc=example' } allow(ldap_user).to receive(:dn) { 'uid=user1,ou=People,dc=example' }
allow(Gitlab::LDAP::Person).to receive(:find_by_uid).and_return(ldap_user) allow(Gitlab::LDAP::Person).to receive(:find_by_uid).and_return(ldap_user)
allow(Gitlab::LDAP::Person).to receive(:find_by_dn).and_return(ldap_user)
end end
context 'and no account for the LDAP user' do context 'and no account for the LDAP user' do
...@@ -177,6 +178,23 @@ describe Gitlab::Saml::User, lib: true do ...@@ -177,6 +178,23 @@ describe Gitlab::Saml::User, lib: true do
]) ])
end end
end end
context 'user has SAML user, and wants to add their LDAP identity' do
it 'adds the LDAP identity to the existing SAML user' do
create(:omniauth_user, email: 'john@mail.com', extern_uid: 'uid=user1,ou=People,dc=example', provider: 'saml', username: 'john')
local_hash = OmniAuth::AuthHash.new(uid: 'uid=user1,ou=People,dc=example', provider: provider, info: info_hash)
local_saml_user = described_class.new(local_hash)
local_saml_user.save
local_gl_user = local_saml_user.gl_user
expect(local_gl_user).to be_valid
expect(local_gl_user.identities.length).to eql 2
identities_as_hash = local_gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } }
expect(identities_as_hash).to match_array([ { provider: 'ldapmain', extern_uid: 'uid=user1,ou=People,dc=example' },
{ provider: 'saml', extern_uid: 'uid=user1,ou=People,dc=example' }
])
end
end
end end
end end
end end
......
...@@ -2,7 +2,7 @@ require "spec_helper" ...@@ -2,7 +2,7 @@ require "spec_helper"
describe 'Git HTTP requests', lib: true do describe 'Git HTTP requests', lib: true do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project) } let(:project) { create(:project, path: 'project.git-project') }
it "gives WWW-Authenticate hints" do it "gives WWW-Authenticate hints" do
clone_get('doesnt/exist.git') clone_get('doesnt/exist.git')
...@@ -268,6 +268,87 @@ describe 'Git HTTP requests', lib: true do ...@@ -268,6 +268,87 @@ describe 'Git HTTP requests', lib: true do
end end
end end
context "when the project path doesn't end in .git" do
context "GET info/refs" do
let(:path) { "/#{project.path_with_namespace}/info/refs" }
context "when no params are added" do
before { get path }
it "redirects to the .git suffix version" do
expect(response).to redirect_to("/#{project.path_with_namespace}.git/info/refs")
end
end
context "when the upload-pack service is requested" do
let(:params) { { service: 'git-upload-pack' } }
before { get path, params }
it "redirects to the .git suffix version" do
expect(response).to redirect_to("/#{project.path_with_namespace}.git/info/refs?service=#{params[:service]}")
end
end
context "when the receive-pack service is requested" do
let(:params) { { service: 'git-receive-pack' } }
before { get path, params }
it "redirects to the .git suffix version" do
expect(response).to redirect_to("/#{project.path_with_namespace}.git/info/refs?service=#{params[:service]}")
end
end
context "when the params are anything else" do
let(:params) { { service: 'git-implode-pack' } }
before { get path, params }
it "redirects to the sign-in page" do
expect(response).to redirect_to(new_user_session_path)
end
end
end
context "POST git-upload-pack" do
it "fails to find a route" do
expect { clone_post(project.path_with_namespace) }.to raise_error(ActionController::RoutingError)
end
end
context "POST git-receive-pack" do
it "failes to find a route" do
expect { push_post(project.path_with_namespace) }.to raise_error(ActionController::RoutingError)
end
end
end
context "retrieving an info/refs file" do
before { project.update_attribute(:visibility_level, Project::PUBLIC) }
context "when the file exists" do
before do
# Provide a dummy file in its place
allow_any_instance_of(Repository).to receive(:blob_at).and_call_original
allow_any_instance_of(Repository).to receive(:blob_at).with('5937ac0a7beb003549fc5fd26fc247adbce4a52e', 'info/refs') do
Gitlab::Git::Blob.find(project.repository, 'master', '.gitignore')
end
get "/#{project.path_with_namespace}/blob/master/info/refs"
end
it "returns the file" do
expect(response.status).to eq(200)
end
end
context "when the file exists" do
before { get "/#{project.path_with_namespace}/blob/master/info/refs" }
it "returns not found" do
expect(response.status).to eq(404)
end
end
end
def clone_get(project, options={}) def clone_get(project, options={})
get "/#{project}/info/refs", { service: 'git-upload-pack' }, auth_env(*options.values_at(:user, :password)) get "/#{project}/info/refs", { service: 'git-upload-pack' }, auth_env(*options.values_at(:user, :password))
end end
......
...@@ -124,7 +124,7 @@ describe Projects::ImportService, services: true do ...@@ -124,7 +124,7 @@ describe Projects::ImportService, services: true do
} }
) )
Gitlab.config.omniauth.providers << provider allow(Gitlab.config.omniauth).to receive(:providers).and_return([provider])
end end
end end
end end
...@@ -18,7 +18,7 @@ describe TodoService, services: true do ...@@ -18,7 +18,7 @@ describe TodoService, services: true do
end end
describe 'Issues' do describe 'Issues' do
let(:issue) { create(:issue, project: project, assignee: john_doe, author: author, description: mentions) } let(:issue) { create(:issue, project: project, assignee: john_doe, author: author, description: "- [ ] Task 1\n- [ ] Task 2 #{mentions}") }
let(:unassigned_issue) { create(:issue, project: project, assignee: nil) } let(:unassigned_issue) { create(:issue, project: project, assignee: nil) }
let(:confidential_issue) { create(:issue, :confidential, project: project, author: author, assignee: assignee, description: mentions) } let(:confidential_issue) { create(:issue, :confidential, project: project, author: author, assignee: assignee, description: mentions) }
...@@ -101,6 +101,19 @@ describe TodoService, services: true do ...@@ -101,6 +101,19 @@ describe TodoService, services: true do
should_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) should_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
should_not_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) should_not_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
end end
it 'does not create todo when when tasks are marked as completed' do
issue.update(description: "- [x] Task 1\n- [X] Task 2 #{mentions}")
service.update_issue(issue, author)
should_not_create_todo(user: admin, target: issue, action: Todo::MENTIONED)
should_not_create_todo(user: assignee, target: issue, action: Todo::MENTIONED)
should_not_create_todo(user: author, target: issue, action: Todo::MENTIONED)
should_not_create_todo(user: john_doe, target: issue, action: Todo::MENTIONED)
should_not_create_todo(user: member, target: issue, action: Todo::MENTIONED)
should_not_create_todo(user: non_member, target: issue, action: Todo::MENTIONED)
end
end end
describe '#close_issue' do describe '#close_issue' do
...@@ -210,7 +223,7 @@ describe TodoService, services: true do ...@@ -210,7 +223,7 @@ describe TodoService, services: true do
end end
describe 'Merge Requests' do describe 'Merge Requests' do
let(:mr_assigned) { create(:merge_request, source_project: project, author: author, assignee: john_doe, description: mentions) } let(:mr_assigned) { create(:merge_request, source_project: project, author: author, assignee: john_doe, description: "- [ ] Task 1\n- [ ] Task 2 #{mentions}") }
let(:mr_unassigned) { create(:merge_request, source_project: project, author: author, assignee: nil) } let(:mr_unassigned) { create(:merge_request, source_project: project, author: author, assignee: nil) }
describe '#new_merge_request' do describe '#new_merge_request' do
...@@ -253,6 +266,19 @@ describe TodoService, services: true do ...@@ -253,6 +266,19 @@ describe TodoService, services: true do
expect { service.update_merge_request(mr_assigned, author) }.not_to change(member.todos, :count) expect { service.update_merge_request(mr_assigned, author) }.not_to change(member.todos, :count)
end end
it 'does not create todo when when tasks are marked as completed' do
mr_assigned.update(description: "- [x] Task 1\n- [X] Task 2 #{mentions}")
service.update_merge_request(mr_assigned, author)
should_not_create_todo(user: admin, target: mr_assigned, action: Todo::MENTIONED)
should_not_create_todo(user: assignee, target: mr_assigned, action: Todo::MENTIONED)
should_not_create_todo(user: author, target: mr_assigned, action: Todo::MENTIONED)
should_not_create_todo(user: john_doe, target: mr_assigned, action: Todo::MENTIONED)
should_not_create_todo(user: member, target: mr_assigned, action: Todo::MENTIONED)
should_not_create_todo(user: non_member, target: mr_assigned, action: Todo::MENTIONED)
end
end end
describe '#close_merge_request' do describe '#close_merge_request' do
......
...@@ -28,6 +28,6 @@ module ImportSpecHelper ...@@ -28,6 +28,6 @@ module ImportSpecHelper
app_id: 'asd123', app_id: 'asd123',
app_secret: 'asd123' app_secret: 'asd123'
) )
Gitlab.config.omniauth.providers << provider allow(Gitlab.config.omniauth).to receive(:providers).and_return([provider])
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment