Commit 95400fa9 authored by Valery Sizov's avatar Valery Sizov

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce into schema

parents 19a7fa79 1d20eefd
...@@ -22,12 +22,15 @@ v 8.9.0 (unreleased) ...@@ -22,12 +22,15 @@ v 8.9.0 (unreleased)
- Added descriptions to notification settings dropdown - 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
- Implement a fair usage of shared runners
- Remove project notification settings associated with deleted projects - Remove project notification settings associated with deleted projects
- Fix 404 page when viewing TODOs that contain milestones or labels in different projects - Fix 404 page when viewing TODOs that contain milestones or labels in different projects
- Add a metric for the number of new Redis connections created by a transaction - Add a metric for the number of new Redis connections created by a transaction
- Fix Error 500 when viewing a blob with binary characters after the 1024-byte mark - Fix Error 500 when viewing a blob with binary characters after the 1024-byte mark
- Redesign navigation for project pages - Redesign navigation for project pages
- Added shortcut 'y' for copying a files content hash URL #14470
- Fix groups API to list only user's accessible projects - Fix groups API to list only user's accessible projects
- Fix horizontal scrollbar for long commit message.
- Add Environments and Deployments - Add Environments and Deployments
- Redesign account and email confirmation emails - Redesign account and email confirmation emails
- Don't fail builds for projects that are deleted - Don't fail builds for projects that are deleted
...@@ -42,8 +45,10 @@ v 8.9.0 (unreleased) ...@@ -42,8 +45,10 @@ v 8.9.0 (unreleased)
- Add DB index on users.state - Add DB index on users.state
- Add rake task 'gitlab:db:configure' for conditionally seeding or migrating the database - Add rake task 'gitlab:db:configure' for conditionally seeding or migrating the database
- Changed the Slack build message to use the singular duration if necessary (Aran Koning) - Changed the Slack build message to use the singular duration if necessary (Aran Koning)
- Fix race condition on merge when build succeeds
- Links from a wiki page to other wiki pages should be rewritten as expected - Links from a wiki page to other wiki pages should be rewritten as expected
- Add option to project to only allow merge requests to be merged if the build succeeds (Rui Santos) - Add option to project to only allow merge requests to be merged if the build succeeds (Rui Santos)
- Added navigation shortcuts to the project pipelines, milestones, builds and forks page. !4393
- Fix issues filter when ordering by milestone - Fix issues filter when ordering by milestone
- Added artifacts:when to .gitlab-ci.yml - this requires GitLab Runner 1.3 - Added artifacts:when to .gitlab-ci.yml - this requires GitLab Runner 1.3
- Bamboo Service: Fix missing credentials & URL handling when base URL contains a path (Benjamin Schmid) - Bamboo Service: Fix missing credentials & URL handling when base URL contains a path (Benjamin Schmid)
...@@ -53,6 +58,7 @@ v 8.9.0 (unreleased) ...@@ -53,6 +58,7 @@ v 8.9.0 (unreleased)
- Add support for using Yubikeys (U2F) for two-factor authentication - Add support for using Yubikeys (U2F) for two-factor authentication
- Link to blank group icon doesn't throw a 404 anymore - Link to blank group icon doesn't throw a 404 anymore
- Remove 'main language' feature - Remove 'main language' feature
- Toggle whitespace button now available for compare branches diffs #17881
- Pipelines can be canceled only when there are running builds - Pipelines can be canceled only when there are running builds
- 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
...@@ -74,6 +80,7 @@ v 8.9.0 (unreleased) ...@@ -74,6 +80,7 @@ v 8.9.0 (unreleased)
- Replace Colorize with Rainbow for coloring console output in Rake tasks. - Replace Colorize with Rainbow for coloring console output in Rake tasks.
- Add workhorse controller and API helpers - Add workhorse controller and API helpers
- An indicator is now displayed at the top of the comment field for confidential issues. - An indicator is now displayed at the top of the comment field for confidential issues.
- Show categorised search queries in the search autocomplete
- 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
...@@ -101,17 +108,21 @@ v 8.9.0 (unreleased) ...@@ -101,17 +108,21 @@ v 8.9.0 (unreleased)
- Various associations are now eager loaded when parsing issue references to reduce the number of queries executed - Various associations are now eager loaded when parsing issue references to reduce the number of queries executed
- Set inverse_of for Project/Service association to reduce the number of queries - Set inverse_of for Project/Service association to reduce the number of queries
v 8.8.5 (unreleased) v 8.8.5
- Ensure branch cleanup regardless of whether the GitHub import process succeeds - Import GitHub repositories respecting the API rate limit !4166
- Fix todos page throwing errors when you have a project pending deletion - Fix todos page throwing errors when you have a project pending deletion !4300
- Reduce number of SQL queries when rendering user references - Disable Webhooks before proceeding with the GitHub import !4470
- Import GitHub repositories respecting the API rate limit - Fix importer for GitHub comments on diff !4488
- Fix importer for GitHub comments on diff - Adjust the SAML control flow to allow LDAP identities to be added to an existing SAML user !4498
- Disable Webhooks before proceeding with the GitHub import - Fix incremental trace upload API when using multi-byte UTF-8 chars in trace !4541
- Fix incremental trace upload API when using multi-byte UTF-8 chars in trace - Prevent unauthorized access for projects build traces
- Forbid scripting for wiki files
- Only show notes through JSON on confidential issues that the user has access to
v 8.8.4 v 8.8.4
- Fix LDAP-based login for users with 2FA enabled. !4493 - Fix LDAP-based login for users with 2FA enabled. !4493
- Added descriptions to notification settings dropdown
- Due date can be removed from milestones
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
...@@ -227,6 +238,9 @@ v 8.8.0 ...@@ -227,6 +238,9 @@ v 8.8.0
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
- Prevent unauthorized access to other projects build traces
- Forbid scripting for wiki files
- Only show notes through JSON on confidential issues that the user has access to
v 8.7.6 v 8.7.6
- Fix links on wiki pages for relative url setups. !4131 (Artem Sidorenko) - Fix links on wiki pages for relative url setups. !4131 (Artem Sidorenko)
...@@ -393,6 +407,11 @@ v 8.7.0 ...@@ -393,6 +407,11 @@ v 8.7.0
- Add RAW build trace output and button on build page - Add RAW build trace output and button on build page
- Add incremental build trace update into CI API - Add incremental build trace update into CI API
v 8.6.9
- Prevent unauthorized access to other projects build traces
- Forbid scripting for wiki files
- Only show notes through JSON on confidential issues that the user has access to
v 8.6.8 v 8.6.8
- Prevent privilege escalation via "impersonate" feature - Prevent privilege escalation via "impersonate" feature
- Prevent privilege escalation via notes API - Prevent privilege escalation via notes API
...@@ -547,6 +566,10 @@ v 8.6.0 ...@@ -547,6 +566,10 @@ v 8.6.0
- Trigger a todo for mentions on commits page - Trigger a todo for mentions on commits page
- Let project owners and admins soft delete issues and merge requests - Let project owners and admins soft delete issues and merge requests
v 8.5.13
- Prevent unauthorized access to other projects build traces
- Forbid scripting for wiki files
v 8.5.12 v 8.5.12
- Prevent privilege escalation via "impersonate" feature - Prevent privilege escalation via "impersonate" feature
- Prevent privilege escalation via notes API - Prevent privilege escalation via notes API
...@@ -726,6 +749,10 @@ v 8.5.0 ...@@ -726,6 +749,10 @@ v 8.5.0
- Show label row when filtering issues or merge requests by label (Nuttanart Pornprasitsakul) - Show label row when filtering issues or merge requests by label (Nuttanart Pornprasitsakul)
- Add Todos - Add Todos
v 8.4.11
- Prevent unauthorized access to other projects build traces
- Forbid scripting for wiki files
v 8.4.10 v 8.4.10
- Prevent privilege escalation via "impersonate" feature - Prevent privilege escalation via "impersonate" feature
- Prevent privilege escalation via notes API - Prevent privilege escalation via notes API
...@@ -868,6 +895,10 @@ v 8.4.0 ...@@ -868,6 +895,10 @@ v 8.4.0
- Add IP check against DNSBLs at account sign-up - Add IP check against DNSBLs at account sign-up
- Added cache:key to .gitlab-ci.yml allowing to fine tune the caching - Added cache:key to .gitlab-ci.yml allowing to fine tune the caching
v 8.3.10
- Prevent unauthorized access to other projects build traces
- Forbid scripting for wiki files
v 8.3.9 v 8.3.9
- Prevent privilege escalation via "impersonate" feature - Prevent privilege escalation via "impersonate" feature
- Prevent privilege escalation via notes API - Prevent privilege escalation via notes API
...@@ -986,6 +1017,10 @@ v 8.3.0 ...@@ -986,6 +1017,10 @@ v 8.3.0
- Expose Git's version in the admin area - Expose Git's version in the admin area
- Show "New Merge Request" buttons on canonical repos when you have a fork (Josh Frye) - Show "New Merge Request" buttons on canonical repos when you have a fork (Josh Frye)
v 8.2.6
- Prevent unauthorized access to other projects build traces
- Forbid scripting for wiki files
v 8.2.5 v 8.2.5
- Prevent privilege escalation via "impersonate" feature - Prevent privilege escalation via "impersonate" feature
- Prevent privilege escalation via notes API - Prevent privilege escalation via notes API
......
...@@ -237,7 +237,6 @@ gem 'gon', '~> 6.0.1' ...@@ -237,7 +237,6 @@ gem 'gon', '~> 6.0.1'
gem 'jquery-atwho-rails', '~> 1.3.2' gem 'jquery-atwho-rails', '~> 1.3.2'
gem 'jquery-rails', '~> 4.1.0' gem 'jquery-rails', '~> 4.1.0'
gem 'jquery-ui-rails', '~> 5.0.0' gem 'jquery-ui-rails', '~> 5.0.0'
gem 'raphael-rails', '~> 2.1.2'
gem 'request_store', '~> 1.3.0' gem 'request_store', '~> 1.3.0'
gem 'select2-rails', '~> 3.5.9' gem 'select2-rails', '~> 3.5.9'
gem 'virtus', '~> 1.0.1' gem 'virtus', '~> 1.0.1'
......
...@@ -580,7 +580,6 @@ GEM ...@@ -580,7 +580,6 @@ GEM
rainbow (2.1.0) rainbow (2.1.0)
raindrops (0.15.0) raindrops (0.15.0)
rake (10.5.0) rake (10.5.0)
raphael-rails (2.1.2)
rb-fsevent (0.9.6) rb-fsevent (0.9.6)
rb-inotify (0.9.5) rb-inotify (0.9.5)
ffi (>= 0.5.0) ffi (>= 0.5.0)
...@@ -971,7 +970,6 @@ DEPENDENCIES ...@@ -971,7 +970,6 @@ DEPENDENCIES
rails (= 4.2.6) rails (= 4.2.6)
rails-deprecated_sanitizer (~> 1.0.3) rails-deprecated_sanitizer (~> 1.0.3)
rainbow (~> 2.1.0) rainbow (~> 2.1.0)
raphael-rails (~> 2.1.2)
rblineprof rblineprof
rdoc (~> 3.6) rdoc (~> 3.6)
recaptcha (~> 3.0) recaptcha (~> 3.0)
......
...@@ -42,10 +42,10 @@ class @LabelManager ...@@ -42,10 +42,10 @@ class @LabelManager
$from = @prioritizedLabels $from = @prioritizedLabels
if $from.find('li').length is 1 if $from.find('li').length is 1
$from.find('.empty-message').show() $from.find('.empty-message').removeClass('hidden')
if not $target.find('li').length if not $target.find('li').length
$target.find('.empty-message').hide() $target.find('.empty-message').addClass('hidden')
$label.detach().appendTo($target) $label.detach().appendTo($target)
...@@ -54,6 +54,9 @@ class @LabelManager ...@@ -54,6 +54,9 @@ class @LabelManager
if action is 'remove' if action is 'remove'
xhr = $.ajax url: url, type: 'DELETE' xhr = $.ajax url: url, type: 'DELETE'
# Restore empty message
$from.find('.empty-message').removeClass('hidden') unless $from.find('li').length
else else
xhr = @savePrioritySort($label, action) xhr = @savePrioritySort($label, action)
......
...@@ -33,10 +33,6 @@ ...@@ -33,10 +33,6 @@
#= require bootstrap/tooltip #= require bootstrap/tooltip
#= require bootstrap/popover #= require bootstrap/popover
#= require select2 #= require select2
#= require raphael
#= require g.raphael
#= require g.bar
#= require branch-graph
#= require ace/ace #= require ace/ace
#= require ace/ext-searchbox #= require ace/ext-searchbox
#= require underscore #= require underscore
...@@ -131,7 +127,7 @@ $ -> ...@@ -131,7 +127,7 @@ $ ->
gl.utils.preventDisabledButtons() gl.utils.preventDisabledButtons()
bootstrapBreakpoint = bp.getBreakpointSize() bootstrapBreakpoint = bp.getBreakpointSize()
$(".nicescroll").niceScroll(cursoropacitymax: '0.4', cursorcolor: '#FFF', cursorborder: "1px solid #FFF") $(".nav-sidebar").niceScroll(cursoropacitymax: '0.4', cursorcolor: '#FFF', cursorborder: "1px solid #FFF")
# Click a .js-select-on-focus field, select the contents # Click a .js-select-on-focus field, select the contents
$(".js-select-on-focus").on "focusin", -> $(".js-select-on-focus").on "focusin", ->
...@@ -262,3 +258,31 @@ $ -> ...@@ -262,3 +258,31 @@ $ ->
gl.awardsHandler = new AwardsHandler() gl.awardsHandler = new AwardsHandler()
checkInitialSidebarSize() checkInitialSidebarSize()
new Aside() new Aside()
# Sidenav pinning
if $(window).width() < 1440 and $.cookie('pin_nav') is 'true'
$.cookie('pin_nav', 'false')
$('.page-with-sidebar')
.toggleClass('page-sidebar-collapsed page-sidebar-expanded')
.removeClass('page-sidebar-pinned')
$('.navbar-fixed-top').removeClass('header-pinned-nav')
$(document)
.off 'click', '.js-nav-pin'
.on 'click', '.js-nav-pin', (e) ->
e.preventDefault()
$(this).toggleClass 'is-active'
if $.cookie('pin_nav') is 'true'
$.cookie 'pin_nav', 'false'
$('.page-with-sidebar')
.removeClass('page-sidebar-pinned')
.toggleClass('page-sidebar-collapsed page-sidebar-expanded')
$('.navbar-fixed-top')
.removeClass('header-pinned-nav')
.toggleClass('header-collapsed header-expanded')
else
$.cookie 'pin_nav', 'true'
$('.page-with-sidebar').addClass('page-sidebar-pinned')
$('.navbar-fixed-top').addClass('header-pinned-nav')
...@@ -40,7 +40,7 @@ class @AwardsHandler ...@@ -40,7 +40,7 @@ class @AwardsHandler
$menu = $ '.emoji-menu' $menu = $ '.emoji-menu'
if $addBtn.hasClass 'js-note-emoji' if $addBtn.hasClass 'js-note-emoji'
$addBtn.parents('.note').find('.js-awards-block').addClass 'current' $addBtn.closest('.note').find('.js-awards-block').addClass 'current'
else else
$addBtn.closest('.js-awards-block').addClass 'current' $addBtn.closest('.js-awards-block').addClass 'current'
......
...@@ -29,6 +29,7 @@ class Dispatcher ...@@ -29,6 +29,7 @@ class Dispatcher
new Todos() new Todos()
when 'projects:milestones:new', 'projects:milestones:edit' when 'projects:milestones:new', 'projects:milestones:edit'
new ZenMode() new ZenMode()
new DueDateSelect()
new GLForm($('.milestone-form')) new GLForm($('.milestone-form'))
when 'groups:milestones:new' when 'groups:milestones:new'
new ZenMode() new ZenMode()
...@@ -72,9 +73,7 @@ class Dispatcher ...@@ -72,9 +73,7 @@ class Dispatcher
new Diff() new Diff()
new ZenMode() new ZenMode()
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
when 'projects:commits:show' when 'projects:commits:show', 'projects:activity'
shortcut_handler = new ShortcutsNavigation()
when 'projects:activity'
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
when 'projects:show' when 'projects:show'
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
...@@ -100,6 +99,7 @@ class Dispatcher ...@@ -100,6 +99,7 @@ class Dispatcher
when 'projects:blob:show', 'projects:blame:show' when 'projects:blob:show', 'projects:blame:show'
new LineHighlighter() new LineHighlighter()
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
new ShortcutsBlob true
when 'projects:labels:new', 'projects:labels:edit' when 'projects:labels:new', 'projects:labels:edit'
new Labels() new Labels()
when 'projects:labels:index' when 'projects:labels:index'
...@@ -137,15 +137,11 @@ class Dispatcher ...@@ -137,15 +137,11 @@ class Dispatcher
new Project() new Project()
new ProjectAvatar() new ProjectAvatar()
switch path[1] switch path[1]
when 'compare'
shortcut_handler = new ShortcutsNavigation()
when 'edit' when 'edit'
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
new ProjectNew() new ProjectNew()
when 'new' when 'new', 'show'
new ProjectNew() new ProjectNew()
when 'show'
new ProjectShow()
when 'wikis' when 'wikis'
new Wikis() new Wikis()
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
...@@ -154,9 +150,9 @@ class Dispatcher ...@@ -154,9 +150,9 @@ class Dispatcher
when 'snippets' when 'snippets'
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
new ZenMode() if path[2] == 'show' new ZenMode() if path[2] == 'show'
when 'labels', 'graphs' when 'labels', 'graphs', 'compare', 'pipelines', 'forks', \
shortcut_handler = new ShortcutsNavigation() 'milestones', 'project_members', 'deploy_keys', 'builds', \
when 'project_members', 'deploy_keys', 'hooks', 'services', 'protected_branches' 'hooks', 'services', 'protected_branches'
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
# If we haven't installed a custom shortcut handler, install the default one # If we haven't installed a custom shortcut handler, install the default one
......
class @DueDateSelect class @DueDateSelect
constructor: -> constructor: ->
# Milestone edit/new form
$datePicker = $('.datepicker')
if $datePicker.length
$dueDate = $('#milestone_due_date')
$datePicker.datepicker
dateFormat: 'yy-mm-dd'
onSelect: (dateText, inst) ->
$dueDate.val(dateText)
.datepicker('setDate', $.datepicker.parseDate('yy-mm-dd', $dueDate.val()))
$('.js-clear-due-date').on 'click', (e) ->
e.preventDefault()
$.datepicker._clearDate($datePicker)
# Issuable sidebar
$loading = $('.js-issuable-update .due_date') $loading = $('.js-issuable-update .due_date')
.find('.block-loading') .find('.block-loading')
.hide() .hide()
...@@ -32,7 +48,7 @@ class @DueDateSelect ...@@ -32,7 +48,7 @@ class @DueDateSelect
date = new Date value.replace(new RegExp('-', 'g'), ',') date = new Date value.replace(new RegExp('-', 'g'), ',')
mediumDate = $.datepicker.formatDate 'M d, yy', date mediumDate = $.datepicker.formatDate 'M d, yy', date
else else
mediumDate = 'None' mediumDate = 'No due date'
data = {} data = {}
data[abilityName] = {} data[abilityName] = {}
...@@ -50,7 +66,8 @@ class @DueDateSelect ...@@ -50,7 +66,8 @@ class @DueDateSelect
$selectbox.hide() $selectbox.hide()
$value.css('display', '') $value.css('display', '')
$valueContent.html(mediumDate) cssClass = if Date.parse(mediumDate) then 'bold' else 'no-value'
$valueContent.html("<span class='#{cssClass}'>#{mediumDate}</span>")
$sidebarValue.html(mediumDate) $sidebarValue.html(mediumDate)
if value isnt '' if value isnt ''
......
...@@ -39,7 +39,7 @@ class @LabelsSelect ...@@ -39,7 +39,7 @@ class @LabelsSelect
</a> </a>
<% }); %>' <% }); %>'
) )
labelNoneHTMLTemplate = _.template('<div class="light">None</div>') labelNoneHTMLTemplate = '<span class="no-value">None</span>'
if newLabelField.length if newLabelField.length
...@@ -145,7 +145,7 @@ class @LabelsSelect ...@@ -145,7 +145,7 @@ class @LabelsSelect
template = labelHTMLTemplate(data) template = labelHTMLTemplate(data)
labelCount = data.labels.length labelCount = data.labels.length
else else
template = labelNoneHTMLTemplate() template = labelNoneHTMLTemplate
$value $value
.removeAttr('style') .removeAttr('style')
.html(template) .html(template)
......
((w) -> ((w) ->
window.gl or= {} w.gl or= {}
window.gl.utils or= {} w.gl.utils or= {}
jQuery.timefor = (time, suffix, expiredLabel) -> w.gl.utils.isInGroupsPage = ->
return '' unless time return $('body').data('page').split(':')[0] is 'groups'
suffix or= 'remaining'
expiredLabel or= 'Past due'
jQuery.timeago.settings.allowFuture = yes w.gl.utils.isInProjectPage = ->
{ suffixFromNow } = jQuery.timeago.settings.strings return $('body').data('page').split(':')[0] is 'projects'
jQuery.timeago.settings.strings.suffixFromNow = suffix
timefor = $.timeago time
if timefor.indexOf('ago') > -1 w.gl.utils.getProjectSlug = ->
timefor = expiredLabel
jQuery.timeago.settings.strings.suffixFromNow = suffixFromNow return if @isInProjectPage() then $('body').data 'project' else null
w.gl.utils.getGroupSlug = ->
return if @isInGroupsPage() then $('body').data 'group' else null
return timefor
gl.utils.updateTooltipTitle = ($tooltipEl, newTitle) -> gl.utils.updateTooltipTitle = ($tooltipEl, newTitle) ->
...@@ -32,6 +31,7 @@ ...@@ -32,6 +31,7 @@
.attr 'title', newTitle .attr 'title', newTitle
.tooltip 'fixTitle' .tooltip 'fixTitle'
gl.utils.preventDisabledButtons = -> gl.utils.preventDisabledButtons = ->
$('.btn').click (e) -> $('.btn').click (e) ->
...@@ -40,4 +40,26 @@ ...@@ -40,4 +40,26 @@
e.stopImmediatePropagation() e.stopImmediatePropagation()
return false return false
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 ) window
...@@ -24,14 +24,10 @@ class @MilestoneSelect ...@@ -24,14 +24,10 @@ class @MilestoneSelect
if issueUpdateURL if issueUpdateURL
milestoneLinkTemplate = _.template( milestoneLinkTemplate = _.template(
'<a href="/<%= namespace %>/<%= path %>/milestones/<%= iid %>"> '<a href="/<%= namespace %>/<%= path %>/milestones/<%= iid %>" class="bold has-tooltip" data-container="body" title="<%= remaining %>"><%= _.escape(title) %></a>'
<span class="has-tooltip" data-container="body" title="<%= remaining %>">
<%= _.escape(title) %>
</span>
</a>'
) )
milestoneLinkNoneTemplate = '<div class="light">None</div>' milestoneLinkNoneTemplate = '<span class="no-value">None</span>'
collapsedSidebarLabelTemplate = _.template( collapsedSidebarLabelTemplate = _.template(
'<span class="has-tooltip" data-container="body" title="<%= remaining %>" data-placement="left"> '<span class="has-tooltip" data-container="body" title="<%= remaining %>" data-placement="left">
......
# This is a manifest file that'll be compiled into including all the files listed below.
# Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
# be included in the compiled file accessible from http://example.com/assets/application.js
# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
# the compiled file.
#
#= require raphael
#= require g.raphael
#= require g.bar
#= require_tree .
$ ->
network_graph = new Network({
url: $(".network-graph").attr('data-url'),
commit_url: $(".network-graph").attr('data-commit-url'),
ref: $(".network-graph").attr('data-ref'),
commit_id: $(".network-graph").attr('data-commit-id')
})
new ShortcutsNetwork(network_graph.branch_graph)
...@@ -67,8 +67,12 @@ class @SearchAutocomplete ...@@ -67,8 +67,12 @@ class @SearchAutocomplete
getData: (term, callback) -> getData: (term, callback) ->
_this = @ _this = @
# Do not trigger request if input is empty unless term
return if @searchInput.val() is '' if contents = @getCategoryContents()
@searchInput.data('glDropdown').filter.options.callback contents
@enableAutocomplete()
return
# Prevent multiple ajax calls # Prevent multiple ajax calls
return if @loadingSuggestions return if @loadingSuggestions
...@@ -122,6 +126,37 @@ class @SearchAutocomplete ...@@ -122,6 +126,37 @@ class @SearchAutocomplete
).always -> ).always ->
_this.loadingSuggestions = false _this.loadingSuggestions = false
getCategoryContents: ->
userId = gon.current_user_id
{ utils, projectOptions, groupOptions, dashboardOptions } = gl
if utils.isInGroupsPage() and groupOptions
options = groupOptions[utils.getGroupSlug()]
else if utils.isInProjectPage() and projectOptions
options = projectOptions[utils.getProjectSlug()]
else if dashboardOptions
options = dashboardOptions
{ issuesPath, mrPath, name } = options
items = [
{ header: "#{name}" }
{ text: 'Issues assigned to me', url: "#{issuesPath}/?assignee_id=#{userId}" }
{ text: "Issues I've created", url: "#{issuesPath}/?author_id=#{userId}" }
'separator'
{ text: 'Merge requests assigned to me', url: "#{mrPath}/?assignee_id=#{userId}" }
{ text: "Merge requests I've created", url: "#{mrPath}/?author_id=#{userId}" }
]
items.splice 0, 1 unless name
return items
serializeState: -> serializeState: ->
{ {
# Search Criteria # Search Criteria
...@@ -209,6 +244,12 @@ class @SearchAutocomplete ...@@ -209,6 +244,12 @@ class @SearchAutocomplete
@isFocused = true @isFocused = true
@wrap.addClass('search-active') @wrap.addClass('search-active')
@getData() if @getValue() is ''
getValue: -> return @searchInput.val()
onClearInputClick: (e) => onClearInputClick: (e) =>
e.preventDefault() e.preventDefault()
@searchInput.val('').focus() @searchInput.val('').focus()
...@@ -229,6 +270,10 @@ class @SearchAutocomplete ...@@ -229,6 +270,10 @@ class @SearchAutocomplete
@locationBadgeEl.text(badgeText).show() @locationBadgeEl.text(badgeText).show()
@wrap.addClass('has-location-badge') @wrap.addClass('has-location-badge')
hasLocationBadge: -> return @wrap.is '.has-location-badge'
restoreOriginalState: -> restoreOriginalState: ->
inputs = Object.keys @originalState inputs = Object.keys @originalState
...@@ -257,13 +302,14 @@ class @SearchAutocomplete ...@@ -257,13 +302,14 @@ class @SearchAutocomplete
@getElement("##{input}").val('') @getElement("##{input}").val('')
removeLocationBadge: -> removeLocationBadge: ->
@locationBadgeEl.hide()
# Reset state @locationBadgeEl.hide()
@resetSearchState() @resetSearchState()
@wrap.removeClass('has-location-badge') @wrap.removeClass('has-location-badge')
@disableAutocomplete()
disableAutocomplete: -> disableAutocomplete: ->
@searchInput.addClass('disabled') @searchInput.addClass('disabled')
......
class @Shortcuts class @Shortcuts
constructor: -> constructor: (skipResetBindings) ->
@enabledHelp = [] @enabledHelp = []
Mousetrap.reset() Mousetrap.reset() if not skipResetBindings
Mousetrap.bind('?', @onToggleHelp) Mousetrap.bind('?', @onToggleHelp)
Mousetrap.bind('s', Shortcuts.focusSearch) Mousetrap.bind('s', Shortcuts.focusSearch)
Mousetrap.bind(['ctrl+shift+p', 'command+shift+p'], @toggleMarkdownPreview) Mousetrap.bind(['ctrl+shift+p', 'command+shift+p'], @toggleMarkdownPreview)
......
#= require shortcuts
class @ShortcutsBlob extends Shortcuts
constructor: (skipResetBindings) ->
super skipResetBindings
Mousetrap.bind('y', ShortcutsBlob.copyToClipboard)
@copyToClipboard: ->
clipboardButton = $('.btn-clipboard')
clipboardButton.click() if clipboardButton
...@@ -3,13 +3,33 @@ expanded = 'page-sidebar-expanded' ...@@ -3,13 +3,33 @@ expanded = 'page-sidebar-expanded'
toggleSidebar = -> toggleSidebar = ->
$('.page-with-sidebar').toggleClass("#{collapsed} #{expanded}") $('.page-with-sidebar').toggleClass("#{collapsed} #{expanded}")
$('header').toggleClass("header-collapsed header-expanded") $('.navbar-fixed-top').toggleClass("header-collapsed header-expanded")
if $.cookie('pin_nav') is 'true'
$('.navbar-fixed-top').toggleClass('header-pinned-nav')
$('.page-with-sidebar').toggleClass('page-sidebar-pinned')
setTimeout ( -> setTimeout ( ->
niceScrollBars = $('.nicescroll').niceScroll(); niceScrollBars = $('.nav-sidebar').niceScroll();
niceScrollBars.updateScrollBar(); niceScrollBars.updateScrollBar();
), 300 ), 300
$(document)
.off 'click', 'body'
.on 'click', 'body', (e) ->
unless $.cookie('pin_nav') is 'true'
$target = $(e.target)
$nav = $target.closest('.sidebar-wrapper')
pageExpanded = $('.page-with-sidebar').hasClass('page-sidebar-expanded')
$toggle = $target.closest('.toggle-nav-collapse, .side-nav-toggle')
if $nav.length is 0 and pageExpanded and $toggle.length is 0
$('.page-with-sidebar')
.toggleClass('page-sidebar-collapsed page-sidebar-expanded')
$('.navbar-fixed-top')
.toggleClass('header-collapsed header-expanded')
$(document).on("click", '.toggle-nav-collapse, .side-nav-toggle', (e) -> $(document).on("click", '.toggle-nav-collapse, .side-nav-toggle', (e) ->
e.preventDefault() e.preventDefault()
......
...@@ -72,7 +72,7 @@ class @UsersSelect ...@@ -72,7 +72,7 @@ class @UsersSelect
assigneeTemplate = _.template( assigneeTemplate = _.template(
'<% if (username) { %> '<% if (username) { %>
<a class="author_link " href="/u/<%= username %>"> <a class="author_link bold" href="/u/<%= username %>">
<% if( avatar ) { %> <% if( avatar ) { %>
<img width="32" class="avatar avatar-inline s32" alt="" src="<%= avatar %>"> <img width="32" class="avatar avatar-inline s32" alt="" src="<%= avatar %>">
<% } %> <% } %>
...@@ -82,7 +82,7 @@ class @UsersSelect ...@@ -82,7 +82,7 @@ class @UsersSelect
</span> </span>
</a> </a>
<% } else { %> <% } else { %>
<span class="assign-yourself"> <span class="no-value assign-yourself">
No assignee - No assignee -
<a href="#" class="js-assign-yourself"> <a href="#" class="js-assign-yourself">
assign yourself assign yourself
......
...@@ -8,8 +8,8 @@ ...@@ -8,8 +8,8 @@
*/ */
@mixin gitlab-theme($color-light, $color, $color-darker, $color-dark) { @mixin gitlab-theme($color-light, $color, $color-darker, $color-dark) {
.page-with-sidebar { .page-with-sidebar {
.toggle-nav-collapse,
.collapse-nav a { .pin-nav-btn {
color: $color-light; color: $color-light;
background: $color; background: $color;
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
* *
*/ */
header { header {
transition-duration: .3s; transition: padding $sidebar-transition-duration;
&.navbar-empty { &.navbar-empty {
height: $header-height; height: $header-height;
...@@ -79,14 +79,9 @@ header { ...@@ -79,14 +79,9 @@ header {
&.header-collapsed { &.header-collapsed {
padding: 0 16px; padding: 0 16px;
.side-nav-toggle {
display: block;
}
} }
.side-nav-toggle { .side-nav-toggle {
display: none;
position: absolute; position: absolute;
left: -10px; left: -10px;
margin: 6px 0; margin: 6px 0;
...@@ -108,9 +103,7 @@ header { ...@@ -108,9 +103,7 @@ header {
.header-content { .header-content {
position: relative; position: relative;
height: $header-height; height: $header-height;
padding-right: 40px;
padding-left: 30px; padding-left: 30px;
transition-duration: .3s;
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
padding-right: 0; padding-right: 0;
...@@ -198,18 +191,6 @@ header { ...@@ -198,18 +191,6 @@ header {
} }
} }
.header-collapsed {
margin-left: 0;
.header-content {
@media (min-width: $screen-sm-max) {
padding-left: 30px;
transition-duration: .3s;
}
}
}
.tanuki-shape { .tanuki-shape {
transition: all 0.8s; transition: all 0.8s;
......
...@@ -159,7 +159,7 @@ ul.content-list { ...@@ -159,7 +159,7 @@ ul.content-list {
background-color: $gray-light; background-color: $gray-light;
border: dotted 1px $gray-dark; border: dotted 1px $gray-dark;
margin: 1px 0; margin: 1px 0;
min-height: 30px; min-height: 52px;
} }
} }
} }
......
...@@ -242,6 +242,12 @@ ...@@ -242,6 +242,12 @@
} }
} }
} }
&.adjust {
.nav-text, .nav-controls {
width: auto;
}
}
} }
.layout-nav { .layout-nav {
...@@ -251,7 +257,7 @@ ...@@ -251,7 +257,7 @@
z-index: 11; z-index: 11;
background: $background-color; background: $background-color;
border-bottom: 1px solid $border-color; border-bottom: 1px solid $border-color;
transition-duration: .3s; transition: padding $sidebar-transition-duration;
text-align: center; text-align: center;
.container-fluid { .container-fluid {
...@@ -347,6 +353,12 @@ ...@@ -347,6 +353,12 @@
.badge { .badge {
color: $gl-icon-color; color: $gl-icon-color;
} }
&:hover {
a, i {
color: $black;
}
}
} }
} }
......
.page-with-sidebar { .page-with-sidebar {
padding-top: $header-height; padding-top: $header-height;
transition-duration: .3s; transition: padding $sidebar-transition-duration;
.sidebar-wrapper { .sidebar-wrapper {
position: fixed; position: fixed;
top: 0; top: 0;
bottom: 0; bottom: 0;
overflow-y: auto;
overflow-x: hidden;
left: 0; left: 0;
height: 100%; height: 100%;
transition-duration: .3s; overflow: hidden;
transition: width $sidebar-transition-duration;
} }
} }
.sidebar-wrapper { .sidebar-wrapper {
z-index: 1000; z-index: 1000;
background: $background-color; background: $background-color;
.nicescroll-rails-hr {
// TODO: Figure out why nicescroll doesn't hide horizontal bar
display: none!important;
}
} }
.content-wrapper { .content-wrapper {
width: 100%; width: 100%;
transition: padding $sidebar-transition-duration;
.container-fluid { .container-fluid {
background: #fff; background: #fff;
...@@ -34,22 +39,19 @@ ...@@ -34,22 +39,19 @@
} }
} }
.sidebar-wrapper { .sidebar-user {
padding: 15px;
.sidebar-user { position: absolute;
padding: 15px 22px; left: 0;
position: fixed; bottom: 0;
bottom: 0; width: $sidebar_width;
width: $sidebar_width; overflow: hidden;
overflow: hidden; font-size: 16px;
transition-duration: .3s; line-height: 36px;
transition: width $sidebar-transition-duration, padding $sidebar-transition-duration;
.username { @media (min-width: $sidebar-breakpoint) {
margin-left: 10px; bottom: 50px;
width: $sidebar_width - 2 * 10px;
font-size: 16px;
line-height: 34px;
}
} }
} }
...@@ -65,19 +67,22 @@ ...@@ -65,19 +67,22 @@
.nav-sidebar { .nav-sidebar {
margin-top: 22 + $header-height; position: absolute;
margin-bottom: 116px; top: 50px;
transition-duration: .3s; bottom: 65px;
list-style: none; width: $sidebar_width;
overflow: hidden; overflow-y: auto;
overflow-x: hidden;
@media (min-width: $sidebar-breakpoint) {
bottom: 115px;
}
&.navbar-collapse { &.navbar-collapse {
padding: 0 !important; padding: 0 !important;
} }
li { li {
width: $sidebar_width;
&.separate-item { &.separate-item {
padding-top: 10px; padding-top: 10px;
margin-top: 10px; margin-top: 10px;
...@@ -90,20 +95,18 @@ ...@@ -90,20 +95,18 @@
} }
a { a {
width: $sidebar_width; padding: 7px 15px 7px 12px;
padding: 7px 15px 7px 23px;
font-size: $gl-font-size; font-size: $gl-font-size;
line-height: 24px; line-height: 24px;
display: block; display: block;
text-decoration: none; text-decoration: none;
font-weight: normal; font-weight: normal;
outline: none; outline: none;
white-space: nowrap;
&:hover { &:hover,
text-decoration: none; &:active,
} &:focus {
&:active, &:focus {
text-decoration: none; text-decoration: none;
} }
...@@ -115,10 +118,6 @@ ...@@ -115,10 +118,6 @@
svg { svg {
margin-right: 13px; margin-right: 13px;
} }
&.back-link i {
transition-duration: .3s;
}
} }
} }
...@@ -129,37 +128,50 @@ ...@@ -129,37 +128,50 @@
} }
} }
.sidebar-subnav { .toggle-nav-collapse {
margin-left: 0;
padding-left: 0;
li {
list-style: none;
}
}
.collapse-nav a {
width: $sidebar_width; width: $sidebar_width;
position: fixed; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
min-height: 50px;
padding: 5px 0; padding: 5px 0;
font-size: 18px; font-size: 18px;
background: transparent; line-height: 30px;
height: 50px; }
text-align: center;
line-height: 40px; .nav-header-btn {
padding: 10px 5px;
color: inherit;
transition-duration: .3s; transition-duration: .3s;
outline: none;
&:hover { &:hover,
&:focus {
color: $white-light;
text-decoration: none; text-decoration: none;
} }
} }
.sidebar-wrapper { .pin-nav-btn {
&.hidden-nav { display: none;
width: 0; position: absolute;
left: 0;
bottom: 0;
height: 50px;
width: $sidebar_width;
line-height: 30px;
@media (min-width: $sidebar-breakpoint) {
display: block;
}
.fa {
transition: transform .15s;
}
&.is-active {
.fa {
transform: rotate(90deg);
}
} }
} }
...@@ -168,62 +180,34 @@ ...@@ -168,62 +180,34 @@
.sidebar-wrapper { .sidebar-wrapper {
width: 0; width: 0;
.nav-sidebar {
width: 0;
li {
width: auto;
a {
span {
display: none;
}
}
}
}
.collapse-nav a {
width: 0;
i {
display: none;
}
}
.sidebar-user {
width: 0;
padding-left: 0;
padding-right: 0;
.username {
display: none;
}
}
} }
} }
.page-sidebar-expanded { .page-sidebar-expanded {
@media (max-width: $screen-sm-max) {
padding-left: 0;
}
.sidebar-wrapper { .sidebar-wrapper {
width: $sidebar_width; width: $sidebar_width;
}
}
.nav-sidebar { .page-sidebar-pinned {
width: $sidebar_width; .content-wrapper,
.layout-nav {
@media (min-width: $sidebar-breakpoint) {
padding-left: $sidebar_width;
} }
}
}
.nav-sidebar li a { header.header-pinned-nav {
width: $sidebar_width; @media (min-width: $sidebar-breakpoint) {
padding-left: ($sidebar_width + $gl-padding);
&.back-link { .side-nav-toggle {
i { display: none;
opacity: 0; }
}
} .header-content {
padding-left: 0;
} }
} }
} }
......
...@@ -6,6 +6,8 @@ $sidebar_width: 220px; ...@@ -6,6 +6,8 @@ $sidebar_width: 220px;
$gutter_collapsed_width: 62px; $gutter_collapsed_width: 62px;
$gutter_width: 290px; $gutter_width: 290px;
$gutter_inner_width: 258px; $gutter_inner_width: 258px;
$sidebar-transition-duration: .15s;
$sidebar-breakpoint: 1440px;
/* /*
* UI elements * UI elements
......
...@@ -7,84 +7,111 @@ ...@@ -7,84 +7,111 @@
margin-right: 9px; margin-right: 9px;
} }
.lists-separator { .commit-header {
margin: 10px 0; padding: 5px 10px;
border-color: #ddd; background-color: $background-color;
border-top: 1px solid #eee;
border-bottom: 1px solid #eee;
font-size: 14px;
&:first-child {
border-top-width: 0;
}
} }
.commits-row { .commit-row-title {
ul { line-height: 1;
margin: 0; margin-bottom: 7px;
li.commit { .notes_count {
padding: 8px 0; float: right;
} margin-right: 10px;
}
.str-truncated {
max-width: 70%;
} }
.commits-row-date { .commit-row-message {
font-size: 15px; color: $gl-dark-link-color;
line-height: 20px; }
margin-bottom: 5px;
.text-expander {
display: inline-block;
background: $gray-light;
color: $gl-placeholder-color;
padding: 0 5px;
cursor: pointer;
border: 1px solid $border-gray-dark;
border-radius: $border-radius-default;
margin-left: 5px;
&:hover {
background-color: darken($gray-light, 10%);
text-decoration: none;
}
} }
} }
li.commit { .commit-actions {
list-style: none; @media (min-width: $screen-sm-min) {
float: right;
margin-left: $gl-padding;
margin-top: 2px;
font-size: 0;
}
.commit-row-title { .btn-transparent {
font-size: $list-font-size; padding-left: 0;
line-height: 20px; padding-right: 0;
margin-bottom: 2px; }
.btn-clipboard { .btn {
margin-top: -1px; &:not(:first-child) {
margin-left: $gl-padding;
} }
}
}
.notes_count { .commit-short-id {
float: right; font-family: $monospace_font;
margin-right: 10px; font-weight: 600;
} }
.commit_short_id { .commit {
min-width: 65px; padding: 10px 0;
color: $gl-dark-link-color;
font-family: $monospace_font;
}
.str-truncated { @media (min-width: $screen-sm-min) {
max-width: 70%; padding-left: 46px;
} }
.commit-row-message { &:not(:last-child) {
color: $gl-dark-link-color; border-bottom: 1px solid #eee;
}
&:hover { a,
text-decoration: underline; button {
} color: $gl-dark-link-color;
} vertical-align: baseline;
}
.text-expander { .avatar {
background: #eee; margin-left: -46px;
color: #555;
padding: 0 5px;
cursor: pointer;
margin-left: 4px;
&:hover {
background-color: #ddd;
}
}
} }
.item-title { .item-title {
display: inline-block; display: inline-block;
max-width: 70%;
@media (min-width: $screen-sm-min) {
max-width: 70%;
}
} }
.commit-row-description { .commit-row-description {
font-size: 14px; font-size: 14px;
border-left: 1px solid #eee; border-left: 1px solid #eee;
padding: 10px 15px; padding: 10px 15px;
margin: 5px 0 10px 5px; margin: 10px 0;
background: #f9f9f9; background: #f9f9f9;
display: none; display: none;
...@@ -93,6 +120,7 @@ li.commit { ...@@ -93,6 +120,7 @@ li.commit {
background: inherit; background: inherit;
padding: 0; padding: 0;
margin: 0; margin: 0;
white-space: pre-wrap;
} }
a { a {
...@@ -102,7 +130,7 @@ li.commit { ...@@ -102,7 +130,7 @@ li.commit {
.commit-row-info { .commit-row-info {
color: $gl-gray; color: $gl-gray;
line-height: 24px; line-height: 1;
a { a {
color: $gl-gray; color: $gl-gray;
...@@ -111,10 +139,6 @@ li.commit { ...@@ -111,10 +139,6 @@ li.commit {
.avatar { .avatar {
margin-right: 8px; margin-right: 8px;
} }
.committed_ago {
display: inline-block;
}
} }
&.inline-commit { &.inline-commit {
......
...@@ -145,7 +145,6 @@ ...@@ -145,7 +145,6 @@
.assign-yourself { .assign-yourself {
margin-top: 10px; margin-top: 10px;
font-weight: normal;
display: block; display: block;
} }
} }
...@@ -158,6 +157,10 @@ ...@@ -158,6 +157,10 @@
font-weight: normal; font-weight: normal;
} }
.no-value {
color: $gl-placeholder-color;
}
.sidebar-collapsed-icon { .sidebar-collapsed-icon {
display: none; display: none;
} }
...@@ -248,11 +251,16 @@ ...@@ -248,11 +251,16 @@
padding-bottom: 0; padding-bottom: 0;
margin-bottom: 10px; margin-bottom: 10px;
} }
.issuable-header-btn {
display: none;
}
} }
.issuable-header-btn { .issuable-header-btn {
background: $gray-normal; background: $gray-normal;
border: 1px solid $border-gray-normal; border: 1px solid $border-gray-normal;
&:hover { &:hover {
background: $gray-dark; background: $gray-dark;
border: 1px solid $border-gray-dark; border: 1px solid $border-gray-dark;
...@@ -322,7 +330,7 @@ ...@@ -322,7 +330,7 @@
margin-left: 5px; margin-left: 5px;
a { a {
color: #8c8c8c; color: $gl-placeholder-color;
} }
} }
......
...@@ -115,6 +115,13 @@ ...@@ -115,6 +115,13 @@
} }
} }
.draggable-handler {
display: inline-block;
opacity: 0;
transition: opacity .3s;
color: $gray-darkest;
}
.prioritized-labels { .prioritized-labels {
margin-bottom: 30px; margin-bottom: 30px;
...@@ -122,6 +129,13 @@ ...@@ -122,6 +129,13 @@
display: none; display: none;
color: $gray-light; color: $gray-light;
} }
li:hover {
.draggable-handler {
display: inline-block;
opacity: 1;
}
}
} }
.other-labels { .other-labels {
......
...@@ -139,6 +139,12 @@ ul.notes { ...@@ -139,6 +139,12 @@ ul.notes {
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
padding-right: 0; padding-right: 0;
} }
@media (max-width: $screen-xs-min) {
.inline {
display: block;
}
}
} }
.note-emoji-button { .note-emoji-button {
...@@ -258,7 +264,11 @@ ul.notes { ...@@ -258,7 +264,11 @@ ul.notes {
position: absolute; position: absolute;
right: 0; right: 0;
top: 0; top: 0;
.note-action-button {
margin-left: 10px;
}
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
position: relative; position: relative;
} }
......
...@@ -5,10 +5,12 @@ ...@@ -5,10 +5,12 @@
font-weight: normal; font-weight: normal;
} }
} }
.no-ssh-key-message, .project-limit-message { .no-ssh-key-message, .project-limit-message {
background-color: #f28d35; background-color: #f28d35;
margin-bottom: 0; margin-bottom: 0;
} }
.new_project, .new_project,
.edit-project { .edit-project {
fieldset.features { fieldset.features {
...@@ -18,13 +20,6 @@ ...@@ -18,13 +20,6 @@
} }
} }
.project-name-holder {
.help-inline {
vertical-align: top;
padding: 7px;
}
}
.project-home-panel { .project-home-panel {
background: $white-light; background: $white-light;
text-align: left; text-align: left;
...@@ -376,6 +371,7 @@ a.deploy-project-label { ...@@ -376,6 +371,7 @@ a.deploy-project-label {
.project-import .btn { .project-import .btn {
float: left; float: left;
margin-bottom: 10px;
margin-right: 10px; margin-right: 10px;
} }
......
...@@ -101,7 +101,7 @@ ...@@ -101,7 +101,7 @@
margin: 0; margin: 0;
.commit { .commit {
padding: 0; padding: 0 0 0 55px;
.commit-row-title { .commit-row-title {
.commit-row-message { .commit-row-message {
...@@ -129,4 +129,6 @@ ...@@ -129,4 +129,6 @@
.tree-controls { .tree-controls {
float: right; float: right;
margin-top: 11px; margin-top: 11px;
position: relative;
z-index: 2;
} }
...@@ -217,10 +217,19 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -217,10 +217,19 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_request.update(merge_error: nil) @merge_request.update(merge_error: nil)
if params[:merge_when_build_succeeds].present? && @merge_request.pipeline && @merge_request.pipeline.active? if params[:merge_when_build_succeeds].present?
MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user, merge_params) if @merge_request.pipeline && @merge_request.pipeline.active?
.execute(@merge_request) MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user, merge_params)
@status = :merge_when_build_succeeds .execute(@merge_request)
@status = :merge_when_build_succeeds
elsif @merge_request.pipeline.success?
# This can be triggered when a user clicks the auto merge button while
# the tests finish at about the same time
MergeWorker.perform_async(@merge_request.id, current_user.id, params)
@status = :success
else
@status = :failed
end
else else
MergeWorker.perform_async(@merge_request.id, current_user.id, params) MergeWorker.perform_async(@merge_request.id, current_user.id, params)
@status = :success @status = :success
......
...@@ -17,7 +17,15 @@ module ButtonHelper ...@@ -17,7 +17,15 @@ module ButtonHelper
def clipboard_button(data = {}) def clipboard_button(data = {})
content_tag :button, content_tag :button,
icon('clipboard'), icon('clipboard'),
class: 'btn btn-clipboard', class: "btn",
data: data,
type: :button
end
def clipboard_button_with_class(data = {}, css_class: 'btn-clipboard')
content_tag :button,
icon('clipboard'),
class: "btn #{css_class}",
data: data, data: data,
type: :button type: :button
end end
......
...@@ -38,10 +38,10 @@ module CiStatusHelper ...@@ -38,10 +38,10 @@ module CiStatusHelper
icon(icon_name + ' fw') icon(icon_name + ' fw')
end end
def render_commit_status(commit, tooltip_placement: 'auto left') def render_commit_status(commit, tooltip_placement: 'auto left', cssclass: '')
project = commit.project project = commit.project
path = builds_namespace_project_commit_path(project.namespace, project, commit) path = builds_namespace_project_commit_path(project.namespace, project, commit)
render_status_with_link('commit', commit.status, path, tooltip_placement) render_status_with_link('commit', commit.status, path, tooltip_placement, cssclass: cssclass)
end end
def render_pipeline_status(pipeline, tooltip_placement: 'auto left') def render_pipeline_status(pipeline, tooltip_placement: 'auto left')
...@@ -57,10 +57,10 @@ module CiStatusHelper ...@@ -57,10 +57,10 @@ module CiStatusHelper
private private
def render_status_with_link(type, status, path, tooltip_placement) def render_status_with_link(type, status, path, tooltip_placement, cssclass: '')
link_to ci_icon_for_status(status), link_to ci_icon_for_status(status),
path, path,
class: "ci-status-link ci-status-icon-#{status.dasherize}", class: "ci-status-link ci-status-icon-#{status.dasherize} #{cssclass}",
title: "#{type.titleize}: #{ci_label_for_status(status)}", title: "#{type.titleize}: #{ci_label_for_status(status)}",
data: { toggle: 'tooltip', placement: tooltip_placement } data: { toggle: 'tooltip', placement: tooltip_placement }
end end
......
...@@ -16,6 +16,16 @@ module CommitsHelper ...@@ -16,6 +16,16 @@ module CommitsHelper
commit_person_link(commit, options.merge(source: :committer)) commit_person_link(commit, options.merge(source: :committer))
end end
def commit_author_avatar(commit, options = {})
options = options.merge(source: :author)
user = commit.send(options[:source])
source_email = clean(commit.send "#{options[:source]}_email".to_sym)
person_email = user.try(:email) || source_email
image_tag(avatar_icon(person_email, options[:size]), class: "avatar #{"s#{options[:size]}" if options[:size]} hidden-xs", width: options[:size], alt: "")
end
def image_diff_class(diff) def image_diff_class(diff)
if diff.deleted_file if diff.deleted_file
"deleted" "deleted"
...@@ -102,24 +112,24 @@ module CommitsHelper ...@@ -102,24 +112,24 @@ module CommitsHelper
if current_controller?(:projects, :commits) if current_controller?(:projects, :commits)
if @repo.blob_at(commit.id, @path) if @repo.blob_at(commit.id, @path)
return link_to( return link_to(
"Browse File »", "Browse File",
namespace_project_blob_path(project.namespace, project, namespace_project_blob_path(project.namespace, project,
tree_join(commit.id, @path)), tree_join(commit.id, @path)),
class: "pull-right" class: "btn btn-default"
) )
elsif @path.present? elsif @path.present?
return link_to( return link_to(
"Browse Directory »", "Browse Directory",
namespace_project_tree_path(project.namespace, project, namespace_project_tree_path(project.namespace, project,
tree_join(commit.id, @path)), tree_join(commit.id, @path)),
class: "pull-right" class: "btn btn-default"
) )
end end
end end
link_to( link_to(
"Browse Files", "Browse Files",
namespace_project_tree_path(project.namespace, project, commit), namespace_project_tree_path(project.namespace, project, commit),
class: "pull-right" class: "btn btn-default"
) )
end end
...@@ -187,12 +197,10 @@ module CommitsHelper ...@@ -187,12 +197,10 @@ module CommitsHelper
source_email = clean(commit.send "#{options[:source]}_email".to_sym) source_email = clean(commit.send "#{options[:source]}_email".to_sym)
person_name = user.try(:name) || source_name person_name = user.try(:name) || source_name
person_email = user.try(:email) || source_email
text = text =
if options[:avatar] if options[:avatar]
avatar = image_tag(avatar_icon(person_email, options[:size]), class: "avatar #{"s#{options[:size]}" if options[:size]}", width: options[:size], alt: "") %Q{<span class="commit-#{options[:source]}-name">#{person_name}</span>}
%Q{#{avatar} <span class="commit-#{options[:source]}-name">#{person_name}</span>}
else else
person_name person_name
end end
......
...@@ -135,6 +135,11 @@ module DiffHelper ...@@ -135,6 +135,11 @@ module DiffHelper
toggle_whitespace_link(url, options) toggle_whitespace_link(url, options)
end end
def diff_compare_whitespace_link(project, from, to, options)
url = namespace_project_compare_path(project.namespace, project, from, to, params_with_whitespace)
toggle_whitespace_link(url, options)
end
private private
def hide_whitespace? def hide_whitespace?
......
...@@ -12,10 +12,10 @@ module NavHelper ...@@ -12,10 +12,10 @@ module NavHelper
end end
def page_sidebar_class def page_sidebar_class
if nav_menu_collapsed? if pinned_nav?
"page-sidebar-collapsed" "page-sidebar-expanded page-sidebar-pinned"
else else
"page-sidebar-expanded" "page-sidebar-collapsed"
end end
end end
...@@ -36,7 +36,15 @@ module NavHelper ...@@ -36,7 +36,15 @@ module NavHelper
end end
def nav_header_class def nav_header_class
class_name = " with-horizontal-nav" if defined?(nav) && nav class_name = ''
class_name << " with-horizontal-nav" if defined?(nav) && nav
if pinned_nav?
class_name << " header-expanded header-pinned-nav"
else
class_name << " header-collapsed"
end
class_name class_name
end end
...@@ -47,4 +55,8 @@ module NavHelper ...@@ -47,4 +55,8 @@ module NavHelper
def nav_control_class def nav_control_class
"nav-control" if current_user "nav-control" if current_user
end end
def pinned_nav?
cookies[:pin_nav] == 'true'
end
end end
...@@ -41,7 +41,7 @@ module ProjectsHelper ...@@ -41,7 +41,7 @@ module ProjectsHelper
author_html = author_html.html_safe author_html = author_html.html_safe
if opts[:name] if opts[:name]
link_to(author_html, user_path(author), class: "author_link #{"#{opts[:mobile_classes]}" if opts[:mobile_classes]}").html_safe link_to(author_html, user_path(author), class: "author_link #{"#{opts[:extra_class]}" if opts[:extra_class]} #{"#{opts[:mobile_classes]}" if opts[:mobile_classes]}").html_safe
else else
title = opts[:title].sub(":name", sanitize(author.name)) title = opts[:title].sub(":name", sanitize(author.name))
link_to(author_html, user_path(author), class: "author_link has-tooltip", title: title, data: { container: 'body' } ).html_safe link_to(author_html, user_path(author), class: "author_link has-tooltip", title: title, data: { container: 'body' } ).html_safe
......
...@@ -7,15 +7,19 @@ module Ci ...@@ -7,15 +7,19 @@ module Ci
builds = builds =
if current_runner.shared? if current_runner.shared?
# don't run projects which have not enables shared runners builds.
builds.joins(:project).where(projects: { builds_enabled: true, shared_runners_enabled: true }) # don't run projects which have not enabled shared runners
joins(:project).where(projects: { builds_enabled: true, shared_runners_enabled: true }).
# this returns builds that are ordered by number of running builds
# we prefer projects that don't use shared runners at all
joins("LEFT JOIN (#{running_builds_for_shared_runners.to_sql}) AS project_builds ON ci_builds.gl_project_id=project_builds.gl_project_id").
order('COALESCE(project_builds.running_builds, 0) ASC', 'ci_builds.id ASC')
else else
# do run projects which are only assigned to this runner # do run projects which are only assigned to this runner (FIFO)
builds.where(project: current_runner.projects.where(builds_enabled: true)) builds.where(project: current_runner.projects.where(builds_enabled: true)).order('created_at ASC')
end end
builds = builds.order('created_at ASC')
build = builds.find do |build| build = builds.find do |build|
build.can_be_served?(current_runner) build.can_be_served?(current_runner)
end end
...@@ -35,5 +39,12 @@ module Ci ...@@ -35,5 +39,12 @@ module Ci
rescue StateMachines::InvalidTransition rescue StateMachines::InvalidTransition
nil nil
end end
private
def running_builds_for_shared_runners
Ci::Build.running.where(runner: Ci::Runner.shared).
group(:gl_project_id).select(:gl_project_id, 'count(*) AS running_builds')
end
end end
end end
= link_to icon('bars'), '#', class: 'toggle-nav-collapse', title: "Open/Close" = link_to '#', class: 'nav-header-btn text-center toggle-nav-collapse', title: "Open/Close" do
%span.sr-only Toggle navigation
= icon('bars')
.page-with-sidebar.page-sidebar-collapsed{ class: "#{page_gutter_class}" } .page-with-sidebar{ class: "#{page_sidebar_class} #{page_gutter_class}" }
.sidebar-wrapper.nicescroll{ class: nav_sidebar_class } .sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
= render partial: 'layouts/collapse_button'
- if defined?(sidebar) && sidebar - if defined?(sidebar) && sidebar
= render "layouts/nav/#{sidebar}" = render "layouts/nav/#{sidebar}"
- elsif current_user - elsif current_user
...@@ -8,13 +8,14 @@ ...@@ -8,13 +8,14 @@
- else - else
= render 'layouts/nav/explore' = render 'layouts/nav/explore'
.collapse-nav
= render partial: 'layouts/collapse_button'
- if current_user - if current_user
= link_to current_user, class: 'sidebar-user', title: "Profile", data: {user: current_user.username} do = link_to current_user, class: 'sidebar-user', title: "Profile", data: {user: current_user.username} do
= image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s36' = image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s36'
.username .username
= current_user.username = current_user.username
= link_to '#', class: "nav-header-btn text-center pin-nav-btn #{'is-active' if pinned_nav?} js-nav-pin", title: 'Pin/Unpin navigation' do
%span.sr-only Toggle navigation pinning
= icon('thumb-tack')
- if defined?(nav) && nav - if defined?(nav) && nav
.layout-nav .layout-nav
.container-fluid .container-fluid
......
...@@ -36,6 +36,31 @@ ...@@ -36,6 +36,31 @@
- else - else
= hidden_field_tag :search_code, true = hidden_field_tag :search_code, true
:javascript
gl.projectOptions = gl.projectOptions || {};
gl.projectOptions["#{j(@project.path)}"] = {
issuesPath: "#{namespace_project_issues_path(@project.namespace, @project)}",
mrPath: "#{namespace_project_merge_requests_path(@project.namespace, @project)}",
name: "#{j(@project.name)}"
};
- if @group and @group.path
:javascript
gl.groupOptions = gl.groupOptions || {};
gl.groupOptions["#{j(@group.path)}"] = {
name: "#{j(@group.name)}",
issuesPath: "#{issues_group_path(j(@group.path))}",
mrPath: "#{merge_requests_group_path(j(@group.path))}"
};
:javascript
gl.dashboardOptions = {
issuesPath: "#{issues_dashboard_url}",
mrPath: "#{merge_requests_dashboard_url}"
};
- if @snippet || @snippets - if @snippet || @snippets
= hidden_field_tag :snippets, true = hidden_field_tag :snippets, true
= hidden_field_tag :repository_ref, @ref = hidden_field_tag :repository_ref, @ref
......
!!! 5 !!! 5
%html{ lang: "en"} %html{ lang: "en"}
= render "layouts/head" = render "layouts/head"
%body{class: "#{user_application_theme}", 'data-page' => body_data_page} %body{class: "#{user_application_theme}", data: {page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}"}}
= Gon::Base.render_data = Gon::Base.render_data
-# Ideally this would be inside the head, but turbolinks only evaluates page-specific JS in the body. -# Ideally this would be inside the head, but turbolinks only evaluates page-specific JS in the body.
......
%header.navbar.navbar-fixed-top.navbar-gitlab.header-collapsed{ class: nav_header_class } %header.navbar.navbar-fixed-top.navbar-gitlab{ class: nav_header_class }
%div{ class: fluid_layout ? "container-fluid" : "container-fluid" } %div{ class: fluid_layout ? "container-fluid" : "container-fluid" }
.header-content .header-content
%button.side-nav-toggle{type: 'button'} %button.side-nav-toggle{type: 'button'}
......
...@@ -60,7 +60,7 @@ ...@@ -60,7 +60,7 @@
%li %li
= link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, build), rel: 'nofollow' do = link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, build), rel: 'nofollow' do
= icon("download") = icon("download")
%span #{build.name} %span Download '#{build.name}' artifacts
- if can?(current_user, :update_pipeline, @project) - if can?(current_user, :update_pipeline, @project)
- if pipeline.retryable? - if pipeline.retryable?
......
...@@ -9,26 +9,30 @@ ...@@ -9,26 +9,30 @@
= cache(cache_key) do = cache(cache_key) do
%li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" } %li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" }
= commit_author_avatar(commit, size: 36)
.commit-row-title .commit-row-title
%span.item-title %span.item-title
= link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message" = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message"
%span.commit-row-message.visible-xs-inline
&middot;
= commit.short_id
- if commit.status
= render_commit_status(commit, cssclass: 'visible-xs-inline')
- if commit.description? - if commit.description?
%a.text-expander.js-toggle-button ... %a.text-expander.hidden-xs.js-toggle-button ...
.pull-right .commit-actions.hidden-xs
- if commit.status - if commit.status
= render_commit_status(commit) = render_commit_status(commit, cssclass: 'btn btn-transparent')
= clipboard_button(clipboard_text: commit.id) = clipboard_button_with_class({ clipboard_text: commit.id }, css_class: 'btn-transparent')
= link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id" = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-short-id btn btn-transparent"
= link_to_browse_code(project, commit)
- if commit.description? - if commit.description?
.commit-row-description.js-toggle-content %pre.commit-row-description.js-toggle-content
%pre = preserve(markdown(escape_once(commit.description), pipeline: :single_line, author: commit.author))
= preserve(markdown(escape_once(commit.description), pipeline: :single_line, author: commit.author))
.commit-row-info .commit-row-info
by = commit_author_link(commit, avatar: false, size: 24)
= commit_author_link(commit, avatar: true, size: 24) authored
.committed_ago #{time_ago_with_tooltip(commit.committed_date)}
#{time_ago_with_tooltip(commit.committed_date)} &nbsp;
= link_to_browse_code(project, commit)
...@@ -4,18 +4,11 @@ ...@@ -4,18 +4,11 @@
- commits, hidden = limited_commits(@commits) - commits, hidden = limited_commits(@commits)
- commits.chunk { |c| c.committed_date.in_time_zone.to_date }.each do |day, commits| - commits.chunk { |c| c.committed_date.in_time_zone.to_date }.each do |day, commits|
.row.commits-row %li.commit-header= "#{day.strftime('%d %b, %Y')} #{pluralize(commits.count, 'commit')}"
.col-md-2.hidden-xs.hidden-sm %li.commits-row
%h5.commits-row-date %ul.list-unstyled.commit-list
%i.fa.fa-calendar = render commits, project: project
%span= day.strftime('%d %b, %Y')
.light
= pluralize(commits.count, 'commit')
.col-md-10.col-sm-12
%ul.content-list
= render commits, project: project
%hr.lists-separator
- if hidden > 0 - if hidden > 0
.alert.alert-warning %li.alert.alert-warning
#{number_with_delimiter(hidden)} additional commits have been omitted to prevent performance issues. #{number_with_delimiter(hidden)} additional commits have been omitted to prevent performance issues.
...@@ -23,23 +23,20 @@ ...@@ -23,23 +23,20 @@
Create Merge Request Create Merge Request
.control .control
= form_tag(namespace_project_commits_path(@project.namespace, @project, @id), method: :get, class: 'pull-left commits-search-form') do = form_tag(namespace_project_commits_path(@project.namespace, @project, @id), method: :get, class: 'commits-search-form') do
= search_field_tag :search, params[:search], { placeholder: 'Filter by commit message', id: 'commits-search', class: 'form-control search-text-input', spellcheck: false } = search_field_tag :search, params[:search], { placeholder: 'Filter by commit message', id: 'commits-search', class: 'form-control search-text-input input-short', spellcheck: false }
- if current_user && current_user.private_token - if current_user && current_user.private_token
.control .control
= link_to namespace_project_commits_path(@project.namespace, @project, @ref, {format: :atom, private_token: current_user.private_token}), title: "Commits Feed", class: 'btn' do = link_to namespace_project_commits_path(@project.namespace, @project, @ref, {format: :atom, private_token: current_user.private_token}), title: "Commits Feed", class: 'btn' do
= icon("rss") = icon("rss")
%ul.breadcrumb.repo-breadcrumb %ul.breadcrumb.repo-breadcrumb
= commits_breadcrumbs = commits_breadcrumbs
= render 'projects/commits/mirror_status' = render 'projects/commits/mirror_status'
%div{id: dom_id(@project)} %div{id: dom_id(@project)}
#commits-list.content_list= render "commits", project: @project %ol#commits-list.list-unstyled.content_list
.clear = render "commits", project: @project
= spinner = spinner
:javascript :javascript
......
...@@ -11,6 +11,8 @@ ...@@ -11,6 +11,8 @@
= commit_diff_whitespace_link(@project, @commit, class: 'hidden-xs') = commit_diff_whitespace_link(@project, @commit, class: 'hidden-xs')
- elsif current_controller?(:merge_requests) - elsif current_controller?(:merge_requests)
= diff_merge_request_whitespace_link(@project, @merge_request, class: 'hidden-xs') = diff_merge_request_whitespace_link(@project, @merge_request, class: 'hidden-xs')
- elsif current_controller?(:compare)
= diff_compare_whitespace_link(@project, params[:from], params[:to], class: 'hidden-xs')
.btn-group .btn-group
= inline_diff_btn = inline_diff_btn
= parallel_diff_btn = parallel_diff_btn
......
...@@ -4,9 +4,10 @@ ...@@ -4,9 +4,10 @@
= render "projects/issues/head" = render "projects/issues/head"
%div{ class: (container_class) } %div{ class: (container_class) }
.top-area .top-area.adjust
.nav-text .nav-text
Labels can be applied to issues and merge requests. Labels can be applied to issues and merge requests. Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging.
.nav-controls .nav-controls
- if can?(current_user, :admin_label, @project) - if can?(current_user, :admin_label, @project)
= link_to new_namespace_project_label_path(@project.namespace, @project), class: "btn btn-new" do = link_to new_namespace_project_label_path(@project.namespace, @project), class: "btn btn-new" do
...@@ -19,10 +20,9 @@ ...@@ -19,10 +20,9 @@
.prioritized-labels{ class: ('hide' if hide) } .prioritized-labels{ class: ('hide' if hide) }
%h5 Prioritized Labels %h5 Prioritized Labels
%ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_namespace_project_labels_path(@project.namespace, @project) } %ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_namespace_project_labels_path(@project.namespace, @project) }
%p.empty-message{ class: ('hidden' unless @prioritized_labels.empty?) } No prioritized labels yet
- if @prioritized_labels.present? - if @prioritized_labels.present?
= render @prioritized_labels = render @prioritized_labels
- else
%p.empty-message No prioritized labels yet
.other-labels .other-labels
- if can?(current_user, :admin_label, @project) - if can?(current_user, :admin_label, @project)
%h5{ class: ('hide' if hide) } Other Labels %h5{ class: ('hide' if hide) } Other Labels
......
...@@ -2,4 +2,5 @@ ...@@ -2,4 +2,5 @@
= icon("sort-amount-desc") = icon("sort-amount-desc")
Most recent commits displayed first Most recent commits displayed first
= render "projects/commits/commits", project: @merge_request.project %ol#commits-list.list-unstyled
= render "projects/commits/commits", project: @merge_request.project
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
= f.label :due_date, "Due Date", class: "control-label" = f.label :due_date, "Due Date", class: "control-label"
.col-sm-10 .col-sm-10
= f.text_field :due_date, class: "datepicker form-control", placeholder: "Select due date" = f.text_field :due_date, class: "datepicker form-control", placeholder: "Select due date"
%a.inline.prepend-top-5.js-clear-due-date{ href: "#" } Clear due date
.form-actions .form-actions
- if @milestone.new_record? - if @milestone.new_record?
...@@ -27,10 +28,3 @@ ...@@ -27,10 +28,3 @@
-else -else
= f.submit 'Save changes', class: "btn-save btn" = f.submit 'Save changes', class: "btn-save btn"
= link_to "Cancel", namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-cancel" = link_to "Cancel", namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-cancel"
:javascript
$(".datepicker").datepicker({
dateFormat: "yy-mm-dd",
onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) }
}).datepicker("setDate", $.datepicker.parseDate('yy-mm-dd', $('#milestone_due_date').val()));
- page_title "Network", @ref - page_title "Network", @ref
- page_specific_javascripts asset_path("network/application.js")
= render "projects/commits/head" = render "projects/commits/head"
= render "head" = render "head"
%div{ class: (container_class) } %div{ class: (container_class) }
...@@ -14,14 +15,5 @@ ...@@ -14,14 +15,5 @@
= check_box_tag :filter_ref, 1, @options[:filter_ref] = check_box_tag :filter_ref, 1, @options[:filter_ref]
%span Begin with the selected commit %span Begin with the selected commit
.network-graph .network-graph{ data: { url: '#{escape_javascript(@url)}', commit_url: '#{escape_javascript(@commit_url)}', ref: '#{escape_javascript(@ref)}', commit_id: '#{escape_javascript(@commit.id)}' } }
= spinner nil, true = spinner nil, true
:javascript
network_graph = new Network({
url: "#{escape_javascript(@url)}",
commit_url: "#{escape_javascript(@commit_url)}",
ref: "#{escape_javascript(@ref)}",
commit_id: '#{@commit.id}'
})
new ShortcutsNetwork(network_graph.branch_graph)
...@@ -11,26 +11,22 @@ ...@@ -11,26 +11,22 @@
.project-edit-content .project-edit-content
= form_for @project, html: { class: 'new_project form-horizontal js-requires-input' } do |f| = form_for @project, html: { class: 'new_project form-horizontal js-requires-input' } do |f|
.form-group.project-name-holder .form-group
= f.label :path, class: 'control-label' do = f.label :path, class: 'control-label' do
Project path Project owner
.col-sm-10 .col-sm-10
.input-group = f.select :namespace_id, namespaces_options(:current_user), {}, {class: 'select2 js-select-namespace', tabindex: 1}
- if current_user.can_select_namespace?
.input-group-addon
= root_url
= f.select :namespace_id, namespaces_options(params[:namespace_id] || :current_user, display_path: true), {}, {class: 'select2 js-select-namespace', tabindex: 1}
.input-group-addon
\/
- else
.input-group-addon
#{root_url}#{current_user.username}/
= f.text_field :path, placeholder: "my-awesome-project", class: "form-control", tabindex: 2, autofocus: true, required: true
- if current_user.can_create_group? - if current_user.can_create_group?
.help-block .help-block
Want to house several dependent projects under the same namespace? Want to house several dependent projects under the same namespace?
= link_to "Create a group", new_group_path = link_to "Create a group", new_group_path
.form-group
= f.label :path, class: 'control-label' do
Project name
.col-sm-10
= f.text_field :path, placeholder: "my-awesome-project", class: "form-control", tabindex: 2, autofocus: true, required: true
- if import_sources_enabled? - if import_sources_enabled?
.project-import.js-toggle-container .project-import.js-toggle-container
......
%span.label-row %span.label-row
- if can?(current_user, :admin_label, @project) - if can?(current_user, :admin_label, @project)
.draggable-handler
= icon('bars')
.js-toggle-priority.toggle-priority{ data: { url: remove_priority_namespace_project_label_path(@project.namespace, @project, label), .js-toggle-priority.toggle-priority{ data: { url: remove_priority_namespace_project_label_path(@project.namespace, @project, label),
dom_id: dom_id(label) } } dom_id: dom_id(label) } }
%button.add-priority.btn.has-tooltip{ title: 'Prioritize', :'data-placement' => 'top' } %button.add-priority.btn.has-tooltip{ title: 'Prioritize', :'data-placement' => 'top' }
......
...@@ -29,20 +29,21 @@ ...@@ -29,20 +29,21 @@
= icon('spinner spin', class: 'block-loading') = icon('spinner spin', class: 'block-loading')
- if can_edit_issuable - if can_edit_issuable
= link_to 'Edit', '#', class: 'edit-link pull-right' = link_to 'Edit', '#', class: 'edit-link pull-right'
.value.bold.hide-collapsed .value.hide-collapsed
- if issuable.assignee - if issuable.assignee
= link_to_member(@project, issuable.assignee, size: 32) do = link_to_member(@project, issuable.assignee, size: 32, extra_class: 'bold') do
- if issuable.instance_of?(MergeRequest) && !issuable.can_be_merged_by?(issuable.assignee) - if issuable.instance_of?(MergeRequest) && !issuable.can_be_merged_by?(issuable.assignee)
%span.pull-right.cannot-be-merged{ data: { toggle: 'tooltip', placement: 'left' }, title: 'Not allowed to merge' } %span.pull-right.cannot-be-merged{ data: { toggle: 'tooltip', placement: 'left' }, title: 'Not allowed to merge' }
= icon('exclamation-triangle') = icon('exclamation-triangle')
%span.username %span.username
= issuable.assignee.to_reference = issuable.assignee.to_reference
- else - else
%span.assign-yourself %span.assign-yourself.no-value
No assignee No assignee
- if can_edit_issuable - if can_edit_issuable
\-
%a.js-assign-yourself{ href: '#' } %a.js-assign-yourself{ href: '#' }
\- assign yourself assign yourself
.selectbox.hide-collapsed .selectbox.hide-collapsed
= f.hidden_field 'assignee_id', value: issuable.assignee_id, id: 'issue_assignee_id' = f.hidden_field 'assignee_id', value: issuable.assignee_id, id: 'issue_assignee_id'
...@@ -62,13 +63,11 @@ ...@@ -62,13 +63,11 @@
= icon('spinner spin', class: 'block-loading') = icon('spinner spin', class: 'block-loading')
- if can_edit_issuable - if can_edit_issuable
= link_to 'Edit', '#', class: 'edit-link pull-right' = link_to 'Edit', '#', class: 'edit-link pull-right'
.value.bold.hide-collapsed .value.hide-collapsed
- if issuable.milestone - if issuable.milestone
= link_to namespace_project_milestone_path(@project.namespace, @project, issuable.milestone) do = link_to issuable.milestone.title, namespace_project_milestone_path(@project.namespace, @project, issuable.milestone), class: "bold has-tooltip", title: milestone_remaining_days(issuable.milestone), data: { container: "body", html: 1 }
%span.has-tooltip{title: milestone_remaining_days(issuable.milestone), data: {container: 'body', html: 1}}
= issuable.milestone.title
- else - else
.light None %span.no-value None
.selectbox.hide-collapsed .selectbox.hide-collapsed
= f.hidden_field 'milestone_id', value: issuable.milestone_id, id: nil = f.hidden_field 'milestone_id', value: issuable.milestone_id, id: nil
...@@ -85,14 +84,14 @@ ...@@ -85,14 +84,14 @@
= icon('spinner spin', class: 'block-loading') = icon('spinner spin', class: 'block-loading')
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project) - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
= link_to 'Edit', '#', class: 'edit-link pull-right' = link_to 'Edit', '#', class: 'edit-link pull-right'
.value.bold.hide-collapsed .value.hide-collapsed
%span.value-content %span.value-content
- if issuable.due_date - if issuable.due_date
= issuable.due_date.to_s(:medium) %span.bold= issuable.due_date.to_s(:medium)
- else - else
None %span.no-value No due date
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project) - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
%span.light.js-remove-due-date-holder{ class: ("hidden" if issuable.due_date.nil?) } %span.no-value.js-remove-due-date-holder{ class: ("hidden" if issuable.due_date.nil?) }
\- \-
%a.js-remove-due-date{ href: "#", role: "button" } %a.js-remove-due-date{ href: "#", role: "button" }
remove due date remove due date
...@@ -124,7 +123,7 @@ ...@@ -124,7 +123,7 @@
- issuable.labels_array.each do |label| - issuable.labels_array.each do |label|
= link_to_label(label, type: issuable.to_ability_name) = link_to_label(label, type: issuable.to_ability_name)
- else - else
.light None %span.no-value None
.selectbox.hide-collapsed .selectbox.hide-collapsed
- issuable.labels_array.each do |label| - issuable.labels_array.each do |label|
= hidden_field_tag "#{issuable.to_ability_name}[label_names][]", label.id, id: nil = hidden_field_tag "#{issuable.to_ability_name}[label_names][]", label.id, id: nil
......
...@@ -86,6 +86,7 @@ module Gitlab ...@@ -86,6 +86,7 @@ module Gitlab
config.assets.precompile << "mailers/*.css" config.assets.precompile << "mailers/*.css"
config.assets.precompile << "graphs/application.js" config.assets.precompile << "graphs/application.js"
config.assets.precompile << "users/application.js" config.assets.precompile << "users/application.js"
config.assets.precompile << "network/application.js"
# Version of your assets, change this if you want to expire all your assets # Version of your assets, change this if you want to expire all your assets
config.assets.version = '1.0' config.assets.version = '1.0'
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class ChangeProjectOfEnvironment < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# When using the methods "add_concurrent_index" or "add_column_with_default"
# you must disable the use of transactions as these methods can not run in an
# existing transaction. When using "add_concurrent_index" make sure that this
# method is the _only_ method called in the migration, any other changes
# should go in a separate migration. This ensures that upon failure _only_ the
# index creation fails and can be retried or reverted easily.
#
# To disable transactions uncomment the following line and remove these
# comments:
# disable_ddl_transaction!
def change
change_column_null :environments, :project_id, true
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20160615142710) do ActiveRecord::Schema.define(version: 20160616084004) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
......
...@@ -34,6 +34,15 @@ First, you need to provide information on whether the migration can be applied: ...@@ -34,6 +34,15 @@ First, you need to provide information on whether the migration can be applied:
3. online with errors on new instances while migrating 3. online with errors on new instances while migrating
4. offline (needs to happen without app servers to prevent db corruption) 4. offline (needs to happen without app servers to prevent db corruption)
For example:
```
# rubocop:disable all
# Migration type: online without errors (works on previous version and new one)
class MyMigration < ActiveRecord::Migration
...
```
It is always preferable to have a migration run online. If you expect the migration It is always preferable to have a migration run online. If you expect the migration
to take particularly long (for instance, if it loops through all notes), to take particularly long (for instance, if it loops through all notes),
this is valuable information to add. this is valuable information to add.
...@@ -48,7 +57,6 @@ be possible to downgrade in case of a vulnerability or bugs. ...@@ -48,7 +57,6 @@ be possible to downgrade in case of a vulnerability or bugs.
In your migration, add a comment describing how the reversibility of the In your migration, add a comment describing how the reversibility of the
migration was tested. migration was tested.
## Removing indices ## Removing indices
If you need to remove index, please add a condition like in following example: If you need to remove index, please add a condition like in following example:
...@@ -70,6 +78,7 @@ so: ...@@ -70,6 +78,7 @@ so:
``` ```
class MyMigration < ActiveRecord::Migration class MyMigration < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction! disable_ddl_transaction!
def change def change
...@@ -90,8 +99,11 @@ value of `10` you'd write the following: ...@@ -90,8 +99,11 @@ value of `10` you'd write the following:
``` ```
class MyMigration < ActiveRecord::Migration class MyMigration < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
def up def up
add_column_with_default(:projects, :foo, :integer, 10) add_column_with_default(:projects, :foo, :integer, default: 10)
end end
def down def down
......
...@@ -10,7 +10,8 @@ class Spinach::Features::NewProject < Spinach::FeatureSteps ...@@ -10,7 +10,8 @@ class Spinach::Features::NewProject < Spinach::FeatureSteps
end end
step 'I see "New Project" page' do step 'I see "New Project" page' do
expect(page).to have_content('Project path') expect(page).to have_content('Project owner')
expect(page).to have_content('Project name')
end end
step 'I see all possible import optios' do step 'I see all possible import optios' do
......
...@@ -202,8 +202,8 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps ...@@ -202,8 +202,8 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end end
step 'I see Browse dir link' do step 'I see Browse dir link' do
expect(page).to have_link 'Browse Directory »' expect(page).to have_link 'Browse Directory'
expect(page).not_to have_link 'Browse Code »' expect(page).not_to have_link 'Browse Code'
end end
step 'I click on readme file' do step 'I click on readme file' do
...@@ -219,7 +219,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps ...@@ -219,7 +219,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
step 'I see Browse code link' do step 'I see Browse code link' do
expect(page).to have_link 'Browse Files' expect(page).to have_link 'Browse Files'
expect(page).not_to have_link 'Browse Directory »' expect(page).not_to have_link 'Browse Directory'
end end
step 'I click on Permalink' do step 'I click on Permalink' do
......
...@@ -577,10 +577,10 @@ describe 'Issues', feature: true do ...@@ -577,10 +577,10 @@ describe 'Issues', feature: true do
first('.ui-state-default').click first('.ui-state-default').click
end end
expect(page).to have_no_content 'None' expect(page).to have_no_content 'No due date'
click_link 'remove due date' click_link 'remove due date'
expect(page).to have_content 'None' expect(page).to have_content 'No due date'
end end
end end
end end
......
...@@ -47,4 +47,83 @@ describe "Search", feature: true do ...@@ -47,4 +47,83 @@ describe "Search", feature: true do
expect(page).to have_link(snippet.title) expect(page).to have_link(snippet.title)
end end
end end
describe 'Right header search field', feature: true do
describe 'Search in project page' do
before do
visit namespace_project_path(project.namespace, project)
end
it 'top right search form is present' do
expect(page).to have_selector('#search')
end
it 'top right search form contains location badge' do
expect(page).to have_selector('.has-location-badge')
end
context 'clicking the search field', js: true do
it 'should show category search dropdown' do
page.find('#search').click
expect(page).to have_selector('.dropdown-header', text: /#{project.name}/i)
end
end
context 'click the links in the category search dropdown', js: true do
before do
page.find('#search').click
end
it 'should take user to her issues page when issues assigned is clicked' do
find('.dropdown-menu').click_link 'Issues assigned to me'
sleep 2
expect(page).to have_selector('.issues-holder')
expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name)
end
it 'should take user to her issues page when issues authored is clicked' do
find('.dropdown-menu').click_link "Issues I've created"
sleep 2
expect(page).to have_selector('.issues-holder')
expect(find('.js-author-search .dropdown-toggle-text')).to have_content(user.name)
end
it 'should take user to her MR page when MR assigned is clicked' do
find('.dropdown-menu').click_link 'Merge requests assigned to me'
sleep 2
expect(page).to have_selector('.merge-requests-holder')
expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name)
end
it 'should take user to her MR page when MR authored is clicked' do
find('.dropdown-menu').click_link "Merge requests I've created"
sleep 2
expect(page).to have_selector('.merge-requests-holder')
expect(find('.js-author-search .dropdown-toggle-text')).to have_content(user.name)
end
end
context 'entering text into the search field', js: true do
before do
page.within '.search-input-wrap' do
fill_in "search", with: project.name[0..3]
end
end
it 'should not display the category search dropdown' do
expect(page).not_to have_selector('.dropdown-header', text: /#{project.name}/i)
end
end
end
end
end end
.search.search-form.has-location-badge
%form.navbar-form
.search-input-container
%div.location-badge
This project
.search-input-wrap
.dropdown
%input#search.search-input.dropdown-menu-toggle
.dropdown-menu.dropdown-select
.dropdown-content
#= require notes #= require notes
#= require gl_form #= require gl_form
window.gon = {} window.gon or= {}
window.disableButtonIfEmptyField = -> null window.disableButtonIfEmptyField = -> null
describe 'Notes', -> describe 'Notes', ->
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
#= require project_select #= require project_select
#= require project #= require project
window.gon = {} window.gon or= {}
window.gon.api_version = 'v3' window.gon.api_version = 'v3'
describe 'Project Title', -> describe 'Project Title', ->
......
#= require gl_dropdown
#= require search_autocomplete
#= require jquery
#= require lib/common_utils
#= require lib/type_utility
#= require fuzzaldrin-plus
widget = null
userId = 1
window.gon or= {}
window.gon.current_user_id = userId
dashboardIssuesPath = '/dashboard/issues'
dashboardMRsPath = '/dashboard/merge_requests'
projectIssuesPath = '/gitlab-org/gitlab-ce/issues'
projectMRsPath = '/gitlab-org/gitlab-ce/merge_requests'
groupIssuesPath = '/groups/gitlab-org/issues'
groupMRsPath = '/groups/gitlab-org/merge_requests'
projectName = 'GitLab Community Edition'
groupName = 'Gitlab Org'
# Add required attributes to body before starting the test.
# section would be dashboard|group|project
addBodyAttributes = (section = 'dashboard') ->
$body = $ 'body'
$body.removeAttr 'data-page'
$body.removeAttr 'data-project'
$body.removeAttr 'data-group'
switch section
when 'dashboard'
$body.data 'page', 'root:index'
when 'group'
$body.data 'page', 'groups:show'
$body.data 'group', 'gitlab-org'
when 'project'
$body.data 'page', 'projects:show'
$body.data 'project', 'gitlab-ce'
# Mock `gl` object in window for dashboard specific page. App code will need it.
mockDashboardOptions = ->
window.gl or= {}
window.gl.dashboardOptions =
issuesPath: dashboardIssuesPath
mrPath : dashboardMRsPath
# Mock `gl` object in window for project specific page. App code will need it.
mockProjectOptions = ->
window.gl or= {}
window.gl.projectOptions =
'gitlab-ce' :
issuesPath : projectIssuesPath
mrPath : projectMRsPath
projectName : projectName
mockGroupOptions = ->
window.gl or= {}
window.gl.groupOptions =
'gitlab-org' :
issuesPath : groupIssuesPath
mrPath : groupMRsPath
projectName : groupName
assertLinks = (list, issuesPath, mrsPath) ->
issuesAssignedToMeLink = "#{issuesPath}/?assignee_id=#{userId}"
issuesIHaveCreatedLink = "#{issuesPath}/?author_id=#{userId}"
mrsAssignedToMeLink = "#{mrsPath}/?assignee_id=#{userId}"
mrsIHaveCreatedLink = "#{mrsPath}/?author_id=#{userId}"
a1 = "a[href='#{issuesAssignedToMeLink}']"
a2 = "a[href='#{issuesIHaveCreatedLink}']"
a3 = "a[href='#{mrsAssignedToMeLink}']"
a4 = "a[href='#{mrsIHaveCreatedLink}']"
expect(list.find(a1).length).toBe 1
expect(list.find(a1).text()).toBe ' Issues assigned to me '
expect(list.find(a2).length).toBe 1
expect(list.find(a2).text()).toBe " Issues I've created "
expect(list.find(a3).length).toBe 1
expect(list.find(a3).text()).toBe ' Merge requests assigned to me '
expect(list.find(a4).length).toBe 1
expect(list.find(a4).text()).toBe " Merge requests I've created "
describe 'Search autocomplete dropdown', ->
fixture.preload 'search_autocomplete.html'
beforeEach ->
fixture.load 'search_autocomplete.html'
widget = new SearchAutocomplete
it 'should show Dashboard specific dropdown menu', ->
addBodyAttributes()
mockDashboardOptions()
widget.searchInput.focus()
list = widget.wrap.find('.dropdown-menu').find 'ul'
assertLinks list, dashboardIssuesPath, dashboardMRsPath
it 'should show Group specific dropdown menu', ->
addBodyAttributes 'group'
mockGroupOptions()
widget.searchInput.focus()
list = widget.wrap.find('.dropdown-menu').find 'ul'
assertLinks list, groupIssuesPath, groupMRsPath
it 'should show Project specific dropdown menu', ->
addBodyAttributes 'project'
mockProjectOptions()
widget.searchInput.focus()
list = widget.wrap.find('.dropdown-menu').find 'ul'
assertLinks list, projectIssuesPath, projectMRsPath
it 'should not show category related menu if there is text in the input', ->
addBodyAttributes 'project'
mockProjectOptions()
widget.searchInput.val 'help'
widget.searchInput.focus()
list = widget.wrap.find('.dropdown-menu').find 'ul'
link = "a[href='#{projectIssuesPath}/?assignee_id=#{userId}']"
expect(list.find(link).length).toBe 0
...@@ -45,11 +45,73 @@ module Ci ...@@ -45,11 +45,73 @@ module Ci
end end
end end
context 'deleted projects' do
before do
project.update(pending_delete: true)
end
context 'for shared runners' do
before do
project.update(shared_runners_enabled: true)
end
it 'does not pick a build' do
expect(service.execute(shared_runner)).to be_nil
end
end
context 'for specific runner' do
it 'does not pick a build' do
expect(service.execute(specific_runner)).to be_nil
end
end
end
context 'allow shared runners' do context 'allow shared runners' do
before do before do
project.update(shared_runners_enabled: true) project.update(shared_runners_enabled: true)
end end
context 'for multiple builds' do
let!(:project2) { create :empty_project, shared_runners_enabled: true }
let!(:pipeline2) { create :ci_pipeline, project: project2 }
let!(:project3) { create :empty_project, shared_runners_enabled: true }
let!(:pipeline3) { create :ci_pipeline, project: project3 }
let!(:build1_project1) { pending_build }
let!(:build2_project1) { FactoryGirl.create :ci_build, pipeline: pipeline }
let!(:build3_project1) { FactoryGirl.create :ci_build, pipeline: pipeline }
let!(:build1_project2) { FactoryGirl.create :ci_build, pipeline: pipeline2 }
let!(:build2_project2) { FactoryGirl.create :ci_build, pipeline: pipeline2 }
let!(:build1_project3) { FactoryGirl.create :ci_build, pipeline: pipeline3 }
it 'prefers projects without builds first' do
# it gets for one build from each of the projects
expect(service.execute(shared_runner)).to eq(build1_project1)
expect(service.execute(shared_runner)).to eq(build1_project2)
expect(service.execute(shared_runner)).to eq(build1_project3)
# then it gets a second build from each of the projects
expect(service.execute(shared_runner)).to eq(build2_project1)
expect(service.execute(shared_runner)).to eq(build2_project2)
# in the end the third build
expect(service.execute(shared_runner)).to eq(build3_project1)
end
it 'equalises number of running builds' do
# after finishing the first build for project 1, get a second build from the same project
expect(service.execute(shared_runner)).to eq(build1_project1)
build1_project1.success
expect(service.execute(shared_runner)).to eq(build2_project1)
expect(service.execute(shared_runner)).to eq(build1_project2)
build1_project2.success
expect(service.execute(shared_runner)).to eq(build2_project2)
expect(service.execute(shared_runner)).to eq(build1_project3)
expect(service.execute(shared_runner)).to eq(build3_project1)
end
end
context 'shared runner' do context 'shared runner' do
let(:build) { service.execute(shared_runner) } let(:build) { service.execute(shared_runner) }
......
This source diff could not be displayed because it is too large. You can view the blob instead.
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