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, "exclude": [
"color-case": "lower", "app/assets/stylesheets/framework/tw_bootstrap_variables.scss",
"block-indent": " ", "app/assets/stylesheets/framework/fonts.scss"
"color-shorthand": true, ],
"element-case": "lower", "always-semicolon": true,
"space-before-colon": "", "color-case": "lower",
"space-after-colon": " ", "block-indent": " ",
"space-before-combinator": " ", "color-shorthand": true,
"space-after-combinator": " ", "element-case": "lower",
"space-between-declarations": "\n", "space-before-colon": "",
"space-before-opening-brace": " ", "space-after-colon": " ",
"space-after-opening-brace": "\n", "space-before-combinator": " ",
"space-before-closing-brace": "\n", "space-after-combinator": " ",
"unitless-zero": true "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: ...@@ -128,7 +128,6 @@ scss-lint:
- bundle exec rake scss_lint - bundle exec rake scss_lint
tags: tags:
- ruby - ruby
allow_failure: true
brakeman: brakeman:
stage: test stage: test
......
...@@ -100,7 +100,7 @@ linters: ...@@ -100,7 +100,7 @@ linters:
# Selectors should always use hyphenated-lowercase, rather than camelCase or # Selectors should always use hyphenated-lowercase, rather than camelCase or
# snake_case. # snake_case.
SelectorFormat: SelectorFormat:
enabled: true enabled: false
convention: hyphenated_lowercase convention: hyphenated_lowercase
# Prefer the shortest shorthand form possible for properties that support it. # 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. 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 - Add ability to move issue to another project
- Prevent tokens in the import URL to be showed by the UI - 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) - 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 - Add confidential issues
- Bump gitlab_git to 9.0.3 (Stan Hu) - Bump gitlab_git to 9.0.3 (Stan Hu)
- Fix diff image view modes (2-up, swipe, onion skin) not working (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) ...@@ -19,9 +45,11 @@ v 8.6.0 (unreleased)
setup. A password can be provided during setup (see installation docs), or 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. GitLab will ask the user to create a new one upon first visit.
- Fix issue when pushing to projects ending in .wiki - 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) - Add support for wiki with UTF-8 page names (Hiroyuki Sato)
- Fix wiki search results point to raw source (Hiroyuki Sato) - Fix wiki search results point to raw source (Hiroyuki Sato)
- Don't load all of GitLab in mail_room - 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) - 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 - Update `omniauth-saml` to 1.5.0 to allow for custom response attributes to be set
- Memoize @group in Admin::GroupsController (Yatish Mehta) - Memoize @group in Admin::GroupsController (Yatish Mehta)
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
- [Issue tracker guidelines](#issue-tracker-guidelines) - [Issue tracker guidelines](#issue-tracker-guidelines)
- [Issue weight](#issue-weight) - [Issue weight](#issue-weight)
- [Regression issues](#regression-issues) - [Regression issues](#regression-issues)
- [Technical debt](#technical-debt)
- [Merge requests](#merge-requests) - [Merge requests](#merge-requests)
- [Merge request guidelines](#merge-request-guidelines) - [Merge request guidelines](#merge-request-guidelines)
- [Merge request description format](#merge-request-description-format) - [Merge request description format](#merge-request-description-format)
...@@ -242,6 +243,28 @@ addressed. ...@@ -242,6 +243,28 @@ addressed.
[8.3 Regressions]: https://gitlab.com/gitlab-org/gitlab-ce/issues/4127 [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 [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 ## Merge requests
We welcome merge requests with fixes and improvements to GitLab code, tests, We welcome merge requests with fixes and improvements to GitLab code, tests,
......
...@@ -234,7 +234,7 @@ end ...@@ -234,7 +234,7 @@ end
group :development do group :development do
gem "foreman" gem "foreman"
gem 'brakeman', '~> 3.1.0', require: false gem 'brakeman', '~> 3.2.0', require: false
gem "annotate", "~> 2.6.0" gem "annotate", "~> 2.6.0"
gem "letter_opener", '~> 1.1.2' gem "letter_opener", '~> 1.1.2'
...@@ -279,7 +279,7 @@ group :development, :test do ...@@ -279,7 +279,7 @@ group :development, :test do
gem 'capybara-screenshot', '~> 1.0.0' gem 'capybara-screenshot', '~> 1.0.0'
gem 'poltergeist', '~> 1.9.0' gem 'poltergeist', '~> 1.9.0'
gem 'teaspoon', '~> 1.0.0' gem 'teaspoon', '~> 1.1.0'
gem 'teaspoon-jasmine', '~> 2.2.0' gem 'teaspoon-jasmine', '~> 2.2.0'
gem 'spring', '~> 1.6.4' gem 'spring', '~> 1.6.4'
......
...@@ -84,21 +84,19 @@ GEM ...@@ -84,21 +84,19 @@ GEM
bootstrap-sass (3.3.6) bootstrap-sass (3.3.6)
autoprefixer-rails (>= 5.2.1) autoprefixer-rails (>= 5.2.1)
sass (>= 3.3.4) sass (>= 3.3.4)
brakeman (3.1.4) brakeman (3.2.1)
erubis (~> 2.6) erubis (~> 2.6)
fastercsv (~> 1.5)
haml (>= 3.0, < 5.0) haml (>= 3.0, < 5.0)
highline (>= 1.6.20, < 2.0) highline (>= 1.6.20, < 2.0)
multi_json (~> 1.2) ruby2ruby (~> 2.3.0)
ruby2ruby (>= 2.1.1, < 2.3.0) ruby_parser (~> 3.8.1)
ruby_parser (~> 3.7.0)
safe_yaml (>= 1.0) safe_yaml (>= 1.0)
sass (~> 3.0) sass (~> 3.0)
slim (>= 1.3.6, < 4.0) slim (>= 1.3.6, < 4.0)
terminal-table (~> 1.4) terminal-table (~> 1.4)
browser (1.0.1) browser (1.0.1)
builder (3.2.2) builder (3.2.2)
bullet (4.14.10) bullet (5.0.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
uniform_notifier (~> 1.9.0) uniform_notifier (~> 1.9.0)
bundler-audit (0.4.0) bundler-audit (0.4.0)
...@@ -208,7 +206,6 @@ GEM ...@@ -208,7 +206,6 @@ GEM
faraday_middleware-multi_json (0.0.6) faraday_middleware-multi_json (0.0.6)
faraday_middleware faraday_middleware
multi_json multi_json
fastercsv (1.5.5)
ffaker (2.0.0) ffaker (2.0.0)
ffi (1.9.10) ffi (1.9.10)
fission (0.5.0) fission (0.5.0)
...@@ -328,8 +325,8 @@ GEM ...@@ -328,8 +325,8 @@ GEM
fog-xml (0.1.2) fog-xml (0.1.2)
fog-core fog-core
nokogiri (~> 1.5, >= 1.5.11) nokogiri (~> 1.5, >= 1.5.11)
font-awesome-rails (4.5.0.0) font-awesome-rails (4.5.0.1)
railties (>= 3.2, < 5.0) railties (>= 3.2, < 5.1)
foreman (0.78.0) foreman (0.78.0)
thor (~> 0.19.1) thor (~> 0.19.1)
formatador (0.2.5) formatador (0.2.5)
...@@ -706,10 +703,10 @@ GEM ...@@ -706,10 +703,10 @@ GEM
ruby-saml (1.1.2) ruby-saml (1.1.2)
nokogiri (>= 1.5.10) nokogiri (>= 1.5.10)
uuid (~> 2.3) uuid (~> 2.3)
ruby2ruby (2.2.0) ruby2ruby (2.3.0)
ruby_parser (~> 3.1) ruby_parser (~> 3.1)
sexp_processor (~> 4.0) sexp_processor (~> 4.0)
ruby_parser (3.7.2) ruby_parser (3.8.1)
sexp_processor (~> 4.1) sexp_processor (~> 4.1)
rubyntlm (0.5.2) rubyntlm (0.5.2)
rubypants (0.2.0) rubypants (0.2.0)
...@@ -718,7 +715,7 @@ GEM ...@@ -718,7 +715,7 @@ GEM
safe_yaml (1.0.4) safe_yaml (1.0.4)
sanitize (2.1.0) sanitize (2.1.0)
nokogiri (>= 1.4.4) nokogiri (>= 1.4.4)
sass (3.4.20) sass (3.4.21)
sass-rails (5.0.4) sass-rails (5.0.4)
railties (>= 4.0.0, < 5.0) railties (>= 4.0.0, < 5.0)
sass (~> 3.1) sass (~> 3.1)
...@@ -742,7 +739,7 @@ GEM ...@@ -742,7 +739,7 @@ GEM
sentry-raven (0.15.6) sentry-raven (0.15.6)
faraday (>= 0.7.6) faraday (>= 0.7.6)
settingslogic (2.0.9) settingslogic (2.0.9)
sexp_processor (4.6.0) sexp_processor (4.7.0)
sham_rack (1.3.6) sham_rack (1.3.6)
rack rack
shoulda-matchers (2.8.0) shoulda-matchers (2.8.0)
...@@ -806,8 +803,8 @@ GEM ...@@ -806,8 +803,8 @@ GEM
systemu (2.6.5) systemu (2.6.5)
task_list (1.0.2) task_list (1.0.2)
html-pipeline html-pipeline
teaspoon (1.0.2) teaspoon (1.1.5)
railties (>= 3.2.5, < 5) railties (>= 3.2.5, < 6)
teaspoon-jasmine (2.2.0) teaspoon-jasmine (2.2.0)
teaspoon (>= 1.0.0) teaspoon (>= 1.0.0)
temple (0.7.6) temple (0.7.6)
...@@ -868,7 +865,7 @@ GEM ...@@ -868,7 +865,7 @@ GEM
equalizer (~> 0.0, >= 0.0.9) equalizer (~> 0.0, >= 0.0.9)
warden (1.2.4) warden (1.2.4)
rack (>= 1.0) rack (>= 1.0)
web-console (2.2.1) web-console (2.3.0)
activemodel (>= 4.0) activemodel (>= 4.0)
binding_of_caller (>= 0.7.2) binding_of_caller (>= 0.7.2)
railties (>= 4.0) railties (>= 4.0)
...@@ -910,7 +907,7 @@ DEPENDENCIES ...@@ -910,7 +907,7 @@ DEPENDENCIES
better_errors (~> 1.0.1) better_errors (~> 1.0.1)
binding_of_caller (~> 0.7.2) binding_of_caller (~> 0.7.2)
bootstrap-sass (~> 3.3.0) bootstrap-sass (~> 3.3.0)
brakeman (~> 3.1.0) brakeman (~> 3.2.0)
browser (~> 1.0.0) browser (~> 1.0.0)
bullet bullet
bundler-audit bundler-audit
...@@ -1048,7 +1045,7 @@ DEPENDENCIES ...@@ -1048,7 +1045,7 @@ DEPENDENCIES
sprockets (~> 3.3.5) sprockets (~> 3.3.5)
state_machines-activerecord (~> 0.3.0) state_machines-activerecord (~> 0.3.0)
task_list (~> 1.0.2) task_list (~> 1.0.2)
teaspoon (~> 1.0.0) teaspoon (~> 1.1.0)
teaspoon-jasmine (~> 2.2.0) teaspoon-jasmine (~> 2.2.0)
test_after_commit (~> 0.4.2) test_after_commit (~> 0.4.2)
thin (~> 1.6.1) thin (~> 1.6.1)
...@@ -1064,3 +1061,6 @@ DEPENDENCIES ...@@ -1064,3 +1061,6 @@ DEPENDENCIES
web-console (~> 2.0) web-console (~> 2.0)
webmock (~> 1.21.0) webmock (~> 1.21.0)
wikicloth (= 0.8.1) wikicloth (= 0.8.1)
BUNDLED WITH
1.11.2
8.6.0-pre 8.7.0-pre
...@@ -74,6 +74,8 @@ ...@@ -74,6 +74,8 @@
dataType: "json" dataType: "json"
).done (label) -> ).done (label) ->
callback(label) callback(label)
.error (message) ->
callback(message.responseJSON)
# Return group projects list. Filtered by query # Return group projects list. Filtered by query
groupProjects: (group_id, query, callback) -> groupProjects: (group_id, query, callback) ->
......
...@@ -43,6 +43,7 @@ ...@@ -43,6 +43,7 @@
#= require jquery.nicescroll #= require jquery.nicescroll
#= require_tree . #= require_tree .
#= require fuzzaldrin-plus #= require fuzzaldrin-plus
#= require cropper
window.slugify = (text) -> window.slugify = (text) ->
text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase() text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase()
...@@ -218,13 +219,20 @@ $ -> ...@@ -218,13 +219,20 @@ $ ->
$this = $(this) $this = $(this)
$this.attr 'value', $this.val() $this.attr 'value', $this.val()
$sidebarGutterToggle = $('.js-sidebar-toggle')
$navIconToggle = $('.toggle-nav-collapse')
$(document) $(document)
.off 'breakpoint:change' .off 'breakpoint:change'
.on 'breakpoint:change', (e, breakpoint) -> .on 'breakpoint:change', (e, breakpoint) ->
if breakpoint is 'sm' or breakpoint is 'xs' 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') 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) $(document)
.off 'click', '.js-sidebar-toggle' .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 class GitLabDropdownFilter
BLUR_KEYCODES = [27, 40] BLUR_KEYCODES = [27, 40]
HAS_VALUE_CLASS = "has-value"
constructor: (@dropdown, @options) -> constructor: (@input, @options) ->
@input = @dropdown.find(".dropdown-input .dropdown-input-field") $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 # Key events
timeout = "" timeout = ""
@input.on "keyup", (e) => @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 if @options.enterCallback
@options.enterCallback() @options.enterCallback()
return return
...@@ -95,7 +111,9 @@ class GitLabDropdown ...@@ -95,7 +111,9 @@ class GitLabDropdown
# Init filiterable # Init filiterable
if @options.filterable if @options.filterable
@filter = new GitLabDropdownFilter @dropdown, @input = @dropdown.find('.dropdown-input .dropdown-input-field')
@filter = new GitLabDropdownFilter @input,
remote: @options.filterRemote remote: @options.filterRemote
query: @options.data query: @options.data
keys: @options.search.fields keys: @options.search.fields
...@@ -103,6 +121,7 @@ class GitLabDropdown ...@@ -103,6 +121,7 @@ class GitLabDropdown
return @fullData return @fullData
callback: (data) => callback: (data) =>
@parseData data @parseData data
@highlightRow 1
enterCallback: => enterCallback: =>
@selectFirstRow() @selectFirstRow()
...@@ -224,11 +243,19 @@ class GitLabDropdown ...@@ -224,11 +243,19 @@ class GitLabDropdown
noResults: -> noResults: ->
html = "<li>" html = "<li>"
html += "<a href='#' class='is-focused'>" html += "<a href='#' class='dropdown-menu-empty-link is-focused'>"
html += "No matching results." html += "No matching results."
html += "</a>" html += "</a>"
html += "</li>" 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) -> rowClicked: (el) ->
fieldName = @options.fieldName fieldName = @options.fieldName
field = @dropdown.parent().find("input[name='#{fieldName}']") field = @dropdown.parent().find("input[name='#{fieldName}']")
...@@ -272,7 +299,7 @@ class GitLabDropdown ...@@ -272,7 +299,7 @@ class GitLabDropdown
if @dropdown.find(".dropdown-toggle-page").length if @dropdown.find(".dropdown-toggle-page").length
selector = ".dropdown-page-one .dropdown-content li:first-child a" 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" $(selector).trigger "click"
$.fn.glDropdown = (opts) -> $.fn.glDropdown = (opts) ->
......
#= require jquery.waitforimages
class @IssuableContext class @IssuableContext
constructor: -> constructor: ->
@initParticipants()
new UsersSelect() new UsersSelect()
$('select.select2').select2({width: 'resolve', dropdownAutoWidth: true}) $('select.select2').select2({width: 'resolve', dropdownAutoWidth: true})
...@@ -17,3 +17,27 @@ class @IssuableContext ...@@ -17,3 +17,27 @@ class @IssuableContext
block.find('.js-select2').select2("open") block.find('.js-select2').select2("open")
$(".right-sidebar").niceScroll() $(".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 ...@@ -7,7 +7,6 @@ class @Issue
# Prevent duplicate event bindings # Prevent duplicate event bindings
@disableTaskList() @disableTaskList()
@fixAffixScroll() @fixAffixScroll()
@initParticipants()
if $('a.btn-close').length if $('a.btn-close').length
@initTaskList() @initTaskList()
@initIssueBtnEventListeners() @initIssueBtnEventListeners()
...@@ -85,27 +84,3 @@ class @Issue ...@@ -85,27 +84,3 @@ class @Issue
type: 'PATCH' type: 'PATCH'
url: $('form.js-issuable-update').attr('action') url: $('form.js-issuable-update').attr('action')
data: patchData 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 ...@@ -6,7 +6,7 @@ class @LabelsSelect
labelUrl = $dropdown.data('labels') labelUrl = $dropdown.data('labels')
selectedLabel = $dropdown.data('selected') selectedLabel = $dropdown.data('selected')
if selectedLabel if selectedLabel
selectedLabel = selectedLabel.split(',') selectedLabel = selectedLabel.toString().split(',')
newLabelField = $('#new_label_name') newLabelField = $('#new_label_name')
newColorField = $('#new_label_color') newColorField = $('#new_label_color')
showNo = $dropdown.data('show-no') showNo = $dropdown.data('show-no')
...@@ -14,28 +14,81 @@ class @LabelsSelect ...@@ -14,28 +14,81 @@ class @LabelsSelect
defaultLabel = $dropdown.data('default-label') defaultLabel = $dropdown.data('default-label')
if newLabelField.length 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) -> $('.suggest-colors-dropdown a').on 'click', (e) ->
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
newColorField.val $(this).data('color') newColorField
$('.js-dropdown-label-color-preview') .val($(this).data('color'))
.trigger('change')
$colorPreview
.css 'background-color', $(this).data('color') .css 'background-color', $(this).data('color')
.parent()
.addClass 'is-active' .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.preventDefault()
e.stopPropagation() 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 '' 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 newColorField.on 'keyup change', enableLabelCreateButton
Api.newLabel projectId, {
name: newLabelField.val() # Send the API call to create the label
color: newColorField.val() $newLabelCreateButton
}, (label) -> .disable()
$('.js-new-label-btn').enable() .on 'click', (e) ->
$('.dropdown-menu-back', $dropdown.parent()).trigger 'click' 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( $dropdown.glDropdown(
data: (term, callback) -> data: (term, callback) ->
...@@ -68,8 +121,11 @@ class @LabelsSelect ...@@ -68,8 +121,11 @@ class @LabelsSelect
else else
selected = if label.title is selectedLabel then 'is-active' 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> "<li>
<a href='#' class='#{selected}'> <a href='#' class='#{selected}'>
#{color}
#{label.title} #{label.title}
</a> </a>
</li>" </li>"
......
class @Profile class @Profile
constructor: -> constructor: (opts = {}) ->
{
@form = $('.edit-user')
} = opts
# Automatically submit the Preferences form when any of its radio buttons change # Automatically submit the Preferences form when any of its radio buttons change
$('.js-preferences-form').on 'change.preference', 'input[type=radio]', -> $('.js-preferences-form').on 'change.preference', 'input[type=radio]', ->
$(this).parents('form').submit() $(this).parents('form').submit()
...@@ -17,14 +21,46 @@ class @Profile ...@@ -17,14 +21,46 @@ class @Profile
$('.update-notifications').on 'ajax:complete', -> $('.update-notifications').on 'ajax:complete', ->
$(this).find('.btn-save').enable() $(this).find('.btn-save').enable()
$('.js-choose-user-avatar-button').bind "click", -> @bindEvents()
form = $(this).closest("form")
form.find(".js-user-avatar-input").click() 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", -> $.ajax
form = $(this).closest("form") url: @form.attr('action')
filename = $(this).val().replace(/^.*[\\\/]/, '') type: @form.attr('method')
form.find(".js-avatar-filename").text(filename) 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 # Extract the SSH Key title from its comment
......
...@@ -4,7 +4,6 @@ expanded = 'page-sidebar-expanded' ...@@ -4,7 +4,6 @@ expanded = 'page-sidebar-expanded'
toggleSidebar = -> toggleSidebar = ->
$('.page-with-sidebar').toggleClass("#{collapsed} #{expanded}") $('.page-with-sidebar').toggleClass("#{collapsed} #{expanded}")
$('header').toggleClass("header-collapsed header-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") $('.toggle-nav-collapse i').toggleClass("fa-angle-right fa-angle-left")
$.cookie("collapsed_nav", $('.page-with-sidebar').hasClass(collapsed), { path: '/' }) $.cookie("collapsed_nav", $('.page-with-sidebar').hasClass(collapsed), { path: '/' })
......
...@@ -30,6 +30,7 @@ class @UsersSelect ...@@ -30,6 +30,7 @@ class @UsersSelect
if showNullUser if showNullUser
showDivider += 1 showDivider += 1
users.unshift( users.unshift(
beforeDivider: true
name: 'Unassigned', name: 'Unassigned',
id: 0 id: 0
) )
...@@ -39,6 +40,7 @@ class @UsersSelect ...@@ -39,6 +40,7 @@ class @UsersSelect
name = showAnyUser name = showAnyUser
name = 'Any User' if name == true name = 'Any User' if name == true
anyUser = { anyUser = {
beforeDivider: true
name: name, name: name,
id: null id: null
} }
...@@ -75,20 +77,27 @@ class @UsersSelect ...@@ -75,20 +77,27 @@ class @UsersSelect
selected = if user.id is selectedId then "is-active" else "" selected = if user.id is selectedId then "is-active" else ""
img = "" img = ""
if avatar if user.beforeDivider?
img = "<img src='#{avatar}' class='avatar avatar-inline' width='30' />" "<li>
<a href='#' class='#{selected}'>
"<li>
<a href='#' class='dropdown-menu-user-link #{selected}'>
#{img}
<strong class='dropdown-menu-user-full-name'>
#{user.name} #{user.name}
</strong> </a>
<span class='dropdown-menu-user-username'> </li>"
#{username} else
</span> if avatar
</a> img = "<img src='#{avatar}' class='avatar avatar-inline' width='30' />"
</li>"
"<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) => $('.ajax-users-select').each (i, select) =>
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
*= require_self *= require_self
*= require dropzone/basic *= require dropzone/basic
*= require cal-heatmap *= require cal-heatmap
*= require cropper.css
*/ */
/* /*
......
...@@ -292,8 +292,11 @@ table { ...@@ -292,8 +292,11 @@ table {
} }
.btn-sign-in { .btn-sign-in {
margin-top: 10px;
text-shadow: none; text-shadow: none;
@media (min-width: $screen-sm-min) {
margin-top: 11px;
}
} }
.side-filters { .side-filters {
...@@ -375,7 +378,7 @@ table { ...@@ -375,7 +378,7 @@ table {
position: absolute; position: absolute;
top: 0; top: 0;
right: 0; right: 0;
width: 250px !important; min-width: 250px;
visibility: hidden; visibility: hidden;
} }
} }
......
...@@ -130,6 +130,12 @@ ...@@ -130,6 +130,12 @@
text-decoration: none; text-decoration: none;
outline: 0; outline: 0;
} }
&.dropdown-menu-empty-link {
&.is-focused {
background-color: $dropdown-empty-row-bg;
}
}
} }
} }
...@@ -183,7 +189,7 @@ ...@@ -183,7 +189,7 @@
} }
.dropdown-select { .dropdown-select {
width: 280px; width: 300px;
} }
.dropdown-menu-align-right { .dropdown-menu-align-right {
...@@ -237,7 +243,7 @@ ...@@ -237,7 +243,7 @@
.dropdown-title-button { .dropdown-title-button {
position: absolute; position: absolute;
top: -1px; top: 0;
padding: 0; padding: 0;
color: $dropdown-title-btn-color; color: $dropdown-title-btn-color;
font-size: 14px; font-size: 14px;
...@@ -270,6 +276,22 @@ ...@@ -270,6 +276,22 @@
font-size: 12px; font-size: 12px;
pointer-events: none; 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 { .dropdown-input-field {
...@@ -286,13 +308,13 @@ ...@@ -286,13 +308,13 @@
border-color: $dropdown-input-focus-border; border-color: $dropdown-input-focus-border;
box-shadow: 0 0 4px $dropdown-input-focus-shadow; box-shadow: 0 0 4px $dropdown-input-focus-shadow;
+ .fa { ~ .fa {
color: $dropdown-link-color; color: $dropdown-link-color;
} }
} }
&:hover { &:hover {
+ .fa { ~ .fa {
color: $dropdown-link-color; color: $dropdown-link-color;
} }
} }
...@@ -338,11 +360,12 @@ ...@@ -338,11 +360,12 @@
} }
} }
.dropdown-menu-labels { .dropdown-label-box {
.label { position: relative;
position: relative; top: 3px;
width: 30px; margin-right: 5px;
margin-right: 5px; display: inline-block;
text-indent: -99999px; width: 15px;
} height: 15px;
border-radius: $border-radius-base;
} }
...@@ -50,6 +50,10 @@ ...@@ -50,6 +50,10 @@
} }
} }
a {
color: $gl-dark-link-color;
}
.left-options { .left-options {
margin-top: -3px; margin-top: -3px;
} }
......
...@@ -11,3 +11,11 @@ ...@@ -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 */ /* latin-ext */
@font-face { @font-face {
font-family: 'Source Sans Pro'; font-family: 'Source Sans Pro';
......
...@@ -70,6 +70,11 @@ header { ...@@ -70,6 +70,11 @@ header {
.header-content { .header-content {
height: $header-height; height: $header-height;
padding-right: 20px;
@media (min-width: $screen-sm-min) {
padding-right: 0;
}
.title { .title {
margin: 0; margin: 0;
......
...@@ -102,6 +102,10 @@ ...@@ -102,6 +102,10 @@
display: inline-block; display: inline-block;
} }
.icon-label {
display: none;
}
input { input {
height: 34px; height: 34px;
display: inline-block; display: inline-block;
...@@ -124,9 +128,38 @@ ...@@ -124,9 +128,38 @@
} }
} }
/* Hide on extra small devices (phones) */
@media (max-width: $screen-xs-max) { @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) */ /* Small devices (tablets, 768px and lower) */
......
...@@ -44,6 +44,7 @@ ...@@ -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 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); @include border-radius ($border-radius-default);
border: none; border: none;
min-width: 175px;
} }
.select2-results .select2-result-label { .select2-results .select2-result-label {
......
#logo {
z-index: 2;
position: absolute;
width: 58px;
cursor: pointer;
}
.page-with-sidebar { .page-with-sidebar {
padding-top: $header-height; padding-top: $header-height;
transition-duration: .3s; transition-duration: .3s;
...@@ -18,28 +25,10 @@ ...@@ -18,28 +25,10 @@
position: absolute; position: absolute;
left: 0; 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 { .sidebar-wrapper {
z-index: 999; z-index: 1000;
background: $background-color; background: $background-color;
} }
...@@ -202,53 +191,27 @@ ...@@ -202,53 +191,27 @@
} }
} }
@mixin expanded-sidebar { .collapse-nav a {
padding-left: $sidebar_collapsed_width; width: $sidebar_width;
position: fixed;
@media (min-width: $screen-md-min) { bottom: 0;
padding-left: $sidebar_width; left: 0;
} font-size: 13px;
background: transparent;
&.right-sidebar-collapsed { height: 40px;
/* Extra small devices (phones, less than 768px) */ text-align: center;
padding-right: 0; line-height: 40px;
/* Small devices (tablets, 768px and up) */ transition-duration: .3s;
@media (min-width: $screen-sm-min) { outline: none;
padding-right: $sidebar_collapsed_width;
}
}
.sidebar-wrapper {
width: $sidebar_width;
.nav-sidebar {
width: $sidebar_width;
}
.nav-sidebar li a{
width: 230px;
&.back-link { &:hover {
i { text-decoration: none;
opacity: 0;
}
}
}
} }
} }
@mixin collapsed-sidebar { .page-sidebar-collapsed {
padding-left: $sidebar_collapsed_width; 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 { .sidebar-wrapper {
width: $sidebar_collapsed_width; width: $sidebar_collapsed_width;
...@@ -293,35 +256,48 @@ ...@@ -293,35 +256,48 @@
} }
} }
.collapse-nav a { .page-sidebar-expanded {
width: $sidebar_width; padding-left: $sidebar_collapsed_width;
position: fixed;
bottom: 0; @media (min-width: $screen-md-min) {
left: 0; padding-left: $sidebar_width;
font-size: 13px; }
background: transparent;
height: 40px; .sidebar-wrapper {
text-align: center; width: $sidebar_width;
line-height: 40px;
transition-duration: .3s; .nav-sidebar {
outline: none; width: $sidebar_width;
} }
.nav-sidebar li a {
width: 230px;
.collapse-nav a:hover { &.back-link {
text-decoration: none; i {
background: #f2f6f7; opacity: 0;
}
}
}
}
} }
.page-sidebar-collapsed { .right-sidebar-collapsed {
/* Extra small devices (phones, less than 768px) */
@include collapsed-sidebar;
padding-right: 0; padding-right: 0;
/* Small devices (tablets, 768px and up) */
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
@include collapsed-sidebar; padding-right: $sidebar_collapsed_width;
} }
} }
.page-sidebar-expanded { .right-sidebar-expanded {
@include expanded-sidebar; 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 ...@@ -168,13 +168,14 @@ $regular_font: 'Source Sans Pro', "Helvetica Neue", Helvetica, Arial, sans-serif
*/ */
$dropdown-bg: #fff; $dropdown-bg: #fff;
$dropdown-link-color: #555; $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-border-color: rgba(#000, .1);
$dropdown-shadow-color: rgba(#000, .1); $dropdown-shadow-color: rgba(#000, .1);
$dropdown-divider-color: rgba(#000, .1); $dropdown-divider-color: rgba(#000, .1);
$dropdown-header-color: #959494; $dropdown-header-color: #959494;
$dropdown-title-btn-color: #bfbfbf; $dropdown-title-btn-color: #bfbfbf;
$dropdown-input-color: #c7c7c7; $dropdown-input-color: #555;
$dropdown-input-focus-border: rgb(58, 171, 240); $dropdown-input-focus-border: rgb(58, 171, 240);
$dropdown-input-focus-shadow: rgba(#000, .2); $dropdown-input-focus-shadow: rgba(#000, .2);
$dropdown-loading-bg: rgba(#fff, .6); $dropdown-loading-bg: rgba(#fff, .6);
......
...@@ -3,12 +3,12 @@ img { ...@@ -3,12 +3,12 @@ img {
height: auto; height: auto;
} }
p.details { p.details {
font-style:italic; font-style: italic;
color:#777 color: #777
} }
.footer p { .footer p {
font-size:small; font-size: small;
color:#777 color: #777
} }
pre.commit-message { pre.commit-message {
white-space: pre-wrap; white-space: pre-wrap;
...@@ -20,5 +20,5 @@ pre.commit-message { ...@@ -20,5 +20,5 @@ pre.commit-message {
color: #090; color: #090;
} }
.file-stats .deleted-file { .file-stats .deleted-file {
color: #B00; color: #b00;
} }
...@@ -99,6 +99,10 @@ li.commit { ...@@ -99,6 +99,10 @@ li.commit {
color: $gl-gray; color: $gl-gray;
} }
.avatar {
margin-right: 8px;
}
.committed_ago { .committed_ago {
display: inline-block; display: inline-block;
} }
......
...@@ -9,28 +9,45 @@ ...@@ -9,28 +9,45 @@
} }
&.suggest-colors-dropdown { &.suggest-colors-dropdown {
margin-bottom: 5px; margin-top: 10px;
margin-bottom: 10px;
border-radius: $border-radius-base;
overflow: hidden;
a { a {
@include border-radius(0); @include border-radius(0);
width: 36.7px; width: (100% / 7);
margin-right: 0; margin-right: 0;
margin-bottom: -5px; margin-bottom: -5px;
} }
} }
} }
.dropdown-label-color-preview { .dropdown-new-label {
display: none; .dropdown-content {
margin-top: 5px; max-height: 260px;
width: 100%; }
height: 25px; }
.dropdown-label-color-input {
position: relative;
margin-bottom: 10px;
&.is-active { &.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-row {
.label { .label {
padding: 9px; padding: 9px;
...@@ -45,3 +62,10 @@ ...@@ -45,3 +62,10 @@
.label-subscription { .label-subscription {
display: inline-block; display: inline-block;
} }
.dropdown-labels-error {
padding: 5px 10px;
margin-bottom: 10px;
background-color: $gl-danger;
color: $white-light;
}
...@@ -197,3 +197,24 @@ ...@@ -197,3 +197,24 @@
width: 105px; 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 @@ ...@@ -229,6 +229,10 @@
padding: 0 3px; padding: 0 3px;
color: #999; color: #999;
} }
a {
color: $gl-dark-link-color;
}
} }
.last-push-widget { .last-push-widget {
......
.ci-status { .container-fluid .content {
padding: 2px 7px; .ci-status {
margin-right: 5px; padding: 2px 7px;
border: 1px solid #eee; margin-right: 5px;
white-space: nowrap; border: 1px solid #eee;
@include border-radius(4px); white-space: nowrap;
@include border-radius(4px);
&:hover { &:hover {
text-decoration: none; text-decoration: none;
} }
&.ci-failed { &.ci-failed {
color: $gl-danger; color: $gl-danger;
border-color: $gl-danger; border-color: $gl-danger;
} }
&.ci-success { &.ci-success {
color: $gl-success; color: $gl-success;
border-color: $gl-success; border-color: $gl-success;
} }
&.ci-info { &.ci-info {
color: $gl-info; color: $gl-info;
border-color: $gl-info; border-color: $gl-info;
} }
&.ci-disabled { &.ci-canceled,
color: $gl-gray; &.ci-skipped,
border-color: $gl-gray; &.ci-disabled {
color: $gl-gray;
border-color: $gl-gray;
}
&.ci-pending,
&.ci-running {
color: $gl-warning;
border-color: $gl-warning;
}
} }
&.ci-pending, .ci-status-icon-success {
&.ci-running { color: $gl-success;
}
.ci-status-icon-failed {
color: $gl-danger;
}
.ci-status-icon-running,
.ci-status-icon-pending {
color: $gl-warning; color: $gl-warning;
border-color: $gl-warning;
} }
} .ci-status-icon-canceled,
.ci-status-icon-disabled,
.ci-status-icon-success { .ci-status-icon-not-found,
@extend .cgreen; .ci-status-icon-skipped {
} color: $gl-gray;
.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;
} }
...@@ -6,7 +6,6 @@ module GlobalMilestones ...@@ -6,7 +6,6 @@ module GlobalMilestones
@milestones = MilestonesFinder.new.execute(@projects, params) @milestones = MilestonesFinder.new.execute(@projects, params)
@milestones = GlobalMilestone.build_collection(@milestones) @milestones = GlobalMilestone.build_collection(@milestones)
@milestones = @milestones.sort_by { |x| x.due_date.nil? ? epoch : x.due_date } @milestones = @milestones.sort_by { |x| x.due_date.nil? ? epoch : x.due_date }
@milestones = Kaminari.paginate_array(@milestones).page(params[:page])
end end
def milestone def milestone
......
class Dashboard::ApplicationController < ApplicationController class Dashboard::ApplicationController < ApplicationController
layout 'dashboard' layout 'dashboard'
private
def projects
@projects ||= current_user.authorized_projects.sorted_by_activity.non_archived
end
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 ...@@ -2,18 +2,19 @@ class Dashboard::MilestonesController < Dashboard::ApplicationController
include GlobalMilestones include GlobalMilestones
before_action :projects before_action :projects
before_action :milestones, only: [:index]
before_action :milestone, only: [:show] before_action :milestone, only: [:show]
def index 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 end
def show def show
end end
private
def projects
@projects ||= current_user.authorized_projects.sorted_by_activity.non_archived
end
end end
...@@ -3,7 +3,7 @@ class DashboardController < Dashboard::ApplicationController ...@@ -3,7 +3,7 @@ class DashboardController < Dashboard::ApplicationController
include MergeRequestsAction include MergeRequestsAction
before_action :event_filter, only: :activity 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 respond_to :html
...@@ -20,29 +20,6 @@ class DashboardController < Dashboard::ApplicationController ...@@ -20,29 +20,6 @@ class DashboardController < Dashboard::ApplicationController
end end
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 protected
def load_events def load_events
...@@ -57,8 +34,4 @@ class DashboardController < Dashboard::ApplicationController ...@@ -57,8 +34,4 @@ class DashboardController < Dashboard::ApplicationController
@events = @event_filter.apply_filter(@events).with_associations @events = @event_filter.apply_filter(@events).with_associations
@events = @events.limit(20).offset(params[:offset] || 0) @events = @events.limit(20).offset(params[:offset] || 0)
end end
def projects
@projects ||= current_user.authorized_projects.sorted_by_activity.non_archived
end
end end
...@@ -2,11 +2,15 @@ class Groups::MilestonesController < Groups::ApplicationController ...@@ -2,11 +2,15 @@ class Groups::MilestonesController < Groups::ApplicationController
include GlobalMilestones include GlobalMilestones
before_action :group_projects before_action :group_projects
before_action :milestones, only: [:index]
before_action :milestone, only: [:show, :update] before_action :milestone, only: [:show, :update]
before_action :authorize_admin_milestones!, only: [:new, :create, :update] before_action :authorize_admin_milestones!, only: [:new, :create, :update]
def index def index
respond_to do |format|
format.html do
@milestones = Kaminari.paginate_array(milestones).page(params[:page])
end
end
end end
def new def new
......
...@@ -11,15 +11,16 @@ class ProfilesController < Profiles::ApplicationController ...@@ -11,15 +11,16 @@ class ProfilesController < Profiles::ApplicationController
def update def update
user_params.except!(:email) if @user.ldap_user? 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| 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
end end
......
...@@ -26,6 +26,10 @@ class RootController < Dashboard::ProjectsController ...@@ -26,6 +26,10 @@ class RootController < Dashboard::ProjectsController
redirect_to activity_dashboard_path redirect_to activity_dashboard_path
when 'starred_project_activity' when 'starred_project_activity'
redirect_to activity_dashboard_path(filter: 'starred') redirect_to activity_dashboard_path(filter: 'starred')
when 'groups'
redirect_to dashboard_groups_path
when 'todos'
redirect_to dashboard_todos_path
else else
return return
end end
......
...@@ -70,7 +70,8 @@ module DropdownsHelper ...@@ -70,7 +70,8 @@ module DropdownsHelper
def dropdown_filter(placeholder) def dropdown_filter(placeholder)
content_tag :div, class: "dropdown-input" do 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 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 filter_output.html_safe
end end
......
...@@ -194,7 +194,7 @@ module EventsHelper ...@@ -194,7 +194,7 @@ module EventsHelper
end end
def event_to_atom(xml, event) def event_to_atom(xml, event)
if event.proper?(current_user) if event.visible_to_user?(current_user)
xml.entry do xml.entry do
event_link = event_feed_url(event) event_link = event_feed_url(event)
event_title = event_feed_title(event) event_title = event_feed_title(event)
......
...@@ -114,7 +114,7 @@ module LabelsHelper ...@@ -114,7 +114,7 @@ module LabelsHelper
if @project if @project
namespace_project_labels_path(@project.namespace, @project, :json) namespace_project_labels_path(@project.namespace, @project, :json)
else else
labels_dashboard_path(:json) dashboard_labels_path(:json)
end end
end end
......
...@@ -50,7 +50,7 @@ module MilestonesHelper ...@@ -50,7 +50,7 @@ module MilestonesHelper
if @project if @project
namespace_project_milestones_path(@project.namespace, @project, :json) namespace_project_milestones_path(@project.namespace, @project, :json)
else else
milestones_dashboard_path(:json) dashboard_milestones_path(:json)
end end
end end
......
...@@ -12,7 +12,9 @@ module PreferencesHelper ...@@ -12,7 +12,9 @@ module PreferencesHelper
projects: 'Your Projects (default)', projects: 'Your Projects (default)',
stars: 'Starred Projects', stars: 'Starred Projects',
project_activity: "Your Projects' Activity", 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 }.with_indifferent_access.freeze
# Returns an Array usable by a select field for more user-friendly option text # Returns an Array usable by a select field for more user-friendly option text
......
...@@ -230,7 +230,7 @@ class Commit ...@@ -230,7 +230,7 @@ class Commit
end end
def revert_message def revert_message
%Q{Revert "#{title}"\n\n#{revert_description}} %Q{Revert "#{title.strip}"\n\n#{revert_description}}
end end
def reverts_commit?(commit) def reverts_commit?(commit)
......
...@@ -41,7 +41,7 @@ module Issuable ...@@ -41,7 +41,7 @@ module Issuable
scope :join_project, -> { joins(:project) } scope :join_project, -> { joins(:project) }
scope :references_project, -> { references(: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, delegate :name,
:email, :email,
......
...@@ -73,15 +73,15 @@ class Event < ActiveRecord::Base ...@@ -73,15 +73,15 @@ class Event < ActiveRecord::Base
end end
end end
def proper?(user = nil) def visible_to_user?(user = nil)
if push? if push?
true true
elsif membership_changed? elsif membership_changed?
true true
elsif created_project? elsif created_project?
true true
elsif issue? elsif issue? || issue_note?
Ability.abilities.allowed?(user, :read_issue, issue) Ability.abilities.allowed?(user, :read_issue, note? ? note_target : target)
else else
((merge_request? || note?) && target) || milestone? ((merge_request? || note?) && target) || milestone?
end end
...@@ -298,6 +298,10 @@ class Event < ActiveRecord::Base ...@@ -298,6 +298,10 @@ class Event < ActiveRecord::Base
target.noteable_type == "Commit" target.noteable_type == "Commit"
end end
def issue_note?
note? && target && target.noteable_type == "Issue"
end
def note_project_snippet? def note_project_snippet?
target.noteable_type == "Snippet" target.noteable_type == "Snippet"
end end
......
...@@ -146,7 +146,8 @@ class Issue < ActiveRecord::Base ...@@ -146,7 +146,8 @@ class Issue < ActiveRecord::Base
return false unless user.can?(:admin_issue, to_project) return false unless user.can?(:admin_issue, to_project)
end end
!moved? && user.can?(:admin_issue, self.project) !moved? && persisted? &&
user.can?(:admin_issue, self.project)
end end
def to_branch_name def to_branch_name
......
...@@ -97,12 +97,12 @@ class Label < ActiveRecord::Base ...@@ -97,12 +97,12 @@ class Label < ActiveRecord::Base
end end
end end
def open_issues_count def open_issues_count(user = nil)
issues.opened.count issues.visible_to_user(user).opened.count
end end
def closed_issues_count def closed_issues_count(user = nil)
issues.closed.count issues.visible_to_user(user).closed.count
end end
def open_merge_requests_count def open_merge_requests_count
......
...@@ -83,7 +83,7 @@ class Milestone < ActiveRecord::Base ...@@ -83,7 +83,7 @@ class Milestone < ActiveRecord::Base
end end
def self.upcoming 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 end
def to_reference(from_project = nil) def to_reference(from_project = nil)
......
...@@ -304,7 +304,7 @@ class Project < ActiveRecord::Base ...@@ -304,7 +304,7 @@ class Project < ActiveRecord::Base
end end
def find_with_namespace(id) 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 return nil if !namespace_path || !project_path
......
...@@ -467,6 +467,18 @@ class Repository ...@@ -467,6 +467,18 @@ class Repository
end end
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 def head_commit
@head_commit ||= commit(self.root_ref) @head_commit ||= commit(self.root_ref)
end end
...@@ -877,6 +889,8 @@ class Repository ...@@ -877,6 +889,8 @@ class Repository
end end
def avatar def avatar
return nil unless exists?
@avatar ||= cache.fetch(:avatar) do @avatar ||= cache.fetch(:avatar) do
AVATAR_FILES.find do |file| AVATAR_FILES.find do |file|
blob_at_branch('master', file) blob_at_branch('master', file)
......
...@@ -184,7 +184,7 @@ class User < ActiveRecord::Base ...@@ -184,7 +184,7 @@ class User < ActiveRecord::Base
# User's Dashboard preference # User's Dashboard preference
# Note: When adding an option, it MUST go on the end of the array. # 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 # User's Project preference
# Note: When adding an option, it MUST go on the end of the array. # Note: When adding an option, it MUST go on the end of the array.
......
module Ci module Ci
class CreateBuildsService class CreateBuildsService
def execute(commit, stage, ref, tag, user, trigger_request, status) 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 # check when to create next build
builds_attrs = builds_attrs.select do |build_attrs| builds_attrs = builds_attrs.select do |build_attrs|
......
...@@ -54,7 +54,8 @@ module Issues ...@@ -54,7 +54,8 @@ module Issues
new_note = note.dup new_note = note.dup
new_params = { project: @new_project, noteable: @new_issue, new_params = { project: @new_project, noteable: @new_issue,
note: unfold_references(new_note.note), 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) new_note.update(new_params)
end end
...@@ -78,6 +79,8 @@ module Issues ...@@ -78,6 +79,8 @@ module Issues
end end
def unfold_references(content) def unfold_references(content)
return unless content
rewriter = Gitlab::Gfm::ReferenceRewriter.new(content, @old_project, rewriter = Gitlab::Gfm::ReferenceRewriter.new(content, @old_project,
@current_user) @current_user)
rewriter.rewrite(@new_project) rewriter.rewrite(@new_project)
......
...@@ -162,6 +162,7 @@ class NotificationService ...@@ -162,6 +162,7 @@ class NotificationService
recipients = add_subscribed_users(recipients, note.noteable) recipients = add_subscribed_users(recipients, note.noteable)
recipients = reject_unsubscribed_users(recipients, note.noteable) recipients = reject_unsubscribed_users(recipients, note.noteable)
recipients = reject_users_without_access(recipients, note.noteable)
recipients.delete(note.author) recipients.delete(note.author)
recipients = recipients.uniq recipients = recipients.uniq
...@@ -376,6 +377,14 @@ class NotificationService ...@@ -376,6 +377,14 @@ class NotificationService
end end
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) def add_subscribed_users(recipients, target)
return recipients unless target.respond_to? :subscribers return recipients unless target.respond_to? :subscribers
...@@ -464,15 +473,16 @@ class NotificationService ...@@ -464,15 +473,16 @@ class NotificationService
end end
recipients = reject_unsubscribed_users(recipients, target) recipients = reject_unsubscribed_users(recipients, target)
recipients = reject_users_without_access(recipients, target)
recipients.delete(current_user) recipients.delete(current_user)
recipients.uniq recipients.uniq
end end
def build_relabeled_recipients(target, current_user, labels:) def build_relabeled_recipients(target, current_user, labels:)
recipients = add_labels_subscribers([], target, labels: labels) recipients = add_labels_subscribers([], target, labels: labels)
recipients = reject_unsubscribed_users(recipients, target) recipients = reject_unsubscribed_users(recipients, target)
recipients = reject_users_without_access(recipients, target)
recipients.delete(current_user) recipients.delete(current_user)
recipients.uniq recipients.uniq
end end
......
...@@ -10,6 +10,8 @@ ...@@ -10,6 +10,8 @@
- if current_user - if current_user
= link_to issues_dashboard_url(format: :atom, private_token: current_user.private_token), class: 'btn' do = link_to issues_dashboard_url(format: :atom, private_token: current_user.private_token), class: 'btn' do
= icon('rss') = icon('rss')
%span.icon-label
Subscribe
= render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue" = render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue"
= render 'shared/issuable/filter', type: :issues = render 'shared/issuable/filter', type: :issues
......
...@@ -77,7 +77,7 @@ ...@@ -77,7 +77,7 @@
%em Authorization was granted by entering your username and password in the application. %em Authorization was granted by entering your username and password in the application.
%td= token.created_at %td= token.created_at
%td= token.scopes %td= token.scopes
%td= render 'delete_form', token: token %td= render 'doorkeeper/authorized_applications/delete_form', token: token
- else - else
.profile-settings-message.text-center .profile-settings-message.text-center
You don't have any authorized applications 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{class: "#{event.body? ? "event-block" : "event-inline" }"}
.event-item-timestamp .event-item-timestamp
#{time_ago_with_tooltip(event.created_at)} #{time_ago_with_tooltip(event.created_at)}
......
...@@ -10,6 +10,8 @@ ...@@ -10,6 +10,8 @@
- if current_user - if current_user
= link_to issues_group_url(@group, format: :atom, private_token: current_user.private_token), class: 'btn' do = link_to issues_group_url(@group, format: :atom, private_token: current_user.private_token), class: 'btn' do
= icon('rss') = icon('rss')
%span.icon-label
Subscribe
= render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue" = render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue"
= render 'shared/issuable/filter', type: :issues = render 'shared/issuable/filter', type: :issues
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
.cover-title .cover-title
%h1 %h1
= @group.name = @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) = visibility_level_icon(@group.visibility_level, fw: false)
.cover-desc.username .cover-desc.username
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
= icon('bars') = icon('bars')
.navbar-collapse.collapse .navbar-collapse.collapse
%ul.nav.navbar-nav.pull-right %ul.nav.navbar-nav
%li.hidden-sm.hidden-xs %li.hidden-sm.hidden-xs
= render 'layouts/search' = render 'layouts/search'
%li.visible-sm.visible-xs %li.visible-sm.visible-xs
...@@ -38,8 +38,9 @@ ...@@ -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 = link_to destroy_user_session_path, class: 'logout', method: :delete, title: 'Sign out', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('sign-out') = icon('sign-out')
- else - else
.pull-right %li
= link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-success' %div
= link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-success'
%h1.title= title %h1.title= title
......
...@@ -24,12 +24,13 @@ ...@@ -24,12 +24,13 @@
= f.password_field :current_password, required: true, class: 'form-control' = f.password_field :current_password, required: true, class: 'form-control'
%p.help-block %p.help-block
You must provide your current password in order to change it. You must provide your current password in order to change it.
.form-group .form-group
= f.label :password, 'New password', class: 'label-light' = f.label :password, 'New password', class: 'label-light'
= f.password_field :password, required: true, class: 'form-control' = f.password_field :password, required: true, class: 'form-control'
.form-group .form-group
= f.label :password_confirmation, class: 'label-light' = f.label :password_confirmation, class: 'label-light'
= f.password_field :password_confirmation, required: true, class: 'form-control' = f.password_field :password_confirmation, required: true, class: 'form-control'
.prepend-top-default.append-bottom-default .prepend-top-default.append-bottom-default
= f.submit 'Save password', class: "btn btn-create append-right-10" = 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" = link_to "I forgot my password", reset_profile_password_path, method: :put, class: "account-btn-link"
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
%a.btn.js-choose-user-avatar-button %a.btn.js-choose-user-avatar-button
Browse file... Browse file...
%span.avatar-file-name.prepend-left-default.js-avatar-filename No file chosen %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 .help-block
The maximum file size allowed is 200KB. The maximum file size allowed is 200KB.
- if @user.avatar? - if @user.avatar?
...@@ -94,3 +94,25 @@ ...@@ -94,3 +94,25 @@
.prepend-top-default.append-bottom-default .prepend-top-default.append-bottom-default
= f.submit 'Update profile settings', class: "btn btn-success" = f.submit 'Update profile settings', class: "btn btn-success"
= link_to "Cancel", user_path(current_user), class: "btn btn-cancel" = 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 %fieldset.builds-feature
%legend %legend
Builds: 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 .form-group
.col-sm-offset-2.col-sm-10 .col-sm-offset-2.col-sm-10
%p Get recent application code using the following command: %p Get recent application code using the following command:
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
.cover-title.project-home-desc .cover-title.project-home-desc
%h1 %h1
= @project.name = @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) = visibility_level_icon(@project.visibility_level, fw: false)
- if @project.description.present? - if @project.description.present?
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
- else - else
Name Name
%b.caret %b.caret
%ul.dropdown-menu %ul.dropdown-menu.dropdown-menu-align-right
%li %li
= link_to namespace_project_branches_path(sort: nil) do = link_to namespace_project_branches_path(sort: nil) do
Name Name
......
...@@ -27,6 +27,9 @@ ...@@ -27,6 +27,9 @@
= link_to 'Cancel running', cancel_all_namespace_project_builds_path(@project.namespace, @project), = link_to 'Cancel running', cancel_all_namespace_project_builds_path(@project.namespace, @project),
data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post 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 = link_to ci_lint_path, class: 'btn btn-default' do
= icon('wrench') = icon('wrench')
%span CI Lint %span CI Lint
......
...@@ -5,10 +5,10 @@ ...@@ -5,10 +5,10 @@
.panel-heading .panel-heading
Commits (#{@commits.count}) Commits (#{@commits.count})
- if hidden > 0 - if hidden > 0
%ul.well-list %ul.content-list
- commits.each do |commit| - commits.each do |commit|
= render "projects/commits/inline_commit", commit: commit, project: @project = render "projects/commits/inline_commit", commit: commit, project: @project
%li.warning-row.unstyled %li.warning-row.unstyled
#{number_with_delimiter(hidden)} additional commits have been omitted to prevent performance issues. #{number_with_delimiter(hidden)} additional commits have been omitted to prevent performance issues.
- else - else
%ul.well-list= render commits, project: @project %ul.content-list= render commits, project: @project
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
.light .light
= pluralize(commits.count, 'commit') = pluralize(commits.count, 'commit')
.col-md-10.col-sm-12 .col-md-10.col-sm-12
%ul.bordered-list %ul.content-list
= render commits, project: project = render commits, project: project
%hr.lists-separator %hr.lists-separator
......
- if current_user && can?(current_user, :push_code, @project) && @issue.can_be_worked_on?(current_user) - if current_user && can?(current_user, :push_code, @project) && @issue.can_be_worked_on?(current_user)
.pull-right .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') = icon('code-fork')
New Branch New Branch
...@@ -11,6 +11,8 @@ ...@@ -11,6 +11,8 @@
- if current_user - 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 = link_to namespace_project_issues_path(@project.namespace, @project, :atom, { private_token: current_user.private_token }), class: 'btn append-right-10' do
= icon('rss') = icon('rss')
%span.icon-label
Subscribe
= render 'shared/issuable/search_form', path: namespace_project_issues_path(@project.namespace, @project) = render 'shared/issuable/search_form', path: namespace_project_issues_path(@project.namespace, @project)
- if can? current_user, :create_issue, @project - if can? current_user, :create_issue, @project
= link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { assignee_id: @issuable_finder.assignee.try(:id), milestone_id: @issuable_finder.milestones.try(:first).try(:id) }), class: "btn btn-new", title: "New Issue", id: "new_issue_link" do = link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { assignee_id: @issuable_finder.assignee.try(:id), milestone_id: @issuable_finder.milestones.try(:first).try(:id) }), class: "btn btn-new", title: "New Issue", id: "new_issue_link" do
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
%strong.append-right-20 %strong.append-right-20
= link_to_label(label) do = link_to_label(label) do
= pluralize label.open_issues_count, 'open issue' = pluralize label.open_issues_count(current_user), 'open issue'
- if current_user - if current_user
.label-subscription{data: {url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label)}} .label-subscription{data: {url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label)}}
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
%span.caret %span.caret
%span.sr-only %span.sr-only
Select Archive Format Select Archive Format
%ul.col-xs-10.dropdown-menu{ role: 'menu' } %ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' }
%li %li
= link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'zip'), rel: 'nofollow' do = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'zip'), rel: 'nofollow' do
%i.fa.fa-download %i.fa.fa-download
......
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
= icon('users') = icon('users')
= number_with_delimiter(group.users.count) = 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) = visibility_level_icon(group.visibility_level, fw: false)
= image_tag group_icon(group), class: "avatar s40 hidden-xs" = image_tag group_icon(group), class: "avatar s40 hidden-xs"
......
...@@ -31,18 +31,18 @@ ...@@ -31,18 +31,18 @@
.issues_bulk_update.hide .issues_bulk_update.hide
= form_tag bulk_update_namespace_project_issues_path(@project.namespace, @project), method: :post do = form_tag bulk_update_namespace_project_issues_path(@project.namespace, @project), method: :post do
.filter-item.inline .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 %ul
%li %li
%a{href: "#", data: {id: "reopen"}} Open %a{href: "#", data: {id: "reopen"}} Open
%li %li
%a{href: "#", data: {id: "close"}} Closed %a{href: "#", data: {id: "close"}} Closed
.filter-item.inline .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]" } }) 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 .filter-item.inline
= dropdown_tag("Milestone", options: { title: "Assign milestone", toggle_class: 'js-milestone-select', filter: true, dropdown_class: "dropdown-menu-selectable", = 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, :js), use_id: true } }) 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 'update[issues_ids]', []
= hidden_field_tag :state_event, params[:state_event] = hidden_field_tag :state_event, params[:state_event]
.filter-item.inline .filter-item.inline
......
...@@ -127,11 +127,12 @@ ...@@ -127,11 +127,12 @@
for this project. for this project.
- if issuable.new_record? - 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 - else
.pull-right .pull-right
- if current_user.can?(:"destroy_#{issuable.to_ability_name}", @project) - 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') = icon('trash-o')
Delete 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 @@ ...@@ -24,16 +24,21 @@
- else - else
View labels View labels
- if can? current_user, :admin_label, @project and @project - 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_title("Create new label", back: true)
= dropdown_content do = 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"} %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 .suggest-colors.suggest-colors-dropdown
- suggested_colors.each do |color| - suggested_colors.each do |color|
= link_to '#', style: "background-color: #{color}", data: { color: color } do = link_to '#', style: "background-color: #{color}", data: { color: color } do
&nbsp &nbsp
%button.btn.btn-primary.js-new-label-btn{type: "button"} .dropdown-label-color-input
Create .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 = dropdown_loading
...@@ -17,4 +17,4 @@ ...@@ -17,4 +17,4 @@
%a.js-participants-more{href: "#", data: {original_text: "+ #{participants_size - 7} more", less_text: "- show less"}} %a.js-participants-more{href: "#", data: {original_text: "+ #{participants_size - 7} more", less_text: "- show less"}}
+ #{participants_extra} more + #{participants_extra} more
:javascript :javascript
Issue.prototype.PARTICIPANTS_ROW_COUNT = #{participants_row}; IssuableContext.prototype.PARTICIPANTS_ROW_COUNT = #{participants_row};
...@@ -33,11 +33,11 @@ ...@@ -33,11 +33,11 @@
.value.bold.hide-collapsed .value.bold.hide-collapsed
- if issuable.assignee - if issuable.assignee
= link_to_member(@project, issuable.assignee, size: 32) do = link_to_member(@project, issuable.assignee, size: 32) 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 %span.username
= issuable.assignee.to_reference = 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 - else
.light None .light None
...@@ -77,7 +77,7 @@ ...@@ -77,7 +77,7 @@
Labels Labels
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project) - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
= link_to 'Edit', '#', class: 'edit-link pull-right' = link_to 'Edit', '#', class: 'edit-link pull-right'
.value.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? - if issuable.labels.any?
- issuable.labels.each do |label| - issuable.labels.each do |label|
= link_to_label(label, type: issuable.to_ability_name) = link_to_label(label, type: issuable.to_ability_name)
......
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
%span %span
= icon('star') = icon('star')
= project.star_count = 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) = visibility_level_icon(project.visibility_level, fw: false)
.title .title
......
...@@ -16,6 +16,18 @@ Rails.application.routes.draw do ...@@ -16,6 +16,18 @@ Rails.application.routes.draw do
end end
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 namespace :ci do
# CI API # CI API
Ci::API::API.logger Rails.logger Ci::API::API.logger Rails.logger
...@@ -351,11 +363,10 @@ Rails.application.routes.draw do ...@@ -351,11 +363,10 @@ Rails.application.routes.draw do
get :issues get :issues
get :merge_requests get :merge_requests
get :activity get :activity
get :labels
get :milestones
scope module: :dashboard do scope module: :dashboard do
resources :milestones, only: [:index, :show] resources :milestones, only: [:index, :show]
resources :labels, only: [:index]
resources :groups, only: [:index] resources :groups, only: [:index]
resources :snippets, only: [:index] resources :snippets, only: [:index]
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
## User documentation ## User documentation
- [API](api/README.md) Automate GitLab via a simple and powerful API. - [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 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. - [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). - [Importing to GitLab](workflow/importing/README.md).
...@@ -45,4 +45,3 @@ ...@@ -45,4 +45,3 @@
contributing to documentation. contributing to documentation.
- [Development](development/README.md) Explains the architecture and the guidelines for shell commands. - [Development](development/README.md) Explains the architecture and the guidelines for shell commands.
- [Legal](legal/README.md) Contributor license agreements. - [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: ...@@ -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 * `only` and `except` are inclusive. If both `only` and `except` are defined
in a job specification, the ref is filtered by `only` and `except`. 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 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 * `only` and `except` allow to specify a repository path to filter jobs for
forks. forks.
...@@ -348,6 +348,17 @@ job: ...@@ -348,6 +348,17 @@ job:
- branches - 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 The repository path can be used to have jobs executed only for the parent
repository and not forks: repository and not forks:
......
...@@ -72,9 +72,9 @@ p { margin: 0; padding: 0; } ...@@ -72,9 +72,9 @@ p { margin: 0; padding: 0; }
### Colors ### Colors
HEX (hexadecimal) colors short-form should use shortform where possible, and HEX (hexadecimal) colors should use shorthand where possible, and should use
should use lower case letters to differenciate between letters and numbers, e. lower case letters to differentiate between letters and numbers, e.g. `#E3E3E3`
g. `#E3E3E3` vs. `#e3e3e3`. vs. `#e3e3e3`.
```scss ```scss
// Bad // Bad
...@@ -160,6 +160,7 @@ is slightly more performant. ...@@ -160,6 +160,7 @@ is slightly more performant.
``` ```
### Selectors with a `js-` Prefix ### Selectors with a `js-` Prefix
Do not use any selector prefixed with `js-` for styling purposes. These 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 selectors are intended for use only with JavaScript to allow for removal or
renaming without breaking styling. renaming without breaking styling.
...@@ -187,8 +188,28 @@ CSSComb globally (system-wide). Run it in the GitLab directory with ...@@ -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. 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 [csscomb]: https://github.com/csscomb/csscomb.js
[node]: https://github.com/nodejs/node [node]: https://github.com/nodejs/node
[npm]: https://www.npmjs.com/ [npm]: https://www.npmjs.com/
[scss-lint]: https://github.com/brigade/scss-lint [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 [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 @@ ...@@ -2,26 +2,14 @@
Step-by-step guides on the basics of working with Git and GitLab. Step-by-step guides on the basics of working with Git and GitLab.
* [Start using Git on the command line](start-using-git.md) - [Start using Git on the command line](start-using-git.md)
- [Create and add your SSH Keys](create-your-ssh-keys.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)
* [Command Line basic commands](command-line-commands.md) - [Create a group](create-group.md)
- [Create a branch](create-branch.md)
* [Basic Git commands](basic-git-commands.md) - [Fork a project](fork-project.md)
- [Add a file](add-file.md)
* [Create a project](create-project.md) - [Add an image](add-image.md)
- [Create a Merge Request](add-merge-request.md)
* [Create a group](create-group.md) - [Create an Issue](create-issue.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 # Basic Git commands
### Go to the master branch to pull the latest changes from there This section is now merged into [Start using Git](start-using-git.md).
```
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
```
# Start using Git on the command line # 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 ## Open a shell
...@@ -59,3 +60,63 @@ To view the information that you entered, type: ...@@ -59,3 +60,63 @@ To view the information that you entered, type:
``` ```
git config --global --list 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 ...@@ -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: documents in order to understand and properly configure GitLab Performance Monitoring:
- [GitLab Configuration](gitlab_configuration.md) - [GitLab Configuration](gitlab_configuration.md)
- [InfluxDB Configuration](influxdb_configuration.md) - [InfluxDB Install/Configuration](influxdb_configuration.md)
- [InfluxDB Schema](influxdb_schema.md) - [InfluxDB Schema](influxdb_schema.md)
- [Grafana Install/Configuration](grafana_configuration.md)
## Introduction to GitLab Performance Monitoring ## 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