Commit fcb85381 authored by James Lopez's avatar James Lopez

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce into fix/project-import_url

parents cc4d04f9 30e4d3ce
{
"always-semicolon": true,
"color-case": "lower",
"block-indent": " ",
"color-shorthand": true,
"element-case": "lower",
"space-before-colon": "",
"space-after-colon": " ",
"space-before-combinator": " ",
"space-after-combinator": " ",
"space-between-declarations": "\n",
"space-before-opening-brace": " ",
"space-after-opening-brace": "\n",
"space-before-closing-brace": "\n",
"unitless-zero": true
"exclude": [
"app/assets/stylesheets/framework/tw_bootstrap_variables.scss",
"app/assets/stylesheets/framework/fonts.scss"
],
"always-semicolon": true,
"color-case": "lower",
"block-indent": " ",
"color-shorthand": true,
"element-case": "lower",
"space-before-colon": "",
"space-after-colon": " ",
"space-before-combinator": " ",
"space-after-combinator": " ",
"space-between-declarations": "\n",
"space-before-opening-brace": " ",
"space-after-opening-brace": "\n",
"space-before-closing-brace": "\n",
"unitless-zero": true
}
......@@ -128,7 +128,6 @@ scss-lint:
- bundle exec rake scss_lint
tags:
- ruby
allow_failure: true
brakeman:
stage: test
......
......@@ -100,7 +100,7 @@ linters:
# Selectors should always use hyphenated-lowercase, rather than camelCase or
# snake_case.
SelectorFormat:
enabled: true
enabled: false
convention: hyphenated_lowercase
# Prefer the shortest shorthand form possible for properties that support it.
......
Please view this file on the master branch, on stable branches it's out of date.
v 8.6.0 (unreleased)
v 8.7.0 (unreleased)
- Don't attempt to look up an avatar in repo if repo directory does not exist (Stan hu)
- Preserve time notes/comments have been updated at when moving issue
- Make HTTP(s) label consistent on clone bar (Stan Hu)
- Fix avatar stretching by providing a cropping feature
- Add links to CI setup documentation from project settings and builds pages
- Implement 'Groups View' as an option for dashboard preferences !3379 (Elias W.)
- Implement 'TODOs View' as an option for dashboard preferences !3379 (Elias W.)
v 8.6.2 (unreleased)
- Comments on confidential issues don't show up in activity feed to non-members
v 8.6.1
- Add option to reload the schema before restoring a database backup. !2807
- Display navigation controls on mobile. !3214
- Fixed bug where participants would not work correctly on merge requests. !3329
- Fix sorting issues by votes on the groups issues page results in SQL errors. !3333
- Restrict notifications for confidential issues. !3334
- Do not allow to move issue if it has not been persisted. !3340
- Add a confirmation step before deleting an issuable. !3341
- Fixes issue with signin button overflowing on mobile. !3342
- Auto collapses the navigation sidebar when resizing. !3343
- Fix build dependencies, when the dependency is a string. !3344
- Shows error messages when trying to create label in dropdown menu. !3345
- Fixes issue with assign milestone not loading milestone list. !3346
- Fix an issue causing the Dashboard/Milestones page to be blank. !3348
v 8.6.0
- Add ability to move issue to another project
- Prevent tokens in the import URL to be showed by the UI
- Fix bug where wrong commit ID was being used in a merge request diff to show old image (Stan Hu)
- Make HTTP(s) label consistent on clone bar (Stan Hu)
- Add confidential issues
- Bump gitlab_git to 9.0.3 (Stan Hu)
- Fix diff image view modes (2-up, swipe, onion skin) not working (Stan Hu)
......@@ -19,9 +45,11 @@ v 8.6.0 (unreleased)
setup. A password can be provided during setup (see installation docs), or
GitLab will ask the user to create a new one upon first visit.
- Fix issue when pushing to projects ending in .wiki
- Properly display YAML front matter in Markdown
- Add support for wiki with UTF-8 page names (Hiroyuki Sato)
- Fix wiki search results point to raw source (Hiroyuki Sato)
- Don't load all of GitLab in mail_room
- Add information about `image` and `services` field at `job` level in the `.gitlab-ci.yml` documentation (Pat Turner)
- HTTP error pages work independently from location and config (Artem Sidorenko)
- Update `omniauth-saml` to 1.5.0 to allow for custom response attributes to be set
- Memoize @group in Admin::GroupsController (Yatish Mehta)
......
......@@ -16,6 +16,7 @@
- [Issue tracker guidelines](#issue-tracker-guidelines)
- [Issue weight](#issue-weight)
- [Regression issues](#regression-issues)
- [Technical debt](#technical-debt)
- [Merge requests](#merge-requests)
- [Merge request guidelines](#merge-request-guidelines)
- [Merge request description format](#merge-request-description-format)
......@@ -242,6 +243,28 @@ addressed.
[8.3 Regressions]: https://gitlab.com/gitlab-org/gitlab-ce/issues/4127
[update the notes]: https://gitlab.com/gitlab-org/release-tools/blob/master/doc/pro-tips.md#update-the-regression-issue
### Technical debt
In order to track things that can be improved in GitLab's codebase, we created
the ~"technical debt" label in [GitLab's issue tracker][ce-tracker].
This label should be added to issues that describe things that can be improved,
shortcuts that have been taken, code that needs refactoring, features that need
additional attention, and all other things that have been left behind due to
high velocity of development.
Everyone can create an issue, though you may need to ask for adding a specific
label, if you do not have permissions to do it by yourself. Additional labels
can be combined with the `technical debt` label, to make it easier to schedule
the improvements for a release.
Issues tagged with the `technical debt` label have the same priority like issues
that describe a new feature to be introduced in GitLab, and should be scheduled
for a release by the appropriate person.
Make sure to mention the merge request that the `technical debt` issue is
associated with in the description of the issue.
## Merge requests
We welcome merge requests with fixes and improvements to GitLab code, tests,
......
......@@ -234,7 +234,7 @@ end
group :development do
gem "foreman"
gem 'brakeman', '~> 3.1.0', require: false
gem 'brakeman', '~> 3.2.0', require: false
gem "annotate", "~> 2.6.0"
gem "letter_opener", '~> 1.1.2'
......@@ -279,7 +279,7 @@ group :development, :test do
gem 'capybara-screenshot', '~> 1.0.0'
gem 'poltergeist', '~> 1.9.0'
gem 'teaspoon', '~> 1.0.0'
gem 'teaspoon', '~> 1.1.0'
gem 'teaspoon-jasmine', '~> 2.2.0'
gem 'spring', '~> 1.6.4'
......
......@@ -84,21 +84,19 @@ GEM
bootstrap-sass (3.3.6)
autoprefixer-rails (>= 5.2.1)
sass (>= 3.3.4)
brakeman (3.1.4)
brakeman (3.2.1)
erubis (~> 2.6)
fastercsv (~> 1.5)
haml (>= 3.0, < 5.0)
highline (>= 1.6.20, < 2.0)
multi_json (~> 1.2)
ruby2ruby (>= 2.1.1, < 2.3.0)
ruby_parser (~> 3.7.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)
browser (1.0.1)
builder (3.2.2)
bullet (4.14.10)
bullet (5.0.0)
activesupport (>= 3.0.0)
uniform_notifier (~> 1.9.0)
bundler-audit (0.4.0)
......@@ -208,7 +206,6 @@ GEM
faraday_middleware-multi_json (0.0.6)
faraday_middleware
multi_json
fastercsv (1.5.5)
ffaker (2.0.0)
ffi (1.9.10)
fission (0.5.0)
......@@ -328,8 +325,8 @@ GEM
fog-xml (0.1.2)
fog-core
nokogiri (~> 1.5, >= 1.5.11)
font-awesome-rails (4.5.0.0)
railties (>= 3.2, < 5.0)
font-awesome-rails (4.5.0.1)
railties (>= 3.2, < 5.1)
foreman (0.78.0)
thor (~> 0.19.1)
formatador (0.2.5)
......@@ -706,10 +703,10 @@ GEM
ruby-saml (1.1.2)
nokogiri (>= 1.5.10)
uuid (~> 2.3)
ruby2ruby (2.2.0)
ruby2ruby (2.3.0)
ruby_parser (~> 3.1)
sexp_processor (~> 4.0)
ruby_parser (3.7.2)
ruby_parser (3.8.1)
sexp_processor (~> 4.1)
rubyntlm (0.5.2)
rubypants (0.2.0)
......@@ -718,7 +715,7 @@ GEM
safe_yaml (1.0.4)
sanitize (2.1.0)
nokogiri (>= 1.4.4)
sass (3.4.20)
sass (3.4.21)
sass-rails (5.0.4)
railties (>= 4.0.0, < 5.0)
sass (~> 3.1)
......@@ -742,7 +739,7 @@ GEM
sentry-raven (0.15.6)
faraday (>= 0.7.6)
settingslogic (2.0.9)
sexp_processor (4.6.0)
sexp_processor (4.7.0)
sham_rack (1.3.6)
rack
shoulda-matchers (2.8.0)
......@@ -806,8 +803,8 @@ GEM
systemu (2.6.5)
task_list (1.0.2)
html-pipeline
teaspoon (1.0.2)
railties (>= 3.2.5, < 5)
teaspoon (1.1.5)
railties (>= 3.2.5, < 6)
teaspoon-jasmine (2.2.0)
teaspoon (>= 1.0.0)
temple (0.7.6)
......@@ -868,7 +865,7 @@ GEM
equalizer (~> 0.0, >= 0.0.9)
warden (1.2.4)
rack (>= 1.0)
web-console (2.2.1)
web-console (2.3.0)
activemodel (>= 4.0)
binding_of_caller (>= 0.7.2)
railties (>= 4.0)
......@@ -910,7 +907,7 @@ DEPENDENCIES
better_errors (~> 1.0.1)
binding_of_caller (~> 0.7.2)
bootstrap-sass (~> 3.3.0)
brakeman (~> 3.1.0)
brakeman (~> 3.2.0)
browser (~> 1.0.0)
bullet
bundler-audit
......@@ -1048,7 +1045,7 @@ DEPENDENCIES
sprockets (~> 3.3.5)
state_machines-activerecord (~> 0.3.0)
task_list (~> 1.0.2)
teaspoon (~> 1.0.0)
teaspoon (~> 1.1.0)
teaspoon-jasmine (~> 2.2.0)
test_after_commit (~> 0.4.2)
thin (~> 1.6.1)
......@@ -1064,3 +1061,6 @@ DEPENDENCIES
web-console (~> 2.0)
webmock (~> 1.21.0)
wikicloth (= 0.8.1)
BUNDLED WITH
1.11.2
8.6.0-pre
8.7.0-pre
......@@ -74,6 +74,8 @@
dataType: "json"
).done (label) ->
callback(label)
.error (message) ->
callback(message.responseJSON)
# Return group projects list. Filtered by query
groupProjects: (group_id, query, callback) ->
......
......@@ -43,6 +43,7 @@
#= require jquery.nicescroll
#= require_tree .
#= require fuzzaldrin-plus
#= require cropper
window.slugify = (text) ->
text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase()
......@@ -218,13 +219,20 @@ $ ->
$this = $(this)
$this.attr 'value', $this.val()
$sidebarGutterToggle = $('.js-sidebar-toggle')
$navIconToggle = $('.toggle-nav-collapse')
$(document)
.off 'breakpoint:change'
.on 'breakpoint:change', (e, breakpoint) ->
if breakpoint is 'sm' or breakpoint is 'xs'
$gutterIcon = $('.js-sidebar-toggle').find('i')
$gutterIcon = $sidebarGutterToggle.find('i')
if $gutterIcon.hasClass('fa-angle-double-right')
$gutterIcon.closest('a').trigger('click')
$sidebarGutterToggle.trigger('click')
$navIcon = $navIconToggle.find('.fa')
if $navIcon.hasClass('fa-angle-left')
$navIconToggle.trigger('click')
$(document)
.off 'click', '.js-sidebar-toggle'
......
class GitLabCrop
# Matches everything but the file name
FILENAMEREGEX = /^.*[\\\/]/
constructor: (input, opts = {}) ->
@fileInput = $(input)
# We should rename to avoid spec to fail
# Form will submit the proper input filed with a file using FormData
@fileInput
.attr('name', "#{@fileInput.attr('name')}-trigger")
.attr('id', "#{@fileInput.attr('id')}-trigger")
# Set defaults
{
@exportWidth = 200
@exportHeight = 200
@cropBoxWidth = 200
@cropBoxHeight = 200
@form = @fileInput.parents('form')
# Required params
@filename
@previewImage
@modalCrop
@pickImageEl
@uploadImageBtn
@modalCropImg
} = opts
# Ensure needed elements are jquery objects
# If selector is provided we will convert them to a jQuery Object
@filename = @getElement(@filename)
@previewImage = @getElement(@previewImage)
@pickImageEl = @getElement(@pickImageEl)
# Modal elements usually are outside the @form element
@modalCrop = if _.isString(@modalCrop) then $(@modalCrop) else @modalCrop
@uploadImageBtn = if _.isString(@uploadImageBtn) then $(@uploadImageBtn) else @uploadImageBtn
@modalCropImg = if _.isString(@modalCropImg) then $(@modalCropImg) else @modalCropImg
@cropActionsBtn = @modalCrop.find('[data-method]')
@bindEvents()
getElement: (selector) ->
$(selector, @form)
bindEvents: ->
_this = @
@fileInput.on 'change', (e) ->
_this.onFileInputChange(e, @)
@pickImageEl.on 'click', @onPickImageClick
@modalCrop.on 'shown.bs.modal', @onModalShow
@modalCrop.on 'hidden.bs.modal', @onModalHide
@uploadImageBtn.on 'click', @onUploadImageBtnClick
@cropActionsBtn.on 'click', (e) ->
btn = @
_this.onActionBtnClick(btn)
@croppedImageBlob = null
onPickImageClick: =>
@fileInput.trigger('click')
onModalShow: =>
_this = @
@modalCropImg.cropper(
viewMode: 1
center: false
aspectRatio: 1
modal: true
scalable: false
rotatable: false
zoomable: true
dragMode: 'move'
guides: false
zoomOnTouch: false
zoomOnWheel: false
cropBoxMovable: false
cropBoxResizable: false
toggleDragModeOnDblclick: false
built: ->
$image = $(@)
container = $image.cropper 'getContainerData'
cropBoxWidth = _this.cropBoxWidth;
cropBoxHeight = _this.cropBoxHeight;
$image.cropper('setCropBoxData',
width: cropBoxWidth,
height: cropBoxHeight,
left: (container.width - cropBoxWidth) / 2,
top: (container.height - cropBoxHeight) / 2
)
)
onModalHide: =>
@modalCropImg
.attr('src', '') # Remove attached image
.cropper('destroy') # Destroy cropper instance
onUploadImageBtnClick: (e) =>
e.preventDefault()
@setBlob()
@setPreview()
@modalCrop.modal('hide')
@fileInput.val('')
onActionBtnClick: (btn) ->
data = $(btn).data()
if @modalCropImg.data('cropper') && data.method
result = @modalCropImg.cropper data.method, data.option
onFileInputChange: (e, input) ->
@readFile(input)
readFile: (input) ->
_this = @
reader = new FileReader
reader.onload = ->
_this.modalCropImg.attr('src', reader.result)
_this.modalCrop.modal('show')
reader.readAsDataURL(input.files[0])
dataURLtoBlob: (dataURL) ->
binary = atob(dataURL.split(',')[1])
array = []
for v, k in binary
array.push(binary.charCodeAt(k))
new Blob([new Uint8Array(array)], type: 'image/png')
setPreview: ->
@previewImage.attr('src', @dataURL)
filename = @fileInput.val().replace(FILENAMEREGEX, '')
@filename.text(filename)
setBlob: ->
@dataURL = @modalCropImg.cropper('getCroppedCanvas',
width: 200
height: 200
).toDataURL('image/png')
@croppedImageBlob = @dataURLtoBlob(@dataURL)
getBlob: ->
@croppedImageBlob
$.fn.glCrop = (opts) ->
return @.each ->
$(@).data('glcrop', new GitLabCrop(@, opts))
class GitLabDropdownFilter
BLUR_KEYCODES = [27, 40]
HAS_VALUE_CLASS = "has-value"
constructor: (@dropdown, @options) ->
@input = @dropdown.find(".dropdown-input .dropdown-input-field")
constructor: (@input, @options) ->
$inputContainer = @input.parent()
$clearButton = $inputContainer.find('.js-dropdown-input-clear')
# Clear click
$clearButton.on 'click', (e) =>
e.preventDefault()
e.stopPropagation()
@input
.val('')
.trigger('keyup')
.focus()
# Key events
timeout = ""
@input.on "keyup", (e) =>
if e.keyCode is 13 && @input.val() isnt ""
if @input.val() isnt "" and !$inputContainer.hasClass HAS_VALUE_CLASS
$inputContainer.addClass HAS_VALUE_CLASS
else if @input.val() is "" and $inputContainer.hasClass HAS_VALUE_CLASS
$inputContainer.removeClass HAS_VALUE_CLASS
if e.keyCode is 13 and @input.val() isnt ""
if @options.enterCallback
@options.enterCallback()
return
......@@ -95,7 +111,9 @@ class GitLabDropdown
# Init filiterable
if @options.filterable
@filter = new GitLabDropdownFilter @dropdown,
@input = @dropdown.find('.dropdown-input .dropdown-input-field')
@filter = new GitLabDropdownFilter @input,
remote: @options.filterRemote
query: @options.data
keys: @options.search.fields
......@@ -103,6 +121,7 @@ class GitLabDropdown
return @fullData
callback: (data) =>
@parseData data
@highlightRow 1
enterCallback: =>
@selectFirstRow()
......@@ -224,11 +243,19 @@ class GitLabDropdown
noResults: ->
html = "<li>"
html += "<a href='#' class='is-focused'>"
html += "<a href='#' class='dropdown-menu-empty-link is-focused'>"
html += "No matching results."
html += "</a>"
html += "</li>"
highlightRow: (index) ->
if @input.val() isnt ""
selector = '.dropdown-content li:first-child a'
if @dropdown.find(".dropdown-toggle-page").length
selector = ".dropdown-page-one .dropdown-content li:first-child a"
$(selector).addClass 'is-focused'
rowClicked: (el) ->
fieldName = @options.fieldName
field = @dropdown.parent().find("input[name='#{fieldName}']")
......@@ -272,7 +299,7 @@ class GitLabDropdown
if @dropdown.find(".dropdown-toggle-page").length
selector = ".dropdown-page-one .dropdown-content li:first-child a"
# similute a click on the first link
# simulate a click on the first link
$(selector).trigger "click"
$.fn.glDropdown = (opts) ->
......
#= require jquery.waitforimages
class @IssuableContext
constructor: ->
@initParticipants()
new UsersSelect()
$('select.select2').select2({width: 'resolve', dropdownAutoWidth: true})
......@@ -17,3 +17,27 @@ class @IssuableContext
block.find('.js-select2').select2("open")
$(".right-sidebar").niceScroll()
initParticipants: ->
_this = @
$(document).on "click", ".js-participants-more", @toggleHiddenParticipants
$(".js-participants-author").each (i) ->
if i >= _this.PARTICIPANTS_ROW_COUNT
$(@)
.addClass "js-participants-hidden"
.hide()
toggleHiddenParticipants: (e) ->
e.preventDefault()
currentText = $(this).text().trim()
lessText = $(this).data("less-text")
originalText = $(this).data("original-text")
if currentText is originalText
$(this).text(lessText)
else
$(this).text(originalText)
$(".js-participants-hidden").toggle()
......@@ -7,7 +7,6 @@ class @Issue
# Prevent duplicate event bindings
@disableTaskList()
@fixAffixScroll()
@initParticipants()
if $('a.btn-close').length
@initTaskList()
@initIssueBtnEventListeners()
......@@ -85,27 +84,3 @@ class @Issue
type: 'PATCH'
url: $('form.js-issuable-update').attr('action')
data: patchData
initParticipants: ->
_this = @
$(document).on "click", ".js-participants-more", @toggleHiddenParticipants
$(".js-participants-author").each (i) ->
if i >= _this.PARTICIPANTS_ROW_COUNT
$(@)
.addClass "js-participants-hidden"
.hide()
toggleHiddenParticipants: (e) ->
e.preventDefault()
currentText = $(this).text().trim()
lessText = $(this).data("less-text")
originalText = $(this).data("original-text")
if currentText is originalText
$(this).text(lessText)
else
$(this).text(originalText)
$(".js-participants-hidden").toggle()
......@@ -6,7 +6,7 @@ class @LabelsSelect
labelUrl = $dropdown.data('labels')
selectedLabel = $dropdown.data('selected')
if selectedLabel
selectedLabel = selectedLabel.split(',')
selectedLabel = selectedLabel.toString().split(',')
newLabelField = $('#new_label_name')
newColorField = $('#new_label_color')
showNo = $dropdown.data('show-no')
......@@ -14,28 +14,81 @@ class @LabelsSelect
defaultLabel = $dropdown.data('default-label')
if newLabelField.length
$newLabelCreateButton = $('.js-new-label-btn')
$colorPreview = $('.js-dropdown-label-color-preview')
$newLabelError = $dropdown.parent().find('.js-label-error')
$newLabelError.hide()
# Suggested colors in the dropdown to chose from pre-chosen colors
$('.suggest-colors-dropdown a').on 'click', (e) ->
e.preventDefault()
e.stopPropagation()
newColorField.val $(this).data('color')
$('.js-dropdown-label-color-preview')
newColorField
.val($(this).data('color'))
.trigger('change')
$colorPreview
.css 'background-color', $(this).data('color')
.parent()
.addClass 'is-active'
$('.js-new-label-btn').on 'click', (e) ->
# Cancel button takes back to first page
resetForm = ->
newLabelField
.val ''
.trigger 'change'
newColorField
.val ''
.trigger 'change'
$colorPreview
.css 'background-color', ''
.parent()
.removeClass 'is-active'
$('.dropdown-menu-back').on 'click', ->
resetForm()
$('.js-cancel-label-btn').on 'click', (e) ->
e.preventDefault()
e.stopPropagation()
resetForm()
$('.dropdown-menu-back', $dropdown.parent()).trigger 'click'
# Listen for change and keyup events on label and color field
# This allows us to enable the button when ready
enableLabelCreateButton = ->
if newLabelField.val() isnt '' and newColorField.val() isnt ''
$('.js-new-label-btn').disable()
$newLabelCreateButton.enable()
else
$newLabelCreateButton.disable()
newLabelField.on 'keyup change', enableLabelCreateButton
# Create new label with API
Api.newLabel projectId, {
name: newLabelField.val()
color: newColorField.val()
}, (label) ->
$('.js-new-label-btn').enable()
$('.dropdown-menu-back', $dropdown.parent()).trigger 'click'
newColorField.on 'keyup change', enableLabelCreateButton
# Send the API call to create the label
$newLabelCreateButton
.disable()
.on 'click', (e) ->
e.preventDefault()
e.stopPropagation()
if newLabelField.val() isnt '' and newColorField.val() isnt ''
$newLabelError.hide()
$('.js-new-label-btn').disable()
# Create new label with API
Api.newLabel projectId, {
name: newLabelField.val()
color: newColorField.val()
}, (label) ->
$('.js-new-label-btn').enable()
if label.message?
$newLabelError
.text label.message
.show()
else
$('.dropdown-menu-back', $dropdown.parent()).trigger 'click'
$dropdown.glDropdown(
data: (term, callback) ->
......@@ -68,8 +121,11 @@ class @LabelsSelect
else
selected = if label.title is selectedLabel then 'is-active' else ''
color = if label.color? then "<span class='dropdown-label-box' style='background-color: #{label.color}'></span>" else ""
"<li>
<a href='#' class='#{selected}'>
#{color}
#{label.title}
</a>
</li>"
......
class @Profile
constructor: ->
constructor: (opts = {}) ->
{
@form = $('.edit-user')
} = opts
# Automatically submit the Preferences form when any of its radio buttons change
$('.js-preferences-form').on 'change.preference', 'input[type=radio]', ->
$(this).parents('form').submit()
......@@ -17,14 +21,46 @@ class @Profile
$('.update-notifications').on 'ajax:complete', ->
$(this).find('.btn-save').enable()
$('.js-choose-user-avatar-button').bind "click", ->
form = $(this).closest("form")
form.find(".js-user-avatar-input").click()
@bindEvents()
cropOpts =
filename: '.js-avatar-filename'
previewImage: '.avatar-image .avatar'
modalCrop: '.modal-profile-crop'
pickImageEl: '.js-choose-user-avatar-button'
uploadImageBtn: '.js-upload-user-avatar'
modalCropImg: '.modal-profile-crop-image'
@avatarGlCrop = $('.js-user-avatar-input').glCrop(cropOpts).data 'glcrop'
bindEvents: ->
@form.on 'submit', @onSubmitForm
onSubmitForm: (e) =>
e.preventDefault()
@saveForm()
saveForm: ->
self = @
formData = new FormData(@form[0])
formData.append('user[avatar]', @avatarGlCrop.getBlob(), 'avatar.png')
$('.js-user-avatar-input').bind "change", ->
form = $(this).closest("form")
filename = $(this).val().replace(/^.*[\\\/]/, '')
form.find(".js-avatar-filename").text(filename)
$.ajax
url: @form.attr('action')
type: @form.attr('method')
data: formData
dataType: "json"
processData: false
contentType: false
success: (response) ->
new Flash(response.message, 'notice')
error: (jqXHR) ->
new Flash(jqXHR.responseJSON.message, 'alert')
complete: ->
window.scrollTo 0, 0
# Enable submit button after requests ends
self.form.find(':input[disabled]').enable()
$ ->
# Extract the SSH Key title from its comment
......
......@@ -4,7 +4,6 @@ expanded = 'page-sidebar-expanded'
toggleSidebar = ->
$('.page-with-sidebar').toggleClass("#{collapsed} #{expanded}")
$('header').toggleClass("header-collapsed header-expanded")
$('.sidebar-wrapper').toggleClass("sidebar-collapsed sidebar-expanded")
$('.toggle-nav-collapse i').toggleClass("fa-angle-right fa-angle-left")
$.cookie("collapsed_nav", $('.page-with-sidebar').hasClass(collapsed), { path: '/' })
......
......@@ -30,6 +30,7 @@ class @UsersSelect
if showNullUser
showDivider += 1
users.unshift(
beforeDivider: true
name: 'Unassigned',
id: 0
)
......@@ -39,6 +40,7 @@ class @UsersSelect
name = showAnyUser
name = 'Any User' if name == true
anyUser = {
beforeDivider: true
name: name,
id: null
}
......@@ -75,20 +77,27 @@ class @UsersSelect
selected = if user.id is selectedId then "is-active" else ""
img = ""
if avatar
img = "<img src='#{avatar}' class='avatar avatar-inline' width='30' />"
"<li>
<a href='#' class='dropdown-menu-user-link #{selected}'>
#{img}
<strong class='dropdown-menu-user-full-name'>
if user.beforeDivider?
"<li>
<a href='#' class='#{selected}'>
#{user.name}
</strong>
<span class='dropdown-menu-user-username'>
#{username}
</span>
</a>
</li>"
</a>
</li>"
else
if avatar
img = "<img src='#{avatar}' class='avatar avatar-inline' width='30' />"
"<li>
<a href='#' class='dropdown-menu-user-link #{selected}'>
#{img}
<strong class='dropdown-menu-user-full-name'>
#{user.name}
</strong>
<span class='dropdown-menu-user-username'>
#{username}
</span>
</a>
</li>"
)
$('.ajax-users-select').each (i, select) =>
......
......@@ -9,6 +9,7 @@
*= require_self
*= require dropzone/basic
*= require cal-heatmap
*= require cropper.css
*/
/*
......
......@@ -292,8 +292,11 @@ table {
}
.btn-sign-in {
margin-top: 10px;
text-shadow: none;
@media (min-width: $screen-sm-min) {
margin-top: 11px;
}
}
.side-filters {
......@@ -375,7 +378,7 @@ table {
position: absolute;
top: 0;
right: 0;
width: 250px !important;
min-width: 250px;
visibility: hidden;
}
}
......
......@@ -130,6 +130,12 @@
text-decoration: none;
outline: 0;
}
&.dropdown-menu-empty-link {
&.is-focused {
background-color: $dropdown-empty-row-bg;
}
}
}
}
......@@ -183,7 +189,7 @@
}
.dropdown-select {
width: 280px;
width: 300px;
}
.dropdown-menu-align-right {
......@@ -237,7 +243,7 @@
.dropdown-title-button {
position: absolute;
top: -1px;
top: 0;
padding: 0;
color: $dropdown-title-btn-color;
font-size: 14px;
......@@ -270,6 +276,22 @@
font-size: 12px;
pointer-events: none;
}
.dropdown-input-clear {
display: none;
cursor: pointer;
pointer-events: all;
}
&.has-value {
.dropdown-input-clear {
display: block;
}
.dropdown-input-search {
display: none;
}
}
}
.dropdown-input-field {
......@@ -286,13 +308,13 @@
border-color: $dropdown-input-focus-border;
box-shadow: 0 0 4px $dropdown-input-focus-shadow;
+ .fa {
~ .fa {
color: $dropdown-link-color;
}
}
&:hover {
+ .fa {
~ .fa {
color: $dropdown-link-color;
}
}
......@@ -338,11 +360,12 @@
}
}
.dropdown-menu-labels {
.label {
position: relative;
width: 30px;
margin-right: 5px;
text-indent: -99999px;
}
.dropdown-label-box {
position: relative;
top: 3px;
margin-right: 5px;
display: inline-block;
width: 15px;
height: 15px;
border-radius: $border-radius-base;
}
......@@ -50,6 +50,10 @@
}
}
a {
color: $gl-dark-link-color;
}
.left-options {
margin-top: -3px;
}
......
......@@ -11,3 +11,11 @@
}
}
}
@media (max-width: $screen-xs-max) {
.filter-item {
display: block;
margin: 0 0 10px;
}
}
// Disabling "SpaceAfterPropertyColon" linter because the linter doesn't like
// the way the `src` property is formatted in this file.
// scss-lint:disable SpaceAfterPropertyColon
/* latin-ext */
@font-face {
font-family: 'Source Sans Pro';
......
......@@ -70,6 +70,11 @@ header {
.header-content {
height: $header-height;
padding-right: 20px;
@media (min-width: $screen-sm-min) {
padding-right: 0;
}
.title {
margin: 0;
......
......@@ -102,6 +102,10 @@
display: inline-block;
}
.icon-label {
display: none;
}
input {
height: 34px;
display: inline-block;
......@@ -124,9 +128,38 @@
}
}
/* Hide on extra small devices (phones) */
@media (max-width: $screen-xs-max) {
display: none;
padding-bottom: 0;
.btn, form, .dropdown, .dropdown-menu-toggle, .form-control {
margin: 0 0 10px;
display: block;
width: 100%;
}
form {
display: block;
height: auto;
input {
width: 100%;
margin: 0 0 10px;
}
}
.input-short {
width: 100%;
}
.icon-label {
display: inline-block;
}
// Applies on /dashboard/issues
.project-item-select-holder {
display: block;
margin: 0;
}
}
/* Small devices (tablets, 768px and lower) */
......
......@@ -44,6 +44,7 @@
@include box-shadow(rgba(76, 86, 103, 0.247059) 0 0 1px 0, rgba(31, 37, 50, 0.317647) 0 2px 18px 0);
@include border-radius ($border-radius-default);
border: none;
min-width: 175px;
}
.select2-results .select2-result-label {
......
#logo {
z-index: 2;
position: absolute;
width: 58px;
cursor: pointer;
}
.page-with-sidebar {
padding-top: $header-height;
transition-duration: .3s;
......@@ -18,28 +25,10 @@
position: absolute;
left: 0;
}
#logo {
z-index: 2;
position: absolute;
width: 58px;
cursor: pointer;
}
&.right-sidebar-expanded {
/* Extra small devices (phones, less than 768px) */
/* No media query since this is the default in Bootstrap */
padding-right: 0;
/* Small devices (tablets, 768px and up) */
@media (min-width: $screen-sm-min) {
padding-right: $gutter_width;
}
}
}
.sidebar-wrapper {
z-index: 999;
z-index: 1000;
background: $background-color;
}
......@@ -202,53 +191,27 @@
}
}
@mixin expanded-sidebar {
padding-left: $sidebar_collapsed_width;
@media (min-width: $screen-md-min) {
padding-left: $sidebar_width;
}
&.right-sidebar-collapsed {
/* Extra small devices (phones, less than 768px) */
padding-right: 0;
/* Small devices (tablets, 768px and up) */
@media (min-width: $screen-sm-min) {
padding-right: $sidebar_collapsed_width;
}
}
.sidebar-wrapper {
width: $sidebar_width;
.nav-sidebar {
width: $sidebar_width;
}
.nav-sidebar li a{
width: 230px;
.collapse-nav a {
width: $sidebar_width;
position: fixed;
bottom: 0;
left: 0;
font-size: 13px;
background: transparent;
height: 40px;
text-align: center;
line-height: 40px;
transition-duration: .3s;
outline: none;
&.back-link {
i {
opacity: 0;
}
}
}
&:hover {
text-decoration: none;
}
}
@mixin collapsed-sidebar {
.page-sidebar-collapsed {
padding-left: $sidebar_collapsed_width;
&.right-sidebar-collapsed {
/* Extra small devices (phones, less than 768px) */
padding-right: 0;
/* Small devices (tablets, 768px and up) */
@media (min-width: $screen-sm-min) {
padding-right: $sidebar_collapsed_width;
}
}
.sidebar-wrapper {
width: $sidebar_collapsed_width;
......@@ -293,35 +256,48 @@
}
}
.collapse-nav a {
width: $sidebar_width;
position: fixed;
bottom: 0;
left: 0;
font-size: 13px;
background: transparent;
height: 40px;
text-align: center;
line-height: 40px;
transition-duration: .3s;
outline: none;
}
.page-sidebar-expanded {
padding-left: $sidebar_collapsed_width;
@media (min-width: $screen-md-min) {
padding-left: $sidebar_width;
}
.sidebar-wrapper {
width: $sidebar_width;
.nav-sidebar {
width: $sidebar_width;
}
.nav-sidebar li a {
width: 230px;
.collapse-nav a:hover {
text-decoration: none;
background: #f2f6f7;
&.back-link {
i {
opacity: 0;
}
}
}
}
}
.page-sidebar-collapsed {
/* Extra small devices (phones, less than 768px) */
@include collapsed-sidebar;
.right-sidebar-collapsed {
padding-right: 0;
/* Small devices (tablets, 768px and up) */
@media (min-width: $screen-sm-min) {
@include collapsed-sidebar;
padding-right: $sidebar_collapsed_width;
}
}
.page-sidebar-expanded {
@include expanded-sidebar;
.right-sidebar-expanded {
padding-right: 0;
@media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) {
padding-right: $sidebar_collapsed_width;
}
@media (min-width: $screen-md-min) {
padding-right: $gutter_width;
}
}
......@@ -168,13 +168,14 @@ $regular_font: 'Source Sans Pro', "Helvetica Neue", Helvetica, Arial, sans-serif
*/
$dropdown-bg: #fff;
$dropdown-link-color: #555;
$dropdown-link-hover-bg: rgba(#000, .04);
$dropdown-link-hover-bg: $row-hover;
$dropdown-empty-row-bg: rgba(#000, .04);
$dropdown-border-color: rgba(#000, .1);
$dropdown-shadow-color: rgba(#000, .1);
$dropdown-divider-color: rgba(#000, .1);
$dropdown-header-color: #959494;
$dropdown-title-btn-color: #bfbfbf;
$dropdown-input-color: #c7c7c7;
$dropdown-input-color: #555;
$dropdown-input-focus-border: rgb(58, 171, 240);
$dropdown-input-focus-shadow: rgba(#000, .2);
$dropdown-loading-bg: rgba(#fff, .6);
......
......@@ -3,12 +3,12 @@ img {
height: auto;
}
p.details {
font-style:italic;
color:#777
font-style: italic;
color: #777
}
.footer p {
font-size:small;
color:#777
font-size: small;
color: #777
}
pre.commit-message {
white-space: pre-wrap;
......@@ -20,5 +20,5 @@ pre.commit-message {
color: #090;
}
.file-stats .deleted-file {
color: #B00;
color: #b00;
}
......@@ -99,6 +99,10 @@ li.commit {
color: $gl-gray;
}
.avatar {
margin-right: 8px;
}
.committed_ago {
display: inline-block;
}
......
......@@ -9,28 +9,45 @@
}
&.suggest-colors-dropdown {
margin-bottom: 5px;
margin-top: 10px;
margin-bottom: 10px;
border-radius: $border-radius-base;
overflow: hidden;
a {
@include border-radius(0);
width: 36.7px;
width: (100% / 7);
margin-right: 0;
margin-bottom: -5px;
}
}
}
.dropdown-label-color-preview {
display: none;
margin-top: 5px;
width: 100%;
height: 25px;
.dropdown-new-label {
.dropdown-content {
max-height: 260px;
}
}
.dropdown-label-color-input {
position: relative;
margin-bottom: 10px;
&.is-active {
display: block;
padding-left: 32px;
}
}
.dropdown-label-color-preview {
position: absolute;
left: 0;
top: 0;
width: 32px;
height: 32px;
border-top-left-radius: $border-radius-base;
border-bottom-left-radius: $border-radius-base;
}
.label-row {
.label {
padding: 9px;
......@@ -45,3 +62,10 @@
.label-subscription {
display: inline-block;
}
.dropdown-labels-error {
padding: 5px 10px;
margin-bottom: 10px;
background-color: $gl-danger;
color: $white-light;
}
......@@ -197,3 +197,24 @@
width: 105px;
}
}
.modal-profile-crop {
.modal-dialog {
width: 380px;
@media (max-width: $screen-sm-min) {
width: auto;
}
}
.profile-crop-image-container {
height: 300px;
margin: 0 auto;
}
.crop-controls {
padding: 10px 0 0;
text-align: center;
}
}
......@@ -229,6 +229,10 @@
padding: 0 3px;
color: #999;
}
a {
color: $gl-dark-link-color;
}
}
.last-push-widget {
......
.ci-status {
padding: 2px 7px;
margin-right: 5px;
border: 1px solid #eee;
white-space: nowrap;
@include border-radius(4px);
.container-fluid .content {
.ci-status {
padding: 2px 7px;
margin-right: 5px;
border: 1px solid #eee;
white-space: nowrap;
@include border-radius(4px);
&:hover {
text-decoration: none;
}
&:hover {
text-decoration: none;
}
&.ci-failed {
color: $gl-danger;
border-color: $gl-danger;
}
&.ci-failed {
color: $gl-danger;
border-color: $gl-danger;
}
&.ci-success {
color: $gl-success;
border-color: $gl-success;
}
&.ci-success {
color: $gl-success;
border-color: $gl-success;
}
&.ci-info {
color: $gl-info;
border-color: $gl-info;
}
&.ci-info {
color: $gl-info;
border-color: $gl-info;
}
&.ci-disabled {
color: $gl-gray;
border-color: $gl-gray;
&.ci-canceled,
&.ci-skipped,
&.ci-disabled {
color: $gl-gray;
border-color: $gl-gray;
}
&.ci-pending,
&.ci-running {
color: $gl-warning;
border-color: $gl-warning;
}
}
&.ci-pending,
&.ci-running {
.ci-status-icon-success {
color: $gl-success;
}
.ci-status-icon-failed {
color: $gl-danger;
}
.ci-status-icon-running,
.ci-status-icon-pending {
color: $gl-warning;
border-color: $gl-warning;
}
}
.ci-status-icon-success {
@extend .cgreen;
}
.ci-status-icon-failed {
@extend .cred;
}
.ci-status-icon-running,
.ci-status-icon-pending {
// These are standard text color
}
.ci-status-icon-canceled,
.ci-status-icon-disabled,
.ci-status-icon-not-found,
.ci-status-icon-skipped {
@extend .cgray;
.ci-status-icon-canceled,
.ci-status-icon-disabled,
.ci-status-icon-not-found,
.ci-status-icon-skipped {
color: $gl-gray;
}
}
......@@ -6,7 +6,6 @@ module GlobalMilestones
@milestones = MilestonesFinder.new.execute(@projects, params)
@milestones = GlobalMilestone.build_collection(@milestones)
@milestones = @milestones.sort_by { |x| x.due_date.nil? ? epoch : x.due_date }
@milestones = Kaminari.paginate_array(@milestones).page(params[:page])
end
def milestone
......
class Dashboard::ApplicationController < ApplicationController
layout 'dashboard'
private
def projects
@projects ||= current_user.authorized_projects.sorted_by_activity.non_archived
end
end
class Dashboard::LabelsController < Dashboard::ApplicationController
def index
labels = Label.where(project_id: projects).select(:title, :color).uniq(:title)
respond_to do |format|
format.json { render json: labels }
end
end
end
......@@ -2,18 +2,19 @@ class Dashboard::MilestonesController < Dashboard::ApplicationController
include GlobalMilestones
before_action :projects
before_action :milestones, only: [:index]
before_action :milestone, only: [:show]
def index
respond_to do |format|
format.html do
@milestones = Kaminari.paginate_array(milestones).page(params[:page])
end
format.json do
render json: milestones
end
end
end
def show
end
private
def projects
@projects ||= current_user.authorized_projects.sorted_by_activity.non_archived
end
end
......@@ -3,7 +3,7 @@ class DashboardController < Dashboard::ApplicationController
include MergeRequestsAction
before_action :event_filter, only: :activity
before_action :projects, only: [:issues, :merge_requests, :labels, :milestones]
before_action :projects, only: [:issues, :merge_requests]
respond_to :html
......@@ -20,29 +20,6 @@ class DashboardController < Dashboard::ApplicationController
end
end
def labels
labels = Label.where(project_id: @projects).select(:title, :color).uniq(:title)
respond_to do |format|
format.json do
render json: labels
end
end
end
def milestones
milestones = Milestone.where(project_id: @projects).active
epoch = DateTime.parse('1970-01-01')
grouped_milestones = GlobalMilestone.build_collection(milestones)
grouped_milestones = grouped_milestones.sort_by { |x| x.due_date.nil? ? epoch : x.due_date }
respond_to do |format|
format.json do
render json: grouped_milestones
end
end
end
protected
def load_events
......@@ -57,8 +34,4 @@ class DashboardController < Dashboard::ApplicationController
@events = @event_filter.apply_filter(@events).with_associations
@events = @events.limit(20).offset(params[:offset] || 0)
end
def projects
@projects ||= current_user.authorized_projects.sorted_by_activity.non_archived
end
end
......@@ -2,11 +2,15 @@ class Groups::MilestonesController < Groups::ApplicationController
include GlobalMilestones
before_action :group_projects
before_action :milestones, only: [:index]
before_action :milestone, only: [:show, :update]
before_action :authorize_admin_milestones!, only: [:new, :create, :update]
def index
respond_to do |format|
format.html do
@milestones = Kaminari.paginate_array(milestones).page(params[:page])
end
end
end
def new
......
......@@ -11,15 +11,16 @@ class ProfilesController < Profiles::ApplicationController
def update
user_params.except!(:email) if @user.ldap_user?
if @user.update_attributes(user_params)
flash[:notice] = "Profile was successfully updated"
else
messages = @user.errors.full_messages.uniq.join('. ')
flash[:alert] = "Failed to update profile. #{messages}"
end
respond_to do |format|
format.html { redirect_back_or_default(default: { action: 'show' }) }
if @user.update_attributes(user_params)
message = "Profile was successfully updated"
format.html { redirect_back_or_default(default: { action: 'show' }, options: { notice: message }) }
format.json { render json: { message: message } }
else
message = @user.errors.full_messages.uniq.join('. ')
format.html { redirect_back_or_default(default: { action: 'show' }, options: { alert: "Failed to update profile. #{message}" }) }
format.json { render json: { message: message }, status: :unprocessable_entity }
end
end
end
......
......@@ -26,6 +26,10 @@ class RootController < Dashboard::ProjectsController
redirect_to activity_dashboard_path
when 'starred_project_activity'
redirect_to activity_dashboard_path(filter: 'starred')
when 'groups'
redirect_to dashboard_groups_path
when 'todos'
redirect_to dashboard_todos_path
else
return
end
......
......@@ -70,7 +70,8 @@ module DropdownsHelper
def dropdown_filter(placeholder)
content_tag :div, class: "dropdown-input" do
filter_output = search_field_tag nil, nil, class: "dropdown-input-field", placeholder: placeholder
filter_output << icon('search')
filter_output << icon('search', class: "dropdown-input-search")
filter_output << icon('times', class: "dropdown-input-clear js-dropdown-input-clear", role: "button")
filter_output.html_safe
end
......
......@@ -194,7 +194,7 @@ module EventsHelper
end
def event_to_atom(xml, event)
if event.proper?(current_user)
if event.visible_to_user?(current_user)
xml.entry do
event_link = event_feed_url(event)
event_title = event_feed_title(event)
......
......@@ -114,7 +114,7 @@ module LabelsHelper
if @project
namespace_project_labels_path(@project.namespace, @project, :json)
else
labels_dashboard_path(:json)
dashboard_labels_path(:json)
end
end
......
......@@ -50,7 +50,7 @@ module MilestonesHelper
if @project
namespace_project_milestones_path(@project.namespace, @project, :json)
else
milestones_dashboard_path(:json)
dashboard_milestones_path(:json)
end
end
......
......@@ -12,7 +12,9 @@ module PreferencesHelper
projects: 'Your Projects (default)',
stars: 'Starred Projects',
project_activity: "Your Projects' Activity",
starred_project_activity: "Starred Projects' Activity"
starred_project_activity: "Starred Projects' Activity",
groups: "Your Groups",
todos: "Your Todos"
}.with_indifferent_access.freeze
# Returns an Array usable by a select field for more user-friendly option text
......
......@@ -230,7 +230,7 @@ class Commit
end
def revert_message
%Q{Revert "#{title}"\n\n#{revert_description}}
%Q{Revert "#{title.strip}"\n\n#{revert_description}}
end
def reverts_commit?(commit)
......
......@@ -41,7 +41,7 @@ module Issuable
scope :join_project, -> { joins(:project) }
scope :references_project, -> { references(:project) }
scope :non_archived, -> { join_project.merge(Project.non_archived) }
scope :non_archived, -> { join_project.merge(Project.non_archived.only(:where)) }
delegate :name,
:email,
......
......@@ -73,15 +73,15 @@ class Event < ActiveRecord::Base
end
end
def proper?(user = nil)
def visible_to_user?(user = nil)
if push?
true
elsif membership_changed?
true
elsif created_project?
true
elsif issue?
Ability.abilities.allowed?(user, :read_issue, issue)
elsif issue? || issue_note?
Ability.abilities.allowed?(user, :read_issue, note? ? note_target : target)
else
((merge_request? || note?) && target) || milestone?
end
......@@ -298,6 +298,10 @@ class Event < ActiveRecord::Base
target.noteable_type == "Commit"
end
def issue_note?
note? && target && target.noteable_type == "Issue"
end
def note_project_snippet?
target.noteable_type == "Snippet"
end
......
......@@ -146,7 +146,8 @@ class Issue < ActiveRecord::Base
return false unless user.can?(:admin_issue, to_project)
end
!moved? && user.can?(:admin_issue, self.project)
!moved? && persisted? &&
user.can?(:admin_issue, self.project)
end
def to_branch_name
......
......@@ -97,12 +97,12 @@ class Label < ActiveRecord::Base
end
end
def open_issues_count
issues.opened.count
def open_issues_count(user = nil)
issues.visible_to_user(user).opened.count
end
def closed_issues_count
issues.closed.count
def closed_issues_count(user = nil)
issues.visible_to_user(user).closed.count
end
def open_merge_requests_count
......
......@@ -83,7 +83,7 @@ class Milestone < ActiveRecord::Base
end
def self.upcoming
self.where('due_date > ?', Time.now).order(due_date: :asc).first
self.where('due_date > ?', Time.now).reorder(due_date: :asc).first
end
def to_reference(from_project = nil)
......
......@@ -304,7 +304,7 @@ class Project < ActiveRecord::Base
end
def find_with_namespace(id)
namespace_path, project_path = id.split('/')
namespace_path, project_path = id.split('/', 2)
return nil if !namespace_path || !project_path
......
......@@ -467,6 +467,18 @@ class Repository
end
end
def gitlab_ci_yml
return nil if !exists? || empty?
@gitlab_ci_yml ||= tree(:head).blobs.find do |file|
file.name == '.gitlab-ci.yml'
end
rescue Rugged::ReferenceError
# For unknow reason spinach scenario "Scenario: I change project path"
# lead to "Reference 'HEAD' not found" exception from Repository#empty?
nil
end
def head_commit
@head_commit ||= commit(self.root_ref)
end
......@@ -877,6 +889,8 @@ class Repository
end
def avatar
return nil unless exists?
@avatar ||= cache.fetch(:avatar) do
AVATAR_FILES.find do |file|
blob_at_branch('master', file)
......
......@@ -184,7 +184,7 @@ class User < ActiveRecord::Base
# User's Dashboard preference
# Note: When adding an option, it MUST go on the end of the array.
enum dashboard: [:projects, :stars, :project_activity, :starred_project_activity]
enum dashboard: [:projects, :stars, :project_activity, :starred_project_activity, :groups, :todos]
# User's Project preference
# Note: When adding an option, it MUST go on the end of the array.
......
module Ci
class CreateBuildsService
def execute(commit, stage, ref, tag, user, trigger_request, status)
builds_attrs = commit.config_processor.builds_for_stage_and_ref(stage, ref, tag)
builds_attrs = commit.config_processor.builds_for_stage_and_ref(stage, ref, tag, trigger_request)
# check when to create next build
builds_attrs = builds_attrs.select do |build_attrs|
......
......@@ -54,7 +54,8 @@ module Issues
new_note = note.dup
new_params = { project: @new_project, noteable: @new_issue,
note: unfold_references(new_note.note),
created_at: note.created_at }
created_at: note.created_at,
updated_at: note.updated_at }
new_note.update(new_params)
end
......@@ -78,6 +79,8 @@ module Issues
end
def unfold_references(content)
return unless content
rewriter = Gitlab::Gfm::ReferenceRewriter.new(content, @old_project,
@current_user)
rewriter.rewrite(@new_project)
......
......@@ -162,6 +162,7 @@ class NotificationService
recipients = add_subscribed_users(recipients, note.noteable)
recipients = reject_unsubscribed_users(recipients, note.noteable)
recipients = reject_users_without_access(recipients, note.noteable)
recipients.delete(note.author)
recipients = recipients.uniq
......@@ -376,6 +377,14 @@ class NotificationService
end
end
def reject_users_without_access(recipients, target)
return recipients unless target.is_a?(Issue)
recipients.select do |user|
user.can?(:read_issue, target)
end
end
def add_subscribed_users(recipients, target)
return recipients unless target.respond_to? :subscribers
......@@ -464,15 +473,16 @@ class NotificationService
end
recipients = reject_unsubscribed_users(recipients, target)
recipients = reject_users_without_access(recipients, target)
recipients.delete(current_user)
recipients.uniq
end
def build_relabeled_recipients(target, current_user, labels:)
recipients = add_labels_subscribers([], target, labels: labels)
recipients = reject_unsubscribed_users(recipients, target)
recipients = reject_users_without_access(recipients, target)
recipients.delete(current_user)
recipients.uniq
end
......
......@@ -10,6 +10,8 @@
- if current_user
= link_to issues_dashboard_url(format: :atom, private_token: current_user.private_token), class: 'btn' do
= icon('rss')
%span.icon-label
Subscribe
= render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue"
= render 'shared/issuable/filter', type: :issues
......
......@@ -77,7 +77,7 @@
%em Authorization was granted by entering your username and password in the application.
%td= token.created_at
%td= token.scopes
%td= render 'delete_form', token: token
%td= render 'doorkeeper/authorized_applications/delete_form', token: token
- else
.profile-settings-message.text-center
You don't have any authorized applications
- if event.proper?(current_user)
- if event.visible_to_user?(current_user)
.event-item{class: "#{event.body? ? "event-block" : "event-inline" }"}
.event-item-timestamp
#{time_ago_with_tooltip(event.created_at)}
......
......@@ -10,6 +10,8 @@
- if current_user
= link_to issues_group_url(@group, format: :atom, private_token: current_user.private_token), class: 'btn' do
= icon('rss')
%span.icon-label
Subscribe
= render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue"
= render 'shared/issuable/filter', type: :issues
......
......@@ -17,7 +17,7 @@
.cover-title
%h1
= @group.name
%span.visibility-icon.has_tooltip{ data: { container: 'body' }, title: visibility_icon_description(@group) }
%span.visibility-icon.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@group) }
= visibility_level_icon(@group.visibility_level, fw: false)
.cover-desc.username
......
......@@ -6,7 +6,7 @@
= icon('bars')
.navbar-collapse.collapse
%ul.nav.navbar-nav.pull-right
%ul.nav.navbar-nav
%li.hidden-sm.hidden-xs
= render 'layouts/search'
%li.visible-sm.visible-xs
......@@ -38,8 +38,9 @@
= link_to destroy_user_session_path, class: 'logout', method: :delete, title: 'Sign out', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('sign-out')
- else
.pull-right
= link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-success'
%li
%div
= link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-success'
%h1.title= title
......
......@@ -24,12 +24,13 @@
= f.password_field :current_password, required: true, class: 'form-control'
%p.help-block
You must provide your current password in order to change it.
.form-group
= f.label :password, 'New password', class: 'label-light'
= f.password_field :password, required: true, class: 'form-control'
.form-group
= f.label :password_confirmation, class: 'label-light'
= f.password_field :password_confirmation, required: true, class: 'form-control'
.prepend-top-default.append-bottom-default
= f.submit 'Save password', class: "btn btn-create append-right-10"
.form-group
= f.label :password, 'New password', class: 'label-light'
= f.password_field :password, required: true, class: 'form-control'
.form-group
= f.label :password_confirmation, class: 'label-light'
= f.password_field :password_confirmation, required: true, class: 'form-control'
.prepend-top-default.append-bottom-default
= f.submit 'Save password', class: "btn btn-create append-right-10"
- unless @user.password_automatically_set?
= link_to "I forgot my password", reset_profile_password_path, method: :put, class: "account-btn-link"
......@@ -26,7 +26,7 @@
%a.btn.js-choose-user-avatar-button
Browse file...
%span.avatar-file-name.prepend-left-default.js-avatar-filename No file chosen
= f.file_field :avatar, class: "js-user-avatar-input hidden"
= f.file_field :avatar, class: "js-user-avatar-input hidden", accept: "image/*"
.help-block
The maximum file size allowed is 200KB.
- if @user.avatar?
......@@ -94,3 +94,25 @@
.prepend-top-default.append-bottom-default
= f.submit 'Update profile settings', class: "btn btn-success"
= link_to "Cancel", user_path(current_user), class: "btn btn-cancel"
.modal.modal-profile-crop
.modal-dialog
.modal-content
.modal-header
%button.close{:type => "button", :'data-dismiss' => "modal"}
%span
&times;
%h4.modal-title
Position and size your new avatar
.modal-body
.profile-crop-image-container
%img.modal-profile-crop-image
.crop-controls
.btn-group
%button.btn.btn-primary{ data: { method: "zoom", option: "0.1" } }
%span.fa.fa-search-plus
%button.btn.btn-primary{ data: { method: "zoom", option: "-0.1" } }
%span.fa.fa-search-minus
.modal-footer
%button.btn.btn-primary.js-upload-user-avatar{:type => "button"}
Set new profile picture
%fieldset.builds-feature
%legend
Builds:
- unless @repository.gitlab_ci_yml
.form-group
.col-sm-offset-2.col-sm-10
%p Builds need to be configured before you can begin using Continuous Integration.
= link_to 'Get started with Builds', help_page_path('ci/quick_start', 'README'), class: 'btn btn-info'
%hr
.form-group
.col-sm-offset-2.col-sm-10
%p Get recent application code using the following command:
......
......@@ -5,7 +5,7 @@
.cover-title.project-home-desc
%h1
= @project.name
%span.visibility-icon.has_tooltip{data: { container: 'body' }, title: visibility_icon_description(@project)}
%span.visibility-icon.has-tooltip{data: { container: 'body' }, title: visibility_icon_description(@project)}
= visibility_level_icon(@project.visibility_level, fw: false)
- if @project.description.present?
......
......@@ -16,7 +16,7 @@
- else
Name
%b.caret
%ul.dropdown-menu
%ul.dropdown-menu.dropdown-menu-align-right
%li
= link_to namespace_project_branches_path(sort: nil) do
Name
......
......@@ -27,6 +27,9 @@
= link_to 'Cancel running', cancel_all_namespace_project_builds_path(@project.namespace, @project),
data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
- unless @repository.gitlab_ci_yml
= link_to 'Get started with Builds', help_page_path('ci/quick_start', 'README'), class: 'btn btn-info'
= link_to ci_lint_path, class: 'btn btn-default' do
= icon('wrench')
%span CI Lint
......
......@@ -5,10 +5,10 @@
.panel-heading
Commits (#{@commits.count})
- if hidden > 0
%ul.well-list
%ul.content-list
- commits.each do |commit|
= render "projects/commits/inline_commit", commit: commit, project: @project
%li.warning-row.unstyled
#{number_with_delimiter(hidden)} additional commits have been omitted to prevent performance issues.
- else
%ul.well-list= render commits, project: @project
%ul.content-list= render commits, project: @project
......@@ -12,7 +12,7 @@
.light
= pluralize(commits.count, 'commit')
.col-md-10.col-sm-12
%ul.bordered-list
%ul.content-list
= render commits, project: project
%hr.lists-separator
......
- if current_user && can?(current_user, :push_code, @project) && @issue.can_be_worked_on?(current_user)
.pull-right
= link_to namespace_project_branches_path(@project.namespace, @project, branch_name: @issue.to_branch_name, issue_iid: @issue.iid), method: :post, class: 'btn', title: @issue.to_branch_name do
= link_to namespace_project_branches_path(@project.namespace, @project, branch_name: @issue.to_branch_name, issue_iid: @issue.iid), method: :post, class: 'btn has-tooltip', title: @issue.to_branch_name do
= icon('code-fork')
New Branch
......@@ -11,6 +11,8 @@
- if current_user
= link_to namespace_project_issues_path(@project.namespace, @project, :atom, { private_token: current_user.private_token }), class: 'btn append-right-10' do
= icon('rss')
%span.icon-label
Subscribe
= render 'shared/issuable/search_form', path: namespace_project_issues_path(@project.namespace, @project)
- if can? current_user, :create_issue, @project
= link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { assignee_id: @issuable_finder.assignee.try(:id), milestone_id: @issuable_finder.milestones.try(:first).try(:id) }), class: "btn btn-new", title: "New Issue", id: "new_issue_link" do
......
......@@ -8,7 +8,7 @@
%strong.append-right-20
= link_to_label(label) do
= pluralize label.open_issues_count, 'open issue'
= pluralize label.open_issues_count(current_user), 'open issue'
- if current_user
.label-subscription{data: {url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label)}}
......
......@@ -6,7 +6,7 @@
%span.caret
%span.sr-only
Select Archive Format
%ul.col-xs-10.dropdown-menu{ role: 'menu' }
%ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' }
%li
= link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'zip'), rel: 'nofollow' do
%i.fa.fa-download
......
......@@ -21,7 +21,7 @@
= icon('users')
= number_with_delimiter(group.users.count)
%span.visibility-icon.has_tooltip{data: { container: 'body', placement: 'left' }, title: visibility_icon_description(group)}
%span.visibility-icon.has-tooltip{data: { container: 'body', placement: 'left' }, title: visibility_icon_description(group)}
= visibility_level_icon(group.visibility_level, fw: false)
= image_tag group_icon(group), class: "avatar s40 hidden-xs"
......
......@@ -31,18 +31,18 @@
.issues_bulk_update.hide
= form_tag bulk_update_namespace_project_issues_path(@project.namespace, @project), method: :post do
.filter-item.inline
= dropdown_tag("Status", options: { toggle_class: "js-issue-status", title: "Change status", dropdown_class: "dropdown-menu-selectable", data: { field_name: "update[state_event]" } } ) do
= dropdown_tag("Status", options: { toggle_class: "js-issue-status", title: "Change status", dropdown_class: "dropdown-menu-status dropdown-menu-selectable", data: { field_name: "update[state_event]" } } ) do
%ul
%li
%a{href: "#", data: {id: "reopen"}} Open
%li
%a{href: "#", data: {id: "close"}} Closed
.filter-item.inline
= dropdown_tag("Assignee", options: { toggle_class: "js-user-search", title: "Assign to", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable",
= dropdown_tag("Assignee", options: { toggle_class: "js-user-search js-update-assignee", title: "Assign to", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable",
placeholder: "Search authors", data: { first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: @project.id, field_name: "update[assignee_id]" } })
.filter-item.inline
= dropdown_tag("Milestone", options: { title: "Assign milestone", toggle_class: 'js-milestone-select', filter: true, dropdown_class: "dropdown-menu-selectable",
placeholder: "Search milestones", data: { show_no: true, field_name: "update[milestone_id]", project_id: @project.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :js), use_id: true } })
= dropdown_tag("Milestone", options: { title: "Assign milestone", toggle_class: 'js-milestone-select', filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone",
placeholder: "Search milestones", data: { show_no: true, field_name: "update[milestone_id]", project_id: @project.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), use_id: true } })
= hidden_field_tag 'update[issues_ids]', []
= hidden_field_tag :state_event, params[:state_event]
.filter-item.inline
......
......@@ -127,11 +127,12 @@
for this project.
- if issuable.new_record?
= link_to 'Cancel', namespace_project_issues_path(@project.namespace, @project), class: 'btn btn-cancel'
= link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable.class]), class: 'btn btn-cancel'
- else
.pull-right
- if current_user.can?(:"destroy_#{issuable.to_ability_name}", @project)
= link_to polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), method: :delete, class: 'btn btn-grouped' do
= link_to polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), data: { confirm: "#{issuable.class.name.titleize} will be removed! Are you sure?" },
method: :delete, class: 'btn btn-grouped' do
= icon('trash-o')
Delete
= link_to 'Cancel', namespace_project_issue_path(@project.namespace, @project, issuable), class: 'btn btn-grouped btn-cancel'
= link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), class: 'btn btn-grouped btn-cancel'
......@@ -24,16 +24,21 @@
- else
View labels
- if can? current_user, :admin_label, @project and @project
.dropdown-page-two
.dropdown-page-two.dropdown-new-label
= dropdown_title("Create new label", back: true)
= dropdown_content do
%input#new_label_color{type: "hidden"}
.dropdown-labels-error.js-label-error
%input#new_label_name.dropdown-input-field{type: "text", placeholder: "Name new label"}
.dropdown-label-color-preview.js-dropdown-label-color-preview
.suggest-colors.suggest-colors-dropdown
- suggested_colors.each do |color|
= link_to '#', style: "background-color: #{color}", data: { color: color } do
&nbsp
%button.btn.btn-primary.js-new-label-btn{type: "button"}
Create
.dropdown-label-color-input
.dropdown-label-color-preview.js-dropdown-label-color-preview
%input#new_label_color.dropdown-input-field{ type: "text" }
.clearfix
%button.btn.btn-primary.pull-left.js-new-label-btn{type: "button"}
Create
%button.btn.btn-default.pull-right.js-cancel-label-btn{type: "button"}
Cancel
= dropdown_loading
......@@ -17,4 +17,4 @@
%a.js-participants-more{href: "#", data: {original_text: "+ #{participants_size - 7} more", less_text: "- show less"}}
+ #{participants_extra} more
:javascript
Issue.prototype.PARTICIPANTS_ROW_COUNT = #{participants_row};
IssuableContext.prototype.PARTICIPANTS_ROW_COUNT = #{participants_row};
......@@ -33,11 +33,11 @@
.value.bold.hide-collapsed
- if issuable.assignee
= link_to_member(@project, issuable.assignee, size: 32) do
- 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' }
= icon('exclamation-triangle')
%span.username
= issuable.assignee.to_reference
- if issuable.instance_of?(MergeRequest) && !issuable.can_be_merged_by?(issuable.assignee)
%a.pull-right.cannot-be-merged{href: '#', data: {toggle: 'tooltip'}, title: 'Not allowed to merge'}
= icon('exclamation-triangle')
- else
.light None
......@@ -77,7 +77,7 @@
Labels
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
= link_to 'Edit', '#', class: 'edit-link pull-right'
.value.issuable-show-labels.hide-collapsed{class: ("has-labels" if issuable.labels.any?)}
.value.bold.issuable-show-labels.hide-collapsed{ class: ("has-labels" if issuable.labels.any?) }
- if issuable.labels.any?
- issuable.labels.each do |label|
= link_to_label(label, type: issuable.to_ability_name)
......
......@@ -27,7 +27,7 @@
%span
= icon('star')
= project.star_count
%span.visibility-icon.has_tooltip{data: { container: 'body', placement: 'left' }, title: visibility_icon_description(project)}
%span.visibility-icon.has-tooltip{data: { container: 'body', placement: 'left' }, title: visibility_icon_description(project)}
= visibility_level_icon(project.visibility_level, fw: false)
.title
......
......@@ -16,6 +16,18 @@ Rails.application.routes.draw do
end
end
# Make the built-in Rails routes available in development, otherwise they'd
# get swallowed by the `namespace/project` route matcher below.
#
# See https://git.io/va79N
if Rails.env.development?
get '/rails/mailers' => 'rails/mailers#index'
get '/rails/mailers/:path' => 'rails/mailers#preview'
get '/rails/info/properties' => 'rails/info#properties'
get '/rails/info/routes' => 'rails/info#routes'
get '/rails/info' => 'rails/info#index'
end
namespace :ci do
# CI API
Ci::API::API.logger Rails.logger
......@@ -351,11 +363,10 @@ Rails.application.routes.draw do
get :issues
get :merge_requests
get :activity
get :labels
get :milestones
scope module: :dashboard do
resources :milestones, only: [:index, :show]
resources :labels, only: [:index]
resources :groups, only: [:index]
resources :snippets, only: [:index]
......
......@@ -3,7 +3,7 @@
## User documentation
- [API](api/README.md) Automate GitLab via a simple and powerful API.
- [CI](ci/README.md)
- [CI](ci/README.md) GitLab Continuous Integration (CI) getting started, .gitlab-ci.yml options, and examples.
- [GitLab as OAuth2 authentication service provider](integration/oauth_provider.md). It allows you to login to other applications from GitLab.
- [GitLab Basics](gitlab-basics/README.md) Find step by step how to start working on your commandline and on GitLab.
- [Importing to GitLab](workflow/importing/README.md).
......@@ -45,4 +45,3 @@
contributing to documentation.
- [Development](development/README.md) Explains the architecture and the guidelines for shell commands.
- [Legal](legal/README.md) Contributor license agreements.
- [Release](release/README.md) How to make the monthly and security releases.
......@@ -331,7 +331,7 @@ There are a few rules that apply to the usage of refs policy:
* `only` and `except` are inclusive. If both `only` and `except` are defined
in a job specification, the ref is filtered by `only` and `except`.
* `only` and `except` allow the use of regular expressions.
* `only` and `except` allow the use of special keywords: `branches` and `tags`.
* `only` and `except` allow the use of special keywords: `branches`, `tags`, and `triggers`.
* `only` and `except` allow to specify a repository path to filter jobs for
forks.
......@@ -348,6 +348,17 @@ job:
- branches
```
In this example, `job` will run only for refs that are tagged, or if a build is explicitly requested
via an API trigger.
```yaml
job:
# use special keywords
only:
- tags
- triggers
```
The repository path can be used to have jobs executed only for the parent
repository and not forks:
......
......@@ -72,9 +72,9 @@ p { margin: 0; padding: 0; }
### Colors
HEX (hexadecimal) colors short-form should use shortform where possible, and
should use lower case letters to differenciate between letters and numbers, e.
g. `#E3E3E3` vs. `#e3e3e3`.
HEX (hexadecimal) colors should use shorthand where possible, and should use
lower case letters to differentiate between letters and numbers, e.g. `#E3E3E3`
vs. `#e3e3e3`.
```scss
// Bad
......@@ -160,6 +160,7 @@ is slightly more performant.
```
### Selectors with a `js-` Prefix
Do not use any selector prefixed with `js-` for styling purposes. These
selectors are intended for use only with JavaScript to allow for removal or
renaming without breaking styling.
......@@ -187,8 +188,28 @@ CSSComb globally (system-wide). Run it in the GitLab directory with
Note that this won't fix every problem, but it should fix a majority.
### Ignoring issues
If you want a line or set of lines to be ignored by the linter, you can use
`// scss-lint:disable RuleName` ([more info][disabling-linters]):
```scss
// This lint rule is disabled because the class name comes from a gem.
// scss-lint:disable SelectorFormat
.ui_charcoal {
background-color: #333;
}
// scss-lint:enable SelectorFormat
```
Make sure a comment is added on the line above the `disable` rule, otherwise the
linter will throw a warning. `DisableLinterReason` is enabled to make sure the
style guide isn't being ignored, and to communicate to others why the style
guide is ignored in this instance.
[csscomb]: https://github.com/csscomb/csscomb.js
[node]: https://github.com/nodejs/node
[npm]: https://www.npmjs.com/
[scss-lint]: https://github.com/brigade/scss-lint
[scss-lint-documentation]: https://github.com/brigade/scss-lint/blob/master/lib/scss_lint/linter/README.md
[disabling-linters]: https://github.com/brigade/scss-lint#disabling-linters-via-source
......@@ -2,26 +2,14 @@
Step-by-step guides on the basics of working with Git and GitLab.
* [Start using Git on the command line](start-using-git.md)
* [Create and add your SSH Keys](create-your-ssh-keys.md)
* [Command Line basic commands](command-line-commands.md)
* [Basic Git commands](basic-git-commands.md)
* [Create a project](create-project.md)
* [Create a group](create-group.md)
* [Create a branch](create-branch.md)
* [Fork a project](fork-project.md)
* [Add a file](add-file.md)
* [Add an image](add-image.md)
* [Create a Merge Request](add-merge-request.md)
* [Create an Issue](create-issue.md)
- [Start using Git on the command line](start-using-git.md)
- [Create and add your SSH Keys](create-your-ssh-keys.md)
- [Command Line basics](command-line-commands.md)
- [Create a project](create-project.md)
- [Create a group](create-group.md)
- [Create a branch](create-branch.md)
- [Fork a project](fork-project.md)
- [Add a file](add-file.md)
- [Add an image](add-image.md)
- [Create a Merge Request](add-merge-request.md)
- [Create an Issue](create-issue.md)
# Basic Git commands
### Go to the master branch to pull the latest changes from there
```
git checkout master
```
### Download the latest changes in the project
This is for you to work on an up-to-date copy (it is important to do every time you work on a project), while you setup tracking branches.
```
git pull REMOTE NAME-OF-BRANCH -u
```
(REMOTE: origin) (NAME-OF-BRANCH: could be "master" or an existing branch)
### Create a branch
Spaces won't be recognized, so you need to use a hyphen or underscore.
```
git checkout -b NAME-OF-BRANCH
```
### Work on a branch that has already been created
```
git checkout NAME-OF-BRANCH
```
### View the changes you've made
It's important to be aware of what's happening and what's the status of your changes.
```
git status
```
### Add changes to commit
You'll see your changes in red when you type "git status".
```
git add CHANGES IN RED
git commit -m "DESCRIBE THE INTENTION OF THE COMMIT"
```
### Send changes to gitlab.com
```
git push REMOTE NAME-OF-BRANCH
```
### Delete all changes in the Git repository, but leave unstaged things
```
git checkout .
```
### Delete all changes in the Git repository, including untracked files
```
git clean -f
```
### Merge created branch with master branch
You need to be in the created branch.
```
git checkout NAME-OF-BRANCH
git merge master
```
This section is now merged into [Start using Git](start-using-git.md).
# Start using Git on the command line
If you want to start using a Git and GitLab, make sure that you have created an account on GitLab.
If you want to start using a Git and GitLab, make sure that you have created an
account on GitLab.
## Open a shell
......@@ -59,3 +60,63 @@ To view the information that you entered, type:
```
git config --global --list
```
## Basic Git commands
### Go to the master branch to pull the latest changes from there
```
git checkout master
```
### Download the latest changes in the project
This is for you to work on an up-to-date copy (it is important to do every time you work on a project), while you setup tracking branches.
```
git pull REMOTE NAME-OF-BRANCH -u
```
(REMOTE: origin) (NAME-OF-BRANCH: could be "master" or an existing branch)
### Create a branch
Spaces won't be recognized, so you need to use a hyphen or underscore.
```
git checkout -b NAME-OF-BRANCH
```
### Work on a branch that has already been created
```
git checkout NAME-OF-BRANCH
```
### View the changes you've made
It's important to be aware of what's happening and what's the status of your changes.
```
git status
```
### Add changes to commit
You'll see your changes in red when you type "git status".
```
git add CHANGES IN RED
git commit -m "DESCRIBE THE INTENTION OF THE COMMIT"
```
### Send changes to gitlab.com
```
git push REMOTE NAME-OF-BRANCH
```
### Delete all changes in the Git repository, but leave unstaged things
```
git checkout .
```
### Delete all changes in the Git repository, including untracked files
```
git clean -f
```
### Merge created branch with master branch
You need to be in the created branch.
```
git checkout NAME-OF-BRANCH
git merge master
```
# Get started with GitLab
## Organize
Create projects and groups.
- [Create a new project](../gitlab-basics/create-project.md)
- [Create a new group](../gitlab-basics/create-group.md)
## Prioritize
Create issues, labels, milestones, cast your vote, and review issues.
- [Create a new issue](../gitlab-basics/create-issue.md)
- [Assign labels to issues](../workflow/labels.md)
- [Use milestones as an overview of your project's tracker](../workflow/milestones.md)
- [Use voting to express your like/dislike to issues and merge requests](../workflow/award_emoji.md)
## Collaborate
Create merge requests and review code.
- [Fork a project and contribute to it](../workflow/forking_workflow.md)
- [Create a new merge request](../gitlab-basics/add-merge-request.md)
- [Automatically close issues from merge requests](../customization/issue_closing.md)
- [Automatically merge when your builds succeed](../workflow/merge_when_build_succeeds.md)
- [Revert any commit](../workflow/revert_changes.md)
## Test and Deploy
Use the built-in continuous integration in GitLab.
- [Get started with GitLab CI](../ci/quick_start/README.md)
## Install and Update
Install and update your GitLab installation.
- [Install GitLab](https://about.gitlab.com/installation/)
- [Update GitLab](https://about.gitlab.com/update/)
- [Explore Omnibus GitLab configuration options](http://doc.gitlab.com/omnibus/settings/configuration.html)
# Grafana Configuration
[Grafana](http://grafana.org/) is a tool that allows you to visualize time
series metrics through graphs and dashboards. It supports several backend
data stores, including InfluxDB. GitLab writes performance data to InfluxDB
and Grafana will allow you to query InfluxDB to display useful graphs.
For the easiest installation and configuration, install Grafana on the same
server as InfluxDB. For larger installations, you may want to split out these
services.
## Installation
Grafana supplies package repositories (Yum/Apt) for easy installation.
See [Grafana installation documentation](http://docs.grafana.org/installation/)
for detailed steps.
> **Note**: Before starting Grafana for the first time, set the admin user
and password in `/etc/grafana/grafana.ini`. Otherwise, the default password
will be `admin`.
## Configuration
Login as the admin user. Expand the menu by clicking the Grafana logo in the
top left corner. Choose 'Data Sources' from the menu. Then, click 'Add new'
in the top bar.
![Grafana empty data source page](img/grafana_data_source_empty.png)
Fill in the configuration details for the InfluxDB data source. Save and
Test Connection to ensure the configuration is correct.
- **Name**: InfluxDB
- **Default**: Checked
- **Type**: InfluxDB 0.9.x (Even if you're using InfluxDB 0.10.x)
- **Url**: https://localhost:8086 (Or the remote URL if you've installed InfluxDB
on a separate server)
- **Access**: proxy
- **Database**: gitlab
- **User**: admin (Or the username configured when setting up InfluxDB)
- **Password**: The password configured when you set up InfluxDB
![Grafana data source configurations](img/grafana_data_source_configuration.png)
## Apply retention policies and create continuous queries
If you intend to import the GitLab provided Grafana dashboards, you will need
to copy and run a set of queries against InfluxDB to create the needed data
sets.
On the InfluxDB server, run the following command, substituting your InfluxDB
user and password:
```bash
influxdb --username admin -password super_secret
```
This will drop you in to an InfluxDB interactive session. Copy the entire
contents below and paste it in to the interactive session:
```
CREATE RETENTION POLICY gitlab_30d ON gitlab DURATION 30d REPLICATION 1 DEFAULT
CREATE RETENTION POLICY seven_days ON gitlab DURATION 7d REPLICATION 1
CREATE CONTINUOUS QUERY rails_transaction_timings_seven_days ON gitlab BEGIN SELECT percentile("duration", 95.000) AS "duration_95th", percentile("duration", 99.000) AS "duration_99th", mean("duration") AS "duration_mean", percentile(sql_duration, 95.000) AS "sql_duration_95th", percentile(sql_duration, 99.000) AS "sql_duration_99th", mean(sql_duration) AS "sql_duration_mean", percentile(view_duration, 95.000) AS "view_duration_95th", percentile(view_duration, 99.000) AS "view_duration_99th", mean(view_duration) AS "view_duration_mean" INTO gitlab.seven_days.rails_transaction_timings FROM gitlab.gitlab_30d.rails_transactions GROUP BY time(1m) END
CREATE CONTINUOUS QUERY sidekiq_transaction_timings_seven_days ON gitlab BEGIN SELECT percentile("duration", 95.000) AS "duration_95th", percentile("duration", 99.000) AS "duration_99th", mean("duration") AS "duration_mean", percentile(sql_duration, 95.000) AS "sql_duration_95th", percentile(sql_duration, 99.000) AS "sql_duration_99th", mean(sql_duration) AS "sql_duration_mean", percentile(view_duration, 95.000) AS "view_duration_95th", percentile(view_duration, 99.000) AS "view_duration_99th", mean(view_duration) AS "view_duration_mean" INTO gitlab.seven_days.sidekiq_transaction_timings FROM gitlab.gitlab_30d.sidekiq_transactions GROUP BY time(1m) END
CREATE CONTINUOUS QUERY rails_transaction_counts_seven_days ON gitlab BEGIN SELECT count("duration") AS "count" INTO gitlab.seven_days.rails_transaction_counts FROM gitlab.gitlab_30d.rails_transactions GROUP BY time(1m) END
CREATE CONTINUOUS QUERY sidekiq_transaction_counts_seven_days ON gitlab BEGIN SELECT count("duration") AS "count" INTO gitlab.seven_days.sidekiq_transaction_counts FROM gitlab.gitlab_30d.sidekiq_transactions GROUP BY time(1m) END
CREATE CONTINUOUS QUERY rails_method_call_timings_seven_days ON gitlab BEGIN SELECT percentile("duration", 95.000) AS "duration_95th", percentile("duration", 99.000) AS "duration_99th", mean("duration") AS "duration_mean" INTO gitlab.seven_days.rails_method_call_timings FROM gitlab.gitlab_30d.rails_method_calls GROUP BY time(1m) END
CREATE CONTINUOUS QUERY sidekiq_method_call_timings_seven_days ON gitlab BEGIN SELECT percentile("duration", 95.000) AS "duration_95th", percentile("duration", 99.000) AS "duration_99th", mean("duration") AS "duration_mean" INTO gitlab.seven_days.sidekiq_method_call_timings FROM gitlab.gitlab_30d.sidekiq_method_calls GROUP BY time(1m) END
CREATE CONTINUOUS QUERY rails_method_call_timings_per_method_seven_days ON gitlab BEGIN SELECT percentile("duration", 95.000) AS duration_95th, percentile("duration", 99.000) AS duration_99th, mean("duration") AS duration_mean INTO gitlab.seven_days.rails_method_call_timings_per_method FROM gitlab.gitlab_30d.rails_method_calls GROUP BY time(1m), method END
CREATE CONTINUOUS QUERY sidekiq_method_call_timings_per_method_seven_days ON gitlab BEGIN SELECT percentile("duration", 95.000) AS duration_95th, percentile("duration", 99.000) AS duration_99th, mean("duration") AS duration_mean INTO gitlab.seven_days.sidekiq_method_call_timings_per_method FROM gitlab.gitlab_30d.sidekiq_method_calls GROUP BY time(1m), method END
CREATE CONTINUOUS QUERY rails_memory_usage_per_minute ON gitlab BEGIN SELECT percentile(value, 95.000) AS memory_95th, percentile(value, 99.000) AS memory_99th, mean(value) AS memory_mean INTO gitlab.seven_days.rails_memory_usage_per_minute FROM gitlab.gitlab_30d.rails_memory_usage GROUP BY time(1m) END
CREATE CONTINUOUS QUERY sidekiq_memory_usage_per_minute ON gitlab BEGIN SELECT percentile(value, 95.000) AS memory_95th, percentile(value, 99.000) AS memory_99th, mean(value) AS memory_mean INTO gitlab.seven_days.sidekiq_memory_usage_per_minute FROM gitlab.gitlab_30d.sidekiq_memory_usage GROUP BY time(1m) END
CREATE CONTINUOUS QUERY sidekiq_file_descriptors_per_minute ON gitlab BEGIN SELECT sum(value) AS value INTO gitlab.seven_days.sidekiq_file_descriptors_per_minute FROM gitlab.gitlab_30d.sidekiq_file_descriptors GROUP BY time(1m) END
CREATE CONTINUOUS QUERY rails_file_descriptors_per_minute ON gitlab BEGIN SELECT sum(value) AS value INTO gitlab.seven_days.rails_file_descriptors_per_minute FROM gitlab.gitlab_30d.rails_file_descriptors GROUP BY time(1m) END
CREATE CONTINUOUS QUERY rails_gc_counts_per_minute ON gitlab BEGIN SELECT sum(count) AS count INTO gitlab.seven_days.rails_gc_counts_per_minute FROM gitlab.gitlab_30d.rails_gc_statistics GROUP BY time(1m) END
CREATE CONTINUOUS QUERY sidekiq_gc_counts_per_minute ON gitlab BEGIN SELECT sum(count) AS count INTO gitlab.seven_days.sidekiq_gc_counts_per_minute FROM gitlab.gitlab_30d.sidekiq_gc_statistics GROUP BY time(1m) END
CREATE CONTINUOUS QUERY rails_gc_timings_per_minute ON gitlab BEGIN SELECT percentile(total_time, 95.000) AS duration_95th, percentile(total_time, 99.000) AS duration_99th, mean(total_time) AS duration_mean INTO gitlab.seven_days.rails_gc_timings_per_minute FROM gitlab.gitlab_30d.rails_gc_statistics GROUP BY time(1m) END
CREATE CONTINUOUS QUERY sidekiq_gc_timings_per_minute ON gitlab BEGIN SELECT percentile(total_time, 95.000) AS duration_95th, percentile(total_time, 99.000) AS duration_99th, mean(total_time) AS duration_mean INTO gitlab.seven_days.sidekiq_gc_timings_per_minute FROM gitlab.gitlab_30d.sidekiq_gc_statistics GROUP BY time(1m) END
CREATE CONTINUOUS QUERY rails_gc_major_minor_per_minute ON gitlab BEGIN SELECT sum(major_gc_count) AS major, sum(minor_gc_count) AS minor INTO gitlab.seven_days.rails_gc_major_minor_per_minute FROM gitlab.gitlab_30d.rails_gc_statistics GROUP BY time(1m) END
CREATE CONTINUOUS QUERY sidekiq_gc_major_minor_per_minute ON gitlab BEGIN SELECT sum(major_gc_count) AS major, sum(minor_gc_count) AS minor INTO gitlab.seven_days.sidekiq_gc_major_minor_per_minute FROM gitlab.gitlab_30d.sidekiq_gc_statistics GROUP BY time(1m) END
```
## Import Dashboards
You can now import a set of default dashboards that will give you a good
start on displaying useful information. GitLab has published a set of default
[Grafana dashboards][grafana-dashboards] to get you started. Clone the
repository or download a zip/tarball, then follow these steps to import each
JSON file.
Open the dashboard dropdown menu and click 'Import'
![Grafana dashboard dropdown](/img/grafana_dashboard_dropdown.png)
Click 'Choose file' and browse to the location where you downloaded or cloned
the dashboard repository. Pick one of the JSON files to import.
![Grafana dashboard import](/img/grafana_dashboard_import.png)
Once the dashboard is imported, be sure to click save icon in the top bar. If
you do not save the dashboard after importing it will be removed when you
navigate away.
![Grafana save icon](/img/grafana_save_icon.png)
Repeat this process for each dashboard you wish to import.
[grafana-dashboards]: https://gitlab.com/gitlab-org/grafana-dashboards
---
Read more on:
- [Introduction to GitLab Performance Monitoring](introduction.md)
- [GitLab Configuration](gitlab_configuration.md)
- [InfluxDB Installation/Configuration](influxdb_configuration.md)
- [InfluxDB Schema](influxdb_schema.md)
......@@ -8,8 +8,9 @@ Apart from this introduction, you are advised to read through the following
documents in order to understand and properly configure GitLab Performance Monitoring:
- [GitLab Configuration](gitlab_configuration.md)
- [InfluxDB Configuration](influxdb_configuration.md)
- [InfluxDB Install/Configuration](influxdb_configuration.md)
- [InfluxDB Schema](influxdb_schema.md)
- [Grafana Install/Configuration](grafana_configuration.md)
## Introduction to GitLab Performance Monitoring
......
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