Commit fd285f71 authored by Lin Jen-Shin's avatar Lin Jen-Shin

Merge branch 'master' into feature/runner-lock-on-project

* master: (147 commits)
  Update CHANGELOG
  Remove deprecated issues_tracker and issues_tracker_id from project
  Schema doesn’t reflect the changes of the last 3 migrations
  Revert CHANGELOG
  Also rename "find" in the specs
  Change to new Notes styleguide
  Add guide on changing a document's location
  Change logs.md location in README
  Move logs/logs.md to administration/logs.md
  Make "four phase test"
  Only show branches for revert / cherry-pick
  Instrument all Banzai::ReferenceParser classes
  Removed old comment from update_column_in_batches
  Update columns in batches until no rows are left
  Remove counters from Pipeline navigation
  Handle NULL migration errors in migration helpers
  Fix typo causing related branches to Error 500
  Improved SVG sanitizer specs to include smoke tests for clean.
  Refactored SVG sanitizer
  Added SVG sanitizer fix to the changelog
  ...
parents 1b8f52d9 121c6322
We’re closing our issue tracker on GitHub so we can focus on the GitLab.com project and respond to issues more quickly.
We encourage you to open an issue on the [GitLab.com issue tracker](https://gitlab.com/gitlab-org/gitlab-ce/issues). You can log into GitLab.com using your GitHub account.
Thank you for taking the time to contribute back to GitLab!
Please open a merge request [on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests), we look forward to reviewing your contribution! You can log into GitLab.com using your GitHub account.
......@@ -7,7 +7,8 @@ services:
cache:
key: "ruby21"
paths:
- vendor
- vendor/apt
- vendor/ruby
variables:
MYSQL_ALLOW_EMPTY_PASSWORD: "1"
......@@ -91,9 +92,7 @@ update-knapsack:
- export KNAPSACK_REPORT_PATH=knapsack/spinach_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
- export KNAPSACK_GENERATE_REPORT=true
- cp knapsack/spinach_report.json ${KNAPSACK_REPORT_PATH}
- knapsack spinach "-r rerun"
# retry failed tests 3 times
- retry '[ ! -e tmp/spinach-rerun.txt ] || bin/spinach -r rerun $(cat tmp/spinach-rerun.txt)'
- knapsack spinach "-r rerun" || retry '[ ! -e tmp/spinach-rerun.txt ] || bundle exec spinach -r rerun $(cat tmp/spinach-rerun.txt)'
artifacts:
paths:
- knapsack/
......
......@@ -13,7 +13,8 @@ AllCops:
# Exclude some GitLab files
Exclude:
- 'vendor/**/*'
- 'db/**/*'
- 'db/*'
- 'db/fixtures/**/*'
- 'tmp/**/*'
- 'bin/**/*'
- 'lib/backup/**/*'
......@@ -348,7 +349,7 @@ Style/MultilineArrayBraceLayout:
# Avoid multi-line chains of blocks.
Style/MultilineBlockChain:
Enabled: false
Enabled: true
# Ensures newlines after multiline block do statements.
Style/MultilineBlockLayout:
......
Please view this file on the master branch, on stable branches it's out of date.
v 8.9.0 (unreleased)
- Fix Error 500 when using closes_issues API with an external issue tracker
- Bulk assign/unassign labels to issues.
- Ability to prioritize labels !4009 / !3205 (Thijs Wouters)
- Fix endless redirections when accessing user OAuth applications when they are disabled
- Allow enabling wiki page events from Webhook management UI
- Bump rouge to 1.11.0
- Fix issue with arrow keys not working in search autocomplete dropdown
- Make EmailsOnPushWorker use Sidekiq mailers queue
- Fix wiki page events' webhook to point to the wiki repository
- Don't show tags for revert and cherry-pick operations
- Fix issue todo not remove when leave project !4150 (Long Nguyen)
- 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
- Fix SVG sanitizer to allow more elements
- 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
- Reduce number of fog gem dependencies
- Remove project notification settings associated with deleted projects
......@@ -22,13 +27,17 @@ v 8.9.0 (unreleased)
- `git clone https://host/namespace/project` now works, in addition to using the `.git` suffix
- Bump nokogiri to 1.6.8
- Use gitlab-shell v3.0.0
- Upgrade to jQuery 2
- Use Knapsack to evenly distribute tests across multiple nodes
- 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
- Add DB index on users.state
- 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)
- 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)
- Fix issues filter when ordering by milestone
- Added artifacts:when to .gitlab-ci.yml - this requires GitLab Runner 1.3
- Todos will display target state if issuable target is 'Closed' or 'Merged'
- Fix bug when sorting issues by milestone due date and filtering by two or more labels
- Add support for using Yubikeys (U2F) for two-factor authentication
......@@ -38,35 +47,45 @@ v 8.9.0 (unreleased)
- Use downcased path to container repository as this is expected path by Docker
- Projects pending deletion will render a 404 page
- 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 it possible to lock a runner from being enabled for other projects
- Add Application Setting to configure Container Registry token expire delay (default 5min)
- Cache assigned issue and merge request counts in sidebar nav
- Use Knapsack only in CI environment
- 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
- Reduce number of queries needed to render issue labels in the sidebar
- Improve error handling importing projects
- Remove duplicated notification settings
- Put project Files and Commits tabs under Code tab
- Decouple global notification level from user model
- Replace Colorize with Rainbow for coloring console output in Rake tasks.
- Add workhorse controller and API helpers
- An indicator is now displayed at the top of the comment field for confidential issues.
- RepositoryCheck::SingleRepositoryWorker public and private methods are now instrumented
- Improve issuables APIs performance when accessing notes !4471
- External links now open in a new tab
v 8.8.4 (unreleased)
- 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
- Cache on the database if a project has an active external issue tracker.
- Put project Labels and Milestones pages links under Issues and Merge Requests tabs as subnav
- All classes in the Banzai::ReferenceParser namespace are now instrumented
- Remove deprecated issues_tracker and issues_tracker_id from project model
v 8.8.5 (unreleased)
- Ensure branch cleanup regardless of whether the GitHub import process succeeds
- Fix issue with arrow keys not working in search autocomplete dropdown
- Fix todos page throwing errors when you have a project pending deletion
- 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
- Fix importer for GitHub comments on diff
- 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
- Fix incremental trace upload API when using multi-byte UTF-8 chars in trace
v 8.8.4
- Fix LDAP-based login for users with 2FA enabled. !4493
v 8.8.3
- Fix 404 page when viewing TODOs that contain milestones or labels in different projects. !4312
......@@ -178,6 +197,7 @@ v 8.8.0
- 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)
- 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
- Fix import by `Any Git URL` broken if the URL contains a space
......
......@@ -245,7 +245,7 @@ end
group :development do
gem "foreman"
gem 'brakeman', '~> 3.2.0', require: false
gem 'brakeman', '~> 3.3.0', require: false
gem 'letter_opener_web', '~> 1.3.0'
gem 'quiet_assets', '~> 1.0.2'
......
......@@ -97,16 +97,7 @@ GEM
bootstrap-sass (3.3.6)
autoprefixer-rails (>= 5.2.1)
sass (>= 3.3.4)
brakeman (3.2.1)
erubis (~> 2.6)
haml (>= 3.0, < 5.0)
highline (>= 1.6.20, < 2.0)
ruby2ruby (~> 2.3.0)
ruby_parser (~> 3.8.1)
safe_yaml (>= 1.0)
sass (~> 3.0)
slim (>= 1.3.6, < 4.0)
terminal-table (~> 1.4)
brakeman (3.3.2)
browser (2.0.3)
builder (3.2.2)
bullet (5.0.0)
......@@ -338,7 +329,6 @@ GEM
hashie (3.4.3)
health_check (1.5.1)
rails (>= 2.3.0)
highline (1.7.8)
hipchat (1.5.2)
httparty
mimemagic
......@@ -642,10 +632,7 @@ GEM
ruby-saml (1.1.2)
nokogiri (>= 1.5.10)
uuid (~> 2.3)
ruby2ruby (2.3.0)
ruby_parser (~> 3.1)
sexp_processor (~> 4.0)
ruby_parser (3.8.1)
ruby_parser (3.8.2)
sexp_processor (~> 4.1)
rubyntlm (0.5.2)
rubypants (0.2.0)
......@@ -655,7 +642,7 @@ GEM
safe_yaml (1.0.4)
sanitize (2.1.0)
nokogiri (>= 1.4.4)
sass (3.4.21)
sass (3.4.22)
sass-rails (5.0.4)
railties (>= 4.0.0, < 5.0)
sass (~> 3.1)
......@@ -704,9 +691,6 @@ GEM
tilt (>= 1.3, < 3)
six (0.2.0)
slack-notifier (1.2.1)
slim (3.0.6)
temple (~> 0.7.3)
tilt (>= 1.3.3, < 2.1)
slop (3.6.0)
spinach (0.8.10)
colorize
......@@ -747,10 +731,8 @@ GEM
railties (>= 3.2.5, < 6)
teaspoon-jasmine (2.2.0)
teaspoon (>= 1.0.0)
temple (0.7.6)
term-ansicolor (1.3.2)
tins (~> 1.0)
terminal-table (1.5.2)
test_after_commit (0.4.2)
activerecord (>= 3.2)
thin (1.6.4)
......@@ -759,7 +741,7 @@ GEM
rack (~> 1.0)
thor (0.19.1)
thread_safe (0.3.5)
tilt (2.0.2)
tilt (2.0.5)
timecop (0.8.1)
timfel-krb5-auth (0.8.3)
tinder (1.10.1)
......@@ -848,7 +830,7 @@ DEPENDENCIES
better_errors (~> 1.0.1)
binding_of_caller (~> 0.7.2)
bootstrap-sass (~> 3.3.0)
brakeman (~> 3.2.0)
brakeman (~> 3.3.0)
browser (~> 2.0.3)
bullet
bundler-audit
......
class @Activities
constructor: ->
Pager.init 20, true
Pager.init 20, true, false, @updateTooltips
$(".event-filter-link").on "click", (event) =>
event.preventDefault()
@toggleFilter($(event.currentTarget))
@reloadActivities()
updateTooltips: ->
gl.utils.localTimeAgo($('.js-timeago', '#activity'))
reloadActivities: ->
$(".content_list").html ''
Pager.init 20, true
......
......@@ -35,7 +35,6 @@
#= require raphael
#= require g.raphael
#= require g.bar
#= require Chart
#= require branch-graph
#= require ace/ace
#= require ace/ext-searchbox
......@@ -163,19 +162,6 @@ $ ->
$el.data('placement') || 'bottom'
)
$('.header-logo .home').tooltip(
placement: (_, el) ->
$el = $(el)
if $('.page-with-sidebar').hasClass('page-sidebar-collapsed') then 'right' else 'bottom'
container: 'body'
)
$('.page-with-sidebar').tooltip(
selector: '.sidebar-collapsed .nav-sidebar a, .sidebar-collapsed a.sidebar-user'
placement: 'right'
container: 'body'
)
# Form submitter
$('.trigger-submit').on 'change', ->
$(@).parents('form').submit()
......@@ -208,6 +194,7 @@ $ ->
$('.navbar-toggle').on 'click', ->
$('.header-content .title').toggle()
$('.header-content .header-logo').toggle()
$('.header-content .navbar-collapse').toggle()
$('.navbar-toggle').toggleClass('active')
$('.navbar-toggle i').toggleClass("fa-angle-right fa-angle-left")
......@@ -226,6 +213,10 @@ $ ->
form = btn.closest("form")
new ConfirmDangerModal(form, text)
$(document).on 'click', 'button', ->
$(this).blur()
$('input[type="search"]').each ->
$this = $(this)
$this.attr 'value', $this.val()
......@@ -238,7 +229,6 @@ $ ->
$this.attr 'value', $this.val()
$sidebarGutterToggle = $('.js-sidebar-toggle')
$navIconToggle = $('.toggle-nav-collapse')
$(document)
.off 'breakpoint:change'
......@@ -248,10 +238,6 @@ $ ->
if $gutterIcon.hasClass('fa-angle-double-right')
$sidebarGutterToggle.trigger('click')
$navIcon = $navIconToggle.find('.fa')
if $navIcon.hasClass('fa-angle-left')
$navIconToggle.trigger('click')
fitSidebarForSize = ->
oldBootstrapBreakpoint = bootstrapBreakpoint
bootstrapBreakpoint = bp.getBreakpointSize()
......@@ -264,9 +250,10 @@ $ ->
$(document).trigger('breakpoint:change', [bootstrapBreakpoint])
$(window)
.off "resize"
.on "resize", (e) ->
.off "resize.app"
.on "resize.app", (e) ->
fitSidebarForSize()
gl.awardsHandler = new AwardsHandler()
checkInitialSidebarSize()
new Aside()
......@@ -65,7 +65,7 @@ class @AwardsHandler
$addBtn.removeClass 'is-loading'
$menu = $('.emoji-menu')
@positionMenu($menu, $addBtn)
@renderFrequentlyUsedBlock()
@renderFrequentlyUsedBlock() unless @frequentEmojiBlockRendered
setTimeout =>
$menu.addClass 'is-visible'
......@@ -100,7 +100,7 @@ class @AwardsHandler
$menu.css(css)
addAward: (votesBlock, awardUrl, emoji, checkMutuality = yes, callback) ->
addAward: (votesBlock, awardUrl, emoji, checkMutuality = true, callback) ->
emoji = @normilizeEmojiName emoji
......@@ -111,7 +111,7 @@ class @AwardsHandler
$('.emoji-menu').removeClass 'is-visible'
addAwardToEmojiBar: (votesBlock, emoji, checkForMutuality = yes) ->
addAwardToEmojiBar: (votesBlock, emoji, checkForMutuality = true) ->
@checkMutuality votesBlock, emoji if checkForMutuality
@addEmojiToFrequentlyUsedList emoji
......@@ -153,7 +153,7 @@ class @AwardsHandler
if isAlreadyVoted
@showEmojiLoader $emojiButton
@addAward votesBlock, awardUrl, mutualVote, no, ->
@addAward votesBlock, awardUrl, mutualVote, false, ->
$emojiButton.removeClass 'is-loading'
......@@ -282,7 +282,7 @@ class @AwardsHandler
@createEmojiMenu @getAwardMenuUrl(), => @createEmoji_ votesBlock, emoji
getAwardMenuUrl: -> return gl.awardMenuUrl
getAwardMenuUrl: -> return gon.award_menu_url
resolveNameToCssClass: (emoji) ->
......@@ -336,13 +336,15 @@ class @AwardsHandler
if $.cookie 'frequently_used_emojis'
frequentlyUsedEmojis = @getFrequentlyUsedEmojis()
ul = $("<ul class='clearfix emoji-menu-list'>")
ul = $("<ul class='clearfix emoji-menu-list frequent-emojis'>")
for emoji in frequentlyUsedEmojis
$(".emoji-menu-content [data-emoji='#{emoji}']").closest('li').clone().appendTo(ul)
$('input.emoji-search').after(ul).after($('<h5>').text('Frequently used'))
@frequentEmojiBlockRendered = true
setupSearch: ->
......@@ -365,4 +367,4 @@ class @AwardsHandler
searchEmojis: (term) ->
$(".emoji-menu-content [data-emoji*='#{term}']").closest('li').clone()
$(".emoji-menu-list:not(.frequent-emojis) [data-emoji*='#{term}']").closest('li').clone()
class CiBuild
class @CiBuild
@interval: null
@state: null
constructor: (build_url, build_status, build_state) ->
constructor: (@build_url, @build_status, @state) ->
clearInterval(CiBuild.interval)
@state = build_state
# Init breakpoint checker
@bp = Breakpoints.get()
@hideSidebar()
$('.js-build-sidebar').niceScroll()
$(document)
.off 'click', '.js-sidebar-build-toggle'
.on 'click', '.js-sidebar-build-toggle', @toggleSidebar
$(window)
.off 'resize.build'
.on 'resize.build', @hideSidebar
if $('#build-trace').length
@getInitialBuildTrace()
@initScrollButtonAffix()
if build_status == "running" || build_status == "pending"
if @build_status is "running" or @build_status is "pending"
#
# Bind autoscroll button to follow build output
#
$("#autoscroll-button").bind "click", ->
$('#autoscroll-button').on 'click', ->
state = $(this).data("state")
if "enabled" is state
$(this).data "state", "disabled"
......@@ -27,25 +39,36 @@ class CiBuild
# Only valid for runnig build when output changes during time
#
CiBuild.interval = setInterval =>
if window.location.href.split("#").first() is build_url
last_state = @state
if window.location.href.split("#").first() is @build_url
@getBuildTrace()
, 4000
getInitialBuildTrace: ->
$.ajax
url: build_url + "/trace.json?state=" + encodeURIComponent(@state)
url: @build_url
dataType: 'json'
success: (build_data) ->
$('.js-build-output').html build_data.trace_html
if build_data.status is 'success' or build_data.status is 'failed'
$('.js-build-refresh').remove()
getBuildTrace: ->
$.ajax
url: "#{@build_url}/trace.json?state=#{encodeURIComponent(@state)}"
dataType: "json"
success: (log) =>
return unless last_state is @state
if log.state and log.status is "running"
if log.state
@state = log.state
if log.status is "running"
if log.append
$('.fa-refresh').before log.html
$('.js-build-output').append log.html
else
$('#build-trace code').html log.html
$('#build-trace code').append '<i class="fa fa-refresh fa-spin"/>'
$('.js-build-output').html log.html
@checkAutoscroll()
else if log.status isnt build_status
Turbolinks.visit build_url
, 4000
else if log.status isnt @build_status
Turbolinks.visit @build_url
checkAutoscroll: ->
$("html,body").scrollTop $("#build-trace").height() if "enabled" is $("#autoscroll-button").data("state")
......@@ -61,4 +84,22 @@ class CiBuild
$body.outerHeight() - ($buildTrace.outerHeight() + $buildTrace.offset().top)
)
@CiBuild = CiBuild
shouldHideSidebar: ->
bootstrapBreakpoint = @bp.getBreakpointSize()
bootstrapBreakpoint is 'xs' or bootstrapBreakpoint is 'sm'
toggleSidebar: =>
if @shouldHideSidebar()
$('.js-build-sidebar')
.toggleClass 'right-sidebar-expanded right-sidebar-collapsed'
hideSidebar: =>
if @shouldHideSidebar()
$('.js-build-sidebar')
.removeClass 'right-sidebar-expanded'
.addClass 'right-sidebar-collapsed'
else
$('.js-build-sidebar')
.removeClass 'right-sidebar-collapsed'
.addClass 'right-sidebar-expanded'
......@@ -23,7 +23,6 @@ class Dispatcher
new Issue()
shortcut_handler = new ShortcutsIssuable()
new ZenMode()
gl.awardsHandler = new AwardsHandler()
when 'projects:milestones:show', 'groups:milestones:show', 'dashboard:milestones:show'
new Milestone()
when 'dashboard:todos:index'
......@@ -54,7 +53,6 @@ class Dispatcher
new Diff()
shortcut_handler = new ShortcutsIssuable(true)
new ZenMode()
gl.awardsHandler = new AwardsHandler()
when "projects:merge_requests:diffs"
new Diff()
new ZenMode()
......
......@@ -3,6 +3,7 @@
window.GitLab ?= {}
GitLab.GfmAutoComplete =
dataLoading: false
dataLoaded: false
dataSource: ''
......@@ -35,7 +36,7 @@ GitLab.GfmAutoComplete =
$.fn.atwho.default.callbacks.filter(query, data, searchKey)
beforeInsert: (value) ->
if value.indexOf('undefined')
if not GitLab.GfmAutoComplete.dataLoaded
@at
else
value
......@@ -182,6 +183,8 @@ GitLab.GfmAutoComplete =
$.getJSON(dataSource)
loadData: (data) ->
@dataLoaded = true
# load members
@input.atwho 'load', '@', data.members
# load issues
......
......@@ -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
# the compiled file.
#
#= require Chart
#= require_tree .
......@@ -97,13 +97,22 @@ class @IssuableBulkActions
$labels = @form.find('.labels-filter input[name="update[label_ids][]"]')
$labels.each (k, label) ->
labelIds.push $(label).val() if label
labelIds.push parseInt($(label).val()) if label
labelIds
###*
* Just an alias of @getUnmarkedIndeterminedLabels
* @return {Array} Array of labels
* Returns Label IDs that will be removed from issue selection
* @return {Array} Array of labels IDs
###
getLabelsToRemove: ->
@getUnmarkedIndeterminedLabels()
result = []
indeterminatedLabels = @getUnmarkedIndeterminedLabels()
labelsToApply = @getLabelsToApply()
indeterminatedLabels.map (id) ->
# We need to exclude label IDs that will be applied
# By not doing this will cause issues from selection to not add labels at all
result.push(id) if labelsToApply.indexOf(id) is -1
result
......@@ -95,8 +95,11 @@ class @LabelsSelect
$newLabelCreateButton.enable()
if label.message?
errors = _.map label.message, (value, key) ->
"#{key} #{value[0]}"
$newLabelError
.text label.message
.html errors.join("<br/>")
.show()
else
$('.dropdown-menu-back', $dropdown.parent()).trigger 'click'
......@@ -254,7 +257,7 @@ class @LabelsSelect
search:
fields: ['title']
selectable: true
filterable: true
toggleLabel: (selected, el) ->
selected_labels = $('.js-label-select').siblings('.dropdown-menu-labels').find('.is-active')
......
((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 @@
$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
......@@ -47,4 +47,4 @@ $ ->
# Make logo clickable as part of a workaround for Safari visited
# link behaviour (See !2690).
$('#logo').on 'click', ->
$('#js-shortcuts-home').get(0).click()
Turbolinks.visit('/')
......@@ -24,11 +24,21 @@ class @MilestoneSelect
if issueUpdateURL
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>'
collapsedSidebarLabelTemplate = _.template(
'<span class="has-tooltip" data-container="body" title="<%= remaining %>" data-placement="left">
<%= _.escape(title) %>
</span>'
)
$dropdown.glDropdown(
data: (term, callback) ->
$.ajax(
......@@ -122,8 +132,9 @@ class @MilestoneSelect
if data.milestone?
data.milestone.namespace = _this.currentProject.namespace
data.milestone.path = _this.currentProject.path
data.milestone.remaining = $.timefor data.milestone.due_date
$value.html(milestoneLinkTemplate(data.milestone))
$sidebarCollapsedValue.find('span').text(data.milestone.title)
$sidebarCollapsedValue.find('span').html(collapsedSidebarLabelTemplate(data.milestone))
else
$value.html(milestoneLinkNoneTemplate)
$sidebarCollapsedValue.find('span').text('No')
......
@Pager =
init: (@limit = 0, preload, @disable = false) ->
init: (@limit = 0, preload, @disable = false, @callback = $.noop) ->
@loading = $('.loading').first()
if preload
......@@ -19,6 +19,7 @@
@loading.hide()
success: (data) ->
Pager.append(data.count, data.html)
Pager.callback()
dataType: "json"
append: (count, html) ->
......
......@@ -7,12 +7,17 @@ class @ProjectNew
@toggleSettingsOnclick()
toggleSettings: ->
checked = $("#project_builds_enabled").prop("checked")
if checked
$('.builds-feature').show()
else
$('.builds-feature').hide()
toggleSettings: =>
@_showOrHide('#project_builds_enabled', '.builds-feature')
@_showOrHide('#project_merge_requests_enabled', '.merge-requests-feature')
toggleSettingsOnclick: ->
$("#project_builds_enabled").on 'click', @toggleSettings
$('#project_builds_enabled, #project_merge_requests_enabled').on 'click', @toggleSettings
_showOrHide: (checkElement, container) ->
$container = $(container)
if $(checkElement).prop('checked')
$container.show()
else
$container.hide()
......@@ -4,8 +4,6 @@ expanded = 'page-sidebar-expanded'
toggleSidebar = ->
$('.page-with-sidebar').toggleClass("#{collapsed} #{expanded}")
$('header').toggleClass("header-collapsed header-expanded")
$('.toggle-nav-collapse i').toggleClass("fa-angle-right fa-angle-left")
$.cookie("collapsed_nav", $('.page-with-sidebar').hasClass(collapsed), { path: '/' })
setTimeout ( ->
niceScrollBars = $('.nicescroll').niceScroll();
......@@ -17,10 +15,3 @@ $(document).on("click", '.toggle-nav-collapse, .side-nav-toggle', (e) ->
toggleSidebar()
)
$ ->
size = bp.getBreakpointSize()
if size is "xs" or size is "sm"
if $('.page-with-sidebar').hasClass(expanded)
toggleSidebar()
......@@ -19,3 +19,8 @@ class @Subscription
action = if status == 'subscribed' then 'Unsubscribe' else 'Subscribe'
btn.find('span').text(action)
@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
@parentEl.find(tabSelector).html(data.html)
@loaded[action] = true
# Fix tooltips
gl.utils.localTimeAgo($('.js-timeago', tabSelector))
loadActivities: (source) ->
return if @loaded['activity'] is true
......
......@@ -95,7 +95,7 @@ class @UsersSelect
data: (term, callback) =>
isAuthorFilter = $('.js-author-search')
@users term, term is '' and isAuthorFilter, (users) =>
@users term, (users) =>
if term.length is 0
showDivider = 0
......@@ -221,7 +221,7 @@ class @UsersSelect
multiple: $(select).hasClass('multiselect')
minimumInputLength: 0
query: (query) =>
@users query.term, @projectId?, (users) =>
@users query.term, (users) =>
data = { results: users }
if query.term.length == 0
......@@ -304,7 +304,7 @@ class @UsersSelect
# Return users list. Filtered by query
# Only active users retrieved
users: (query, fromProject, callback) =>
users: (query, callback) =>
url = @buildUrl(@usersPath)
$.ajax(
......@@ -313,7 +313,7 @@ class @UsersSelect
search: query
per_page: 20
active: true
project_id: @projectId if fromProject
project_id: @projectId
group_id: @groupId
current_user: @showCurrentUser
author_id: @authorId
......
......@@ -79,6 +79,23 @@
@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 {
@include btn-default;
@include btn-white;
......@@ -142,24 +159,7 @@
}
&.btn-grouped {
margin-right: $btn-side-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;
}
@include btn-with-margin;
}
&.disabled {
......@@ -203,11 +203,7 @@
.btn-group {
&.btn-grouped {
margin-right: 7px;
float: left;
&:last-child {
margin-right: 0;
}
@include btn-with-margin;
}
}
......
......@@ -76,6 +76,7 @@ label {
.form-control {
@include box-shadow(none);
border-radius: 3px;
padding: $gl-vert-padding $gl-input-padding;
}
.select-wrapper {
......
......@@ -8,32 +8,14 @@
*/
@mixin gitlab-theme($color-light, $color, $color-darker, $color-dark) {
.page-with-sidebar {
.header-logo {
background: $color-darker;
a {
color: $color-light;
h3 {
.collapse-nav a {
color: $color-light;
}
}
background: $color;
&:hover {
background-color: $color-dark;
a {
color: $white-light;
h3 {
color: $white-light;
}
}
}
}
.collapse-nav a {
color: $white-light;
background: $color;
}
.sidebar-wrapper {
......
......@@ -109,10 +109,8 @@ header {
position: relative;
height: $header-height;
padding-right: 40px;
@media (max-width: $screen-xs-min) {
padding-left: 40px;
}
padding-left: 30px;
transition-duration: .3s;
@media (min-width: $screen-sm-min) {
padding-right: 0;
......@@ -122,9 +120,29 @@ header {
margin-top: -5px;
}
.header-logo {
position: absolute;
left: 50%;
margin-left: -18px;
top: 7px;
transition-duration: .3s;
z-index: 999;
&:hover {
cursor: pointer;
}
@media (max-width: $screen-xs-max) {
right: 25px;
left: auto;
}
}
.title {
margin: 0;
font-size: 19px;
max-width: 400px;
display: inline-block;
line-height: $header-height;
font-weight: normal;
color: $gl-text-color;
......@@ -133,6 +151,10 @@ header {
vertical-align: top;
white-space: nowrap;
@media (max-width: $screen-sm-max) {
max-width: 190px;
}
a {
color: $gl-text-color;
&:hover {
......@@ -160,6 +182,10 @@ header {
.navbar-collapse {
float: right;
border-top: none;
@media (max-width: $screen-xs-max) {
float: none;
}
}
}
......@@ -176,17 +202,20 @@ header {
margin-left: 0;
.header-content {
@media (min-width: $screen-sm-max) {
padding-left: 30px;
transition-duration: .3s;
}
}
}
.header-expanded {
margin-left: 0;
.tanuki-shape {
transition: all 0.8s;
.header-content {
padding-left: $sidebar_width;
transition-duration: .3s;
&:hover, &.highlight {
fill: rgb(255, 255, 255);
transition: all 0.1s;
}
}
......
......@@ -2,6 +2,7 @@
font-family: $regular_font;
font-size: $font-size-base;
&.ui-datepicker,
&.ui-datepicker-inline {
border: 1px solid #ddd;
padding: 10px;
......@@ -10,6 +11,25 @@
.ui-datepicker-header {
background: #fff;
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 {
......@@ -36,21 +56,18 @@
}
.ui-state-highlight {
border: 1px solid #eee;
background: #eee;
border: 0;
background: transparent;
}
.ui-state-active {
.ui-datepicker-calendar {
.ui-state-active,
.ui-state-hover,
.ui-state-focus {
border: 1px solid $gl-primary;
background: $gl-primary;
color: #fff;
}
.ui-state-hover,
.ui-state-focus {
border: 1px solid $row-hover;
background: $row-hover;
color: #333;
}
}
......
......@@ -137,8 +137,16 @@ ul.content-list {
padding-top: 1px;
float: right;
.btn {
padding: 10px 14px;
> .btn,
> .btn-group {
margin-right: $gl-padding-top;
display: inline-block;
margin-top: 4px;
margin-bottom: 4px;
&:last-child {
margin-right: 0;
}
}
}
......
@mixin fade($gradient-direction, $rgba, $gradient-color) {
visibility: visible;
opacity: 1;
z-index: 2;
position: absolute;
bottom: 12px;
width: 43px;
......@@ -68,6 +69,7 @@
}
&.sub-nav {
text-align: center;
background-color: $background-color;
.container-fluid {
......@@ -207,7 +209,7 @@
@media (max-width: $screen-xs-max) {
padding-bottom: 0;
width: 100%;
.btn, form, .dropdown, .dropdown-menu-toggle, .form-control {
margin: 0 0 10px;
display: block;
......@@ -238,16 +240,6 @@
margin: 0;
}
}
/* Small devices (tablets, 768px and lower) */
@media (max-width: $screen-sm-max) {
width: 100%;
text-align: left;
input {
width: 300px;
}
}
}
}
......@@ -259,6 +251,7 @@
background: $background-color;
border-bottom: 1px solid $border-color;
transition-duration: .3s;
text-align: center;
.container-fluid {
position: relative;
......@@ -361,7 +354,7 @@
.fade-right {
@media (min-width: $screen-xs-max) {
right: 67px;
right: 68px;
}
@media (max-width: $screen-xs-min) {
right: 0;
......
......@@ -8,7 +8,7 @@
background: #fff;
border-color: $input-border;
height: 35px;
padding: $gl-vert-padding $gl-btn-padding;
padding: $gl-vert-padding $gl-input-padding;
font-size: $gl-font-size;
line-height: 1.42857143;
border-radius: $border-radius-base;
......
......@@ -35,24 +35,11 @@
}
.sidebar-wrapper {
.header-logo {
height: $header-height;
padding: 8px 26px;
width: $sidebar_width;
position: fixed;
z-index: 999;
overflow: hidden;
transition-duration: .3s;
&:hover {
background-color: #eee;
}
}
.sidebar-user {
padding: 15px 22px;
position: fixed;
bottom: 40px;
bottom: 0;
width: $sidebar_width;
overflow: hidden;
transition-duration: .3s;
......@@ -97,10 +84,10 @@
}
a {
text-align: center;
padding: 8px;
width: $sidebar_width;
padding: 7px 15px 7px 23px;
font-size: $gl-font-size;
color: $gray;
line-height: 24px;
display: block;
text-decoration: none;
font-weight: normal;
......@@ -118,10 +105,9 @@
font-size: 16px;
}
.nav-link-text {
margin-top: 3px;
font-size: 13px;
line-height: 18px;
i,
svg {
margin-right: 13px;
}
&.back-link i {
......@@ -129,6 +115,12 @@
}
}
}
.count {
float: right;
padding: 0 8px;
@include border-radius(6px);
}
}
.sidebar-subnav {
......@@ -143,11 +135,12 @@
.collapse-nav a {
width: $sidebar_width;
position: fixed;
bottom: 0;
top: 0;
left: 0;
font-size: 13px;
padding: 5px 0;
font-size: 18px;
background: transparent;
height: 40px;
height: 50px;
text-align: center;
line-height: 40px;
transition-duration: .3s;
......@@ -170,25 +163,8 @@
.sidebar-wrapper {
width: 0;
.header-logo {
width: 0;
padding: 8px 0;
a {
padding-left: ($sidebar_collapsed_width - 36) / 2;
.gitlab-text-container {
display: none;
}
}
}
#logo {
display: none;
}
.nav-sidebar {
width: $sidebar_collapsed_width;
width: 0;
li {
width: auto;
......@@ -203,6 +179,10 @@
.collapse-nav a {
width: 0;
i {
display: none;
}
}
.sidebar-user {
......@@ -218,9 +198,8 @@
}
.page-sidebar-expanded {
padding-left: $sidebar_width;
@media (max-width: $screen-xs-min) {
@media (max-width: $screen-sm-max) {
padding-left: 0;
}
......@@ -241,20 +220,6 @@
}
}
}
.layout-nav {
@media (max-width: $screen-xs-min) {
padding-right: 0;
}
@media (min-width: $screen-xs-min) and (max-width: $screen-md-min) {
padding-right: 90px;
}
@media (min-width: $screen-md-min) {
padding-right: $sidebar_width;
}
}
}
.right-sidebar-collapsed {
......@@ -273,8 +238,10 @@
padding-right: 0;
@media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) {
&:not(.build-sidebar) {
padding-right: $sidebar_collapsed_width;
}
}
@media (min-width: $screen-md-min) {
padding-right: $gutter_width;
......
......@@ -192,3 +192,8 @@
.text-info:hover {
color: $brand-info;
}
// Prevent datetimes on tooltips to break into two lines
.local-timeago {
white-space: nowrap;
}
......@@ -2,7 +2,7 @@
* Layout
*/
$sidebar_collapsed_width: 62px;
$sidebar_width: 90px;
$sidebar_width: 220px;
$gutter_collapsed_width: 62px;
$gutter_width: 290px;
$gutter_inner_width: 258px;
......@@ -57,6 +57,7 @@ $code_line_height: 1.5;
*/
$gl-padding: 16px;
$gl-btn-padding: 10px;
$gl-input-padding: 10px;
$gl-vert-padding: 6px;
$gl-padding-top: 10px;
......@@ -79,8 +80,8 @@ $provider-btn-not-active-color: #4688f1;
$link-underline-blue: #4a8bee;
$layout-link-gray: #7e7c7c;
$todo-alert-blue: #428bca;
$btn-side-margin: 7px;
$btn-sm-side-margin: 5px;
$btn-side-margin: 10px;
$btn-sm-side-margin: 7px;
$btn-xs-side-margin: 5px;
/*
......@@ -259,3 +260,6 @@ $calendar-header-color: #b8b8b8;
$calendar-hover-bg: #ecf3fe;
$calendar-border-color: rgba(#000, .1);
$calendar-unselectable-bg: #faf9f9;
$ci-output-bg: #1d1f21;
$ci-text-color: #c5c8c6;
......@@ -101,13 +101,21 @@
line-height: 20px;
outline: 0;
&:hover,
&.active,
&:active {
background-color: $white-dark;
background-color: $row-hover;
border-color: $row-hover-border;
box-shadow: none;
outline: 0;
}
&.btn {
&:focus {
outline: 0;
}
}
&.is-loading {
.award-control-icon-normal,
.emoji-icon {
......
......@@ -53,37 +53,92 @@
left: 70px;
}
}
}
.build-widget {
padding: 10px;
background: $background-color;
margin-bottom: 20px;
border-radius: 4px;
.build-header {
position: relative;
padding-right: 40px;
.title {
margin-top: 0;
color: #666;
line-height: 1.5;
@media (min-width: $screen-sm-min) {
padding-right: 0;
}
.attr-name {
color: #777;
a {
color: $gl-gray;
&:hover {
color: $gl-link-color;
text-decoration: none;
}
}
.alert-disabled {
background: $background-color;
a {
color: #3084bb !important;
code {
color: $code-color;
}
.avatar {
float: none;
margin-right: 2px;
margin-left: 2px;
}
}
table.builds {
.build-link {
a {
color: $gl-dark-link-color;
}
}
}
.build-trace {
background: $ci-output-bg;
color: $ci-text-color;
white-space: pre;
overflow-x: auto;
font-size: 12px;
.fa-refresh {
font-size: 24px;
}
.bash {
display: block;
}
}
.right-sidebar.build-sidebar {
padding-top: $gl-padding;
padding-bottom: $gl-padding;
&.right-sidebar-collapsed {
display: none;
}
.block {
width: 100%;
}
.build-sidebar-header {
padding-top: 0;
.gutter-toggle {
margin-top: 0;
}
}
}
.build-detail-row {
margin-bottom: 5px;
}
.build-light-text {
color: $gl-placeholder-color;
}
.build-gutter-toggle {
position: absolute;
top: 50%;
right: 0;
margin-top: -17px;
}
......@@ -29,7 +29,7 @@
}
}
.issuable-sidebar {
.right-sidebar {
a {
color: inherit;
}
......@@ -74,6 +74,10 @@
}
}
.block-first {
padding-top: 0;
}
.title {
color: $gl-text-color;
margin-bottom: 10px;
......
......@@ -50,11 +50,26 @@
.label-row {
.label-name {
display: block;
margin-bottom: 10px;
@media (min-width: $screen-sm-min) {
display: inline-block;
width: 170px;
width: 200px;
margin-bottom: 0;
}
}
@media (max-width: $screen-xs-min) {
.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 @@
padding: 3px 4px;
}
.label-subscription {
display: inline-block;
}
.dropdown-labels-error {
padding: 5px 10px;
margin-bottom: 10px;
......@@ -79,62 +90,27 @@
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 {
.btn-action {
color: $gl-dark-link-color;
.prepend-left-10, .prepend-description-left {
display: inline-block;
width: 40%;
.fa {
font-size: 18px;
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 {
border-color: transparent;
padding: 6px;
color: $gl-text-color;
&:hover {
color: $gl-link-color;
&.label-subscribe-button {
padding-left: 0;
&.remove-row {
color: $gl-danger;
}
}
i {
color: $gl-text-color;
}
.append-right-20 {
a {
color: $gl-text-color;
}
@media (max-width: $screen-xs-min) {
display: block;
margin-bottom: 10px;
}
.dropdown {
@media (min-width: $screen-sm-min) {
float: right;
}
}
}
......@@ -186,3 +162,23 @@
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 @@
font-size: 17px;
margin: 5px 0;
color: $gl-gray-dark;
&.has-conflicts .fa-exclamation-triangle {
color: $gl-warning;
}
}
p:last-child {
......
......@@ -129,17 +129,8 @@
display: none;
font-size: 15px;
.form-actions {
padding-left: 20px;
.btn-save {
float: left;
}
.note-form-option {
float: left;
padding: 2px 0 0 25px;
}
.md-area {
background-color: #fff;
}
}
......
......@@ -11,18 +11,15 @@
$magenta: #cd00cd;
$cyan: #00cdcd;
$white: #e5e5e5;
$l-black: #7f7f7f;
$l-red: #f00;
$l-green: #0f0;
$l-yellow: #ff0;
$l-blue: #5c5cff;
$l-magenta: #f0f;
$l-cyan: #0ff;
$l-white: #fff;
$l-black: #373b41;
$l-red: #c66;
$l-green: #b5bd68;
$l-yellow: #f0c674;
$l-blue: #81a2be;
$l-magenta: #b294bb;
$l-cyan: #8abeb7;
$l-white: $ci-text-color;
.term-bold {
font-weight: bold;
}
.term-italic {
font-style: italic;
}
......
......@@ -6,6 +6,7 @@ class ApplicationController < ActionController::Base
include Gitlab::GonHelper
include GitlabRoutingHelper
include PageLayoutHelper
include WorkhorseHelper
before_action :authenticate_user_from_token!
before_action :authenticate_user!
......
......@@ -42,7 +42,7 @@ class JwtController < ApplicationController
end
def authenticate_user(login, password)
user = Gitlab::Auth.find_in_gitlab_or_ldap(login, password)
user = Gitlab::Auth.find_with_user_password(login, password)
Gitlab::Auth.rate_limit!(request.ip, success: user.present?, login: login)
user
end
......
......@@ -3,10 +3,11 @@ class Profiles::NotificationsController < Profiles::ApplicationController
@user = current_user
@group_notifications = current_user.notification_settings.for_groups
@project_notifications = current_user.notification_settings.for_projects
@global_notification_setting = current_user.global_notification_setting
end
def update
if current_user.update_attributes(user_params)
if current_user.update_attributes(user_params) && update_notification_settings
flash[:notice] = "Notification settings saved"
else
flash[:alert] = "Failed to save new settings"
......@@ -16,6 +17,18 @@ class Profiles::NotificationsController < Profiles::ApplicationController
end
def user_params
params.require(:user).permit(:notification_email, :notification_level)
params.require(:user).permit(:notification_email)
end
def global_notification_setting_params
params.require(:global_notification_setting).permit(:level)
end
private
def update_notification_settings
return true unless global_notification_setting_params
current_user.global_notification_setting.update_attributes(global_notification_setting_params)
end
end
......@@ -10,10 +10,7 @@ class Projects::AvatarsController < Projects::ApplicationController
return if cached_blob?
headers.store(*Gitlab::Workhorse.send_git_blob(@repository, @blob))
headers['Content-Disposition'] = 'inline'
headers['Content-Type'] = safe_content_type(@blob)
head :ok # 'render nothing: true' messes up the Content-Type
send_git_blob @repository, @blob
else
render_404
end
......
......@@ -41,7 +41,7 @@ class Projects::BuildsController < Projects::ApplicationController
def trace
respond_to do |format|
format.json do
render json: @build.trace_with_state(params[:state]).merge!(id: @build.id, status: @build.status)
render json: @build.trace_with_state(params[:state].presence).merge!(id: @build.id, status: @build.status)
end
end
end
......
......@@ -43,7 +43,7 @@ class Projects::GitHttpController < Projects::ApplicationController
return if project && project.public? && upload_pack?
authenticate_or_request_with_http_basic do |login, password|
auth_result = Gitlab::Auth.find(login, password, project: project, ip: request.ip)
auth_result = Gitlab::Auth.find_for_git_client(login, password, project: project, ip: request.ip)
if auth_result.type == :ci && upload_pack?
@ci = true
......
......@@ -61,12 +61,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController
format.json { render json: @merge_request }
format.patch { render text: @merge_request.to_patch }
format.diff do
headers.store(*Gitlab::Workhorse.send_git_diff(@project.repository,
@merge_request.diff_base_commit.id,
@merge_request.last_commit.id))
headers['Content-Disposition'] = 'inline'
return render_404 unless @merge_request.diff_refs
head :ok
send_git_diff @project.repository, @merge_request.diff_refs
end
end
end
......
......@@ -18,10 +18,7 @@ class Projects::RawController < Projects::ApplicationController
if @blob.lfs_pointer?
send_lfs_object
else
headers.store(*Gitlab::Workhorse.send_git_blob(@repository, @blob))
headers['Content-Disposition'] = 'inline'
headers['Content-Type'] = safe_content_type(@blob)
head :ok # 'render nothing: true' messes up the Content-Type
send_git_blob @repository, @blob
end
else
render_404
......
......@@ -11,8 +11,7 @@ class Projects::RepositoriesController < Projects::ApplicationController
end
def archive
headers.store(*Gitlab::Workhorse.send_git_archive(@project, params[:ref], params[:format]))
head :ok
send_git_archive @repository, ref: params[:ref], format: params[:format]
rescue => ex
logger.error("#{self.class.name}: #{ex}")
return git_not_found!
......
......@@ -95,7 +95,7 @@ class Projects::WikisController < Projects::ApplicationController
ext.analyze(text, author: current_user)
render json: {
body: view_context.markdown(text, pipeline: :wiki, project_wiki: @project_wiki),
body: view_context.markdown(text, pipeline: :wiki, project_wiki: @project_wiki, page_slug: params[:id]),
references: {
users: ext.users.map(&:username)
}
......
......@@ -234,7 +234,7 @@ class ProjectsController < Projects::ApplicationController
:issues_tracker_id, :default_branch,
:wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar,
:builds_enabled, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex,
:public_builds,
:public_builds, :only_allow_merge_if_build_succeeds
)
end
......
......@@ -14,4 +14,8 @@ module BranchesHelper
::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(branch_name)
end
def project_branches
options_for_select(@project.repository.branch_names, @project.default_branch)
end
end
......@@ -67,9 +67,9 @@ module DropdownsHelper
end
end
def dropdown_filter(placeholder)
def dropdown_filter(placeholder, search_id: nil)
content_tag :div, class: "dropdown-input" do
filter_output = search_field_tag nil, nil, class: "dropdown-input-field", placeholder: placeholder
filter_output = search_field_tag search_id, nil, class: "dropdown-input-field", placeholder: placeholder
filter_output << icon('search', class: "dropdown-input-search")
filter_output << icon('times', class: "dropdown-input-clear js-dropdown-input-clear", role: "button")
......
......@@ -108,7 +108,7 @@ module GitlabMarkdownHelper
def render_wiki_content(wiki_page)
case wiki_page.format
when :markdown
markdown(wiki_page.content, pipeline: :wiki, project_wiki: @project_wiki)
markdown(wiki_page.content, pipeline: :wiki, project_wiki: @project_wiki, page_slug: wiki_page.slug)
when :asciidoc
asciidoc(wiki_page.content)
else
......
......@@ -32,7 +32,7 @@ module LabelsHelper
# link_to_label(label) { "My Custom Label Text" }
#
# 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
link = send("namespace_project_#{type.to_s.pluralize}_path",
project.namespace,
......@@ -40,9 +40,9 @@ module LabelsHelper
label_name: [label.name])
if block_given?
link_to link, &block
link_to link, class: css_class, &block
else
link_to render_colored_label(label, tooltip: tooltip), link
link_to render_colored_label(label, tooltip: tooltip), link, class: css_class
end
end
......
......@@ -56,7 +56,7 @@ module MilestonesHelper
def milestone_remaining_days(milestone)
if milestone.expired?
content_tag(:strong, 'expired')
content_tag(:strong, 'Past due')
elsif milestone.due_date
days = milestone.remaining_days
content = content_tag(:strong, days)
......
......@@ -30,17 +30,13 @@ module NavHelper
else
"page-gutter right-sidebar-expanded"
end
elsif current_path?('builds#show')
"page-gutter build-sidebar right-sidebar-expanded"
end
end
def nav_header_class
class_name =
if nav_menu_collapsed?
"header-collapsed"
else
"header-expanded"
end
class_name += " with-horizontal-nav" if defined?(nav) && nav
class_name = " with-horizontal-nav" if defined?(nav) && nav
class_name
end
......
......@@ -61,4 +61,23 @@ module NotificationsHelper
end
end
end
def notification_level_radio_buttons
html = ""
NotificationSetting.levels.each_key do |level|
level = level.to_sym
next if level == :global
html << content_tag(:div, class: "radio") do
content_tag(:label, { value: level }) do
radio_button_tag(:"global_notification_setting[level]", level, @global_notification_setting.level.to_sym == level) +
content_tag(:div, level.to_s.capitalize, class: "level-title") +
content_tag(:p, notification_description(level))
end
end
end
html.html_safe
end
end
# Helpers to send Git blobs, diffs or archives through Workhorse.
# Workhorse will also serve files when using `send_file`.
module WorkhorseHelper
# Send a Git blob through Workhorse
def send_git_blob(repository, blob)
headers.store(*Gitlab::Workhorse.send_git_blob(repository, blob))
headers['Content-Disposition'] = 'inline'
headers['Content-Type'] = safe_content_type(blob)
head :ok # 'render nothing: true' messes up the Content-Type
end
# Send a Git diff through Workhorse
def send_git_diff(repository, diff_refs)
headers.store(*Gitlab::Workhorse.send_git_diff(repository, diff_refs))
headers['Content-Disposition'] = 'inline'
head :ok
end
# Archive a Git repository and send it through Workhorse
def send_git_archive(repository, ref:, format:)
headers.store(*Gitlab::Workhorse.send_git_archive(repository, ref: ref, format: format))
head :ok
end
end
......@@ -194,7 +194,7 @@ module Ci
def trace_length
if raw_trace
raw_trace.length
raw_trace.bytesize
else
0
end
......@@ -216,7 +216,7 @@ module Ci
recreate_trace_dir
File.truncate(path_to_trace, offset) if File.exist?(path_to_trace)
File.open(path_to_trace, 'a') do |f|
File.open(path_to_trace, 'ab') do |f|
f.write(trace_part)
end
end
......
......@@ -3,7 +3,7 @@ module Ci
extend Ci::Model
belongs_to :trigger, class_name: 'Ci::Trigger'
belongs_to :commit, class_name: 'Ci::Pipeline', foreign_key: :commit_id
belongs_to :pipeline, class_name: 'Ci::Pipeline', foreign_key: :commit_id
has_many :builds, class_name: 'Ci::Build'
serialize :variables
......
......@@ -260,19 +260,20 @@ class MergeRequest < ActiveRecord::Base
end
def mergeable?
return false unless open? && !work_in_progress? && !broken?
return false unless mergeable_state?
check_if_can_be_merged
can_be_merged?
end
def gitlab_merge_status
if work_in_progress?
"work_in_progress"
else
merge_status_name
end
def mergeable_state?
return false unless open?
return false if work_in_progress?
return false if broken?
return false unless mergeable_ci_state?
true
end
def can_cancel_merge_when_build_succeeds?(current_user)
......@@ -481,6 +482,12 @@ class MergeRequest < ActiveRecord::Base
::Gitlab::GitAccess.new(user, project).can_push_to_branch?(target_branch)
end
def mergeable_ci_state?
return true unless project.only_allow_merge_if_build_succeeds?
!pipeline || pipeline.success?
end
def state_human_name
if merged?
"Merged"
......
......@@ -7,7 +7,6 @@ class NotificationSetting < ActiveRecord::Base
belongs_to :source, polymorphic: true
validates :user, presence: true
validates :source, presence: true
validates :level, presence: true
validates :user_id, uniqueness: { scope: [:source_type, :source_id],
message: "already exists in source",
......
......@@ -146,7 +146,6 @@ class Project < ActiveRecord::Base
message: Gitlab::Regex.project_path_regex_message }
validates :issues_enabled, :merge_requests_enabled,
:wiki_enabled, inclusion: { in: [true, false] }
validates :issues_tracker_id, length: { maximum: 255 }, allow_blank: true
validates :namespace, presence: true
validates_uniqueness_of :name, scope: :namespace_id
validates_uniqueness_of :path, scope: :namespace_id
......@@ -253,20 +252,69 @@ class Project < ActiveRecord::Base
non_archived.where(table[:name].matches(pattern))
end
def find_with_namespace(id)
namespace_path, project_path = id.split('/', 2)
# Finds a single project for the given path.
#
# path - The full project path (including namespace path).
#
# Returns a Project, or nil if no project could be found.
def find_with_namespace(path)
where_paths_in([path]).reorder(nil).take
end
# Builds a relation to find multiple projects by their full paths.
#
# Each path must be in the following format:
#
# namespace_path/project_path
#
# For example:
#
# gitlab-org/gitlab-ce
#
# Usage:
#
# Project.where_paths_in(%w{gitlab-org/gitlab-ce gitlab-org/gitlab-ee})
#
# This would return the projects with the full paths matching the values
# given.
#
# paths - An Array of full paths (namespace path + project path) for which
# to find the projects.
#
# Returns an ActiveRecord::Relation.
def where_paths_in(paths)
wheres = []
cast_lower = Gitlab::Database.postgresql?
return nil if !namespace_path || !project_path
paths.each do |path|
namespace_path, project_path = path.split('/', 2)
# Use of unscoped ensures we're not secretly adding any ORDER BYs, which
# have a negative impact on performance (and aren't needed for this
# query).
projects = unscoped.
joins(:namespace).
iwhere('namespaces.path' => namespace_path)
next unless namespace_path && project_path
projects.find_by('projects.path' => project_path) ||
projects.iwhere('projects.path' => project_path).take
namespace_path = connection.quote(namespace_path)
project_path = connection.quote(project_path)
where = "(namespaces.path = #{namespace_path}
AND projects.path = #{project_path})"
if cast_lower
where = "(
#{where}
OR (
LOWER(namespaces.path) = LOWER(#{namespace_path})
AND LOWER(projects.path) = LOWER(#{project_path})
)
)"
end
wheres << where
end
if wheres.empty?
none
else
joins(:namespace).where(wheres.join(' OR '))
end
end
def visibility_levels
......@@ -523,13 +571,21 @@ class Project < ActiveRecord::Base
end
def external_issue_tracker
if has_external_issue_tracker.nil? # To populate existing projects
cache_has_external_issue_tracker
end
if has_external_issue_tracker?
return @external_issue_tracker if defined?(@external_issue_tracker)
@external_issue_tracker ||=
services.issue_trackers.active.without_defaults.first
@external_issue_tracker = services.external_issue_trackers.first
else
nil
end
end
def can_have_issues_tracker_id?
self.issues_enabled && !self.default_issues_tracker?
def cache_has_external_issue_tracker
update_column(:has_external_issue_tracker, services.external_issue_trackers.any?)
end
def build_missing_services
......
......@@ -38,9 +38,9 @@ class IssueTrackerService < Service
if enabled_in_gitlab_config
self.properties = {
title: issues_tracker['title'],
project_url: add_issues_tracker_id(issues_tracker['project_url']),
issues_url: add_issues_tracker_id(issues_tracker['issues_url']),
new_issue_url: add_issues_tracker_id(issues_tracker['new_issue_url'])
project_url: issues_tracker['project_url'],
issues_url: issues_tracker['issues_url'],
new_issue_url: issues_tracker['new_issue_url']
}
else
self.properties = {}
......@@ -83,16 +83,4 @@ class IssueTrackerService < Service
def issues_tracker
Gitlab.config.issues_tracker[to_param]
end
def add_issues_tracker_id(url)
if self.project
id = self.project.issues_tracker_id
if id
url = url.gsub(":issues_tracker_id", id)
end
end
url
end
end
......@@ -16,6 +16,7 @@ class Service < ActiveRecord::Base
after_initialize :initialize_properties
after_commit :reset_updated_properties
after_commit :cache_project_has_external_issue_tracker
belongs_to :project
has_one :service_hook
......@@ -34,6 +35,7 @@ class Service < ActiveRecord::Base
scope :note_hooks, -> { where(note_events: true, active: true) }
scope :build_hooks, -> { where(build_events: true, active: true) }
scope :wiki_page_hooks, -> { where(wiki_page_events: true, active: true) }
scope :external_issue_trackers, -> { issue_trackers.active.without_defaults }
default_value_for :category, 'common'
......@@ -192,4 +194,12 @@ class Service < ActiveRecord::Base
service.project_id = project_id
service if service.save
end
private
def cache_project_has_external_issue_tracker
if project && !project.destroyed?
project.cache_has_external_issue_tracker
end
end
end
......@@ -10,6 +10,8 @@ class User < ActiveRecord::Base
include CaseSensitivity
include TokenAuthenticatable
DEFAULT_NOTIFICATION_LEVEL = :participating
add_authentication_token_field :authentication_token
default_value_for :admin, false
......@@ -99,7 +101,6 @@ class User < ActiveRecord::Base
presence: true,
uniqueness: { case_sensitive: false }
validates :notification_level, presence: true
validate :namespace_uniq, if: ->(user) { user.username_changed? }
validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? }
validate :unique_email, if: ->(user) { user.email_changed? }
......@@ -133,13 +134,6 @@ class User < ActiveRecord::Base
# Note: When adding an option, it MUST go on the end of the array.
enum project_view: [:readme, :activity, :files]
# Notification level
# Note: When adding an option, it MUST go on the end of the array.
#
# TODO: Add '_prefix: :notification' to enum when update to Rails 5. https://github.com/rails/rails/pull/19813
# Because user.notification_disabled? is much better than user.disabled?
enum notification_level: [:disabled, :participating, :watch, :global, :mention]
alias_attribute :private_token, :authentication_token
delegate :path, to: :namespace, allow_nil: true, prefix: true
......@@ -800,6 +794,17 @@ class User < ActiveRecord::Base
notification_settings.find_or_initialize_by(source: source)
end
# Lazy load global notification setting
# Initializes User setting with Participating level if setting not persisted
def global_notification_setting
return @global_notification_setting if defined?(@global_notification_setting)
@global_notification_setting = notification_settings.find_or_initialize_by(source: nil)
@global_notification_setting.update_attributes(level: NotificationSetting.levels[DEFAULT_NOTIFICATION_LEVEL]) unless @global_notification_setting.persisted?
@global_notification_setting
end
def assigned_open_merge_request_count(force: false)
Rails.cache.fetch(['users', id, 'assigned_open_merge_request_count'], force: force) do
assigned_merge_requests.opened.count
......
......@@ -11,7 +11,7 @@ module Ci
trigger_request = trigger.trigger_requests.create!(
variables: variables,
commit: pipeline,
pipeline: pipeline,
)
if pipeline.create_builds(nil, trigger_request)
......
......@@ -279,10 +279,11 @@ class NotificationService
end
def users_with_global_level_watch(ids)
User.where(
id: ids,
notification_level: NotificationSetting.levels[:watch]
).pluck(:id)
NotificationSetting.where(
user_id: ids,
source_type: nil,
level: NotificationSetting.levels[:watch]
).pluck(:user_id)
end
# Build a list of users based on project notifcation settings
......@@ -352,7 +353,9 @@ class NotificationService
users = users.reject(&:blocked?)
users.reject do |user|
next user.notification_level == level unless project
global_notification_setting = user.global_notification_setting
next global_notification_setting.level == level unless project
setting = user.notification_settings_for(project)
......@@ -361,13 +364,13 @@ class NotificationService
end
# reject users who globally set mention notification and has no setting per project/group
next user.notification_level == level unless setting
next global_notification_setting.level == level unless setting
# reject users who set mention notification in project
next true if setting.level == level
# reject users who have mention level in project and disabled in global settings
setting.global? && user.notification_level == level
setting.global? && global_notification_setting.level == level
end
end
......@@ -456,7 +459,6 @@ class NotificationService
def build_recipients(target, project, current_user, action: nil, previous_assignee: nil)
recipients = target.participants(current_user)
recipients = add_project_watchers(recipients, project)
recipients = reject_mention_users(recipients, project)
......
......@@ -20,7 +20,7 @@ class TodoService
# * mark all pending todos related to the issue for the current user as done
#
def update_issue(issue, current_user)
create_mention_todos(issue.project, issue, current_user)
update_issuable(issue, current_user)
end
# When close an issue we should:
......@@ -53,7 +53,7 @@ class TodoService
# * create a todo for each mentioned user on merge request
#
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
# When close a merge request we should:
......@@ -153,6 +153,13 @@ class TodoService
create_mention_todos(issuable.project, issuable, author)
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)
# Skip system notes, and notes on project snippet
return if note.system? || note.for_snippet?
......
......@@ -7,9 +7,6 @@
= awards.count
- if current_user
:javascript
gl.awardMenuUrl = "#{emojis_path}"
.award-menu-holder.js-award-holder
%button.btn.award-control.js-add-award{ type: "button" }
= icon('smile-o', class: "award-control-icon award-control-icon-normal")
......
......@@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.link href: issues_dashboard_url(format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml"
xml.link href: issues_dashboard_url, rel: "alternate", type: "text/html"
xml.id issues_dashboard_url
xml.updated @issues.first.created_at.xmlschema if @issues.any?
xml.updated @issues.first.created_at.xmlschema if @issues.reorder(nil).any?
xml << render(partial: 'issues/issue', collection: @issues) if @issues.any?
xml << render(partial: 'issues/issue', collection: @issues) if @issues.reorder(nil).any?
end
......@@ -6,7 +6,8 @@
- if @user.two_factor_otp_enabled?
%h5 Authenticate via Two-Factor App
= 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'
%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
......
......@@ -34,7 +34,7 @@
%strong.member-access-level= member.human_access
- if show_controls
- 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
= icon('pencil')
......
......@@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.link href: issues_group_url(format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml"
xml.link href: issues_group_url, rel: "alternate", type: "text/html"
xml.id issues_group_url
xml.updated @issues.first.created_at.xmlschema if @issues.any?
xml.updated @issues.first.created_at.xmlschema if @issues.reorder(nil).any?
xml << render(partial: 'issues/issue', collection: @issues) if @issues.any?
xml << render(partial: 'issues/issue', collection: @issues) if @issues.reorder(nil).any?
end
......@@ -39,9 +39,8 @@
.col-md-6
.form-group
= f.label :due_date, "Due Date", class: "control-label"
.col-sm-10= f.hidden_field :due_date
.col-sm-10
.datepicker
= f.text_field :due_date, class: "datepicker form-control", placeholder: "Select due date"
.form-actions
= f.submit 'Create Milestone', class: "btn-create btn"
......
- if nav_menu_collapsed?
= link_to icon('angle-right'), '#', class: 'toggle-nav-collapse', title: "Open/Close"
- else
= link_to icon('angle-left'), '#', class: 'toggle-nav-collapse', title: "Open/Close"
= link_to icon('bars'), '#', class: 'toggle-nav-collapse', title: "Open/Close"
.page-with-sidebar{ class: "#{page_sidebar_class} #{page_gutter_class}" }
.page-with-sidebar.page-sidebar-collapsed{ class: "#{page_gutter_class}" }
.sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
= link_to root_path, class: 'gitlab-text-container-link', title: 'Dashboard', id: 'js-shortcuts-home' do
.header-logo
#logo
= brand_header_logo
- if defined?(sidebar) && sidebar
= render "layouts/nav/#{sidebar}"
......@@ -16,7 +12,9 @@
= render partial: 'layouts/collapse_button'
- if current_user
= 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 s46'
= image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s36'
.username
= current_user.username
- if defined?(nav) && nav
.layout-nav
.container-fluid
......
.page-with-sidebar{ class: page_sidebar_class }
= render "layouts/broadcast"
.sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
.header-logo
%a#logo
= brand_header_logo
= link_to root_path, class: 'gitlab-text-container-link', title: 'Dashboard', id: 'js-shortcuts-home' do
.gitlab-text-container
%h3 GitLab
- if defined?(sidebar) && sidebar
= render "layouts/ci/#{sidebar}"
......
%header.navbar.navbar-fixed-top.navbar-gitlab{ class: nav_header_class }
%header.navbar.navbar-fixed-top.navbar-gitlab.header-collapsed{ class: nav_header_class }
%div{ class: fluid_layout ? "container-fluid" : "container-fluid" }
.header-content
%button.side-nav-toggle{type: 'button'}
......@@ -50,6 +50,10 @@
%h1.title= title
.header-logo
#logo
= brand_header_logo
= yield :header_content
= render 'shared/outdated_browser'
......
......@@ -2,102 +2,106 @@
= nav_link(controller: :dashboard, html_options: {class: 'home'}) do
= link_to admin_root_path, title: 'Overview' do
= icon('dashboard fw')
.nav-link-text
%span
Overview
= nav_link(controller: [:admin, :projects]) do
= link_to admin_namespaces_projects_path, title: 'Projects' do
= icon('cube fw')
.nav-link-text
%span
Projects
= nav_link(controller: :users) do
= link_to admin_users_path, title: 'Users' do
= icon('user fw')
.nav-link-text
%span
Users
= nav_link(controller: :groups) do
= link_to admin_groups_path, title: 'Groups' do
= icon('group fw')
.nav-link-text
%span
Groups
= nav_link(controller: :deploy_keys) do
= link_to admin_deploy_keys_path, title: 'Deploy Keys' do
= icon('key fw')
.nav-link-text
%span
Deploy Keys
= nav_link path: ['runners#index', 'runners#show'] do
= link_to admin_runners_path, title: 'Runners' do
= icon('cog fw')
.nav-link-text
%span
Runners
%span.count= number_with_delimiter(Ci::Runner.count(:all))
= nav_link path: 'builds#index' do
= link_to admin_builds_path, title: 'Builds' do
= icon('link fw')
.nav-link-text
%span
Builds
%span.count= number_with_delimiter(Ci::Build.count(:all))
= nav_link(controller: :logs) do
= link_to admin_logs_path, title: 'Logs' do
= icon('file-text fw')
.nav-link-text
%span
Logs
= nav_link(controller: :health_check) do
= link_to admin_health_check_path, title: 'Health Check' do
= icon('medkit fw')
.nav-link-text
%span
Health Check
= nav_link(controller: :broadcast_messages) do
= link_to admin_broadcast_messages_path, title: 'Messages' do
= icon('bullhorn fw')
.nav-link-text
%span
Messages
= nav_link(controller: :hooks) do
= link_to admin_hooks_path, title: 'Hooks' do
= icon('external-link fw')
.nav-link-text
%span
Hooks
= nav_link(controller: :background_jobs) do
= link_to admin_background_jobs_path, title: 'Background Jobs' do
= icon('cog fw')
.nav-link-text
%span
Background Jobs
= nav_link(controller: :appearances) do
= link_to admin_appearances_path, title: 'Appearances' do
= icon('image')
.nav-link-text
%span
Appearance
= nav_link(controller: :applications) do
= link_to admin_applications_path, title: 'Applications' do
= icon('cloud fw')
.nav-link-text
%span
Applications
= nav_link(controller: :services) do
= link_to admin_application_settings_services_path, title: 'Service Templates' do
= icon('copy fw')
.nav-link-text
%span
Service Templates
= nav_link(controller: :labels) do
= link_to admin_labels_path, title: 'Labels' do
= icon('tags fw')
.nav-link-text
%span
Labels
= nav_link(controller: :abuse_reports) do
= link_to admin_abuse_reports_path, title: "Abuse Reports" do
= icon('exclamation-circle fw')
.nav-link-text
%span
Abuse Reports
%span.count= number_with_delimiter(AbuseReport.count(:all))
- if askimet_enabled?
= nav_link(controller: :spam_logs) do
= link_to admin_spam_logs_path, title: "Spam Logs" do
= icon('exclamation-triangle fw')
.nav-link-text
%span
Spam Logs
%span.count= number_with_delimiter(SpamLog.count(:all))
= nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do
= link_to admin_application_settings_path, title: 'Settings' do
= icon('cogs fw')
.nav-link-text
%span
Settings
......@@ -2,50 +2,53 @@
= nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: "#{project_tab_class} home"}) do
= link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do
= navbar_icon('project')
.nav-link-text
%span
Projects
= nav_link(controller: :todos) do
= link_to dashboard_todos_path, title: 'Todos' do
= icon('bell fw')
.nav-link-text
%span
Todos
%span.count= number_with_delimiter(todos_pending_count)
= nav_link(path: 'dashboard#activity') do
= link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do
= navbar_icon('activity')
.nav-link-text
%span
Activity
= nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do
= link_to dashboard_groups_path, title: 'Groups' do
= navbar_icon('group')
.nav-link-text
%span
Groups
= nav_link(controller: 'dashboard/milestones') do
= link_to dashboard_milestones_path, title: 'Milestones' do
= navbar_icon('milestones')
.nav-link-text
%span
Milestones
= nav_link(path: 'dashboard#issues') do
= link_to assigned_issues_dashboard_path, title: 'Issues', class: 'dashboard-shortcuts-issues' do
= navbar_icon('issues')
.nav-link-text
%span
Issues
%span.count= number_with_delimiter(current_user.assigned_issues.opened.count)
= nav_link(path: 'dashboard#merge_requests') do
= link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'dashboard-shortcuts-merge_requests' do
= navbar_icon('mr')
.nav-link-text
%span
Merge Requests
%span.count= number_with_delimiter(current_user.assigned_merge_requests.opened.count)
= nav_link(controller: :snippets) do
= link_to dashboard_snippets_path, title: 'Snippets' do
= icon('clipboard fw')
.nav-link-text
%span
Snippets
= nav_link(controller: :help) do
= link_to help_path, title: 'Help' do
= icon('question-circle fw')
.nav-link-text
%span
Help
= nav_link(html_options: {class: profile_tab_class}) do
= link_to profile_path, title: 'Profile Settings', data: {placement: 'bottom'} do
= icon('user fw')
.nav-link-text
%span
Profile Settings
......@@ -2,20 +2,20 @@
= nav_link(path: ['dashboard#show', 'root#show', 'projects#trending', 'projects#starred', 'projects#index'], html_options: {class: 'home'}) do
= link_to explore_root_path, title: 'Projects' do
= icon('bookmark fw')
.nav-link-text
%span
Projects
= nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do
= link_to explore_groups_path, title: 'Groups' do
= icon('group fw')
.nav-link-text
%span
Groups
= nav_link(controller: :snippets) do
= link_to explore_snippets_path, title: 'Snippets' do
= icon('clipboard fw')
.nav-link-text
%span
Snippets
= nav_link(controller: :help) do
= link_to help_path, title: 'Help' do
= icon('question-circle fw')
.nav-link-text
%span
Help
......@@ -5,36 +5,30 @@
.fade-left
= nav_link(path: 'groups#show', html_options: {class: 'home'}) do
= link_to group_path(@group), title: 'Home' do
= navbar_icon('group')
%span
Group
= nav_link(path: 'groups#activity') do
= link_to activity_group_path(@group), title: 'Activity' do
= navbar_icon('activity')
%span
Activity
= nav_link(controller: [:group, :milestones]) do
= link_to group_milestones_path(@group), title: 'Milestones' do
= navbar_icon('milestones')
%span
Milestones
= nav_link(path: 'groups#issues') do
= link_to issues_group_path(@group), title: 'Issues' do
= navbar_icon('issues')
%span
Issues
- issues = IssuesFinder.new(current_user, group_id: @group.id, state: 'opened').execute
%span.badge.count= number_with_delimiter(issues.count)
= nav_link(path: 'groups#merge_requests') do
= link_to merge_requests_group_path(@group), title: 'Merge Requests' do
= navbar_icon('mr')
%span
Merge Requests
- merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened').execute
%span.badge.count= number_with_delimiter(merge_requests.count)
= nav_link(controller: [:group_members]) do
= link_to group_group_members_path(@group), title: 'Members' do
= navbar_icon('members')
%span
Members
.fade-right
......@@ -2,51 +2,41 @@
.fade-left
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
= link_to profile_path, title: 'Profile Settings' do
= icon('user fw')
%span
Profile
= nav_link(controller: [:accounts, :two_factor_auths]) do
= link_to profile_account_path, title: 'Account' do
= icon('gear fw')
%span
Account
- if current_application_settings.user_oauth_applications?
= nav_link(controller: 'oauth/applications') do
= link_to applications_profile_path, title: 'Applications' do
= icon('cloud fw')
%span
Applications
= nav_link(controller: :emails) do
= link_to profile_emails_path, title: 'Emails' do
= icon('envelope-o fw')
%span
Emails
- unless current_user.ldap_user?
= nav_link(controller: :passwords) do
= link_to edit_profile_password_path, title: 'Password' do
= icon('lock fw')
%span
Password
= nav_link(controller: :notifications) do
= link_to profile_notifications_path, title: 'Notifications' do
= icon('inbox fw')
%span
Notifications
= nav_link(controller: :keys) do
= link_to profile_keys_path, title: 'SSH Keys' do
= icon('key fw')
%span
SSH Keys
= nav_link(controller: :preferences) do
= link_to profile_preferences_path, title: 'Preferences' do
-# TODO (rspeicher): Better icon?
= icon('image fw')
%span
Preferences
= nav_link(path: 'profiles#audit_log') do
= link_to audit_log_profile_path, title: 'Audit Log' do
= icon('history fw')
%span
Audit Log
.fade-right
......@@ -24,55 +24,41 @@
.fade-left
= nav_link(path: 'projects#show', html_options: {class: 'home'}) do
= link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do
= navbar_icon('project')
%span
Project
= nav_link(path: 'projects#activity') do
= link_to activity_project_path(@project), title: 'Activity', class: 'shortcuts-project-activity' do
= navbar_icon('activity')
%span
Activity
- if project_nav_tab? :files
= nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare repositories tags branches releases network)) do
= link_to project_files_path(@project), title: 'Code', class: 'shortcuts-tree' do
= icon('code fw')
%span
Code
- if project_nav_tab? :pipelines
= nav_link(controller: :pipelines) do
= link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do
= navbar_icon('pipelines')
%span
Pipelines
- if project_nav_tab? :container_registry
= nav_link(controller: %w(container_registry)) do
= link_to project_container_registry_path(@project), title: 'Container Registry', class: 'shortcuts-container-registry' do
= icon('hdd-o fw')
%span
Registry
- if project_nav_tab? :graphs
= nav_link(controller: %w(graphs)) do
= link_to namespace_project_graph_path(@project.namespace, @project, current_ref), title: 'Graphs', class: 'shortcuts-graphs' do
= icon('area-chart fw')
%span
Graphs
- if project_nav_tab? :milestones
= nav_link(controller: :milestones) do
= link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones' do
= navbar_icon('milestones')
%span
Milestones
- if project_nav_tab? :issues
= nav_link(controller: :issues) do
= nav_link(controller: [:issues, :labels, :milestones]) do
= link_to url_for_project_issues(@project, only_path: true), title: 'Issues', class: 'shortcuts-issues' do
= navbar_icon('issues')
%span
Issues
- if @project.default_issues_tracker?
......@@ -81,29 +67,19 @@
- if project_nav_tab? :merge_requests
= nav_link(controller: :merge_requests) do
= link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do
= navbar_icon('mr')
%span
Merge Requests
%span.badge.count.merge_counter= number_with_delimiter(@project.merge_requests.opened.count)
- if project_nav_tab? :labels
= nav_link(controller: :labels) do
= link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels' do
= icon('tags fw')
%span
Labels
- if project_nav_tab? :wiki
= nav_link(controller: :wikis) do
= link_to get_project_wiki_path(@project), title: 'Wiki', class: 'shortcuts-wiki' do
= navbar_icon('wiki')
%span
Wiki
- if project_nav_tab? :snippets
= nav_link(controller: :snippets) do
= link_to namespace_project_snippets_path(@project.namespace, @project), title: 'Snippets', class: 'shortcuts-snippets' do
= icon('clipboard fw')
%span
Snippets
......@@ -129,5 +105,4 @@
%li.hidden
= link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits' do
Commits
.fade-right
......@@ -5,8 +5,8 @@
- content_for :scripts_body_top do
- project = @target_project || @project
- if @project_wiki
- markdown_preview_path = namespace_project_wikis_markdown_preview_path(project.namespace, project)
- if @project_wiki && @page
- markdown_preview_path = namespace_project_wiki_markdown_preview_path(project.namespace, project, params[:id])
- else
- markdown_preview_path = markdown_preview_namespace_project_path(project.namespace, project)
- if current_user
......
%li.notification-list-item
%span.notification.fa.fa-holder.append-right-5
- if setting.global?
= notification_icon(current_user.notification_level)
= notification_icon(current_user.global_notification_setting.level)
- else
= notification_icon(setting.level)
......
%li.notification-list-item
%span.notification.fa.fa-holder.append-right-5
- if setting.global?
= notification_icon(current_user.notification_level)
= notification_icon(current_user.global_notification_setting.level)
- else
= notification_icon(setting.level)
......
......@@ -26,33 +26,7 @@
= f.select :notification_email, @user.all_emails, { include_blank: false }, class: "select2"
.form-group
= f.label :notification_level, class: 'label-light'
.radio
= f.label :notification_level, value: :disabled do
= f.radio_button :notification_level, :disabled
.level-title
Disabled
%p You will not get any notifications via email
.radio
= f.label :notification_level, value: :mention do
= f.radio_button :notification_level, :mention
.level-title
On Mention
%p You will receive notifications only for comments in which you were @mentioned
.radio
= f.label :notification_level, value: :participating do
= f.radio_button :notification_level, :participating
.level-title
Participating
%p You will only receive notifications from related resources (e.g. from your commits or assigned issues)
.radio
= f.label :notification_level, value: :watch do
= f.radio_button :notification_level, :watch
.level-title
Watch
%p You will receive notifications for any activity
= notification_level_radio_buttons
.prepend-top-default
= f.submit 'Update settings', class: "btn btn-create"
......
%fieldset.builds-feature
%h5.prepend-top-0
Merge Requests
.form-group
.checkbox
= f.label :only_allow_merge_if_build_succeeds do
= f.check_box :only_allow_merge_if_build_succeeds
%strong Only allow merge requests to be merged if the build succeeds
.help-block
Builds need to be configured to enable this feature.
= link_to icon('question-circle'), help_page_path('workflow', 'merge_requests#only-allow-merge-requests-to-be-merged-if-the-build-succeeds')
- page_title 'Artifacts', "#{@build.name} (##{@build.id})", 'Builds'
- header_title project_title(@project, "Builds", project_builds_path(@project))
.top-block.row-content-block.clearfix
.pull-right
......
......@@ -3,13 +3,14 @@
= render "projects/commits/head"
%div{ class: (container_class) }
.row-content-block.second-block.content-component-block
.pull-right
.top-area
.nav-text
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
= icon('plus')
New branch
&nbsp;
.dropdown.inline
%button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
%span.light
......@@ -26,8 +27,6 @@
= sort_title_recently_updated
= link_to namespace_project_branches_path(sort: 'last_updated') do
= sort_title_oldest_updated
.oneline
Protected branches can be managed in project settings
- unless @branches.empty?
%ul.content-list.all-branches
- @branches.each do |branch|
......
.content-block.build-header
= ci_status_with_icon(@build.status)
Build
%strong ##{@build.id}
for commit
= link_to ci_status_path(@build.pipeline) do
%strong= @build.pipeline.short_sha
from
= link_to namespace_project_commits_path(@project.namespace, @project, @build.ref) do
%code
= @build.ref
- if @build.user
= render "user"
= time_ago_with_tooltip(@build.created_at)
%button.btn.btn-default.pull-right.visible-xs-block.visible-sm-block.build-gutter-toggle.js-sidebar-build-toggle{ role: "button", type: "button" }
= icon('angle-double-left')
%aside.right-sidebar.right-sidebar-expanded.build-sidebar.js-build-sidebar
.block.build-sidebar-header.visible-xs-block.visible-sm-block.append-bottom-default
Build
%strong ##{@build.id}
%a.gutter-toggle.pull-right.js-sidebar-build-toggle{ href: "#" }
= icon('angle-double-right')
- if @build.coverage
.block.block-first
.title
Test coverage
%p.build-detail-row
#{@build.coverage}%
- if can?(current_user, :read_build, @project) && @build.artifacts?
.block{ class: ("block-first" if !@build.coverage) }
.title
Build artifacts
.btn-group.btn-group-justified{ role: :group }
= link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default' do
Download
- if @build.artifacts_metadata?
= link_to browse_namespace_project_build_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default' do
Browse
.block{ class: ("block-first" if !@build.coverage && !(can?(current_user, :read_build, @project) && @build.artifacts?)) }
.title
Build details
- if @build.retryable?
= link_to "Retry", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'pull-right', method: :post
- if @build.merge_request
%p.build-detail-row
%span.build-light-text Merge Request:
= link_to "#{@build.merge_request.to_reference}", merge_request_path(@build.merge_request)
- if @build.duration
%p.build-detail-row
%span.build-light-text Duration:
#{duration_in_words(@build.finished_at, @build.started_at)}
- if @build.finished_at
%p.build-detail-row
%span.build-light-text Finished:
#{time_ago_with_tooltip(@build.finished_at)}
- if @build.erased_at
%p.build-detail-row
%span.build-light-text Erased:
#{time_ago_with_tooltip(@build.erased_at)}
%p.build-detail-row
%span.build-light-text Runner:
- if @build.runner && current_user && current_user.admin
= link_to "##{@build.runner.id}", admin_runner_path(@build.runner.id)
- elsif @build.runner
\##{@build.runner.id}
.btn-group.btn-group-justified{ role: :group }
- if @build.has_trace?
= link_to 'Raw', raw_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default'
- if @build.active?
= link_to "Cancel", cancel_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default', method: :post
- if can?(current_user, :update_build, @project) && @build.erasable?
= link_to erase_namespace_project_build_path(@project.namespace, @project, @build),
class: "btn btn-sm btn-default", method: :post,
data: { confirm: "Are you sure you want to erase this build?" } do
Erase
- if @build.trigger_request
.build-widget
%h4.title
Trigger
%p
%span.build-light-text Token:
#{@build.trigger_request.trigger.short_token}
- if @build.trigger_request.variables
%p
%span.build-light-text Variables:
%code
- @build.trigger_request.variables.each do |key, value|
#{key}=#{value}
.block
.title
Commit message
%p.build-light-text.append-bottom-0
#{@build.pipeline.git_commit_message}
- if @build.tags.any?
.block
.title
Tags
- @build.tag_list.each do |tag|
%span.label.label-primary
= tag
by
%a{ href: user_path(@build.user) }
= image_tag avatar_icon(@build.user, 24), class: "avatar s24"
%strong= @build.user.to_reference
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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