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