Commit 7d9e8b04 authored by Kamil Trzcinski's avatar Kamil Trzcinski

Merge remote-tracking branch 'origin/master' into environments-and-deployments

parents 672aec4a 066020fc
...@@ -7,7 +7,8 @@ services: ...@@ -7,7 +7,8 @@ services:
cache: cache:
key: "ruby21" key: "ruby21"
paths: paths:
- vendor - vendor/apt
- vendor/ruby
variables: variables:
MYSQL_ALLOW_EMPTY_PASSWORD: "1" MYSQL_ALLOW_EMPTY_PASSWORD: "1"
...@@ -91,9 +92,7 @@ update-knapsack: ...@@ -91,9 +92,7 @@ update-knapsack:
- export KNAPSACK_REPORT_PATH=knapsack/spinach_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json - export KNAPSACK_REPORT_PATH=knapsack/spinach_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
- export KNAPSACK_GENERATE_REPORT=true - export KNAPSACK_GENERATE_REPORT=true
- cp knapsack/spinach_report.json ${KNAPSACK_REPORT_PATH} - cp knapsack/spinach_report.json ${KNAPSACK_REPORT_PATH}
- knapsack spinach "-r rerun" - knapsack spinach "-r rerun" || retry '[ ! -e tmp/spinach-rerun.txt ] || bundle exec spinach -r rerun $(cat tmp/spinach-rerun.txt)'
# retry failed tests 3 times
- retry '[ ! -e tmp/spinach-rerun.txt ] || bin/spinach -r rerun $(cat tmp/spinach-rerun.txt)'
artifacts: artifacts:
paths: paths:
- knapsack/ - knapsack/
......
...@@ -349,7 +349,7 @@ Style/MultilineArrayBraceLayout: ...@@ -349,7 +349,7 @@ Style/MultilineArrayBraceLayout:
# Avoid multi-line chains of blocks. # Avoid multi-line chains of blocks.
Style/MultilineBlockChain: Style/MultilineBlockChain:
Enabled: false Enabled: true
# Ensures newlines after multiline block do statements. # Ensures newlines after multiline block do statements.
Style/MultilineBlockLayout: Style/MultilineBlockLayout:
......
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
v 8.9.0 (unreleased) v 8.9.0 (unreleased)
- Fix Error 500 when using closes_issues API with an external issue tracker
- Add more information into RSS feed for issues (Alexander Matyushentsev)
- Bulk assign/unassign labels to issues. - Bulk assign/unassign labels to issues.
- Ability to prioritize labels !4009 / !3205 (Thijs Wouters) - Ability to prioritize labels !4009 / !3205 (Thijs Wouters)
- Fix endless redirections when accessing user OAuth applications when they are disabled - Fix endless redirections when accessing user OAuth applications when they are disabled
...@@ -9,9 +11,11 @@ v 8.9.0 (unreleased) ...@@ -9,9 +11,11 @@ v 8.9.0 (unreleased)
- Fix issue with arrow keys not working in search autocomplete dropdown - Fix issue with arrow keys not working in search autocomplete dropdown
- Make EmailsOnPushWorker use Sidekiq mailers queue - Make EmailsOnPushWorker use Sidekiq mailers queue
- Fix wiki page events' webhook to point to the wiki repository - Fix wiki page events' webhook to point to the wiki repository
- Don't show tags for revert and cherry-pick operations
- Fix issue todo not remove when leave project !4150 (Long Nguyen) - Fix issue todo not remove when leave project !4150 (Long Nguyen)
- Allow customisable text on the 'nearly there' page after a user signs up - Allow customisable text on the 'nearly there' page after a user signs up
- Bump recaptcha gem to 3.0.0 to remove deprecated stoken support - Bump recaptcha gem to 3.0.0 to remove deprecated stoken support
- Fix SVG sanitizer to allow more elements
- Allow forking projects with restricted visibility level - Allow forking projects with restricted visibility level
- Added descriptions to notification settings dropdown - Added descriptions to notification settings dropdown
- Improve note validation to prevent errors when creating invalid note via API - Improve note validation to prevent errors when creating invalid note via API
...@@ -34,6 +38,9 @@ v 8.9.0 (unreleased) ...@@ -34,6 +38,9 @@ v 8.9.0 (unreleased)
- Links from a wiki page to other wiki pages should be rewritten as expected - Links from a wiki page to other wiki pages should be rewritten as expected
- Add option to project to only allow merge requests to be merged if the build succeeds (Rui Santos) - Add option to project to only allow merge requests to be merged if the build succeeds (Rui Santos)
- Fix issues filter when ordering by milestone - Fix issues filter when ordering by milestone
- Added artifacts:when to .gitlab-ci.yml - this requires GitLab Runner 1.3
- Bamboo Service: Fix missing credentials & URL handling when base URL contains a path (Benjamin Schmid)
- TeamCity Service: Fix URL handling when base URL contains a path
- Todos will display target state if issuable target is 'Closed' or 'Merged' - Todos will display target state if issuable target is 'Closed' or 'Merged'
- Fix bug when sorting issues by milestone due date and filtering by two or more labels - Fix bug when sorting issues by milestone due date and filtering by two or more labels
- Add support for using Yubikeys (U2F) for two-factor authentication - Add support for using Yubikeys (U2F) for two-factor authentication
...@@ -55,6 +62,7 @@ v 8.9.0 (unreleased) ...@@ -55,6 +62,7 @@ v 8.9.0 (unreleased)
- Improve error handling importing projects - Improve error handling importing projects
- Remove duplicated notification settings - Remove duplicated notification settings
- Put project Files and Commits tabs under Code tab - Put project Files and Commits tabs under Code tab
- Decouple global notification level from user model
- Replace Colorize with Rainbow for coloring console output in Rake tasks. - Replace Colorize with Rainbow for coloring console output in Rake tasks.
- Add workhorse controller and API helpers - Add workhorse controller and API helpers
- An indicator is now displayed at the top of the comment field for confidential issues. - An indicator is now displayed at the top of the comment field for confidential issues.
...@@ -66,6 +74,9 @@ v 8.9.0 (unreleased) ...@@ -66,6 +74,9 @@ v 8.9.0 (unreleased)
- Improved UX of date pickers on issue & milestone forms - Improved UX of date pickers on issue & milestone forms
- Cache on the database if a project has an active external issue tracker. - Cache on the database if a project has an active external issue tracker.
- Put project Labels and Milestones pages links under Issues and Merge Requests tabs as subnav - Put project Labels and Milestones pages links under Issues and Merge Requests tabs as subnav
- All classes in the Banzai::ReferenceParser namespace are now instrumented
- Remove deprecated issues_tracker and issues_tracker_id from project model
- Allow users to create confidential issues in private projects
v 8.8.5 (unreleased) v 8.8.5 (unreleased)
- Ensure branch cleanup regardless of whether the GitHub import process succeeds - Ensure branch cleanup regardless of whether the GitHub import process succeeds
......
...@@ -245,7 +245,7 @@ end ...@@ -245,7 +245,7 @@ end
group :development do group :development do
gem "foreman" gem "foreman"
gem 'brakeman', '~> 3.2.0', require: false gem 'brakeman', '~> 3.3.0', require: false
gem 'letter_opener_web', '~> 1.3.0' gem 'letter_opener_web', '~> 1.3.0'
gem 'quiet_assets', '~> 1.0.2' gem 'quiet_assets', '~> 1.0.2'
......
...@@ -97,16 +97,7 @@ GEM ...@@ -97,16 +97,7 @@ GEM
bootstrap-sass (3.3.6) bootstrap-sass (3.3.6)
autoprefixer-rails (>= 5.2.1) autoprefixer-rails (>= 5.2.1)
sass (>= 3.3.4) sass (>= 3.3.4)
brakeman (3.2.1) brakeman (3.3.2)
erubis (~> 2.6)
haml (>= 3.0, < 5.0)
highline (>= 1.6.20, < 2.0)
ruby2ruby (~> 2.3.0)
ruby_parser (~> 3.8.1)
safe_yaml (>= 1.0)
sass (~> 3.0)
slim (>= 1.3.6, < 4.0)
terminal-table (~> 1.4)
browser (2.0.3) browser (2.0.3)
builder (3.2.2) builder (3.2.2)
bullet (5.0.0) bullet (5.0.0)
...@@ -338,7 +329,6 @@ GEM ...@@ -338,7 +329,6 @@ GEM
hashie (3.4.3) hashie (3.4.3)
health_check (1.5.1) health_check (1.5.1)
rails (>= 2.3.0) rails (>= 2.3.0)
highline (1.7.8)
hipchat (1.5.2) hipchat (1.5.2)
httparty httparty
mimemagic mimemagic
...@@ -642,10 +632,7 @@ GEM ...@@ -642,10 +632,7 @@ GEM
ruby-saml (1.1.2) ruby-saml (1.1.2)
nokogiri (>= 1.5.10) nokogiri (>= 1.5.10)
uuid (~> 2.3) uuid (~> 2.3)
ruby2ruby (2.3.0) ruby_parser (3.8.2)
ruby_parser (~> 3.1)
sexp_processor (~> 4.0)
ruby_parser (3.8.1)
sexp_processor (~> 4.1) sexp_processor (~> 4.1)
rubyntlm (0.5.2) rubyntlm (0.5.2)
rubypants (0.2.0) rubypants (0.2.0)
...@@ -655,7 +642,7 @@ GEM ...@@ -655,7 +642,7 @@ GEM
safe_yaml (1.0.4) safe_yaml (1.0.4)
sanitize (2.1.0) sanitize (2.1.0)
nokogiri (>= 1.4.4) nokogiri (>= 1.4.4)
sass (3.4.21) sass (3.4.22)
sass-rails (5.0.4) sass-rails (5.0.4)
railties (>= 4.0.0, < 5.0) railties (>= 4.0.0, < 5.0)
sass (~> 3.1) sass (~> 3.1)
...@@ -704,9 +691,6 @@ GEM ...@@ -704,9 +691,6 @@ GEM
tilt (>= 1.3, < 3) tilt (>= 1.3, < 3)
six (0.2.0) six (0.2.0)
slack-notifier (1.2.1) slack-notifier (1.2.1)
slim (3.0.6)
temple (~> 0.7.3)
tilt (>= 1.3.3, < 2.1)
slop (3.6.0) slop (3.6.0)
spinach (0.8.10) spinach (0.8.10)
colorize colorize
...@@ -747,10 +731,8 @@ GEM ...@@ -747,10 +731,8 @@ GEM
railties (>= 3.2.5, < 6) railties (>= 3.2.5, < 6)
teaspoon-jasmine (2.2.0) teaspoon-jasmine (2.2.0)
teaspoon (>= 1.0.0) teaspoon (>= 1.0.0)
temple (0.7.6)
term-ansicolor (1.3.2) term-ansicolor (1.3.2)
tins (~> 1.0) tins (~> 1.0)
terminal-table (1.5.2)
test_after_commit (0.4.2) test_after_commit (0.4.2)
activerecord (>= 3.2) activerecord (>= 3.2)
thin (1.6.4) thin (1.6.4)
...@@ -759,7 +741,7 @@ GEM ...@@ -759,7 +741,7 @@ GEM
rack (~> 1.0) rack (~> 1.0)
thor (0.19.1) thor (0.19.1)
thread_safe (0.3.5) thread_safe (0.3.5)
tilt (2.0.2) tilt (2.0.5)
timecop (0.8.1) timecop (0.8.1)
timfel-krb5-auth (0.8.3) timfel-krb5-auth (0.8.3)
tinder (1.10.1) tinder (1.10.1)
...@@ -848,7 +830,7 @@ DEPENDENCIES ...@@ -848,7 +830,7 @@ DEPENDENCIES
better_errors (~> 1.0.1) better_errors (~> 1.0.1)
binding_of_caller (~> 0.7.2) binding_of_caller (~> 0.7.2)
bootstrap-sass (~> 3.3.0) bootstrap-sass (~> 3.3.0)
brakeman (~> 3.2.0) brakeman (~> 3.3.0)
browser (~> 2.0.3) browser (~> 2.0.3)
bullet bullet
bundler-audit bundler-audit
......
...@@ -162,19 +162,6 @@ $ -> ...@@ -162,19 +162,6 @@ $ ->
$el.data('placement') || 'bottom' $el.data('placement') || 'bottom'
) )
$('.header-logo .home').tooltip(
placement: (_, el) ->
$el = $(el)
if $('.page-with-sidebar').hasClass('page-sidebar-collapsed') then 'right' else 'bottom'
container: 'body'
)
$('.page-with-sidebar').tooltip(
selector: '.sidebar-collapsed .nav-sidebar a, .sidebar-collapsed a.sidebar-user'
placement: 'right'
container: 'body'
)
# Form submitter # Form submitter
$('.trigger-submit').on 'change', -> $('.trigger-submit').on 'change', ->
$(@).parents('form').submit() $(@).parents('form').submit()
...@@ -207,6 +194,7 @@ $ -> ...@@ -207,6 +194,7 @@ $ ->
$('.navbar-toggle').on 'click', -> $('.navbar-toggle').on 'click', ->
$('.header-content .title').toggle() $('.header-content .title').toggle()
$('.header-content .header-logo').toggle()
$('.header-content .navbar-collapse').toggle() $('.header-content .navbar-collapse').toggle()
$('.navbar-toggle').toggleClass('active') $('.navbar-toggle').toggleClass('active')
$('.navbar-toggle i').toggleClass("fa-angle-right fa-angle-left") $('.navbar-toggle i').toggleClass("fa-angle-right fa-angle-left")
...@@ -241,7 +229,6 @@ $ -> ...@@ -241,7 +229,6 @@ $ ->
$this.attr 'value', $this.val() $this.attr 'value', $this.val()
$sidebarGutterToggle = $('.js-sidebar-toggle') $sidebarGutterToggle = $('.js-sidebar-toggle')
$navIconToggle = $('.toggle-nav-collapse')
$(document) $(document)
.off 'breakpoint:change' .off 'breakpoint:change'
...@@ -251,10 +238,6 @@ $ -> ...@@ -251,10 +238,6 @@ $ ->
if $gutterIcon.hasClass('fa-angle-double-right') if $gutterIcon.hasClass('fa-angle-double-right')
$sidebarGutterToggle.trigger('click') $sidebarGutterToggle.trigger('click')
$navIcon = $navIconToggle.find('.fa')
if $navIcon.hasClass('fa-angle-left')
$navIconToggle.trigger('click')
fitSidebarForSize = -> fitSidebarForSize = ->
oldBootstrapBreakpoint = bootstrapBreakpoint oldBootstrapBreakpoint = bootstrapBreakpoint
bootstrapBreakpoint = bp.getBreakpointSize() bootstrapBreakpoint = bp.getBreakpointSize()
......
...@@ -97,13 +97,22 @@ class @IssuableBulkActions ...@@ -97,13 +97,22 @@ class @IssuableBulkActions
$labels = @form.find('.labels-filter input[name="update[label_ids][]"]') $labels = @form.find('.labels-filter input[name="update[label_ids][]"]')
$labels.each (k, label) -> $labels.each (k, label) ->
labelIds.push $(label).val() if label labelIds.push parseInt($(label).val()) if label
labelIds labelIds
###* ###*
* Just an alias of @getUnmarkedIndeterminedLabels * Returns Label IDs that will be removed from issue selection
* @return {Array} Array of labels * @return {Array} Array of labels IDs
### ###
getLabelsToRemove: -> getLabelsToRemove: ->
@getUnmarkedIndeterminedLabels() result = []
indeterminatedLabels = @getUnmarkedIndeterminedLabels()
labelsToApply = @getLabelsToApply()
indeterminatedLabels.map (id) ->
# We need to exclude label IDs that will be applied
# By not doing this will cause issues from selection to not add labels at all
result.push(id) if labelsToApply.indexOf(id) is -1
result
...@@ -47,4 +47,4 @@ $ -> ...@@ -47,4 +47,4 @@ $ ->
# Make logo clickable as part of a workaround for Safari visited # Make logo clickable as part of a workaround for Safari visited
# link behaviour (See !2690). # link behaviour (See !2690).
$('#logo').on 'click', -> $('#logo').on 'click', ->
$('#js-shortcuts-home').get(0).click() Turbolinks.visit('/')
...@@ -4,8 +4,6 @@ expanded = 'page-sidebar-expanded' ...@@ -4,8 +4,6 @@ expanded = 'page-sidebar-expanded'
toggleSidebar = -> toggleSidebar = ->
$('.page-with-sidebar').toggleClass("#{collapsed} #{expanded}") $('.page-with-sidebar').toggleClass("#{collapsed} #{expanded}")
$('header').toggleClass("header-collapsed header-expanded") $('header').toggleClass("header-collapsed header-expanded")
$('.toggle-nav-collapse i').toggleClass("fa-angle-right fa-angle-left")
$.cookie("collapsed_nav", $('.page-with-sidebar').hasClass(collapsed), { path: '/' })
setTimeout ( -> setTimeout ( ->
niceScrollBars = $('.nicescroll').niceScroll(); niceScrollBars = $('.nicescroll').niceScroll();
...@@ -17,10 +15,3 @@ $(document).on("click", '.toggle-nav-collapse, .side-nav-toggle', (e) -> ...@@ -17,10 +15,3 @@ $(document).on("click", '.toggle-nav-collapse, .side-nav-toggle', (e) ->
toggleSidebar() toggleSidebar()
) )
$ ->
size = bp.getBreakpointSize()
if size is "xs" or size is "sm"
if $('.page-with-sidebar').hasClass(expanded)
toggleSidebar()
...@@ -8,34 +8,16 @@ ...@@ -8,34 +8,16 @@
*/ */
@mixin gitlab-theme($color-light, $color, $color-darker, $color-dark) { @mixin gitlab-theme($color-light, $color, $color-darker, $color-dark) {
.page-with-sidebar { .page-with-sidebar {
.header-logo {
background: $color-darker;
a { .collapse-nav a {
color: $color-light; color: $color-light;
background: $color;
h3 {
color: $color-light;
}
}
&:hover { &:hover {
background-color: $color-dark; color: $white-light;
a {
color: $white-light;
h3 {
color: $white-light;
}
}
} }
} }
.collapse-nav a {
color: $white-light;
background: $color;
}
.sidebar-wrapper { .sidebar-wrapper {
background: $color-darker; background: $color-darker;
......
...@@ -109,10 +109,8 @@ header { ...@@ -109,10 +109,8 @@ header {
position: relative; position: relative;
height: $header-height; height: $header-height;
padding-right: 40px; padding-right: 40px;
padding-left: 30px;
@media (max-width: $screen-xs-min) { transition-duration: .3s;
padding-left: 40px;
}
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
padding-right: 0; padding-right: 0;
...@@ -122,9 +120,29 @@ header { ...@@ -122,9 +120,29 @@ header {
margin-top: -5px; margin-top: -5px;
} }
.header-logo {
position: absolute;
left: 50%;
margin-left: -18px;
top: 7px;
transition-duration: .3s;
z-index: 999;
&:hover {
cursor: pointer;
}
@media (max-width: $screen-xs-max) {
right: 25px;
left: auto;
}
}
.title { .title {
margin: 0; margin: 0;
font-size: 19px; font-size: 19px;
max-width: 400px;
display: inline-block;
line-height: $header-height; line-height: $header-height;
font-weight: normal; font-weight: normal;
color: $gl-text-color; color: $gl-text-color;
...@@ -133,6 +151,10 @@ header { ...@@ -133,6 +151,10 @@ header {
vertical-align: top; vertical-align: top;
white-space: nowrap; white-space: nowrap;
@media (max-width: $screen-sm-max) {
max-width: 190px;
}
a { a {
color: $gl-text-color; color: $gl-text-color;
&:hover { &:hover {
...@@ -160,6 +182,10 @@ header { ...@@ -160,6 +182,10 @@ header {
.navbar-collapse { .navbar-collapse {
float: right; float: right;
border-top: none; border-top: none;
@media (max-width: $screen-xs-max) {
float: none;
}
} }
} }
...@@ -176,17 +202,20 @@ header { ...@@ -176,17 +202,20 @@ header {
margin-left: 0; margin-left: 0;
.header-content { .header-content {
padding-left: 30px;
transition-duration: .3s; @media (min-width: $screen-sm-max) {
padding-left: 30px;
transition-duration: .3s;
}
} }
} }
.header-expanded { .tanuki-shape {
margin-left: 0; transition: all 0.8s;
.header-content { &:hover, &.highlight {
margin-left: $sidebar_width; fill: rgb(255, 255, 255);
transition-duration: .3s; transition: all 0.1s;
} }
} }
......
@mixin fade($gradient-direction, $rgba, $gradient-color) { @mixin fade($gradient-direction, $rgba, $gradient-color) {
visibility: visible; visibility: visible;
opacity: 1; opacity: 1;
z-index: 2;
position: absolute; position: absolute;
bottom: 12px; bottom: 12px;
width: 43px; width: 43px;
...@@ -68,6 +69,7 @@ ...@@ -68,6 +69,7 @@
} }
&.sub-nav { &.sub-nav {
text-align: center;
background-color: $background-color; background-color: $background-color;
.container-fluid { .container-fluid {
...@@ -171,7 +173,6 @@ ...@@ -171,7 +173,6 @@
> form { > form {
display: inline-block; display: inline-block;
margin-top: -1px; margin-top: -1px;
margin-bottom: 12px;
} }
.icon-label { .icon-label {
...@@ -250,6 +251,7 @@ ...@@ -250,6 +251,7 @@
background: $background-color; background: $background-color;
border-bottom: 1px solid $border-color; border-bottom: 1px solid $border-color;
transition-duration: .3s; transition-duration: .3s;
text-align: center;
.container-fluid { .container-fluid {
position: relative; position: relative;
...@@ -352,7 +354,7 @@ ...@@ -352,7 +354,7 @@
.fade-right { .fade-right {
@media (min-width: $screen-xs-max) { @media (min-width: $screen-xs-max) {
right: 67px; right: 68px;
} }
@media (max-width: $screen-xs-min) { @media (max-width: $screen-xs-min) {
right: 0; right: 0;
......
...@@ -35,24 +35,11 @@ ...@@ -35,24 +35,11 @@
} }
.sidebar-wrapper { .sidebar-wrapper {
.header-logo {
height: $header-height;
padding: 8px 26px;
width: $sidebar_width;
position: fixed;
z-index: 999;
overflow: hidden;
transition-duration: .3s;
&:hover {
background-color: #eee;
}
}
.sidebar-user { .sidebar-user {
padding: 15px 22px; padding: 15px 22px;
position: fixed; position: fixed;
bottom: 40px; bottom: 0;
width: $sidebar_width; width: $sidebar_width;
overflow: hidden; overflow: hidden;
transition-duration: .3s; transition-duration: .3s;
...@@ -97,10 +84,10 @@ ...@@ -97,10 +84,10 @@
} }
a { a {
text-align: center; width: $sidebar_width;
padding: 8px; padding: 7px 15px 7px 23px;
font-size: $gl-font-size; font-size: $gl-font-size;
color: $gray; line-height: 24px;
display: block; display: block;
text-decoration: none; text-decoration: none;
font-weight: normal; font-weight: normal;
...@@ -118,10 +105,9 @@ ...@@ -118,10 +105,9 @@
font-size: 16px; font-size: 16px;
} }
.nav-link-text { i,
margin-top: 3px; svg {
font-size: 13px; margin-right: 13px;
line-height: 18px;
} }
&.back-link i { &.back-link i {
...@@ -129,6 +115,12 @@ ...@@ -129,6 +115,12 @@
} }
} }
} }
.count {
float: right;
padding: 0 8px;
@include border-radius(6px);
}
} }
.sidebar-subnav { .sidebar-subnav {
...@@ -143,11 +135,12 @@ ...@@ -143,11 +135,12 @@
.collapse-nav a { .collapse-nav a {
width: $sidebar_width; width: $sidebar_width;
position: fixed; position: fixed;
bottom: 0; top: 0;
left: 0; left: 0;
font-size: 13px; padding: 5px 0;
font-size: 18px;
background: transparent; background: transparent;
height: 40px; height: 50px;
text-align: center; text-align: center;
line-height: 40px; line-height: 40px;
transition-duration: .3s; transition-duration: .3s;
...@@ -170,25 +163,8 @@ ...@@ -170,25 +163,8 @@
.sidebar-wrapper { .sidebar-wrapper {
width: 0; width: 0;
.header-logo {
width: 0;
padding: 8px 0;
a {
padding-left: ($sidebar_collapsed_width - 36) / 2;
.gitlab-text-container {
display: none;
}
}
}
#logo {
display: none;
}
.nav-sidebar { .nav-sidebar {
width: $sidebar_collapsed_width; width: 0;
li { li {
width: auto; width: auto;
...@@ -203,6 +179,10 @@ ...@@ -203,6 +179,10 @@
.collapse-nav a { .collapse-nav a {
width: 0; width: 0;
i {
display: none;
}
} }
.sidebar-user { .sidebar-user {
...@@ -218,9 +198,8 @@ ...@@ -218,9 +198,8 @@
} }
.page-sidebar-expanded { .page-sidebar-expanded {
padding-left: $sidebar_width;
@media (max-width: $screen-xs-min) { @media (max-width: $screen-sm-max) {
padding-left: 0; padding-left: 0;
} }
...@@ -241,20 +220,6 @@ ...@@ -241,20 +220,6 @@
} }
} }
} }
.layout-nav {
@media (max-width: $screen-xs-min) {
padding-right: 0;
}
@media (min-width: $screen-xs-min) and (max-width: $screen-md-min) {
padding-right: 90px;
}
@media (min-width: $screen-md-min) {
padding-right: $sidebar_width;
}
}
} }
.right-sidebar-collapsed { .right-sidebar-collapsed {
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
* Layout * Layout
*/ */
$sidebar_collapsed_width: 62px; $sidebar_collapsed_width: 62px;
$sidebar_width: 90px; $sidebar_width: 220px;
$gutter_collapsed_width: 62px; $gutter_collapsed_width: 62px;
$gutter_width: 290px; $gutter_width: 290px;
$gutter_inner_width: 258px; $gutter_inner_width: 258px;
......
...@@ -42,7 +42,7 @@ class JwtController < ApplicationController ...@@ -42,7 +42,7 @@ class JwtController < ApplicationController
end end
def authenticate_user(login, password) def authenticate_user(login, password)
user = Gitlab::Auth.find_in_gitlab_or_ldap(login, password) user = Gitlab::Auth.find_with_user_password(login, password)
Gitlab::Auth.rate_limit!(request.ip, success: user.present?, login: login) Gitlab::Auth.rate_limit!(request.ip, success: user.present?, login: login)
user user
end end
......
class Profiles::NotificationsController < Profiles::ApplicationController class Profiles::NotificationsController < Profiles::ApplicationController
def show def show
@user = current_user @user = current_user
@group_notifications = current_user.notification_settings.for_groups @group_notifications = current_user.notification_settings.for_groups
@project_notifications = current_user.notification_settings.for_projects @project_notifications = current_user.notification_settings.for_projects
@global_notification_setting = current_user.global_notification_setting
end end
def update def update
if current_user.update_attributes(user_params) if current_user.update_attributes(user_params) && update_notification_settings
flash[:notice] = "Notification settings saved" flash[:notice] = "Notification settings saved"
else else
flash[:alert] = "Failed to save new settings" flash[:alert] = "Failed to save new settings"
...@@ -16,6 +17,18 @@ class Profiles::NotificationsController < Profiles::ApplicationController ...@@ -16,6 +17,18 @@ class Profiles::NotificationsController < Profiles::ApplicationController
end end
def user_params def user_params
params.require(:user).permit(:notification_email, :notification_level) params.require(:user).permit(:notification_email)
end
def global_notification_setting_params
params.require(:global_notification_setting).permit(:level)
end
private
def update_notification_settings
return true unless global_notification_setting_params
current_user.global_notification_setting.update_attributes(global_notification_setting_params)
end end
end end
...@@ -43,7 +43,7 @@ class Projects::GitHttpController < Projects::ApplicationController ...@@ -43,7 +43,7 @@ class Projects::GitHttpController < Projects::ApplicationController
return if project && project.public? && upload_pack? return if project && project.public? && upload_pack?
authenticate_or_request_with_http_basic do |login, password| authenticate_or_request_with_http_basic do |login, password|
auth_result = Gitlab::Auth.find(login, password, project: project, ip: request.ip) auth_result = Gitlab::Auth.find_for_git_client(login, password, project: project, ip: request.ip)
if auth_result.type == :ci && upload_pack? if auth_result.type == :ci && upload_pack?
@ci = true @ci = true
......
...@@ -51,7 +51,7 @@ class SnippetsFinder ...@@ -51,7 +51,7 @@ class SnippetsFinder
snippets = project.snippets.fresh snippets = project.snippets.fresh
if current_user if current_user
if project.team.member?(current_user.id) || current_user.admin? if project.team.member?(current_user) || current_user.admin?
snippets snippets
else else
snippets.public_and_internal snippets.public_and_internal
......
...@@ -14,4 +14,8 @@ module BranchesHelper ...@@ -14,4 +14,8 @@ module BranchesHelper
::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(branch_name) ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(branch_name)
end end
def project_branches
options_for_select(@project.repository.branch_names, @project.default_branch)
end
end end
...@@ -36,13 +36,7 @@ module NavHelper ...@@ -36,13 +36,7 @@ module NavHelper
end end
def nav_header_class def nav_header_class
class_name = class_name = " with-horizontal-nav" if defined?(nav) && nav
if nav_menu_collapsed?
"header-collapsed"
else
"header-expanded"
end
class_name += " with-horizontal-nav" if defined?(nav) && nav
class_name class_name
end end
......
...@@ -61,4 +61,23 @@ module NotificationsHelper ...@@ -61,4 +61,23 @@ module NotificationsHelper
end end
end end
end end
def notification_level_radio_buttons
html = ""
NotificationSetting.levels.each_key do |level|
level = level.to_sym
next if level == :global
html << content_tag(:div, class: "radio") do
content_tag(:label, { value: level }) do
radio_button_tag(:"global_notification_setting[level]", level, @global_notification_setting.level.to_sym == level) +
content_tag(:div, level.to_s.capitalize, class: "level-title") +
content_tag(:p, notification_description(level))
end
end
end
html.html_safe
end
end end
...@@ -543,7 +543,7 @@ class Ability ...@@ -543,7 +543,7 @@ class Ability
def filter_confidential_issues_abilities(user, issue, rules) def filter_confidential_issues_abilities(user, issue, rules)
return rules if user.admin? || !issue.confidential? return rules if user.admin? || !issue.confidential?
unless issue.author == user || issue.assignee == user || issue.project.team.member?(user.id) unless issue.author == user || issue.assignee == user || issue.project.team.member?(user, Gitlab::Access::REPORTER)
rules.delete(:admin_issue) rules.delete(:admin_issue)
rules.delete(:read_issue) rules.delete(:read_issue)
rules.delete(:update_issue) rules.delete(:update_issue)
......
...@@ -51,10 +51,18 @@ class Issue < ActiveRecord::Base ...@@ -51,10 +51,18 @@ class Issue < ActiveRecord::Base
end end
def self.visible_to_user(user) def self.visible_to_user(user)
return where(confidential: false) if user.blank? return where('issues.confidential IS NULL OR issues.confidential IS FALSE') if user.blank?
return all if user.admin? return all if user.admin?
where('issues.confidential = false OR (issues.confidential = true AND (issues.author_id = :user_id OR issues.assignee_id = :user_id OR issues.project_id IN(:project_ids)))', user_id: user.id, project_ids: user.authorized_projects.select(:id)) where('
issues.confidential IS NULL
OR issues.confidential IS FALSE
OR (issues.confidential = TRUE
AND (issues.author_id = :user_id
OR issues.assignee_id = :user_id
OR issues.project_id IN(:project_ids)))',
user_id: user.id,
project_ids: user.authorized_projects(Gitlab::Access::REPORTER).select(:id))
end end
def self.reference_prefix def self.reference_prefix
......
...@@ -88,22 +88,9 @@ class Note < ActiveRecord::Base ...@@ -88,22 +88,9 @@ class Note < ActiveRecord::Base
table = arel_table table = arel_table
pattern = "%#{query}%" pattern = "%#{query}%"
found_notes = joins('LEFT JOIN issues ON issues.id = noteable_id'). Note.joins('LEFT JOIN issues ON issues.id = noteable_id').
where(table[:note].matches(pattern)) where(table[:note].matches(pattern)).
merge(Issue.visible_to_user(as_user))
if as_user
found_notes.where('
issues.confidential IS NULL
OR issues.confidential IS FALSE
OR (issues.confidential IS TRUE
AND (issues.author_id = :user_id
OR issues.assignee_id = :user_id
OR issues.project_id IN(:project_ids)))',
user_id: as_user.id,
project_ids: as_user.authorized_projects.select(:id))
else
found_notes.where('issues.confidential IS NULL OR issues.confidential IS FALSE')
end
end end
end end
......
...@@ -7,7 +7,6 @@ class NotificationSetting < ActiveRecord::Base ...@@ -7,7 +7,6 @@ class NotificationSetting < ActiveRecord::Base
belongs_to :source, polymorphic: true belongs_to :source, polymorphic: true
validates :user, presence: true validates :user, presence: true
validates :source, presence: true
validates :level, presence: true validates :level, presence: true
validates :user_id, uniqueness: { scope: [:source_type, :source_id], validates :user_id, uniqueness: { scope: [:source_type, :source_id],
message: "already exists in source", message: "already exists in source",
......
...@@ -148,7 +148,6 @@ class Project < ActiveRecord::Base ...@@ -148,7 +148,6 @@ class Project < ActiveRecord::Base
message: Gitlab::Regex.project_path_regex_message } message: Gitlab::Regex.project_path_regex_message }
validates :issues_enabled, :merge_requests_enabled, validates :issues_enabled, :merge_requests_enabled,
:wiki_enabled, inclusion: { in: [true, false] } :wiki_enabled, inclusion: { in: [true, false] }
validates :issues_tracker_id, length: { maximum: 255 }, allow_blank: true
validates :namespace, presence: true validates :namespace, presence: true
validates_uniqueness_of :name, scope: :namespace_id validates_uniqueness_of :name, scope: :namespace_id
validates_uniqueness_of :path, scope: :namespace_id validates_uniqueness_of :path, scope: :namespace_id
...@@ -591,10 +590,6 @@ class Project < ActiveRecord::Base ...@@ -591,10 +590,6 @@ class Project < ActiveRecord::Base
update_column(:has_external_issue_tracker, services.external_issue_trackers.any?) update_column(:has_external_issue_tracker, services.external_issue_trackers.any?)
end end
def can_have_issues_tracker_id?
self.issues_enabled && !self.default_issues_tracker?
end
def build_missing_services def build_missing_services
services_templates = Service.where(template: true) services_templates = Service.where(template: true)
......
class BambooService < CiService class BambooService < CiService
include HTTParty
prop_accessor :bamboo_url, :build_key, :username, :password prop_accessor :bamboo_url, :build_key, :username, :password
validates :bamboo_url, presence: true, url: true, if: :activated? validates :bamboo_url, presence: true, url: true, if: :activated?
...@@ -61,18 +59,7 @@ class BambooService < CiService ...@@ -61,18 +59,7 @@ class BambooService < CiService
end end
def build_info(sha) def build_info(sha)
url = URI.join(bamboo_url, "/rest/api/latest/result?label=#{sha}").to_s @response = get_path("rest/api/latest/result?label=#{sha}")
if username.blank? && password.blank?
@response = HTTParty.get(url, verify: false)
else
url << '&os_authType=basic'
auth = {
username: username,
password: password
}
@response = HTTParty.get(url, verify: false, basic_auth: auth)
end
end end
def build_page(sha, ref) def build_page(sha, ref)
...@@ -80,11 +67,11 @@ class BambooService < CiService ...@@ -80,11 +67,11 @@ class BambooService < CiService
if @response.code != 200 || @response['results']['results']['size'] == '0' if @response.code != 200 || @response['results']['results']['size'] == '0'
# If actual build link can't be determined, send user to build summary page. # If actual build link can't be determined, send user to build summary page.
URI.join(bamboo_url, "/browse/#{build_key}").to_s URI.join("#{bamboo_url}/", "browse/#{build_key}").to_s
else else
# If actual build link is available, go to build result page. # If actual build link is available, go to build result page.
result_key = @response['results']['results']['result']['planResultKey']['key'] result_key = @response['results']['results']['result']['planResultKey']['key']
URI.join(bamboo_url, "/browse/#{result_key}").to_s URI.join("#{bamboo_url}/", "browse/#{result_key}").to_s
end end
end end
...@@ -112,8 +99,27 @@ class BambooService < CiService ...@@ -112,8 +99,27 @@ class BambooService < CiService
def execute(data) def execute(data)
return unless supported_events.include?(data[:object_kind]) return unless supported_events.include?(data[:object_kind])
# Bamboo requires a GET and does not take any data. get_path("updateAndBuild.action?buildKey=#{build_key}")
url = URI.join(bamboo_url, "/updateAndBuild.action?buildKey=#{build_key}").to_s end
self.class.get(url, verify: false)
private
def build_url(path)
URI.join("#{bamboo_url}/", path).to_s
end
def get_path(path)
url = build_url(path)
if username.blank? && password.blank?
HTTParty.get(url, verify: false)
else
url << '&os_authType=basic'
HTTParty.get(url, verify: false,
basic_auth: {
username: username,
password: password
})
end
end end
end end
...@@ -38,9 +38,9 @@ class IssueTrackerService < Service ...@@ -38,9 +38,9 @@ class IssueTrackerService < Service
if enabled_in_gitlab_config if enabled_in_gitlab_config
self.properties = { self.properties = {
title: issues_tracker['title'], title: issues_tracker['title'],
project_url: add_issues_tracker_id(issues_tracker['project_url']), project_url: issues_tracker['project_url'],
issues_url: add_issues_tracker_id(issues_tracker['issues_url']), issues_url: issues_tracker['issues_url'],
new_issue_url: add_issues_tracker_id(issues_tracker['new_issue_url']) new_issue_url: issues_tracker['new_issue_url']
} }
else else
self.properties = {} self.properties = {}
...@@ -83,16 +83,4 @@ class IssueTrackerService < Service ...@@ -83,16 +83,4 @@ class IssueTrackerService < Service
def issues_tracker def issues_tracker
Gitlab.config.issues_tracker[to_param] Gitlab.config.issues_tracker[to_param]
end end
def add_issues_tracker_id(url)
if self.project
id = self.project.issues_tracker_id
if id
url = url.gsub(":issues_tracker_id", id)
end
end
url
end
end end
class TeamcityService < CiService class TeamcityService < CiService
include HTTParty
prop_accessor :teamcity_url, :build_type, :username, :password prop_accessor :teamcity_url, :build_type, :username, :password
validates :teamcity_url, presence: true, url: true, if: :activated? validates :teamcity_url, presence: true, url: true, if: :activated?
...@@ -64,15 +62,7 @@ class TeamcityService < CiService ...@@ -64,15 +62,7 @@ class TeamcityService < CiService
end end
def build_info(sha) def build_info(sha)
url = URI.join( @response = get_path("httpAuth/app/rest/builds/branch:unspecified:any,number:#{sha}")
teamcity_url,
"/httpAuth/app/rest/builds/branch:unspecified:any,number:#{sha}"
).to_s
auth = {
username: username,
password: password
}
@response = HTTParty.get(url, verify: false, basic_auth: auth)
end end
def build_page(sha, ref) def build_page(sha, ref)
...@@ -81,14 +71,11 @@ class TeamcityService < CiService ...@@ -81,14 +71,11 @@ class TeamcityService < CiService
if @response.code != 200 if @response.code != 200
# If actual build link can't be determined, # If actual build link can't be determined,
# send user to build summary page. # send user to build summary page.
URI.join(teamcity_url, "/viewLog.html?buildTypeId=#{build_type}").to_s build_url("viewLog.html?buildTypeId=#{build_type}")
else else
# If actual build link is available, go to build result page. # If actual build link is available, go to build result page.
built_id = @response['build']['id'] built_id = @response['build']['id']
URI.join( build_url("viewLog.html?buildId=#{built_id}&buildTypeId=#{build_type}")
teamcity_url,
"/viewLog.html?buildId=#{built_id}&buildTypeId=#{build_type}"
).to_s
end end
end end
...@@ -123,8 +110,8 @@ class TeamcityService < CiService ...@@ -123,8 +110,8 @@ class TeamcityService < CiService
branch = Gitlab::Git.ref_name(data[:ref]) branch = Gitlab::Git.ref_name(data[:ref])
self.class.post( HTTParty.post(
URI.join(teamcity_url, '/httpAuth/app/rest/buildQueue').to_s, build_url('httpAuth/app/rest/buildQueue'),
body: "<build branchName=\"#{branch}\">"\ body: "<build branchName=\"#{branch}\">"\
"<buildType id=\"#{build_type}\"/>"\ "<buildType id=\"#{build_type}\"/>"\
'</build>', '</build>',
...@@ -132,4 +119,18 @@ class TeamcityService < CiService ...@@ -132,4 +119,18 @@ class TeamcityService < CiService
basic_auth: auth basic_auth: auth
) )
end end
private
def build_url(path)
URI.join("#{teamcity_url}/", path).to_s
end
def get_path(path)
HTTParty.get(build_url(path), verify: false,
basic_auth: {
username: username,
password: password
})
end
end end
...@@ -131,8 +131,14 @@ class ProjectTeam ...@@ -131,8 +131,14 @@ class ProjectTeam
max_member_access(user.id) == Gitlab::Access::MASTER max_member_access(user.id) == Gitlab::Access::MASTER
end end
def member?(user_id) def member?(user, min_member_access = nil)
!!find_member(user_id) member = !!find_member(user.id)
if min_member_access
member && max_member_access(user.id) >= min_member_access
else
member
end
end end
def human_max_access(user_id) def human_max_access(user_id)
......
...@@ -10,6 +10,8 @@ class User < ActiveRecord::Base ...@@ -10,6 +10,8 @@ class User < ActiveRecord::Base
include CaseSensitivity include CaseSensitivity
include TokenAuthenticatable include TokenAuthenticatable
DEFAULT_NOTIFICATION_LEVEL = :participating
add_authentication_token_field :authentication_token add_authentication_token_field :authentication_token
default_value_for :admin, false default_value_for :admin, false
...@@ -99,7 +101,6 @@ class User < ActiveRecord::Base ...@@ -99,7 +101,6 @@ class User < ActiveRecord::Base
presence: true, presence: true,
uniqueness: { case_sensitive: false } uniqueness: { case_sensitive: false }
validates :notification_level, presence: true
validate :namespace_uniq, if: ->(user) { user.username_changed? } validate :namespace_uniq, if: ->(user) { user.username_changed? }
validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? } validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? }
validate :unique_email, if: ->(user) { user.email_changed? } validate :unique_email, if: ->(user) { user.email_changed? }
...@@ -133,13 +134,6 @@ class User < ActiveRecord::Base ...@@ -133,13 +134,6 @@ class User < ActiveRecord::Base
# Note: When adding an option, it MUST go on the end of the array. # Note: When adding an option, it MUST go on the end of the array.
enum project_view: [:readme, :activity, :files] enum project_view: [:readme, :activity, :files]
# Notification level
# Note: When adding an option, it MUST go on the end of the array.
#
# TODO: Add '_prefix: :notification' to enum when update to Rails 5. https://github.com/rails/rails/pull/19813
# Because user.notification_disabled? is much better than user.disabled?
enum notification_level: [:disabled, :participating, :watch, :global, :mention]
alias_attribute :private_token, :authentication_token alias_attribute :private_token, :authentication_token
delegate :path, to: :namespace, allow_nil: true, prefix: true delegate :path, to: :namespace, allow_nil: true, prefix: true
...@@ -411,8 +405,8 @@ class User < ActiveRecord::Base ...@@ -411,8 +405,8 @@ class User < ActiveRecord::Base
end end
# Returns projects user is authorized to access. # Returns projects user is authorized to access.
def authorized_projects def authorized_projects(min_access_level = nil)
Project.where("projects.id IN (#{projects_union.to_sql})") Project.where("projects.id IN (#{projects_union(min_access_level).to_sql})")
end end
def viewable_starred_projects def viewable_starred_projects
...@@ -800,6 +794,17 @@ class User < ActiveRecord::Base ...@@ -800,6 +794,17 @@ class User < ActiveRecord::Base
notification_settings.find_or_initialize_by(source: source) notification_settings.find_or_initialize_by(source: source)
end end
# Lazy load global notification setting
# Initializes User setting with Participating level if setting not persisted
def global_notification_setting
return @global_notification_setting if defined?(@global_notification_setting)
@global_notification_setting = notification_settings.find_or_initialize_by(source: nil)
@global_notification_setting.update_attributes(level: NotificationSetting.levels[DEFAULT_NOTIFICATION_LEVEL]) unless @global_notification_setting.persisted?
@global_notification_setting
end
def assigned_open_merge_request_count(force: false) def assigned_open_merge_request_count(force: false)
Rails.cache.fetch(['users', id, 'assigned_open_merge_request_count'], force: force) do Rails.cache.fetch(['users', id, 'assigned_open_merge_request_count'], force: force) do
assigned_merge_requests.opened.count assigned_merge_requests.opened.count
...@@ -819,11 +824,19 @@ class User < ActiveRecord::Base ...@@ -819,11 +824,19 @@ class User < ActiveRecord::Base
private private
def projects_union def projects_union(min_access_level = nil)
Gitlab::SQL::Union.new([personal_projects.select(:id), relations = [personal_projects.select(:id),
groups_projects.select(:id), groups_projects.select(:id),
projects.select(:id), projects.select(:id),
groups.joins(:shared_projects).select(:project_id)]) groups.joins(:shared_projects).select(:project_id)]
if min_access_level
scope = { access_level: Gitlab::Access.values.select { |access| access >= min_access_level } }
relations = [relations.shift] + relations.map { |relation| relation.where(members: scope) }
end
Gitlab::SQL::Union.new(relations)
end end
def ci_projects_union def ci_projects_union
......
...@@ -279,10 +279,11 @@ class NotificationService ...@@ -279,10 +279,11 @@ class NotificationService
end end
def users_with_global_level_watch(ids) def users_with_global_level_watch(ids)
User.where( NotificationSetting.where(
id: ids, user_id: ids,
notification_level: NotificationSetting.levels[:watch] source_type: nil,
).pluck(:id) level: NotificationSetting.levels[:watch]
).pluck(:user_id)
end end
# Build a list of users based on project notifcation settings # Build a list of users based on project notifcation settings
...@@ -352,7 +353,9 @@ class NotificationService ...@@ -352,7 +353,9 @@ class NotificationService
users = users.reject(&:blocked?) users = users.reject(&:blocked?)
users.reject do |user| users.reject do |user|
next user.notification_level == level unless project global_notification_setting = user.global_notification_setting
next global_notification_setting.level == level unless project
setting = user.notification_settings_for(project) setting = user.notification_settings_for(project)
...@@ -361,13 +364,13 @@ class NotificationService ...@@ -361,13 +364,13 @@ class NotificationService
end end
# reject users who globally set mention notification and has no setting per project/group # reject users who globally set mention notification and has no setting per project/group
next user.notification_level == level unless setting next global_notification_setting.level == level unless setting
# reject users who set mention notification in project # reject users who set mention notification in project
next true if setting.level == level next true if setting.level == level
# reject users who have mention level in project and disabled in global settings # reject users who have mention level in project and disabled in global settings
setting.global? && user.notification_level == level setting.global? && global_notification_setting.level == level
end end
end end
...@@ -456,7 +459,6 @@ class NotificationService ...@@ -456,7 +459,6 @@ class NotificationService
def build_recipients(target, project, current_user, action: nil, previous_assignee: nil) def build_recipients(target, project, current_user, action: nil, previous_assignee: nil)
recipients = target.participants(current_user) recipients = target.participants(current_user)
recipients = add_project_watchers(recipients, project) recipients = add_project_watchers(recipients, project)
recipients = reject_mention_users(recipients, project) recipients = reject_mention_users(recipients, project)
......
...@@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear ...@@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.link href: issues_dashboard_url(format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml" xml.link href: issues_dashboard_url(format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml"
xml.link href: issues_dashboard_url, rel: "alternate", type: "text/html" xml.link href: issues_dashboard_url, rel: "alternate", type: "text/html"
xml.id issues_dashboard_url xml.id issues_dashboard_url
xml.updated @issues.first.created_at.xmlschema if @issues.any? xml.updated @issues.first.created_at.xmlschema if @issues.reorder(nil).any?
xml << render(partial: 'issues/issue', collection: @issues) if @issues.any? xml << render(partial: 'issues/issue', collection: @issues) if @issues.reorder(nil).any?
end end
...@@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear ...@@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.link href: issues_group_url(format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml" xml.link href: issues_group_url(format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml"
xml.link href: issues_group_url, rel: "alternate", type: "text/html" xml.link href: issues_group_url, rel: "alternate", type: "text/html"
xml.id issues_group_url xml.id issues_group_url
xml.updated @issues.first.created_at.xmlschema if @issues.any? xml.updated @issues.first.created_at.xmlschema if @issues.reorder(nil).any?
xml << render(partial: 'issues/issue', collection: @issues) if @issues.any? xml << render(partial: 'issues/issue', collection: @issues) if @issues.reorder(nil).any?
end end
...@@ -5,10 +5,28 @@ xml.entry do ...@@ -5,10 +5,28 @@ xml.entry do
xml.updated issue.created_at.xmlschema xml.updated issue.created_at.xmlschema
xml.media :thumbnail, width: "40", height: "40", url: image_url(avatar_icon(issue.author_email)) xml.media :thumbnail, width: "40", height: "40", url: image_url(avatar_icon(issue.author_email))
xml.author do |author| xml.author do
xml.name issue.author_name xml.name issue.author_name
xml.email issue.author_email xml.email issue.author_email
end end
xml.summary issue.title xml.summary issue.title
xml.description issue.description if issue.description
xml.milestone issue.milestone.title if issue.milestone
xml.due_date issue.due_date if issue.due_date
unless issue.labels.empty?
xml.labels do
issue.labels.each do |label|
xml.label label.name
end
end
end
if issue.assignee
xml.assignee do
xml.name issue.assignee.name
xml.email issue.assignee.email
end
end
end end
- if nav_menu_collapsed? = link_to icon('bars'), '#', class: 'toggle-nav-collapse', title: "Open/Close"
= link_to icon('angle-right'), '#', class: 'toggle-nav-collapse', title: "Open/Close"
- else
= link_to icon('angle-left'), '#', class: 'toggle-nav-collapse', title: "Open/Close"
.page-with-sidebar{ class: "#{page_sidebar_class} #{page_gutter_class}" } .page-with-sidebar.page-sidebar-collapsed{ class: "#{page_gutter_class}" }
.sidebar-wrapper.nicescroll{ class: nav_sidebar_class } .sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
= link_to root_path, class: 'gitlab-text-container-link', title: 'Dashboard', id: 'js-shortcuts-home' do
.header-logo
#logo
= brand_header_logo
- if defined?(sidebar) && sidebar - if defined?(sidebar) && sidebar
= render "layouts/nav/#{sidebar}" = render "layouts/nav/#{sidebar}"
...@@ -16,7 +12,9 @@ ...@@ -16,7 +12,9 @@
= render partial: 'layouts/collapse_button' = render partial: 'layouts/collapse_button'
- if current_user - if current_user
= link_to current_user, class: 'sidebar-user', title: "Profile", data: {user: current_user.username} do = link_to current_user, class: 'sidebar-user', title: "Profile", data: {user: current_user.username} do
= image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s46' = image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s36'
.username
= current_user.username
- if defined?(nav) && nav - if defined?(nav) && nav
.layout-nav .layout-nav
.container-fluid .container-fluid
......
.page-with-sidebar{ class: page_sidebar_class } .page-with-sidebar{ class: page_sidebar_class }
= render "layouts/broadcast" = render "layouts/broadcast"
.sidebar-wrapper.nicescroll{ class: nav_sidebar_class } .sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
.header-logo
%a#logo
= brand_header_logo
= link_to root_path, class: 'gitlab-text-container-link', title: 'Dashboard', id: 'js-shortcuts-home' do
.gitlab-text-container
%h3 GitLab
- if defined?(sidebar) && sidebar - if defined?(sidebar) && sidebar
= render "layouts/ci/#{sidebar}" = render "layouts/ci/#{sidebar}"
......
%header.navbar.navbar-fixed-top.navbar-gitlab{ class: nav_header_class } %header.navbar.navbar-fixed-top.navbar-gitlab.header-collapsed{ class: nav_header_class }
%div{ class: fluid_layout ? "container-fluid" : "container-fluid" } %div{ class: fluid_layout ? "container-fluid" : "container-fluid" }
.header-content .header-content
%button.side-nav-toggle{type: 'button'} %button.side-nav-toggle{type: 'button'}
...@@ -50,6 +50,10 @@ ...@@ -50,6 +50,10 @@
%h1.title= title %h1.title= title
.header-logo
#logo
= brand_header_logo
= yield :header_content = yield :header_content
= render 'shared/outdated_browser' = render 'shared/outdated_browser'
......
...@@ -2,102 +2,106 @@ ...@@ -2,102 +2,106 @@
= nav_link(controller: :dashboard, html_options: {class: 'home'}) do = nav_link(controller: :dashboard, html_options: {class: 'home'}) do
= link_to admin_root_path, title: 'Overview' do = link_to admin_root_path, title: 'Overview' do
= icon('dashboard fw') = icon('dashboard fw')
.nav-link-text %span
Overview Overview
= nav_link(controller: [:admin, :projects]) do = nav_link(controller: [:admin, :projects]) do
= link_to admin_namespaces_projects_path, title: 'Projects' do = link_to admin_namespaces_projects_path, title: 'Projects' do
= icon('cube fw') = icon('cube fw')
.nav-link-text %span
Projects Projects
= nav_link(controller: :users) do = nav_link(controller: :users) do
= link_to admin_users_path, title: 'Users' do = link_to admin_users_path, title: 'Users' do
= icon('user fw') = icon('user fw')
.nav-link-text %span
Users Users
= nav_link(controller: :groups) do = nav_link(controller: :groups) do
= link_to admin_groups_path, title: 'Groups' do = link_to admin_groups_path, title: 'Groups' do
= icon('group fw') = icon('group fw')
.nav-link-text %span
Groups Groups
= nav_link(controller: :deploy_keys) do = nav_link(controller: :deploy_keys) do
= link_to admin_deploy_keys_path, title: 'Deploy Keys' do = link_to admin_deploy_keys_path, title: 'Deploy Keys' do
= icon('key fw') = icon('key fw')
.nav-link-text %span
Deploy Keys Deploy Keys
= nav_link path: ['runners#index', 'runners#show'] do = nav_link path: ['runners#index', 'runners#show'] do
= link_to admin_runners_path, title: 'Runners' do = link_to admin_runners_path, title: 'Runners' do
= icon('cog fw') = icon('cog fw')
.nav-link-text %span
Runners Runners
%span.count= number_with_delimiter(Ci::Runner.count(:all))
= nav_link path: 'builds#index' do = nav_link path: 'builds#index' do
= link_to admin_builds_path, title: 'Builds' do = link_to admin_builds_path, title: 'Builds' do
= icon('link fw') = icon('link fw')
.nav-link-text %span
Builds Builds
%span.count= number_with_delimiter(Ci::Build.count(:all))
= nav_link(controller: :logs) do = nav_link(controller: :logs) do
= link_to admin_logs_path, title: 'Logs' do = link_to admin_logs_path, title: 'Logs' do
= icon('file-text fw') = icon('file-text fw')
.nav-link-text %span
Logs Logs
= nav_link(controller: :health_check) do = nav_link(controller: :health_check) do
= link_to admin_health_check_path, title: 'Health Check' do = link_to admin_health_check_path, title: 'Health Check' do
= icon('medkit fw') = icon('medkit fw')
.nav-link-text %span
Health Check Health Check
= nav_link(controller: :broadcast_messages) do = nav_link(controller: :broadcast_messages) do
= link_to admin_broadcast_messages_path, title: 'Messages' do = link_to admin_broadcast_messages_path, title: 'Messages' do
= icon('bullhorn fw') = icon('bullhorn fw')
.nav-link-text %span
Messages Messages
= nav_link(controller: :hooks) do = nav_link(controller: :hooks) do
= link_to admin_hooks_path, title: 'Hooks' do = link_to admin_hooks_path, title: 'Hooks' do
= icon('external-link fw') = icon('external-link fw')
.nav-link-text %span
Hooks Hooks
= nav_link(controller: :background_jobs) do = nav_link(controller: :background_jobs) do
= link_to admin_background_jobs_path, title: 'Background Jobs' do = link_to admin_background_jobs_path, title: 'Background Jobs' do
= icon('cog fw') = icon('cog fw')
.nav-link-text %span
Background Jobs Background Jobs
= nav_link(controller: :appearances) do = nav_link(controller: :appearances) do
= link_to admin_appearances_path, title: 'Appearances' do = link_to admin_appearances_path, title: 'Appearances' do
= icon('image') = icon('image')
.nav-link-text %span
Appearance Appearance
= nav_link(controller: :applications) do = nav_link(controller: :applications) do
= link_to admin_applications_path, title: 'Applications' do = link_to admin_applications_path, title: 'Applications' do
= icon('cloud fw') = icon('cloud fw')
.nav-link-text %span
Applications Applications
= nav_link(controller: :services) do = nav_link(controller: :services) do
= link_to admin_application_settings_services_path, title: 'Service Templates' do = link_to admin_application_settings_services_path, title: 'Service Templates' do
= icon('copy fw') = icon('copy fw')
.nav-link-text %span
Service Templates Service Templates
= nav_link(controller: :labels) do = nav_link(controller: :labels) do
= link_to admin_labels_path, title: 'Labels' do = link_to admin_labels_path, title: 'Labels' do
= icon('tags fw') = icon('tags fw')
.nav-link-text %span
Labels Labels
= nav_link(controller: :abuse_reports) do = nav_link(controller: :abuse_reports) do
= link_to admin_abuse_reports_path, title: "Abuse Reports" do = link_to admin_abuse_reports_path, title: "Abuse Reports" do
= icon('exclamation-circle fw') = icon('exclamation-circle fw')
.nav-link-text %span
Abuse Reports Abuse Reports
%span.count= number_with_delimiter(AbuseReport.count(:all))
- if askimet_enabled? - if askimet_enabled?
= nav_link(controller: :spam_logs) do = nav_link(controller: :spam_logs) do
= link_to admin_spam_logs_path, title: "Spam Logs" do = link_to admin_spam_logs_path, title: "Spam Logs" do
= icon('exclamation-triangle fw') = icon('exclamation-triangle fw')
.nav-link-text %span
Spam Logs Spam Logs
%span.count= number_with_delimiter(SpamLog.count(:all))
= nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do = nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do
= link_to admin_application_settings_path, title: 'Settings' do = link_to admin_application_settings_path, title: 'Settings' do
= icon('cogs fw') = icon('cogs fw')
.nav-link-text %span
Settings Settings
...@@ -2,50 +2,53 @@ ...@@ -2,50 +2,53 @@
= nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: "#{project_tab_class} home"}) do = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: "#{project_tab_class} home"}) do
= link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do = link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do
= navbar_icon('project') = navbar_icon('project')
.nav-link-text %span
Projects Projects
= nav_link(controller: :todos) do = nav_link(controller: :todos) do
= link_to dashboard_todos_path, title: 'Todos' do = link_to dashboard_todos_path, title: 'Todos' do
= icon('bell fw') = icon('bell fw')
.nav-link-text %span
Todos Todos
%span.count= number_with_delimiter(todos_pending_count)
= nav_link(path: 'dashboard#activity') do = nav_link(path: 'dashboard#activity') do
= link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do = link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do
= navbar_icon('activity') = navbar_icon('activity')
.nav-link-text %span
Activity Activity
= nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do = nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do
= link_to dashboard_groups_path, title: 'Groups' do = link_to dashboard_groups_path, title: 'Groups' do
= navbar_icon('group') = navbar_icon('group')
.nav-link-text %span
Groups Groups
= nav_link(controller: 'dashboard/milestones') do = nav_link(controller: 'dashboard/milestones') do
= link_to dashboard_milestones_path, title: 'Milestones' do = link_to dashboard_milestones_path, title: 'Milestones' do
= navbar_icon('milestones') = navbar_icon('milestones')
.nav-link-text %span
Milestones Milestones
= nav_link(path: 'dashboard#issues') do = nav_link(path: 'dashboard#issues') do
= link_to assigned_issues_dashboard_path, title: 'Issues', class: 'dashboard-shortcuts-issues' do = link_to assigned_issues_dashboard_path, title: 'Issues', class: 'dashboard-shortcuts-issues' do
= navbar_icon('issues') = navbar_icon('issues')
.nav-link-text %span
Issues Issues
%span.count= number_with_delimiter(current_user.assigned_issues.opened.count)
= nav_link(path: 'dashboard#merge_requests') do = nav_link(path: 'dashboard#merge_requests') do
= link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'dashboard-shortcuts-merge_requests' do = link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'dashboard-shortcuts-merge_requests' do
= navbar_icon('mr') = navbar_icon('mr')
.nav-link-text %span
Merge Requests Merge Requests
%span.count= number_with_delimiter(current_user.assigned_merge_requests.opened.count)
= nav_link(controller: :snippets) do = nav_link(controller: :snippets) do
= link_to dashboard_snippets_path, title: 'Snippets' do = link_to dashboard_snippets_path, title: 'Snippets' do
= icon('clipboard fw') = icon('clipboard fw')
.nav-link-text %span
Snippets Snippets
= nav_link(controller: :help) do = nav_link(controller: :help) do
= link_to help_path, title: 'Help' do = link_to help_path, title: 'Help' do
= icon('question-circle fw') = icon('question-circle fw')
.nav-link-text %span
Help Help
= nav_link(html_options: {class: profile_tab_class}) do = nav_link(html_options: {class: profile_tab_class}) do
= link_to profile_path, title: 'Profile Settings', data: {placement: 'bottom'} do = link_to profile_path, title: 'Profile Settings', data: {placement: 'bottom'} do
= icon('user fw') = icon('user fw')
.nav-link-text %span
Profile Settings Profile Settings
...@@ -2,20 +2,20 @@ ...@@ -2,20 +2,20 @@
= nav_link(path: ['dashboard#show', 'root#show', 'projects#trending', 'projects#starred', 'projects#index'], html_options: {class: 'home'}) do = nav_link(path: ['dashboard#show', 'root#show', 'projects#trending', 'projects#starred', 'projects#index'], html_options: {class: 'home'}) do
= link_to explore_root_path, title: 'Projects' do = link_to explore_root_path, title: 'Projects' do
= icon('bookmark fw') = icon('bookmark fw')
.nav-link-text %span
Projects Projects
= nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do = nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do
= link_to explore_groups_path, title: 'Groups' do = link_to explore_groups_path, title: 'Groups' do
= icon('group fw') = icon('group fw')
.nav-link-text %span
Groups Groups
= nav_link(controller: :snippets) do = nav_link(controller: :snippets) do
= link_to explore_snippets_path, title: 'Snippets' do = link_to explore_snippets_path, title: 'Snippets' do
= icon('clipboard fw') = icon('clipboard fw')
.nav-link-text %span
Snippets Snippets
= nav_link(controller: :help) do = nav_link(controller: :help) do
= link_to help_path, title: 'Help' do = link_to help_path, title: 'Help' do
= icon('question-circle fw') = icon('question-circle fw')
.nav-link-text %span
Help Help
...@@ -5,36 +5,30 @@ ...@@ -5,36 +5,30 @@
.fade-left .fade-left
= nav_link(path: 'groups#show', html_options: {class: 'home'}) do = nav_link(path: 'groups#show', html_options: {class: 'home'}) do
= link_to group_path(@group), title: 'Home' do = link_to group_path(@group), title: 'Home' do
= navbar_icon('group')
%span %span
Group Group
= nav_link(path: 'groups#activity') do = nav_link(path: 'groups#activity') do
= link_to activity_group_path(@group), title: 'Activity' do = link_to activity_group_path(@group), title: 'Activity' do
= navbar_icon('activity')
%span %span
Activity Activity
= nav_link(controller: [:group, :milestones]) do = nav_link(controller: [:group, :milestones]) do
= link_to group_milestones_path(@group), title: 'Milestones' do = link_to group_milestones_path(@group), title: 'Milestones' do
= navbar_icon('milestones')
%span %span
Milestones Milestones
= nav_link(path: 'groups#issues') do = nav_link(path: 'groups#issues') do
= link_to issues_group_path(@group), title: 'Issues' do = link_to issues_group_path(@group), title: 'Issues' do
= navbar_icon('issues')
%span %span
Issues Issues
- issues = IssuesFinder.new(current_user, group_id: @group.id, state: 'opened').execute - issues = IssuesFinder.new(current_user, group_id: @group.id, state: 'opened').execute
%span.badge.count= number_with_delimiter(issues.count) %span.badge.count= number_with_delimiter(issues.count)
= nav_link(path: 'groups#merge_requests') do = nav_link(path: 'groups#merge_requests') do
= link_to merge_requests_group_path(@group), title: 'Merge Requests' do = link_to merge_requests_group_path(@group), title: 'Merge Requests' do
= navbar_icon('mr')
%span %span
Merge Requests Merge Requests
- merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened').execute - merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened').execute
%span.badge.count= number_with_delimiter(merge_requests.count) %span.badge.count= number_with_delimiter(merge_requests.count)
= nav_link(controller: [:group_members]) do = nav_link(controller: [:group_members]) do
= link_to group_group_members_path(@group), title: 'Members' do = link_to group_group_members_path(@group), title: 'Members' do
= navbar_icon('members')
%span %span
Members Members
.fade-right .fade-right
...@@ -2,51 +2,41 @@ ...@@ -2,51 +2,41 @@
.fade-left .fade-left
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
= link_to profile_path, title: 'Profile Settings' do = link_to profile_path, title: 'Profile Settings' do
= icon('user fw')
%span %span
Profile Profile
= nav_link(controller: [:accounts, :two_factor_auths]) do = nav_link(controller: [:accounts, :two_factor_auths]) do
= link_to profile_account_path, title: 'Account' do = link_to profile_account_path, title: 'Account' do
= icon('gear fw')
%span %span
Account Account
- if current_application_settings.user_oauth_applications? - if current_application_settings.user_oauth_applications?
= nav_link(controller: 'oauth/applications') do = nav_link(controller: 'oauth/applications') do
= link_to applications_profile_path, title: 'Applications' do = link_to applications_profile_path, title: 'Applications' do
= icon('cloud fw')
%span %span
Applications Applications
= nav_link(controller: :emails) do = nav_link(controller: :emails) do
= link_to profile_emails_path, title: 'Emails' do = link_to profile_emails_path, title: 'Emails' do
= icon('envelope-o fw')
%span %span
Emails Emails
- unless current_user.ldap_user? - unless current_user.ldap_user?
= nav_link(controller: :passwords) do = nav_link(controller: :passwords) do
= link_to edit_profile_password_path, title: 'Password' do = link_to edit_profile_password_path, title: 'Password' do
= icon('lock fw')
%span %span
Password Password
= nav_link(controller: :notifications) do = nav_link(controller: :notifications) do
= link_to profile_notifications_path, title: 'Notifications' do = link_to profile_notifications_path, title: 'Notifications' do
= icon('inbox fw')
%span %span
Notifications Notifications
= nav_link(controller: :keys) do = nav_link(controller: :keys) do
= link_to profile_keys_path, title: 'SSH Keys' do = link_to profile_keys_path, title: 'SSH Keys' do
= icon('key fw')
%span %span
SSH Keys SSH Keys
= nav_link(controller: :preferences) do = nav_link(controller: :preferences) do
= link_to profile_preferences_path, title: 'Preferences' do = link_to profile_preferences_path, title: 'Preferences' do
-# TODO (rspeicher): Better icon?
= icon('image fw')
%span %span
Preferences Preferences
= nav_link(path: 'profiles#audit_log') do = nav_link(path: 'profiles#audit_log') do
= link_to audit_log_profile_path, title: 'Audit Log' do = link_to audit_log_profile_path, title: 'Audit Log' do
= icon('history fw')
%span %span
Audit Log Audit Log
.fade-right .fade-right
...@@ -24,48 +24,41 @@ ...@@ -24,48 +24,41 @@
.fade-left .fade-left
= nav_link(path: 'projects#show', html_options: {class: 'home'}) do = nav_link(path: 'projects#show', html_options: {class: 'home'}) do
= link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do = link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do
= navbar_icon('project')
%span %span
Project Project
= nav_link(path: 'projects#activity') do = nav_link(path: 'projects#activity') do
= link_to activity_project_path(@project), title: 'Activity', class: 'shortcuts-project-activity' do = link_to activity_project_path(@project), title: 'Activity', class: 'shortcuts-project-activity' do
= navbar_icon('activity')
%span %span
Activity Activity
- if project_nav_tab? :files - if project_nav_tab? :files
= nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare repositories tags branches releases network)) do = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare repositories tags branches releases network)) do
= link_to project_files_path(@project), title: 'Code', class: 'shortcuts-tree' do = link_to project_files_path(@project), title: 'Code', class: 'shortcuts-tree' do
= icon('code fw')
%span %span
Code Code
- if project_nav_tab? :pipelines - if project_nav_tab? :pipelines
= nav_link(controller: :pipelines) do = nav_link(controller: :pipelines) do
= link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do
= navbar_icon('pipelines')
%span %span
Pipelines Pipelines
- if project_nav_tab? :container_registry - if project_nav_tab? :container_registry
= nav_link(controller: %w(container_registry)) do = nav_link(controller: %w(container_registry)) do
= link_to project_container_registry_path(@project), title: 'Container Registry', class: 'shortcuts-container-registry' do = link_to project_container_registry_path(@project), title: 'Container Registry', class: 'shortcuts-container-registry' do
= icon('hdd-o fw')
%span %span
Registry Registry
- if project_nav_tab? :graphs - if project_nav_tab? :graphs
= nav_link(controller: %w(graphs)) do = nav_link(controller: %w(graphs)) do
= link_to namespace_project_graph_path(@project.namespace, @project, current_ref), title: 'Graphs', class: 'shortcuts-graphs' do = link_to namespace_project_graph_path(@project.namespace, @project, current_ref), title: 'Graphs', class: 'shortcuts-graphs' do
= icon('area-chart fw')
%span %span
Graphs Graphs
- if project_nav_tab? :issues - if project_nav_tab? :issues
= nav_link(controller: [:issues, :labels, :milestones]) do = nav_link(controller: [:issues, :labels, :milestones]) do
= link_to url_for_project_issues(@project, only_path: true), title: 'Issues', class: 'shortcuts-issues' do = link_to url_for_project_issues(@project, only_path: true), title: 'Issues', class: 'shortcuts-issues' do
= navbar_icon('issues')
%span %span
Issues Issues
- if @project.default_issues_tracker? - if @project.default_issues_tracker?
...@@ -74,7 +67,6 @@ ...@@ -74,7 +67,6 @@
- if project_nav_tab? :merge_requests - if project_nav_tab? :merge_requests
= nav_link(controller: :merge_requests) do = nav_link(controller: :merge_requests) do
= link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do
= navbar_icon('mr')
%span %span
Merge Requests Merge Requests
%span.badge.count.merge_counter= number_with_delimiter(@project.merge_requests.opened.count) %span.badge.count.merge_counter= number_with_delimiter(@project.merge_requests.opened.count)
...@@ -82,14 +74,12 @@ ...@@ -82,14 +74,12 @@
- if project_nav_tab? :wiki - if project_nav_tab? :wiki
= nav_link(controller: :wikis) do = nav_link(controller: :wikis) do
= link_to get_project_wiki_path(@project), title: 'Wiki', class: 'shortcuts-wiki' do = link_to get_project_wiki_path(@project), title: 'Wiki', class: 'shortcuts-wiki' do
= navbar_icon('wiki')
%span %span
Wiki Wiki
- if project_nav_tab? :snippets - if project_nav_tab? :snippets
= nav_link(controller: :snippets) do = nav_link(controller: :snippets) do
= link_to namespace_project_snippets_path(@project.namespace, @project), title: 'Snippets', class: 'shortcuts-snippets' do = link_to namespace_project_snippets_path(@project.namespace, @project), title: 'Snippets', class: 'shortcuts-snippets' do
= icon('clipboard fw')
%span %span
Snippets Snippets
......
%li.notification-list-item %li.notification-list-item
%span.notification.fa.fa-holder.append-right-5 %span.notification.fa.fa-holder.append-right-5
- if setting.global? - if setting.global?
= notification_icon(current_user.notification_level) = notification_icon(current_user.global_notification_setting.level)
- else - else
= notification_icon(setting.level) = notification_icon(setting.level)
......
%li.notification-list-item %li.notification-list-item
%span.notification.fa.fa-holder.append-right-5 %span.notification.fa.fa-holder.append-right-5
- if setting.global? - if setting.global?
= notification_icon(current_user.notification_level) = notification_icon(current_user.global_notification_setting.level)
- else - else
= notification_icon(setting.level) = notification_icon(setting.level)
......
...@@ -26,33 +26,7 @@ ...@@ -26,33 +26,7 @@
= f.select :notification_email, @user.all_emails, { include_blank: false }, class: "select2" = f.select :notification_email, @user.all_emails, { include_blank: false }, class: "select2"
.form-group .form-group
= f.label :notification_level, class: 'label-light' = f.label :notification_level, class: 'label-light'
.radio = notification_level_radio_buttons
= f.label :notification_level, value: :disabled do
= f.radio_button :notification_level, :disabled
.level-title
Disabled
%p You will not get any notifications via email
.radio
= f.label :notification_level, value: :mention do
= f.radio_button :notification_level, :mention
.level-title
On Mention
%p You will receive notifications only for comments in which you were @mentioned
.radio
= f.label :notification_level, value: :participating do
= f.radio_button :notification_level, :participating
.level-title
Participating
%p You will only receive notifications from related resources (e.g. from your commits or assigned issues)
.radio
= f.label :notification_level, value: :watch do
= f.radio_button :notification_level, :watch
.level-title
Watch
%p You will receive notifications for any activity
.prepend-top-default .prepend-top-default
= f.submit 'Update settings', class: "btn btn-create" = f.submit 'Update settings', class: "btn btn-create"
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
.form-group.branch .form-group.branch
= label_tag 'target_branch', target_label, class: 'control-label' = label_tag 'target_branch', target_label, class: 'control-label'
.col-sm-10 .col-sm-10
= select_tag "target_branch", grouped_options_refs, class: "select2 select2-sm js-target-branch" = select_tag "target_branch", project_branches, class: "select2 select2-sm js-target-branch"
- if can?(current_user, :push_code, @project) - if can?(current_user, :push_code, @project)
.js-create-merge-request-container .js-create-merge-request-container
.checkbox .checkbox
......
...@@ -24,8 +24,6 @@ ...@@ -24,8 +24,6 @@
MERGED MERGED
- elsif merge_request.closed? - elsif merge_request.closed?
CLOSED CLOSED
%li
= render partial: 'projects/issues/closed_by_box', locals: {merge_request_count: @merge_requests.count}
- if @closed_by_merge_requests.present? - if @closed_by_merge_requests.present?
%li %li
= render partial: 'projects/issues/closed_by_box', locals: {merge_request_count: @merge_requests.count} = render partial: 'projects/issues/closed_by_box', locals: {merge_request_count: @merge_requests.count}
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
%li %li
- sha = @project.repository.find_branch(branch).target - sha = @project.repository.find_branch(branch).target
- pipeline = @project.pipeline(sha, branch) if sha - pipeline = @project.pipeline(sha, branch) if sha
- if ci_copipelinemmit - if pipeline
%span.related-branch-ci-status %span.related-branch-ci-status
= render_pipeline_status(pipeline) = render_pipeline_status(pipeline)
%span.related-branch-info %span.related-branch-info
......
...@@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear ...@@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.link href: namespace_project_issues_url(@project.namespace, @project, format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml" xml.link href: namespace_project_issues_url(@project.namespace, @project, format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml"
xml.link href: namespace_project_issues_url(@project.namespace, @project), rel: "alternate", type: "text/html" xml.link href: namespace_project_issues_url(@project.namespace, @project), rel: "alternate", type: "text/html"
xml.id namespace_project_issues_url(@project.namespace, @project) xml.id namespace_project_issues_url(@project.namespace, @project)
xml.updated @issues.first.created_at.xmlschema if @issues.any? xml.updated @issues.first.created_at.xmlschema if @issues.reorder(nil).any?
xml << render(partial: 'issues/issue', collection: @issues) if @issues.any? xml << render(partial: 'issues/issue', collection: @issues) if @issues.reorder(nil).any?
end end
...@@ -5,18 +5,15 @@ ...@@ -5,18 +5,15 @@
= link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do
%span %span
Pipelines Pipelines
%span.badge.count.ci_counter= number_with_delimiter(@project.pipelines.running_or_pending.count)
- if project_nav_tab? :builds - if project_nav_tab? :builds
= nav_link(controller: %w(builds)) do = nav_link(controller: %w(builds)) do
= link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do = link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do
%span %span
Builds Builds
%span.badge.count.builds_counter= number_with_delimiter(@project.running_or_pending_build_count)
- if project_nav_tab? :environments - if project_nav_tab? :environments
= nav_link(controller: %w(environments)) do = nav_link(controller: %w(environments)) do
= link_to project_environments_path(@project), title: 'Environments', class: 'shortcuts-environments' do = link_to project_environments_path(@project), title: 'Environments', class: 'shortcuts-environments' do
%span %span
Environments Environments
%span.badge.count.environments_counter= number_with_delimiter(@project.environments.count)
- if @merge_requests.any? - if @merge_requests.reorder(nil).any?
- @merge_requests.group_by(&:target_project).each do |group| - @merge_requests.group_by(&:target_project).each do |group|
.panel.panel-default.panel-small .panel.panel-default.panel-small
- project = group[0] - project = group[0]
......
...@@ -35,13 +35,13 @@ ...@@ -35,13 +35,13 @@
.clearfix .clearfix
.error-alert .error-alert
- if issuable.is_a?(Issue) && !issuable.project.private? - if issuable.is_a?(Issue)
.form-group .form-group
.col-sm-offset-2.col-sm-10 .col-sm-offset-2.col-sm-10
.checkbox .checkbox
= f.label :confidential do = f.label :confidential do
= f.check_box :confidential = f.check_box :confidential
This issue is confidential and should only be visible to team members This issue is confidential and should only be visible to team members with at least Reporter access.
- if can?(current_user, :"admin_#{issuable.to_ability_name}", issuable.project) - if can?(current_user, :"admin_#{issuable.to_ability_name}", issuable.project)
- has_due_date = issuable.has_attribute?(:due_date) - has_due_date = issuable.has_attribute?(:due_date)
......
...@@ -12,7 +12,7 @@ Doorkeeper.configure do ...@@ -12,7 +12,7 @@ Doorkeeper.configure do
end end
resource_owner_from_credentials do |routes| resource_owner_from_credentials do |routes|
Gitlab::Auth.find_in_gitlab_or_ldap(params[:username], params[:password]) Gitlab::Auth.find_with_user_password(params[:username], params[:password])
end end
# If you want to restrict access to the web interface for adding oauth authorized applications, you need to declare the block below. # If you want to restrict access to the web interface for adding oauth authorized applications, you need to declare the block below.
......
...@@ -96,13 +96,18 @@ if Gitlab::Metrics.enabled? ...@@ -96,13 +96,18 @@ if Gitlab::Metrics.enabled?
config.instrument_instance_methods(const) config.instrument_instance_methods(const)
end end
# Instruments all Banzai filters # Instruments all Banzai filters and reference parsers
Dir[Rails.root.join('lib', 'banzai', 'filter', '*.rb')].each do |file| {
klass = File.basename(file, File.extname(file)).camelize Filter: Rails.root.join('lib', 'banzai', 'filter', '*.rb'),
const = Banzai::Filter.const_get(klass) ReferenceParser: Rails.root.join('lib', 'banzai', 'reference_parser', '*.rb')
}.each do |const_name, path|
Dir[path].each do |file|
klass = File.basename(file, File.extname(file)).camelize
const = Banzai.const_get(const_name).const_get(klass)
config.instrument_methods(const) config.instrument_methods(const)
config.instrument_instance_methods(const) config.instrument_instance_methods(const)
end
end end
config.instrument_methods(Banzai::Renderer) config.instrument_methods(Banzai::Renderer)
......
class RemoveNotificationSettingNotNullConstraints < ActiveRecord::Migration
def up
change_column :notification_settings, :source_type, :string, null: true
change_column :notification_settings, :source_id, :integer, null: true
end
def down
change_column :notification_settings, :source_type, :string, null: false
change_column :notification_settings, :source_id, :integer, null: false
end
end
class RemoveDeprecatedIssuesTrackerColumnsFromProjects < ActiveRecord::Migration
def change
remove_column :projects, :issues_tracker, :string, default: 'gitlab', null: false
remove_column :projects, :issues_tracker_id, :string
end
end
class MigrateUsersNotificationLevel < ActiveRecord::Migration
# Migrates only users who changed their default notification level :participating
# creating a new record on notification settings table
def up
execute(%Q{
INSERT INTO notification_settings
(user_id, level, created_at, updated_at)
(SELECT id, notification_level, created_at, updated_at FROM users WHERE notification_level != 1)
})
end
# Migrates from notification settings back to user notification_level
# If no value is found the default level of 1 will be used
def down
execute(%Q{
UPDATE users u SET
notification_level = COALESCE((SELECT level FROM notification_settings WHERE user_id = u.id AND source_type IS NULL), 1)
})
end
end
class RemoveNotificationLevelFromUsers < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
def change
remove_column :users, :notification_level, :integer
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20160610211845) do ActiveRecord::Schema.define(version: 20160610301627) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -699,8 +699,8 @@ ActiveRecord::Schema.define(version: 20160610211845) do ...@@ -699,8 +699,8 @@ ActiveRecord::Schema.define(version: 20160610211845) do
create_table "notification_settings", force: :cascade do |t| create_table "notification_settings", force: :cascade do |t|
t.integer "user_id", null: false t.integer "user_id", null: false
t.integer "source_id", null: false t.integer "source_id"
t.string "source_type", null: false t.string "source_type"
t.integer "level", default: 0, null: false t.integer "level", default: 0, null: false
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
...@@ -780,8 +780,6 @@ ActiveRecord::Schema.define(version: 20160610211845) do ...@@ -780,8 +780,6 @@ ActiveRecord::Schema.define(version: 20160610211845) do
t.boolean "merge_requests_enabled", default: true, null: false t.boolean "merge_requests_enabled", default: true, null: false
t.boolean "wiki_enabled", default: true, null: false t.boolean "wiki_enabled", default: true, null: false
t.integer "namespace_id" t.integer "namespace_id"
t.string "issues_tracker", default: "gitlab", null: false
t.string "issues_tracker_id"
t.boolean "snippets_enabled", default: true, null: false t.boolean "snippets_enabled", default: true, null: false
t.datetime "last_activity_at" t.datetime "last_activity_at"
t.string "import_url" t.string "import_url"
...@@ -1017,7 +1015,6 @@ ActiveRecord::Schema.define(version: 20160610211845) do ...@@ -1017,7 +1015,6 @@ ActiveRecord::Schema.define(version: 20160610211845) do
t.boolean "can_create_team", default: true, null: false t.boolean "can_create_team", default: true, null: false
t.string "state" t.string "state"
t.integer "color_scheme_id", default: 1, null: false t.integer "color_scheme_id", default: 1, null: false
t.integer "notification_level", default: 1, null: false
t.datetime "password_expires_at" t.datetime "password_expires_at"
t.integer "created_by_id" t.integer "created_by_id"
t.datetime "last_credential_check_at" t.datetime "last_credential_check_at"
......
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
- [Integration](integration/README.md) How to integrate with systems such as JIRA, Redmine, Twitter. - [Integration](integration/README.md) How to integrate with systems such as JIRA, Redmine, Twitter.
- [Issue closing](customization/issue_closing.md) Customize how to close an issue from commit messages. - [Issue closing](customization/issue_closing.md) Customize how to close an issue from commit messages.
- [Libravatar](customization/libravatar.md) Use Libravatar for user avatars. - [Libravatar](customization/libravatar.md) Use Libravatar for user avatars.
- [Log system](logs/logs.md) Log system. - [Log system](administration/logs.md) Log system.
- [Environment Variables](administration/environment_variables.md) to configure GitLab. - [Environment Variables](administration/environment_variables.md) to configure GitLab.
- [Operations](operations/README.md) Keeping GitLab up and running - [Operations](operations/README.md) Keeping GitLab up and running
- [Raketasks](raketasks/README.md) Backups, maintenance, automatic webhook setup and the importing of projects. - [Raketasks](raketasks/README.md) Backups, maintenance, automatic webhook setup and the importing of projects.
......
## Log system
GitLab has an advanced log system where everything is logged so that you
can analyze your instance using various system log files. In addition to
system log files, GitLab Enterprise Edition comes with Audit Events.
Find more about them [in Audit Events
documentation](http://docs.gitlab.com/ee/administration/audit_events.html)
System log files are typically plain text in a standard log file format.
This guide talks about how to read and use these system log files.
### production.log
This file lives in `/var/log/gitlab/gitlab-rails/production.log` for
omnibus package or in `/home/git/gitlab/log/production.log` for
installations from source.
It contains information about all performed requests. You can see the
URL and type of request, IP address and what exactly parts of code were
involved to service this particular request. Also you can see all SQL
request that have been performed and how much time it took. This task is
more useful for GitLab contributors and developers. Use part of this log
file when you are going to report bug. For example:
```
Started GET "/gitlabhq/yaml_db/tree/master" for 168.111.56.1 at 2015-02-12 19:34:53 +0200
Processing by Projects::TreeController#show as HTML
Parameters: {"project_id"=>"gitlabhq/yaml_db", "id"=>"master"}
... [CUT OUT]
Namespaces"."created_at" DESC, "namespaces"."id" DESC LIMIT 1 [["id", 26]]
CACHE (0.0ms) SELECT "members".* FROM "members" WHERE "members"."source_type" = 'Project' AND "members"."type" IN ('ProjectMember') AND "members"."source_id" = $1 AND "members"."source_type" = $2 AND "members"."user_id" = 1 ORDER BY "members"."created_at" DESC, "members"."id" DESC LIMIT 1 [["source_id", 18], ["source_type", "Project"]]
CACHE (0.0ms) SELECT "members".* FROM "members" WHERE "members"."source_type" = 'Project' AND "members".
(1.4ms) SELECT COUNT(*) FROM "merge_requests" WHERE "merge_requests"."target_project_id" = $1 AND ("merge_requests"."state" IN ('opened','reopened')) [["target_project_id", 18]]
Rendered layouts/nav/_project.html.haml (28.0ms)
Rendered layouts/_collapse_button.html.haml (0.2ms)
Rendered layouts/_flash.html.haml (0.1ms)
Rendered layouts/_page.html.haml (32.9ms)
Completed 200 OK in 166ms (Views: 117.4ms | ActiveRecord: 27.2ms)
```
In this example we can see that server processed an HTTP request with URL
`/gitlabhq/yaml_db/tree/master` from IP 168.111.56.1 at 2015-02-12
19:34:53 +0200. Also we can see that request was processed by
`Projects::TreeController`.
### application.log
This file lives in `/var/log/gitlab/gitlab-rails/application.log` for
omnibus package or in `/home/git/gitlab/log/application.log` for
installations from source.
It helps you discover events happening in your instance such as user creation,
project removing and so on. For example:
```
October 06, 2014 11:56: User "Administrator" (admin@example.com) was created
October 06, 2014 11:56: Documentcloud created a new project "Documentcloud / Underscore"
October 06, 2014 11:56: Gitlab Org created a new project "Gitlab Org / Gitlab Ce"
October 07, 2014 11:25: User "Claudie Hodkiewicz" (nasir_stehr@olson.co.uk) was removed
October 07, 2014 11:25: Project "project133" was removed
```
### githost.log
This file lives in `/var/log/gitlab/gitlab-rails/githost.log` for
omnibus package or in `/home/git/gitlab/log/githost.log` for
installations from source.
GitLab has to interact with Git repositories but in some rare cases
something can go wrong and in this case you will know what exactly
happened. This log file contains all failed requests from GitLab to Git
repositories. In the majority of cases this file will be useful for developers
only. For example:
```
December 03, 2014 13:20 -> ERROR -> Command failed [1]: /usr/bin/git --git-dir=/Users/vsizov/gitlab-development-kit/gitlab/tmp/tests/gitlab-satellites/group184/gitlabhq/.git --work-tree=/Users/vsizov/gitlab-development-kit/gitlab/tmp/tests/gitlab-satellites/group184/gitlabhq merge --no-ff -mMerge branch 'feature_conflict' into 'feature' source/feature_conflict
error: failed to push some refs to '/Users/vsizov/gitlab-development-kit/repositories/gitlabhq/gitlab_git.git'
```
### sidekiq.log
This file lives in `/var/log/gitlab/gitlab-rails/sidekiq.log` for
omnibus package or in `/home/git/gitlab/log/sidekiq.log` for
installations from source.
GitLab uses background jobs for processing tasks which can take a long
time. All information about processing these jobs are written down to
this file. For example:
```
2014-06-10T07:55:20Z 2037 TID-tm504 ERROR: /opt/bitnami/apps/discourse/htdocs/vendor/bundle/ruby/1.9.1/gems/redis-3.0.7/lib/redis/client.rb:228:in `read'
2014-06-10T18:18:26Z 14299 TID-55uqo INFO: Booting Sidekiq 3.0.0 with redis options {:url=>"redis://localhost:6379/0", :namespace=>"sidekiq"}
```
### gitlab-shell.log
This file lives in `/var/log/gitlab/gitlab-shell/gitlab-shell.log` for
omnibus package or in `/home/git/gitlab-shell/gitlab-shell.log` for
installations from source.
GitLab shell is used by Gitlab for executing Git commands and provide
SSH access to Git repositories. For example:
```
I, [2015-02-13T06:17:00.671315 #9291] INFO -- : Adding project root/example.git at </var/opt/gitlab/git-data/repositories/root/dcdcdcdcd.git>.
I, [2015-02-13T06:17:00.679433 #9291] INFO -- : Moving existing hooks directory and symlinking global hooks directory for /var/opt/gitlab/git-data/repositories/root/example.git.
```
### unicorn\_stderr.log
This file lives in `/var/log/gitlab/unicorn/unicorn_stderr.log` for
omnibus package or in `/home/git/gitlab/log/unicorn_stderr.log` for
installations from source.
Unicorn is a high-performance forking Web server which is used for
serving the GitLab application. You can look at this log if, for
example, your application does not respond. This log contains all
information about the state of unicorn processes at any given time.
```
I, [2015-02-13T06:14:46.680381 #9047] INFO -- : Refreshing Gem list
I, [2015-02-13T06:14:56.931002 #9047] INFO -- : listening on addr=127.0.0.1:8080 fd=12
I, [2015-02-13T06:14:56.931381 #9047] INFO -- : listening on addr=/var/opt/gitlab/gitlab-rails/sockets/gitlab.socket fd=13
I, [2015-02-13T06:14:56.936638 #9047] INFO -- : master process ready
I, [2015-02-13T06:14:56.946504 #9092] INFO -- : worker=0 spawned pid=9092
I, [2015-02-13T06:14:56.946943 #9092] INFO -- : worker=0 ready
I, [2015-02-13T06:14:56.947892 #9094] INFO -- : worker=1 spawned pid=9094
I, [2015-02-13T06:14:56.948181 #9094] INFO -- : worker=1 ready
W, [2015-02-13T07:16:01.312916 #9094] WARN -- : #<Unicorn::HttpServer:0x0000000208f618>: worker (pid: 9094) exceeds memory limit (320626688 bytes > 247066940 bytes)
W, [2015-02-13T07:16:01.313000 #9094] WARN -- : Unicorn::WorkerKiller send SIGQUIT (pid: 9094) alive: 3621 sec (trial 1)
I, [2015-02-13T07:16:01.530733 #9047] INFO -- : reaped #<Process::Status: pid 9094 exit 0> worker=1
I, [2015-02-13T07:16:01.534501 #13379] INFO -- : worker=1 spawned pid=13379
I, [2015-02-13T07:16:01.534848 #13379] INFO -- : worker=1 ready
```
...@@ -572,7 +572,7 @@ GET /projects/:id/merge_requests/:merge_request_id/closes_issues ...@@ -572,7 +572,7 @@ GET /projects/:id/merge_requests/:merge_request_id/closes_issues
curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/76/merge_requests/1/closes_issues curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/76/merge_requests/1/closes_issues
``` ```
Example response: Example response when the GitLab issue tracker is used:
```json ```json
[ [
...@@ -618,6 +618,17 @@ Example response: ...@@ -618,6 +618,17 @@ Example response:
] ]
``` ```
Example response when an external issue tracker (e.g. JIRA) is used:
```json
[
{
"id" : "PROJECT-123",
"title" : "Title of this issue"
}
]
```
## Subscribe to a merge request ## Subscribe to a merge request
Subscribes the authenticated user to a merge request to receive notification. If Subscribes the authenticated user to a merge request to receive notification. If
......
...@@ -30,6 +30,7 @@ If you want a quick introduction to GitLab CI, follow our ...@@ -30,6 +30,7 @@ If you want a quick introduction to GitLab CI, follow our
- [when](#when) - [when](#when)
- [artifacts](#artifacts) - [artifacts](#artifacts)
- [artifacts:name](#artifacts-name) - [artifacts:name](#artifacts-name)
- [artifacts:when](#artifacts-when)
- [dependencies](#dependencies) - [dependencies](#dependencies)
- [before_script and after_script](#before_script-and-after_script) - [before_script and after_script](#before_script-and-after_script)
- [Hidden jobs](#hidden-jobs) - [Hidden jobs](#hidden-jobs)
...@@ -651,6 +652,32 @@ job: ...@@ -651,6 +652,32 @@ job:
untracked: true untracked: true
``` ```
#### artifacts:when
>**Note:**
Introduced in GitLab 8.9 and GitLab Runner v1.3.0.
`artifacts:when` is used to upload artifacts on build failure or despite the
failure.
`artifacts:when` can be set to one of the following values:
1. `on_success` - upload artifacts only when build succeeds. This is the default
1. `on_failure` - upload artifacts only when build fails
1. `always` - upload artifacts despite the build status
---
**Example configurations**
To upload artifacts only when build fails.
```yaml
job:
artifacts:
when: on_failure
```
### dependencies ### dependencies
>**Note:** >**Note:**
......
...@@ -103,14 +103,14 @@ Inside the document: ...@@ -103,14 +103,14 @@ Inside the document:
- Every piece of documentation that comes with a new feature should declare the - Every piece of documentation that comes with a new feature should declare the
GitLab version that feature got introduced. Right below the heading add a GitLab version that feature got introduced. Right below the heading add a
note: `_**Note:** This feature was introduced in GitLab 8.3_` note: `>**Note:** This feature was introduced in GitLab 8.3`
- If possible every feature should have a link to the MR that introduced it. - If possible every feature should have a link to the MR that introduced it.
The above note would be then transformed to: The above note would be then transformed to:
`_**Note:** This feature was [introduced][ce-1242] in GitLab 8.3_`, where `>**Note:** This feature was [introduced][ce-1242] in GitLab 8.3`, where
the [link identifier](#links) is named after the repository (CE) and the MR the [link identifier](#links) is named after the repository (CE) and the MR
number number
- If the feature is only in GitLab EE, don't forget to mention it, like: - If the feature is only in GitLab EE, don't forget to mention it, like:
`_**Note:** This feature was introduced in GitLab EE 8.3_`. Otherwise, leave `>**Note:** This feature was introduced in GitLab EE 8.3`. Otherwise, leave
this mention out this mention out
## References ## References
...@@ -141,6 +141,48 @@ Inside the document: ...@@ -141,6 +141,48 @@ Inside the document:
[ruby-dl]: https://www.ruby-lang.org/en/downloads/ "Ruby download website" [ruby-dl]: https://www.ruby-lang.org/en/downloads/ "Ruby download website"
## Changing document location
Changing a document's location is not to be taken lightly. Remember that the
documentation is available to all installations under `help/` and not only to
GitLab.com or http://docs.gitlab.com. Make sure this is discussed with the
Documentation team beforehand.
If you indeed need to change a document's location, do NOT remove the old
document, but rather put a text in it that points to the new location, like:
```
This document was moved to [path/to/new_doc.md](path/to/new_doc.md).
```
where `path/to/new_doc.md` is the relative path to the root directory `doc/`.
---
For example, if you were to move `doc/workflow/lfs/lfs_administration.md` to
`doc/administration/lfs.md`, then the steps would be:
1. Copy `doc/workflow/lfs/lfs_administration.md` to `doc/administration/lfs.md`
1. Replace the contents of `doc/workflow/lfs/lfs_administration.md` with:
```
This document was moved to [administration/lfs.md](../../administration/lfs.md).
```
1. Find and replace any occurrences of the old location with the new one.
A quick way to find them is to use `grep`:
```
grep -nR "lfs_administration.md" doc/
```
The above command will search in the `doc/` directory for
`lfs_administration.md` recursively and will print the file and the line
where this file is mentioned. Note that we used just the filename
(`lfs_administration.md`) and not the whole the relative path
(`workflow/lfs/lfs_administration.md`).
## API ## API
Here is a list of must-have items. Use them in the exact order that appears Here is a list of must-have items. Use them in the exact order that appears
...@@ -222,8 +264,8 @@ curl --data "name=foo" -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab. ...@@ -222,8 +264,8 @@ curl --data "name=foo" -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.
#### Post data using JSON content #### Post data using JSON content
_**Note:** In this example we create a new group. Watch carefully the single > **Note:** In this example we create a new group. Watch carefully the single
and double quotes._ and double quotes.
```bash ```bash
curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" -H "Content-Type: application/json" --data '{"path": "my-group", "name": "My group"}' https://gitlab.example.com/api/v3/groups curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" -H "Content-Type: application/json" --data '{"path": "my-group", "name": "My group"}' https://gitlab.example.com/api/v3/groups
......
## Log system This document was moved to [administration/logs.md](../administration/logs.md).
GitLab has an advanced log system where everything is logged so that you can analyze your instance using various system log files.
In addition to system log files, GitLab Enterprise Edition comes with Audit Events. Find more about them [in Audit Events documentation](http://docs.gitlab.com/ee/administration/audit_events.html)
System log files are typically plain text in a standard log file format. This guide talks about how to read and use these system log files.
#### production.log
This file lives in `/var/log/gitlab/gitlab-rails/production.log` for omnibus package or in `/home/git/gitlab/log/production.log` for installations from the source.
This file contains information about all performed requests. You can see url and type of request, IP address and what exactly parts of code were involved to service this particular request. Also you can see all SQL request that have been performed and how much time it took.
This task is more useful for GitLab contributors and developers. Use part of this log file when you are going to report bug.
```
Started GET "/gitlabhq/yaml_db/tree/master" for 168.111.56.1 at 2015-02-12 19:34:53 +0200
Processing by Projects::TreeController#show as HTML
Parameters: {"project_id"=>"gitlabhq/yaml_db", "id"=>"master"}
... [CUT OUT]
amespaces"."created_at" DESC, "namespaces"."id" DESC LIMIT 1 [["id", 26]]
CACHE (0.0ms) SELECT "members".* FROM "members" WHERE "members"."source_type" = 'Project' AND "members"."type" IN ('ProjectMember') AND "members"."source_id" = $1 AND "members"."source_type" = $2 AND "members"."user_id" = 1 ORDER BY "members"."created_at" DESC, "members"."id" DESC LIMIT 1 [["source_id", 18], ["source_type", "Project"]]
CACHE (0.0ms) SELECT "members".* FROM "members" WHERE "members"."source_type" = 'Project' AND "members".
 (1.4ms) SELECT COUNT(*) FROM "merge_requests" WHERE "merge_requests"."target_project_id" = $1 AND ("merge_requests"."state" IN ('opened','reopened')) [["target_project_id", 18]]
Rendered layouts/nav/_project.html.haml (28.0ms)
Rendered layouts/_collapse_button.html.haml (0.2ms)
Rendered layouts/_flash.html.haml (0.1ms)
Rendered layouts/_page.html.haml (32.9ms)
Completed 200 OK in 166ms (Views: 117.4ms | ActiveRecord: 27.2ms)
```
In this example we can see that server processed HTTP request with url `/gitlabhq/yaml_db/tree/master` from IP 168.111.56.1 at 2015-02-12 19:34:53 +0200. Also we can see that request was processed by Projects::TreeController.
#### application.log
This file lives in `/var/log/gitlab/gitlab-rails/application.log` for omnibus package or in `/home/git/gitlab/log/application.log` for installations from the source.
This log file helps you discover events happening in your instance such as user creation, project removing and so on.
```
October 06, 2014 11:56: User "Administrator" (admin@example.com) was created
October 06, 2014 11:56: Documentcloud created a new project "Documentcloud / Underscore"
October 06, 2014 11:56: Gitlab Org created a new project "Gitlab Org / Gitlab Ce"
October 07, 2014 11:25: User "Claudie Hodkiewicz" (nasir_stehr@olson.co.uk) was removed
October 07, 2014 11:25: Project "project133" was removed
```
#### githost.log
This file lives in `/var/log/gitlab/gitlab-rails/githost.log` for omnibus package or in `/home/git/gitlab/log/githost.log` for installations from the source.
The GitLab has to interact with git repositories but in some rare cases something can go wrong and in this case you will know what exactly happened. This log file contains all failed requests from GitLab to git repository. In majority of cases this file will be useful for developers only.
```
December 03, 2014 13:20 -> ERROR -> Command failed [1]: /usr/bin/git --git-dir=/Users/vsizov/gitlab-development-kit/gitlab/tmp/tests/gitlab-satellites/group184/gitlabhq/.git --work-tree=/Users/vsizov/gitlab-development-kit/gitlab/tmp/tests/gitlab-satellites/group184/gitlabhq merge --no-ff -mMerge branch 'feature_conflict' into 'feature' source/feature_conflict
error: failed to push some refs to '/Users/vsizov/gitlab-development-kit/repositories/gitlabhq/gitlab_git.git'
```
#### sidekiq.log
This file lives in `/var/log/gitlab/gitlab-rails/sidekiq.log` for omnibus package or in `/home/git/gitlab/log/sidekiq.log` for installations from the source.
GitLab uses background jobs for processing tasks which can take a long time. All information about processing these jobs are writing down to this file.
```
2014-06-10T07:55:20Z 2037 TID-tm504 ERROR: /opt/bitnami/apps/discourse/htdocs/vendor/bundle/ruby/1.9.1/gems/redis-3.0.7/lib/redis/client.rb:228:in `read'
2014-06-10T18:18:26Z 14299 TID-55uqo INFO: Booting Sidekiq 3.0.0 with redis options {:url=>"redis://localhost:6379/0", :namespace=>"sidekiq"}
```
#### gitlab-shell.log
This file lives in `/var/log/gitlab/gitlab-shell/gitlab-shell.log` for omnibus package or in `/home/git/gitlab-shell/gitlab-shell.log` for installations from the source.
gitlab-shell is using by Gitlab for executing git commands and provide ssh access to git repositories.
```
I, [2015-02-13T06:17:00.671315 #9291] INFO -- : Adding project root/example.git at </var/opt/gitlab/git-data/repositories/root/dcdcdcdcd.git>.
I, [2015-02-13T06:17:00.679433 #9291] INFO -- : Moving existing hooks directory and symlinking global hooks directory for /var/opt/gitlab/git-data/repositories/root/example.git.
```
#### unicorn_stderr.log
This file lives in `/var/log/gitlab/unicorn/unicorn_stderr.log` for omnibus package or in `/home/git/gitlab/log/unicorn_stderr.log` for installations from the source.
Unicorn is a high-performance forking Web server which is used for serving the GitLab application. You can look at this log if, for example, your application does not respond. This log contains all information about the state of unicorn processes at any given time.
```
I, [2015-02-13T06:14:46.680381 #9047] INFO -- : Refreshing Gem list
I, [2015-02-13T06:14:56.931002 #9047] INFO -- : listening on addr=127.0.0.1:8080 fd=12
I, [2015-02-13T06:14:56.931381 #9047] INFO -- : listening on addr=/var/opt/gitlab/gitlab-rails/sockets/gitlab.socket fd=13
I, [2015-02-13T06:14:56.936638 #9047] INFO -- : master process ready
I, [2015-02-13T06:14:56.946504 #9092] INFO -- : worker=0 spawned pid=9092
I, [2015-02-13T06:14:56.946943 #9092] INFO -- : worker=0 ready
I, [2015-02-13T06:14:56.947892 #9094] INFO -- : worker=1 spawned pid=9094
I, [2015-02-13T06:14:56.948181 #9094] INFO -- : worker=1 ready
W, [2015-02-13T07:16:01.312916 #9094] WARN -- : #<Unicorn::HttpServer:0x0000000208f618>: worker (pid: 9094) exceeds memory limit (320626688 bytes > 247066940 bytes)
W, [2015-02-13T07:16:01.313000 #9094] WARN -- : Unicorn::WorkerKiller send SIGQUIT (pid: 9094) alive: 3621 sec (trial 1)
I, [2015-02-13T07:16:01.530733 #9047] INFO -- : reaped #<Process::Status: pid 9094 exit 0> worker=1
I, [2015-02-13T07:16:01.534501 #13379] INFO -- : worker=1 spawned pid=13379
I, [2015-02-13T07:16:01.534848 #13379] INFO -- : worker=1 ready
```
...@@ -155,6 +155,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps ...@@ -155,6 +155,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
end end
step 'I click on my profile picture' do step 'I click on my profile picture' do
find(:css, '.side-nav-toggle').click
find(:css, '.sidebar-user').click find(:css, '.sidebar-user').click
end end
......
...@@ -179,6 +179,11 @@ module API ...@@ -179,6 +179,11 @@ module API
expose :upvotes, :downvotes expose :upvotes, :downvotes
end end
class ExternalIssue < Grape::Entity
expose :title
expose :id
end
class MergeRequest < ProjectEntity class MergeRequest < ProjectEntity
expose :target_branch, :source_branch expose :target_branch, :source_branch
expose :upvotes, :downvotes expose :upvotes, :downvotes
......
...@@ -418,5 +418,13 @@ module API ...@@ -418,5 +418,13 @@ module API
def send_git_archive(repository, ref:, format:) def send_git_archive(repository, ref:, format:)
header(*Gitlab::Workhorse.send_git_archive(repository, ref: ref, format: format)) header(*Gitlab::Workhorse.send_git_archive(repository, ref: ref, format: format))
end end
def issue_entity(project)
if project.has_external_issue_tracker?
Entities::ExternalIssue
else
Entities::Issue
end
end
end end
end end
...@@ -329,7 +329,7 @@ module API ...@@ -329,7 +329,7 @@ module API
get "#{path}/closes_issues" do get "#{path}/closes_issues" do
merge_request = user_project.merge_requests.find(params[:merge_request_id]) merge_request = user_project.merge_requests.find(params[:merge_request_id])
issues = ::Kaminari.paginate_array(merge_request.closes_issues(current_user)) issues = ::Kaminari.paginate_array(merge_request.closes_issues(current_user))
present paginate(issues), with: Entities::Issue, current_user: current_user present paginate(issues), with: issue_entity(user_project), current_user: current_user
end end
end end
end end
......
...@@ -11,7 +11,7 @@ module API ...@@ -11,7 +11,7 @@ module API
# Example Request: # Example Request:
# POST /session # POST /session
post "/session" do post "/session" do
user = Gitlab::Auth.find_in_gitlab_or_ldap(params[:email] || params[:login], params[:password]) user = Gitlab::Auth.find_with_user_password(params[:email] || params[:login], params[:password])
return unauthorized! unless user return unauthorized! unless user
present user, with: Entities::UserLogin present user, with: Entities::UserLogin
......
module Banzai module Banzai
module Pipeline module Pipeline
class DescriptionPipeline < FullPipeline class DescriptionPipeline < FullPipeline
WHITELIST = Banzai::Filter::SanitizationFilter::LIMITED.deep_dup.merge(
elements: Banzai::Filter::SanitizationFilter::LIMITED[:elements] - %w(pre code img ol ul li)
)
def self.transform_context(context) def self.transform_context(context)
super(context).merge( super(context).merge(
# SanitizationFilter # SanitizationFilter
whitelist: whitelist whitelist: WHITELIST
) )
end end
private
def self.whitelist
# Descriptions are more heavily sanitized, allowing only a few elements.
# See http://git.io/vkuAN
whitelist = Banzai::Filter::SanitizationFilter::LIMITED
whitelist[:elements] -= %w(pre code img ol ul li)
whitelist
end
end end
end end
end end
...@@ -9,6 +9,8 @@ module Ci ...@@ -9,6 +9,8 @@ module Ci
:allow_failure, :type, :stage, :when, :artifacts, :cache, :allow_failure, :type, :stage, :when, :artifacts, :cache,
:dependencies, :before_script, :after_script, :variables, :dependencies, :before_script, :after_script, :variables,
:environment] :environment]
ALLOWED_CACHE_KEYS = [:key, :untracked, :paths]
ALLOWED_ARTIFACTS_KEYS = [:name, :untracked, :paths, :when]
attr_reader :before_script, :after_script, :image, :services, :path, :cache attr_reader :before_script, :after_script, :image, :services, :path, :cache
...@@ -137,6 +139,12 @@ module Ci ...@@ -137,6 +139,12 @@ module Ci
end end
def validate_global_cache! def validate_global_cache!
@cache.keys.each do |key|
unless ALLOWED_CACHE_KEYS.include? key
raise ValidationError, "#{name} cache unknown parameter #{key}"
end
end
if @cache[:key] && !validate_string(@cache[:key]) if @cache[:key] && !validate_string(@cache[:key])
raise ValidationError, "cache:key parameter should be a string" raise ValidationError, "cache:key parameter should be a string"
end end
...@@ -202,7 +210,7 @@ module Ci ...@@ -202,7 +210,7 @@ module Ci
raise ValidationError, "#{name} job: allow_failure parameter should be an boolean" raise ValidationError, "#{name} job: allow_failure parameter should be an boolean"
end end
if job[:when] && !job[:when].in?(%w(on_success on_failure always)) if job[:when] && !job[:when].in?(%w[on_success on_failure always])
raise ValidationError, "#{name} job: when parameter should be on_success, on_failure or always" raise ValidationError, "#{name} job: when parameter should be on_success, on_failure or always"
end end
...@@ -239,6 +247,12 @@ module Ci ...@@ -239,6 +247,12 @@ module Ci
end end
def validate_job_cache!(name, job) def validate_job_cache!(name, job)
job[:cache].keys.each do |key|
unless ALLOWED_CACHE_KEYS.include? key
raise ValidationError, "#{name} job: cache unknown parameter #{key}"
end
end
if job[:cache][:key] && !validate_string(job[:cache][:key]) if job[:cache][:key] && !validate_string(job[:cache][:key])
raise ValidationError, "#{name} job: cache:key parameter should be a string" raise ValidationError, "#{name} job: cache:key parameter should be a string"
end end
...@@ -253,6 +267,12 @@ module Ci ...@@ -253,6 +267,12 @@ module Ci
end end
def validate_job_artifacts!(name, job) def validate_job_artifacts!(name, job)
job[:artifacts].keys.each do |key|
unless ALLOWED_ARTIFACTS_KEYS.include? key
raise ValidationError, "#{name} job: artifacts unknown parameter #{key}"
end
end
if job[:artifacts][:name] && !validate_string(job[:artifacts][:name]) if job[:artifacts][:name] && !validate_string(job[:artifacts][:name])
raise ValidationError, "#{name} job: artifacts:name parameter should be a string" raise ValidationError, "#{name} job: artifacts:name parameter should be a string"
end end
...@@ -264,6 +284,10 @@ module Ci ...@@ -264,6 +284,10 @@ module Ci
if job[:artifacts][:paths] && !validate_array_of_strings(job[:artifacts][:paths]) if job[:artifacts][:paths] && !validate_array_of_strings(job[:artifacts][:paths])
raise ValidationError, "#{name} job: artifacts:paths parameter should be an array of strings" raise ValidationError, "#{name} job: artifacts:paths parameter should be an array of strings"
end end
if job[:artifacts][:when] && !job[:artifacts][:when].in?(%w[on_success on_failure always])
raise ValidationError, "#{name} job: artifacts:when parameter should be on_success, on_failure or always"
end
end end
def validate_job_dependencies!(name, job) def validate_job_dependencies!(name, job)
......
...@@ -3,14 +3,14 @@ module Gitlab ...@@ -3,14 +3,14 @@ module Gitlab
Result = Struct.new(:user, :type) Result = Struct.new(:user, :type)
class << self class << self
def find(login, password, project:, ip:) def find_for_git_client(login, password, project:, ip:)
raise "Must provide an IP for rate limiting" if ip.nil? raise "Must provide an IP for rate limiting" if ip.nil?
result = Result.new result = Result.new
if valid_ci_request?(login, password, project) if valid_ci_request?(login, password, project)
result.type = :ci result.type = :ci
elsif result.user = find_in_gitlab_or_ldap(login, password) elsif result.user = find_with_user_password(login, password)
result.type = :gitlab_or_ldap result.type = :gitlab_or_ldap
elsif result.user = oauth_access_token_check(login, password) elsif result.user = oauth_access_token_check(login, password)
result.type = :oauth result.type = :oauth
...@@ -20,7 +20,7 @@ module Gitlab ...@@ -20,7 +20,7 @@ module Gitlab
result result
end end
def find_in_gitlab_or_ldap(login, password) def find_with_user_password(login, password)
user = User.by_login(login) user = User.by_login(login)
# If no user is found, or it's an LDAP server, try LDAP. # If no user is found, or it's an LDAP server, try LDAP.
......
...@@ -95,7 +95,7 @@ module Grack ...@@ -95,7 +95,7 @@ module Grack
end end
def authenticate_user(login, password) def authenticate_user(login, password)
user = Gitlab::Auth.find_in_gitlab_or_ldap(login, password) user = Gitlab::Auth.find_with_user_password(login, password)
unless user unless user
user = oauth_access_token_check(login, password) user = oauth_access_token_check(login, password)
......
...@@ -31,8 +31,6 @@ module Gitlab ...@@ -31,8 +31,6 @@ module Gitlab
# Any data inserted while running this method (or after it has finished # Any data inserted while running this method (or after it has finished
# running) is _not_ updated automatically. # running) is _not_ updated automatically.
# #
# This method _only_ updates rows where the column's value is set to NULL.
#
# table - The name of the table. # table - The name of the table.
# column - The name of the column to update. # column - The name of the column to update.
# value - The value for the column. # value - The value for the column.
...@@ -55,10 +53,10 @@ module Gitlab ...@@ -55,10 +53,10 @@ module Gitlab
first['count']. first['count'].
to_i to_i
# Update in batches of 5% # Update in batches of 5% until we run out of any rows to update.
batch_size = ((total / 100.0) * 5.0).ceil batch_size = ((total / 100.0) * 5.0).ceil
while processed < total loop do
start_row = exec_query(%Q{ start_row = exec_query(%Q{
SELECT id SELECT id
FROM #{quoted_table} FROM #{quoted_table}
...@@ -66,6 +64,9 @@ module Gitlab ...@@ -66,6 +64,9 @@ module Gitlab
LIMIT 1 OFFSET #{processed} LIMIT 1 OFFSET #{processed}
}).to_hash.first }).to_hash.first
# There are no more rows to process
break unless start_row
stop_row = exec_query(%Q{ stop_row = exec_query(%Q{
SELECT id SELECT id
FROM #{quoted_table} FROM #{quoted_table}
...@@ -126,6 +127,8 @@ module Gitlab ...@@ -126,6 +127,8 @@ module Gitlab
begin begin
transaction do transaction do
update_column_in_batches(table, column, default) update_column_in_batches(table, column, default)
change_column_null(table, column, false) unless allow_null
end end
# We want to rescue _all_ exceptions here, even those that don't inherit # We want to rescue _all_ exceptions here, even those that don't inherit
# from StandardError. # from StandardError.
...@@ -134,8 +137,6 @@ module Gitlab ...@@ -134,8 +137,6 @@ module Gitlab
raise error raise error
end end
change_column_null(table, column, false) unless allow_null
end end
end end
end end
......
...@@ -12,23 +12,45 @@ module Gitlab ...@@ -12,23 +12,45 @@ module Gitlab
def scrub(node) def scrub(node)
unless Whitelist::ALLOWED_ELEMENTS.include?(node.name) unless Whitelist::ALLOWED_ELEMENTS.include?(node.name)
node.unlink node.unlink
else return
node.attributes.each do |attr_name, attr| end
valid_attributes = Whitelist::ALLOWED_ATTRIBUTES[node.name]
valid_attributes = Whitelist::ALLOWED_ATTRIBUTES[node.name]
unless valid_attributes && valid_attributes.include?(attr_name) return unless valid_attributes
if Whitelist::ALLOWED_DATA_ATTRIBUTES_IN_ELEMENTS.include?(node.name) &&
attr_name.start_with?('data-') node.attribute_nodes.each do |attr|
# Arbitrary data attributes are allowed. Verify that the attribute attr_name = attribute_name_with_namespace(attr)
# is a valid data attribute.
attr.unlink unless attr_name =~ DATA_ATTR_PATTERN if valid_attributes.include?(attr_name)
else attr.unlink if unsafe_href?(attr)
attr.unlink else
end # Arbitrary data attributes are allowed.
unless allows_data_attribute?(node) && data_attribute?(attr)
attr.unlink
end end
end end
end end
end end
def attribute_name_with_namespace(attr)
if attr.namespace
"#{attr.namespace.prefix}:#{attr.name}"
else
attr.name
end
end
def allows_data_attribute?(node)
Whitelist::ALLOWED_DATA_ATTRIBUTES_IN_ELEMENTS.include?(node.name)
end
def unsafe_href?(attr)
attribute_name_with_namespace(attr) == 'xlink:href' && !attr.value.start_with?('#')
end
def data_attribute?(attr)
attr.name.start_with?('data-') && attr.name =~ DATA_ATTR_PATTERN && attr.namespace.nil?
end
end end
end end
end end
......
...@@ -16,10 +16,10 @@ retry() { ...@@ -16,10 +16,10 @@ retry() {
} }
if [ -f /.dockerenv ] || [ -f ./dockerinit ]; then if [ -f /.dockerenv ] || [ -f ./dockerinit ]; then
mkdir -p vendor mkdir -p vendor/apt
# Install phantomjs package # Install phantomjs package
pushd vendor pushd vendor/apt
if [ ! -e phantomjs_1.9.8-0jessie_amd64.deb ]; then if [ ! -e phantomjs_1.9.8-0jessie_amd64.deb ]; then
wget -q https://gitlab.com/axil/phantomjs-debian/raw/master/phantomjs_1.9.8-0jessie_amd64.deb wget -q https://gitlab.com/axil/phantomjs-debian/raw/master/phantomjs_1.9.8-0jessie_amd64.deb
fi fi
......
...@@ -105,6 +105,15 @@ describe Projects::IssuesController do ...@@ -105,6 +105,15 @@ describe Projects::IssuesController do
expect(assigns(:issues)).to eq [issue] expect(assigns(:issues)).to eq [issue]
end end
it 'should not list confidential issues for project members with guest role' do
sign_in(member)
project.team << [member, :guest]
get_issues
expect(assigns(:issues)).to eq [issue]
end
it 'should list confidential issues for author' do it 'should list confidential issues for author' do
sign_in(author) sign_in(author)
get_issues get_issues
...@@ -148,7 +157,7 @@ describe Projects::IssuesController do ...@@ -148,7 +157,7 @@ describe Projects::IssuesController do
shared_examples_for 'restricted action' do |http_status| shared_examples_for 'restricted action' do |http_status|
it 'returns 404 for guests' do it 'returns 404 for guests' do
sign_out :user sign_out(:user)
go(id: unescaped_parameter_value.to_param) go(id: unescaped_parameter_value.to_param)
expect(response).to have_http_status :not_found expect(response).to have_http_status :not_found
...@@ -161,6 +170,14 @@ describe Projects::IssuesController do ...@@ -161,6 +170,14 @@ describe Projects::IssuesController do
expect(response).to have_http_status :not_found expect(response).to have_http_status :not_found
end end
it 'returns 404 for project members with guest role' do
sign_in(member)
project.team << [member, :guest]
go(id: unescaped_parameter_value.to_param)
expect(response).to have_http_status :not_found
end
it "returns #{http_status[:success]} for author" do it "returns #{http_status[:success]} for author" do
sign_in(author) sign_in(author)
go(id: unescaped_parameter_value.to_param) go(id: unescaped_parameter_value.to_param)
......
...@@ -67,9 +67,6 @@ FactoryGirl.define do ...@@ -67,9 +67,6 @@ FactoryGirl.define do
'new_issue_url' => 'http://redmine/projects/project_name_in_redmine/issues/new' 'new_issue_url' => 'http://redmine/projects/project_name_in_redmine/issues/new'
} }
) )
project.issues_tracker = 'redmine'
project.issues_tracker_id = 'project_name_in_redmine'
end end
end end
...@@ -84,9 +81,6 @@ FactoryGirl.define do ...@@ -84,9 +81,6 @@ FactoryGirl.define do
'new_issue_url' => 'http://jira.example/secure/CreateIssue.jspa' 'new_issue_url' => 'http://jira.example/secure/CreateIssue.jspa'
} }
) )
project.issues_tracker = 'jira'
project.issues_tracker_id = 'project_name_in_jira'
end end
end end
end end
...@@ -5,8 +5,6 @@ describe "Dashboard Issues Feed", feature: true do ...@@ -5,8 +5,6 @@ describe "Dashboard Issues Feed", feature: true do
let!(:user) { create(:user) } let!(:user) { create(:user) }
let!(:project1) { create(:project) } let!(:project1) { create(:project) }
let!(:project2) { create(:project) } let!(:project2) { create(:project) }
let!(:issue1) { create(:issue, author: user, assignee: user, project: project1) }
let!(:issue2) { create(:issue, author: user, assignee: user, project: project2) }
before do before do
project1.team << [user, :master] project1.team << [user, :master]
...@@ -14,16 +12,51 @@ describe "Dashboard Issues Feed", feature: true do ...@@ -14,16 +12,51 @@ describe "Dashboard Issues Feed", feature: true do
end end
describe "atom feed" do describe "atom feed" do
it "should render atom feed via private token" do it "renders atom feed via private token" do
visit issues_dashboard_path(:atom, private_token: user.private_token) visit issues_dashboard_path(:atom, private_token: user.private_token)
expect(response_headers['Content-Type']). expect(response_headers['Content-Type']).to have_content('application/atom+xml')
to have_content('application/atom+xml')
expect(body).to have_selector('title', text: "#{user.name} issues") expect(body).to have_selector('title', text: "#{user.name} issues")
expect(body).to have_selector('author email', text: issue1.author_email) end
expect(body).to have_selector('entry summary', text: issue1.title)
expect(body).to have_selector('author email', text: issue2.author_email) context "issue with basic fields" do
expect(body).to have_selector('entry summary', text: issue2.title) let!(:issue2) { create(:issue, author: user, assignee: user, project: project2, description: 'test desc') }
it "renders issue fields" do
visit issues_dashboard_path(:atom, private_token: user.private_token)
entry = find(:xpath, "//feed/entry[contains(summary/text(),'#{issue2.title}')]")
expect(entry).to be_present
expect(entry).to have_selector('author email', text: issue2.author_email)
expect(entry).to have_selector('assignee email', text: issue2.author_email)
expect(entry).not_to have_selector('labels')
expect(entry).not_to have_selector('milestone')
expect(entry).to have_selector('description', text: issue2.description)
end
end
context "issue with label and milestone" do
let!(:milestone1) { create(:milestone, project: project1, title: 'v1') }
let!(:label1) { create(:label, project: project1, title: 'label1') }
let!(:issue1) { create(:issue, author: user, assignee: user, project: project1, milestone: milestone1) }
before do
issue1.labels << label1
end
it "renders issue label and milestone info" do
visit issues_dashboard_path(:atom, private_token: user.private_token)
entry = find(:xpath, "//feed/entry[contains(summary/text(),'#{issue1.title}')]")
expect(entry).to be_present
expect(entry).to have_selector('author email', text: issue1.author_email)
expect(entry).to have_selector('assignee email', text: issue1.author_email)
expect(entry).to have_selector('labels label', text: label1.title)
expect(entry).to have_selector('milestone', text: milestone1.title)
expect(entry).not_to have_selector('description')
end
end end
end end
end end
......
...@@ -83,6 +83,23 @@ feature 'Issues > Labels bulk assignment', feature: true do ...@@ -83,6 +83,23 @@ feature 'Issues > Labels bulk assignment', feature: true do
end end
end end
context 'can assign a label to all issues when label is present' do
before do
issue2.labels << bug
issue2.labels << feature
visit namespace_project_issues_path(project.namespace, project)
check 'check_all_issues'
open_labels_dropdown ['bug']
update_issues
end
it do
expect(find("#issue_#{issue1.id}")).to have_content 'bug'
expect(find("#issue_#{issue2.id}")).to have_content 'bug'
end
end
context 'can bulk un-assign' do context 'can bulk un-assign' do
context 'all labels to all issues' do context 'all labels to all issues' do
before do before do
......
...@@ -54,7 +54,7 @@ describe 'Profile > Preferences', feature: true do ...@@ -54,7 +54,7 @@ describe 'Profile > Preferences', feature: true do
end end
end end
describe 'User changes their default dashboard' do describe 'User changes their default dashboard', js: true do
it 'creates a flash message' do it 'creates a flash message' do
select 'Starred Projects', from: 'user_dashboard' select 'Starred Projects', from: 'user_dashboard'
click_button 'Save' click_button 'Save'
...@@ -66,8 +66,10 @@ describe 'Profile > Preferences', feature: true do ...@@ -66,8 +66,10 @@ describe 'Profile > Preferences', feature: true do
select 'Starred Projects', from: 'user_dashboard' select 'Starred Projects', from: 'user_dashboard'
click_button 'Save' click_button 'Save'
click_link 'Dashboard' allowing_for_delay do
expect(page.current_path).to eq starred_dashboard_projects_path find('#logo').click
expect(page.current_path).to eq starred_dashboard_projects_path
end
click_link 'Your Projects' click_link 'Your Projects'
expect(page.current_path).to eq dashboard_projects_path expect(page.current_path).to eq dashboard_projects_path
......
...@@ -16,6 +16,7 @@ describe 'Cherry-pick Commits' do ...@@ -16,6 +16,7 @@ describe 'Cherry-pick Commits' do
it do it do
visit namespace_project_commit_path(project.namespace, project, master_pickable_commit.id) visit namespace_project_commit_path(project.namespace, project, master_pickable_commit.id)
find("a[href='#modal-cherry-pick-commit']").click find("a[href='#modal-cherry-pick-commit']").click
expect(page).not_to have_content('v1.0.0') # Only branches, not tags
page.within('#modal-cherry-pick-commit') do page.within('#modal-cherry-pick-commit') do
uncheck 'create_merge_request' uncheck 'create_merge_request'
click_button 'Cherry-pick' click_button 'Cherry-pick'
......
...@@ -7,10 +7,7 @@ describe IssuesHelper do ...@@ -7,10 +7,7 @@ describe IssuesHelper do
describe "url_for_project_issues" do describe "url_for_project_issues" do
let(:project_url) { ext_project.external_issue_tracker.project_url } let(:project_url) { ext_project.external_issue_tracker.project_url }
let(:ext_expected) do let(:ext_expected) { project_url.gsub(':project_id', ext_project.id.to_s) }
project_url.gsub(':project_id', ext_project.id.to_s)
.gsub(':issues_tracker_id', ext_project.issues_tracker_id.to_s)
end
let(:int_expected) { polymorphic_path([@project.namespace, project]) } let(:int_expected) { polymorphic_path([@project.namespace, project]) }
it "should return internal path if used internal tracker" do it "should return internal path if used internal tracker" do
...@@ -56,11 +53,7 @@ describe IssuesHelper do ...@@ -56,11 +53,7 @@ describe IssuesHelper do
describe "url_for_issue" do describe "url_for_issue" do
let(:issues_url) { ext_project.external_issue_tracker.issues_url} let(:issues_url) { ext_project.external_issue_tracker.issues_url}
let(:ext_expected) do let(:ext_expected) { issues_url.gsub(':id', issue.iid.to_s).gsub(':project_id', ext_project.id.to_s) }
issues_url.gsub(':id', issue.iid.to_s)
.gsub(':project_id', ext_project.id.to_s)
.gsub(':issues_tracker_id', ext_project.issues_tracker_id.to_s)
end
let(:int_expected) { polymorphic_path([@project.namespace, project, issue]) } let(:int_expected) { polymorphic_path([@project.namespace, project, issue]) }
it "should return internal path if used internal tracker" do it "should return internal path if used internal tracker" do
...@@ -106,10 +99,7 @@ describe IssuesHelper do ...@@ -106,10 +99,7 @@ describe IssuesHelper do
describe 'url_for_new_issue' do describe 'url_for_new_issue' do
let(:issues_url) { ext_project.external_issue_tracker.new_issue_url } let(:issues_url) { ext_project.external_issue_tracker.new_issue_url }
let(:ext_expected) do let(:ext_expected) { issues_url.gsub(':project_id', ext_project.id.to_s) }
issues_url.gsub(':project_id', ext_project.id.to_s)
.gsub(':issues_tracker_id', ext_project.issues_tracker_id.to_s)
end
let(:int_expected) { new_namespace_project_issue_path(project.namespace, project) } let(:int_expected) { new_namespace_project_issue_path(project.namespace, project) }
it "should return internal path if used internal tracker" do it "should return internal path if used internal tracker" do
......
...@@ -69,6 +69,18 @@ describe Banzai::Filter::RedactorFilter, lib: true do ...@@ -69,6 +69,18 @@ describe Banzai::Filter::RedactorFilter, lib: true do
expect(doc.css('a').length).to eq 0 expect(doc.css('a').length).to eq 0
end end
it 'removes references for project members with guest role' do
member = create(:user)
project = create(:empty_project, :public)
project.team << [member, :guest]
issue = create(:issue, :confidential, project: project)
link = reference_link(project: project.id, issue: issue.id, reference_type: 'issue')
doc = filter(link, current_user: member)
expect(doc.css('a').length).to eq 0
end
it 'allows references for author' do it 'allows references for author' do
author = create(:user) author = create(:user)
project = create(:empty_project, :public) project = create(:empty_project, :public)
......
...@@ -501,6 +501,7 @@ module Ci ...@@ -501,6 +501,7 @@ module Ci
}) })
config_processor = GitlabCiYamlProcessor.new(config, path) config_processor = GitlabCiYamlProcessor.new(config, path)
builds = config_processor.builds_for_stage_and_ref("test", "master") builds = config_processor.builds_for_stage_and_ref("test", "master")
expect(builds.size).to eq(1) expect(builds.size).to eq(1)
expect(builds.first[:when]).to eq(when_state) expect(builds.first[:when]).to eq(when_state)
...@@ -601,6 +602,23 @@ module Ci ...@@ -601,6 +602,23 @@ module Ci
allow_failure: false allow_failure: false
}) })
end end
%w[on_success on_failure always].each do |when_state|
it "returns artifacts for when #{when_state} defined" do
config = YAML.dump({
rspec: {
script: "rspec",
artifacts: { paths: ["logs/", "binaries/"], when: when_state }
}
})
config_processor = GitlabCiYamlProcessor.new(config, path)
builds = config_processor.builds_for_stage_and_ref("test", "master")
expect(builds.size).to eq(1)
expect(builds.first[:options][:artifacts][:when]).to eq(when_state)
end
end
end end
describe "Dependencies" do describe "Dependencies" do
...@@ -967,6 +985,13 @@ EOT ...@@ -967,6 +985,13 @@ EOT
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: artifacts:name parameter should be a string") end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: artifacts:name parameter should be a string")
end end
it "returns errors if job artifacts:when is not an a predefined value" do
config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", artifacts: { when: 1 } } })
expect do
GitlabCiYamlProcessor.new(config)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: artifacts:when parameter should be on_success, on_failure or always")
end
it "returns errors if job artifacts:untracked is not an array of strings" do it "returns errors if job artifacts:untracked is not an array of strings" do
config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", artifacts: { untracked: "string" } } }) config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", artifacts: { untracked: "string" } } })
expect do expect do
......
...@@ -3,7 +3,7 @@ require 'spec_helper' ...@@ -3,7 +3,7 @@ require 'spec_helper'
describe Gitlab::Auth, lib: true do describe Gitlab::Auth, lib: true do
let(:gl_auth) { described_class } let(:gl_auth) { described_class }
describe 'find' do describe 'find_for_git_client' do
it 'recognizes CI' do it 'recognizes CI' do
token = '123' token = '123'
project = create(:empty_project) project = create(:empty_project)
...@@ -11,7 +11,7 @@ describe Gitlab::Auth, lib: true do ...@@ -11,7 +11,7 @@ describe Gitlab::Auth, lib: true do
ip = 'ip' ip = 'ip'
expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: 'gitlab-ci-token') expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: 'gitlab-ci-token')
expect(gl_auth.find('gitlab-ci-token', token, project: project, ip: ip)).to eq(Gitlab::Auth::Result.new(nil, :ci)) expect(gl_auth.find_for_git_client('gitlab-ci-token', token, project: project, ip: ip)).to eq(Gitlab::Auth::Result.new(nil, :ci))
end end
it 'recognizes master passwords' do it 'recognizes master passwords' do
...@@ -19,7 +19,7 @@ describe Gitlab::Auth, lib: true do ...@@ -19,7 +19,7 @@ describe Gitlab::Auth, lib: true do
ip = 'ip' ip = 'ip'
expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: user.username) expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: user.username)
expect(gl_auth.find(user.username, 'password', project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, :gitlab_or_ldap)) expect(gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, :gitlab_or_ldap))
end end
it 'recognizes OAuth tokens' do it 'recognizes OAuth tokens' do
...@@ -29,7 +29,7 @@ describe Gitlab::Auth, lib: true do ...@@ -29,7 +29,7 @@ describe Gitlab::Auth, lib: true do
ip = 'ip' ip = 'ip'
expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: 'oauth2') expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: 'oauth2')
expect(gl_auth.find("oauth2", token.token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, :oauth)) expect(gl_auth.find_for_git_client("oauth2", token.token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, :oauth))
end end
it 'returns double nil for invalid credentials' do it 'returns double nil for invalid credentials' do
...@@ -37,11 +37,11 @@ describe Gitlab::Auth, lib: true do ...@@ -37,11 +37,11 @@ describe Gitlab::Auth, lib: true do
ip = 'ip' ip = 'ip'
expect(gl_auth).to receive(:rate_limit!).with(ip, success: false, login: login) expect(gl_auth).to receive(:rate_limit!).with(ip, success: false, login: login)
expect(gl_auth.find(login, 'bar', project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new) expect(gl_auth.find_for_git_client(login, 'bar', project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new)
end end
end end
describe 'find_in_gitlab_or_ldap' do describe 'find_with_user_password' do
let!(:user) do let!(:user) do
create(:user, create(:user,
username: username, username: username,
...@@ -52,25 +52,25 @@ describe Gitlab::Auth, lib: true do ...@@ -52,25 +52,25 @@ describe Gitlab::Auth, lib: true do
let(:password) { 'my-secret' } let(:password) { 'my-secret' }
it "should find user by valid login/password" do it "should find user by valid login/password" do
expect( gl_auth.find_in_gitlab_or_ldap(username, password) ).to eql user expect( gl_auth.find_with_user_password(username, password) ).to eql user
end end
it 'should find user by valid email/password with case-insensitive email' do it 'should find user by valid email/password with case-insensitive email' do
expect(gl_auth.find_in_gitlab_or_ldap(user.email.upcase, password)).to eql user expect(gl_auth.find_with_user_password(user.email.upcase, password)).to eql user
end end
it 'should find user by valid username/password with case-insensitive username' do it 'should find user by valid username/password with case-insensitive username' do
expect(gl_auth.find_in_gitlab_or_ldap(username.upcase, password)).to eql user expect(gl_auth.find_with_user_password(username.upcase, password)).to eql user
end end
it "should not find user with invalid password" do it "should not find user with invalid password" do
password = 'wrong' password = 'wrong'
expect( gl_auth.find_in_gitlab_or_ldap(username, password) ).not_to eql user expect( gl_auth.find_with_user_password(username, password) ).not_to eql user
end end
it "should not find user with invalid login" do it "should not find user with invalid login" do
user = 'wrong' user = 'wrong'
expect( gl_auth.find_in_gitlab_or_ldap(username, password) ).not_to eql user expect( gl_auth.find_with_user_password(username, password) ).not_to eql user
end end
context "with ldap enabled" do context "with ldap enabled" do
...@@ -81,13 +81,13 @@ describe Gitlab::Auth, lib: true do ...@@ -81,13 +81,13 @@ describe Gitlab::Auth, lib: true do
it "tries to autheticate with db before ldap" do it "tries to autheticate with db before ldap" do
expect(Gitlab::LDAP::Authentication).not_to receive(:login) expect(Gitlab::LDAP::Authentication).not_to receive(:login)
gl_auth.find_in_gitlab_or_ldap(username, password) gl_auth.find_with_user_password(username, password)
end end
it "uses ldap as fallback to for authentication" do it "uses ldap as fallback to for authentication" do
expect(Gitlab::LDAP::Authentication).to receive(:login) expect(Gitlab::LDAP::Authentication).to receive(:login)
gl_auth.find_in_gitlab_or_ldap('ldap_user', 'password') gl_auth.find_with_user_password('ldap_user', 'password')
end end
end end
end end
......
...@@ -120,6 +120,19 @@ describe Gitlab::Database::MigrationHelpers, lib: true do ...@@ -120,6 +120,19 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
model.add_column_with_default(:projects, :foo, :integer, default: 10) model.add_column_with_default(:projects, :foo, :integer, default: 10)
end.to raise_error(RuntimeError) end.to raise_error(RuntimeError)
end end
it 'removes the added column whenever changing a column NULL constraint fails' do
expect(model).to receive(:change_column_null).
with(:projects, :foo, false).
and_raise(RuntimeError)
expect(model).to receive(:remove_column).
with(:projects, :foo)
expect do
model.add_column_with_default(:projects, :foo, :integer, default: 10)
end.to raise_error(RuntimeError)
end
end end
context 'inside a transaction' do context 'inside a transaction' do
......
...@@ -43,6 +43,18 @@ describe Gitlab::ProjectSearchResults, lib: true do ...@@ -43,6 +43,18 @@ describe Gitlab::ProjectSearchResults, lib: true do
expect(results.issues_count).to eq 1 expect(results.issues_count).to eq 1
end end
it 'should not list project confidential issues for project members with guest role' do
project.team << [member, :guest]
results = described_class.new(member, project, query)
issues = results.objects('issues')
expect(issues).to include issue
expect(issues).not_to include security_issue_1
expect(issues).not_to include security_issue_2
expect(results.issues_count).to eq 1
end
it 'should list project confidential issues for author' do it 'should list project confidential issues for author' do
results = described_class.new(author, project, query) results = described_class.new(author, project, query)
issues = results.objects('issues') issues = results.objects('issues')
......
require 'spec_helper'
describe Gitlab::Sanitizers::SVG do
let(:scrubber) { Gitlab::Sanitizers::SVG::Scrubber.new }
let(:namespace) { double(Nokogiri::XML::Namespace, prefix: 'xlink', href: 'http://www.w3.org/1999/xlink') }
let(:namespaced_attr) { double(Nokogiri::XML::Attr, name: 'href', namespace: namespace, value: '#awesome_id') }
describe '.clean' do
let(:input_svg_path) { File.join(Rails.root, 'spec', 'fixtures', 'unsanitized.svg') }
let(:data) { open(input_svg_path).read }
let(:sanitized_svg_path) { File.join(Rails.root, 'spec', 'fixtures', 'sanitized.svg') }
let(:sanitized) { open(sanitized_svg_path).read }
it 'delegates sanitization to scrubber' do
expect_any_instance_of(Gitlab::Sanitizers::SVG::Scrubber).to receive(:scrub).at_least(:once)
described_class.clean(data)
end
it 'returns sanitized data' do
expect(described_class.clean(data)).to eq(sanitized)
end
end
context 'scrubber' do
describe '#scrub' do
let(:invalid_element) { double(Nokogiri::XML::Node, name: 'invalid', value: 'invalid') }
let(:invalid_attribute) { double(Nokogiri::XML::Attr, name: 'invalid', namespace: nil) }
let(:valid_element) { double(Nokogiri::XML::Node, name: 'use') }
it 'removes an invalid element' do
expect(invalid_element).to receive(:unlink)
scrubber.scrub(invalid_element)
end
it 'removes an invalid attribute' do
allow(valid_element).to receive(:attribute_nodes) { [invalid_attribute] }
expect(invalid_attribute).to receive(:unlink)
scrubber.scrub(valid_element)
end
it 'accepts valid element' do
allow(valid_element).to receive(:attribute_nodes) { [namespaced_attr] }
expect(valid_element).not_to receive(:unlink)
scrubber.scrub(valid_element)
end
it 'accepts valid namespaced attributes' do
allow(valid_element).to receive(:attribute_nodes) { [namespaced_attr] }
expect(namespaced_attr).not_to receive(:unlink)
scrubber.scrub(valid_element)
end
end
describe '#attribute_name_with_namespace' do
it 'returns name with prefix when attribute is namespaced' do
expect(scrubber.attribute_name_with_namespace(namespaced_attr)).to eq('xlink:href')
end
end
describe '#unsafe_href?' do
let(:unsafe_attr) { double(Nokogiri::XML::Attr, name: 'href', namespace: namespace, value: 'http://evilsite.example.com/random.svg') }
it 'returns true if href attribute is an external url' do
expect(scrubber.unsafe_href?(unsafe_attr)).to be_truthy
end
it 'returns false if href atttribute is an internal reference' do
expect(scrubber.unsafe_href?(namespaced_attr)).to be_falsey
end
end
describe '#data_attribute?' do
let(:data_attr) { double(Nokogiri::XML::Attr, name: 'data-gitlab', namespace: nil, value: 'gitlab is awesome') }
let(:namespaced_attr) { double(Nokogiri::XML::Attr, name: 'data-gitlab', namespace: namespace, value: 'gitlab is awesome') }
let(:other_attr) { double(Nokogiri::XML::Attr, name: 'something', namespace: nil, value: 'content') }
it 'returns true if is a valid data attribute' do
expect(scrubber.data_attribute?(data_attr)).to be_truthy
end
it 'returns false if attribute is namespaced' do
expect(scrubber.data_attribute?(namespaced_attr)).to be_falsey
end
it 'returns false if not a data attribute' do
expect(scrubber.data_attribute?(other_attr)).to be_falsey
end
end
end
end
...@@ -86,6 +86,22 @@ describe Gitlab::SearchResults do ...@@ -86,6 +86,22 @@ describe Gitlab::SearchResults do
expect(results.issues_count).to eq 1 expect(results.issues_count).to eq 1
end end
it 'should not list confidential issues for project members with guest role' do
project_1.team << [member, :guest]
project_2.team << [member, :guest]
results = described_class.new(member, limit_projects, query)
issues = results.objects('issues')
expect(issues).to include issue
expect(issues).not_to include security_issue_1
expect(issues).not_to include security_issue_2
expect(issues).not_to include security_issue_3
expect(issues).not_to include security_issue_4
expect(issues).not_to include security_issue_5
expect(results.issues_count).to eq 1
end
it 'should list confidential issues for author' do it 'should list confidential issues for author' do
results = described_class.new(author, limit_projects, query) results = described_class.new(author, limit_projects, query)
issues = results.objects('issues') issues = results.objects('issues')
......
...@@ -5,6 +5,7 @@ describe Milestone, 'Milestoneish' do ...@@ -5,6 +5,7 @@ describe Milestone, 'Milestoneish' do
let(:assignee) { create(:user) } let(:assignee) { create(:user) }
let(:non_member) { create(:user) } let(:non_member) { create(:user) }
let(:member) { create(:user) } let(:member) { create(:user) }
let(:guest) { create(:user) }
let(:admin) { create(:admin) } let(:admin) { create(:admin) }
let(:project) { create(:project, :public) } let(:project) { create(:project, :public) }
let(:milestone) { create(:milestone, project: project) } let(:milestone) { create(:milestone, project: project) }
...@@ -21,6 +22,7 @@ describe Milestone, 'Milestoneish' do ...@@ -21,6 +22,7 @@ describe Milestone, 'Milestoneish' do
before do before do
project.team << [member, :developer] project.team << [member, :developer]
project.team << [guest, :guest]
end end
describe '#closed_items_count' do describe '#closed_items_count' do
...@@ -28,6 +30,10 @@ describe Milestone, 'Milestoneish' do ...@@ -28,6 +30,10 @@ describe Milestone, 'Milestoneish' do
expect(milestone.closed_items_count(non_member)).to eq 2 expect(milestone.closed_items_count(non_member)).to eq 2
end end
it 'should not count confidential issues for project members with guest role' do
expect(milestone.closed_items_count(guest)).to eq 2
end
it 'should count confidential issues for author' do it 'should count confidential issues for author' do
expect(milestone.closed_items_count(author)).to eq 4 expect(milestone.closed_items_count(author)).to eq 4
end end
...@@ -50,6 +56,10 @@ describe Milestone, 'Milestoneish' do ...@@ -50,6 +56,10 @@ describe Milestone, 'Milestoneish' do
expect(milestone.total_items_count(non_member)).to eq 4 expect(milestone.total_items_count(non_member)).to eq 4
end end
it 'should not count confidential issues for project members with guest role' do
expect(milestone.total_items_count(guest)).to eq 4
end
it 'should count confidential issues for author' do it 'should count confidential issues for author' do
expect(milestone.total_items_count(author)).to eq 7 expect(milestone.total_items_count(author)).to eq 7
end end
...@@ -85,6 +95,10 @@ describe Milestone, 'Milestoneish' do ...@@ -85,6 +95,10 @@ describe Milestone, 'Milestoneish' do
expect(milestone.percent_complete(non_member)).to eq 50 expect(milestone.percent_complete(non_member)).to eq 50
end end
it 'should not count confidential issues for project members with guest role' do
expect(milestone.percent_complete(guest)).to eq 50
end
it 'should count confidential issues for author' do it 'should count confidential issues for author' do
expect(milestone.percent_complete(author)).to eq 57 expect(milestone.percent_complete(author)).to eq 57
end end
......
...@@ -50,6 +50,7 @@ describe Event, models: true do ...@@ -50,6 +50,7 @@ describe Event, models: true do
let(:project) { create(:empty_project, :public) } let(:project) { create(:empty_project, :public) }
let(:non_member) { create(:user) } let(:non_member) { create(:user) }
let(:member) { create(:user) } let(:member) { create(:user) }
let(:guest) { create(:user) }
let(:author) { create(:author) } let(:author) { create(:author) }
let(:assignee) { create(:user) } let(:assignee) { create(:user) }
let(:admin) { create(:admin) } let(:admin) { create(:admin) }
...@@ -61,6 +62,7 @@ describe Event, models: true do ...@@ -61,6 +62,7 @@ describe Event, models: true do
before do before do
project.team << [member, :developer] project.team << [member, :developer]
project.team << [guest, :guest]
end end
context 'issue event' do context 'issue event' do
...@@ -71,6 +73,7 @@ describe Event, models: true do ...@@ -71,6 +73,7 @@ describe Event, models: true do
it { expect(event.visible_to_user?(author)).to eq true } it { expect(event.visible_to_user?(author)).to eq true }
it { expect(event.visible_to_user?(assignee)).to eq true } it { expect(event.visible_to_user?(assignee)).to eq true }
it { expect(event.visible_to_user?(member)).to eq true } it { expect(event.visible_to_user?(member)).to eq true }
it { expect(event.visible_to_user?(guest)).to eq true }
it { expect(event.visible_to_user?(admin)).to eq true } it { expect(event.visible_to_user?(admin)).to eq true }
end end
...@@ -81,6 +84,7 @@ describe Event, models: true do ...@@ -81,6 +84,7 @@ describe Event, models: true do
it { expect(event.visible_to_user?(author)).to eq true } it { expect(event.visible_to_user?(author)).to eq true }
it { expect(event.visible_to_user?(assignee)).to eq true } it { expect(event.visible_to_user?(assignee)).to eq true }
it { expect(event.visible_to_user?(member)).to eq true } it { expect(event.visible_to_user?(member)).to eq true }
it { expect(event.visible_to_user?(guest)).to eq false }
it { expect(event.visible_to_user?(admin)).to eq true } it { expect(event.visible_to_user?(admin)).to eq true }
end end
end end
...@@ -93,6 +97,7 @@ describe Event, models: true do ...@@ -93,6 +97,7 @@ describe Event, models: true do
it { expect(event.visible_to_user?(author)).to eq true } it { expect(event.visible_to_user?(author)).to eq true }
it { expect(event.visible_to_user?(assignee)).to eq true } it { expect(event.visible_to_user?(assignee)).to eq true }
it { expect(event.visible_to_user?(member)).to eq true } it { expect(event.visible_to_user?(member)).to eq true }
it { expect(event.visible_to_user?(guest)).to eq true }
it { expect(event.visible_to_user?(admin)).to eq true } it { expect(event.visible_to_user?(admin)).to eq true }
end end
...@@ -103,6 +108,7 @@ describe Event, models: true do ...@@ -103,6 +108,7 @@ describe Event, models: true do
it { expect(event.visible_to_user?(author)).to eq true } it { expect(event.visible_to_user?(author)).to eq true }
it { expect(event.visible_to_user?(assignee)).to eq true } it { expect(event.visible_to_user?(assignee)).to eq true }
it { expect(event.visible_to_user?(member)).to eq true } it { expect(event.visible_to_user?(member)).to eq true }
it { expect(event.visible_to_user?(guest)).to eq false }
it { expect(event.visible_to_user?(admin)).to eq true } it { expect(event.visible_to_user?(admin)).to eq true }
end end
end end
......
...@@ -162,16 +162,23 @@ describe Note, models: true do ...@@ -162,16 +162,23 @@ describe Note, models: true do
end end
context "confidential issues" do context "confidential issues" do
let(:user) { create :user } let(:user) { create(:user) }
let(:confidential_issue) { create(:issue, :confidential, author: user) } let(:project) { create(:project) }
let(:confidential_note) { create :note, note: "Random", noteable: confidential_issue, project: confidential_issue.project } let(:confidential_issue) { create(:issue, :confidential, project: project, author: user) }
let(:confidential_note) { create(:note, note: "Random", noteable: confidential_issue, project: confidential_issue.project) }
it "returns notes with matching content if user can see the issue" do it "returns notes with matching content if user can see the issue" do
expect(described_class.search(confidential_note.note, as_user: user)).to eq([confidential_note]) expect(described_class.search(confidential_note.note, as_user: user)).to eq([confidential_note])
end end
it "does not return notes with matching content if user can not see the issue" do it "does not return notes with matching content if user can not see the issue" do
user = create :user user = create(:user)
expect(described_class.search(confidential_note.note, as_user: user)).to be_empty
end
it "does not return notes with matching content for project members with guest role" do
user = create(:user)
project.team << [user, :guest]
expect(described_class.search(confidential_note.note, as_user: user)).to be_empty expect(described_class.search(confidential_note.note, as_user: user)).to be_empty
end end
......
...@@ -10,7 +10,6 @@ RSpec.describe NotificationSetting, type: :model do ...@@ -10,7 +10,6 @@ RSpec.describe NotificationSetting, type: :model do
subject { NotificationSetting.new(source_id: 1, source_type: 'Project') } subject { NotificationSetting.new(source_id: 1, source_type: 'Project') }
it { is_expected.to validate_presence_of(:user) } it { is_expected.to validate_presence_of(:user) }
it { is_expected.to validate_presence_of(:source) }
it { is_expected.to validate_presence_of(:level) } it { is_expected.to validate_presence_of(:level) }
it { is_expected.to validate_uniqueness_of(:user_id).scoped_to([:source_id, :source_type]).with_message(/already exists in source/) } it { is_expected.to validate_uniqueness_of(:user_id).scoped_to([:source_id, :source_type]).with_message(/already exists in source/) }
end end
......
...@@ -126,25 +126,25 @@ describe BambooService, models: true do ...@@ -126,25 +126,25 @@ describe BambooService, models: true do
it 'returns a specific URL when status is 500' do it 'returns a specific URL when status is 500' do
stub_request(status: 500) stub_request(status: 500)
expect(service.build_page('123', 'unused')).to eq('http://gitlab.com/browse/foo') expect(service.build_page('123', 'unused')).to eq('http://gitlab.com/bamboo/browse/foo')
end end
it 'returns a specific URL when response has no results' do it 'returns a specific URL when response has no results' do
stub_request(body: %Q({"results":{"results":{"size":"0"}}})) stub_request(body: %Q({"results":{"results":{"size":"0"}}}))
expect(service.build_page('123', 'unused')).to eq('http://gitlab.com/browse/foo') expect(service.build_page('123', 'unused')).to eq('http://gitlab.com/bamboo/browse/foo')
end end
it 'returns a build URL when bamboo_url has no trailing slash' do it 'returns a build URL when bamboo_url has no trailing slash' do
stub_request(body: %Q({"results":{"results":{"result":{"planResultKey":{"key":"42"}}}}})) stub_request(body: %Q({"results":{"results":{"result":{"planResultKey":{"key":"42"}}}}}))
expect(service(bamboo_url: 'http://gitlab.com').build_page('123', 'unused')).to eq('http://gitlab.com/browse/42') expect(service(bamboo_url: 'http://gitlab.com/bamboo').build_page('123', 'unused')).to eq('http://gitlab.com/bamboo/browse/42')
end end
it 'returns a build URL when bamboo_url has a trailing slash' do it 'returns a build URL when bamboo_url has a trailing slash' do
stub_request(body: %Q({"results":{"results":{"result":{"planResultKey":{"key":"42"}}}}})) stub_request(body: %Q({"results":{"results":{"result":{"planResultKey":{"key":"42"}}}}}))
expect(service(bamboo_url: 'http://gitlab.com/').build_page('123', 'unused')).to eq('http://gitlab.com/browse/42') expect(service(bamboo_url: 'http://gitlab.com/bamboo/').build_page('123', 'unused')).to eq('http://gitlab.com/bamboo/browse/42')
end end
end end
...@@ -192,7 +192,7 @@ describe BambooService, models: true do ...@@ -192,7 +192,7 @@ describe BambooService, models: true do
end end
end end
def service(bamboo_url: 'http://gitlab.com') def service(bamboo_url: 'http://gitlab.com/bamboo')
described_class.create( described_class.create(
project: create(:empty_project), project: create(:empty_project),
properties: { properties: {
...@@ -205,7 +205,7 @@ describe BambooService, models: true do ...@@ -205,7 +205,7 @@ describe BambooService, models: true do
end end
def stub_request(status: 200, body: nil, build_state: 'success') def stub_request(status: 200, body: nil, build_state: 'success')
bamboo_full_url = 'http://mic:password@gitlab.com/rest/api/latest/result?label=123&os_authType=basic' bamboo_full_url = 'http://mic:password@gitlab.com/bamboo/rest/api/latest/result?label=123&os_authType=basic'
body ||= %Q({"results":{"results":{"result":{"buildState":"#{build_state}"}}}}) body ||= %Q({"results":{"results":{"result":{"buildState":"#{build_state}"}}}})
WebMock.stub_request(:get, bamboo_full_url).to_return( WebMock.stub_request(:get, bamboo_full_url).to_return(
......
...@@ -126,19 +126,19 @@ describe TeamcityService, models: true do ...@@ -126,19 +126,19 @@ describe TeamcityService, models: true do
it 'returns a specific URL when status is 500' do it 'returns a specific URL when status is 500' do
stub_request(status: 500) stub_request(status: 500)
expect(service.build_page('123', 'unused')).to eq('http://gitlab.com/viewLog.html?buildTypeId=foo') expect(service.build_page('123', 'unused')).to eq('http://gitlab.com/teamcity/viewLog.html?buildTypeId=foo')
end end
it 'returns a build URL when teamcity_url has no trailing slash' do it 'returns a build URL when teamcity_url has no trailing slash' do
stub_request(body: %Q({"build":{"id":"666"}})) stub_request(body: %Q({"build":{"id":"666"}}))
expect(service(teamcity_url: 'http://gitlab.com').build_page('123', 'unused')).to eq('http://gitlab.com/viewLog.html?buildId=666&buildTypeId=foo') expect(service(teamcity_url: 'http://gitlab.com/teamcity').build_page('123', 'unused')).to eq('http://gitlab.com/teamcity/viewLog.html?buildId=666&buildTypeId=foo')
end end
it 'returns a build URL when teamcity_url has a trailing slash' do it 'returns a build URL when teamcity_url has a trailing slash' do
stub_request(body: %Q({"build":{"id":"666"}})) stub_request(body: %Q({"build":{"id":"666"}}))
expect(service(teamcity_url: 'http://gitlab.com/').build_page('123', 'unused')).to eq('http://gitlab.com/viewLog.html?buildId=666&buildTypeId=foo') expect(service(teamcity_url: 'http://gitlab.com/teamcity/').build_page('123', 'unused')).to eq('http://gitlab.com/teamcity/viewLog.html?buildId=666&buildTypeId=foo')
end end
end end
...@@ -180,7 +180,7 @@ describe TeamcityService, models: true do ...@@ -180,7 +180,7 @@ describe TeamcityService, models: true do
end end
end end
def service(teamcity_url: 'http://gitlab.com') def service(teamcity_url: 'http://gitlab.com/teamcity')
described_class.create( described_class.create(
project: create(:empty_project), project: create(:empty_project),
properties: { properties: {
...@@ -193,7 +193,7 @@ describe TeamcityService, models: true do ...@@ -193,7 +193,7 @@ describe TeamcityService, models: true do
end end
def stub_request(status: 200, body: nil, build_status: 'success') def stub_request(status: 200, body: nil, build_status: 'success')
teamcity_full_url = 'http://mic:password@gitlab.com/httpAuth/app/rest/builds/branch:unspecified:any,number:123' teamcity_full_url = 'http://mic:password@gitlab.com/teamcity/httpAuth/app/rest/builds/branch:unspecified:any,number:123'
body ||= %Q({"build":{"status":"#{build_status}","id":"666"}}) body ||= %Q({"build":{"status":"#{build_status}","id":"666"}})
WebMock.stub_request(:get, teamcity_full_url).to_return( WebMock.stub_request(:get, teamcity_full_url).to_return(
......
...@@ -53,7 +53,6 @@ describe Project, models: true do ...@@ -53,7 +53,6 @@ describe Project, models: true do
it { is_expected.to validate_length_of(:path).is_within(0..255) } it { is_expected.to validate_length_of(:path).is_within(0..255) }
it { is_expected.to validate_length_of(:description).is_within(0..2000) } it { is_expected.to validate_length_of(:description).is_within(0..2000) }
it { is_expected.to validate_presence_of(:creator) } it { is_expected.to validate_presence_of(:creator) }
it { is_expected.to validate_length_of(:issues_tracker_id).is_within(0..255) }
it { is_expected.to validate_presence_of(:namespace) } it { is_expected.to validate_presence_of(:namespace) }
it 'should not allow new projects beyond user limits' do it 'should not allow new projects beyond user limits' do
...@@ -321,27 +320,6 @@ describe Project, models: true do ...@@ -321,27 +320,6 @@ describe Project, models: true do
end end
end end
describe :can_have_issues_tracker_id? do
let(:project) { create(:project) }
let(:ext_project) { create(:redmine_project) }
it 'should be true for projects with external issues tracker if issues enabled' do
expect(ext_project.can_have_issues_tracker_id?).to be_truthy
end
it 'should be false for projects with internal issue tracker if issues enabled' do
expect(project.can_have_issues_tracker_id?).to be_falsey
end
it 'should be always false if issues disabled' do
project.issues_enabled = false
ext_project.issues_enabled = false
expect(project.can_have_issues_tracker_id?).to be_falsey
expect(ext_project.can_have_issues_tracker_id?).to be_falsey
end
end
describe :open_branches do describe :open_branches do
let(:project) { create(:project) } let(:project) { create(:project) }
......
...@@ -29,6 +29,9 @@ describe ProjectTeam, models: true do ...@@ -29,6 +29,9 @@ describe ProjectTeam, models: true do
it { expect(project.team.master?(nonmember)).to be_falsey } it { expect(project.team.master?(nonmember)).to be_falsey }
it { expect(project.team.member?(nonmember)).to be_falsey } it { expect(project.team.member?(nonmember)).to be_falsey }
it { expect(project.team.member?(guest)).to be_truthy } it { expect(project.team.member?(guest)).to be_truthy }
it { expect(project.team.member?(reporter, Gitlab::Access::REPORTER)).to be_truthy }
it { expect(project.team.member?(guest, Gitlab::Access::REPORTER)).to be_falsey }
it { expect(project.team.member?(nonmember, Gitlab::Access::GUEST)).to be_falsey }
end end
end end
...@@ -64,6 +67,9 @@ describe ProjectTeam, models: true do ...@@ -64,6 +67,9 @@ describe ProjectTeam, models: true do
it { expect(project.team.master?(nonmember)).to be_falsey } it { expect(project.team.master?(nonmember)).to be_falsey }
it { expect(project.team.member?(nonmember)).to be_falsey } it { expect(project.team.member?(nonmember)).to be_falsey }
it { expect(project.team.member?(guest)).to be_truthy } it { expect(project.team.member?(guest)).to be_truthy }
it { expect(project.team.member?(guest, Gitlab::Access::MASTER)).to be_truthy }
it { expect(project.team.member?(reporter, Gitlab::Access::MASTER)).to be_falsey }
it { expect(project.team.member?(nonmember, Gitlab::Access::GUEST)).to be_falsey }
end end
end end
......
...@@ -5,6 +5,7 @@ describe API::API, api: true do ...@@ -5,6 +5,7 @@ describe API::API, api: true do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:user2) { create(:user) } let(:user2) { create(:user) }
let(:non_member) { create(:user) } let(:non_member) { create(:user) }
let(:guest) { create(:user) }
let(:author) { create(:author) } let(:author) { create(:author) }
let(:assignee) { create(:assignee) } let(:assignee) { create(:assignee) }
let(:admin) { create(:user, :admin) } let(:admin) { create(:user, :admin) }
...@@ -41,7 +42,10 @@ describe API::API, api: true do ...@@ -41,7 +42,10 @@ describe API::API, api: true do
end end
let!(:note) { create(:note_on_issue, author: user, project: project, noteable: issue) } let!(:note) { create(:note_on_issue, author: user, project: project, noteable: issue) }
before { project.team << [user, :reporter] } before do
project.team << [user, :reporter]
project.team << [guest, :guest]
end
describe "GET /issues" do describe "GET /issues" do
context "when unauthenticated" do context "when unauthenticated" do
...@@ -144,6 +148,14 @@ describe API::API, api: true do ...@@ -144,6 +148,14 @@ describe API::API, api: true do
expect(json_response.first['title']).to eq(issue.title) expect(json_response.first['title']).to eq(issue.title)
end end
it 'should return project issues without confidential issues for project members with guest role' do
get api("#{base_url}/issues", guest)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
expect(json_response.first['title']).to eq(issue.title)
end
it 'should return project confidential issues for author' do it 'should return project confidential issues for author' do
get api("#{base_url}/issues", author) get api("#{base_url}/issues", author)
expect(response.status).to eq(200) expect(response.status).to eq(200)
...@@ -278,6 +290,11 @@ describe API::API, api: true do ...@@ -278,6 +290,11 @@ describe API::API, api: true do
expect(response.status).to eq(404) expect(response.status).to eq(404)
end end
it "should return 404 for project members with guest role" do
get api("/projects/#{project.id}/issues/#{confidential_issue.id}", guest)
expect(response.status).to eq(404)
end
it "should return confidential issue for project members" do it "should return confidential issue for project members" do
get api("/projects/#{project.id}/issues/#{confidential_issue.id}", user) get api("/projects/#{project.id}/issues/#{confidential_issue.id}", user)
expect(response.status).to eq(200) expect(response.status).to eq(200)
...@@ -413,6 +430,12 @@ describe API::API, api: true do ...@@ -413,6 +430,12 @@ describe API::API, api: true do
expect(response.status).to eq(403) expect(response.status).to eq(403)
end end
it "should return 403 for project members with guest role" do
put api("/projects/#{project.id}/issues/#{confidential_issue.id}", guest),
title: 'updated title'
expect(response.status).to eq(403)
end
it "should update a confidential issue for project members" do it "should update a confidential issue for project members" do
put api("/projects/#{project.id}/issues/#{confidential_issue.id}", user), put api("/projects/#{project.id}/issues/#{confidential_issue.id}", user),
title: 'updated title' title: 'updated title'
......
...@@ -563,6 +563,21 @@ describe API::API, api: true do ...@@ -563,6 +563,21 @@ describe API::API, api: true do
expect(json_response).to be_an Array expect(json_response).to be_an Array
expect(json_response.length).to eq(0) expect(json_response.length).to eq(0)
end end
it 'handles external issues' do
jira_project = create(:jira_project, :public, name: 'JIR_EXT1')
issue = ExternalIssue.new("#{jira_project.name}-123", jira_project)
merge_request = create(:merge_request, :simple, author: user, assignee: user, source_project: jira_project)
merge_request.update_attribute(:description, "Closes #{issue.to_reference(jira_project)}")
get api("/projects/#{jira_project.id}/merge_requests/#{merge_request.id}/closes_issues", user)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['title']).to eq(issue.title)
expect(json_response.first['id']).to eq(issue.id)
end
end end
describe 'POST :id/merge_requests/:merge_request_id/subscription' do describe 'POST :id/merge_requests/:merge_request_id/subscription' do
......
...@@ -146,6 +146,7 @@ describe API::API, api: true do ...@@ -146,6 +146,7 @@ describe API::API, api: true do
let(:milestone) { create(:milestone, project: public_project) } let(:milestone) { create(:milestone, project: public_project) }
let(:issue) { create(:issue, project: public_project) } let(:issue) { create(:issue, project: public_project) }
let(:confidential_issue) { create(:issue, confidential: true, project: public_project) } let(:confidential_issue) { create(:issue, confidential: true, project: public_project) }
before do before do
public_project.team << [user, :developer] public_project.team << [user, :developer]
milestone.issues << issue << confidential_issue milestone.issues << issue << confidential_issue
...@@ -160,6 +161,18 @@ describe API::API, api: true do ...@@ -160,6 +161,18 @@ describe API::API, api: true do
expect(json_response.map { |issue| issue['id'] }).to include(issue.id, confidential_issue.id) expect(json_response.map { |issue| issue['id'] }).to include(issue.id, confidential_issue.id)
end end
it 'does not return confidential issues to team members with guest role' do
member = create(:user)
project.team << [member, :guest]
get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", member)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.size).to eq(1)
expect(json_response.map { |issue| issue['id'] }).to include(issue.id)
end
it 'does not return confidential issues to regular users' do it 'does not return confidential issues to regular users' do
get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", create(:user)) get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", create(:user))
......
...@@ -44,7 +44,7 @@ describe JwtController do ...@@ -44,7 +44,7 @@ describe JwtController do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:headers) { { authorization: credentials('user', 'password') } } let(:headers) { { authorization: credentials('user', 'password') } }
before { expect(Gitlab::Auth).to receive(:find_in_gitlab_or_ldap).with('user', 'password').and_return(user) } before { expect(Gitlab::Auth).to receive(:find_with_user_password).with('user', 'password').and_return(user) }
subject! { get '/jwt/auth', parameters, headers } subject! { get '/jwt/auth', parameters, headers }
......
...@@ -72,6 +72,7 @@ describe NotificationService, services: true do ...@@ -72,6 +72,7 @@ describe NotificationService, services: true do
should_not_email(@u_disabled) should_not_email(@u_disabled)
should_not_email(@unsubscriber) should_not_email(@unsubscriber)
should_not_email(@u_outsider_mentioned) should_not_email(@u_outsider_mentioned)
should_not_email(@u_lazy_participant)
end end
it 'filters out "mentioned in" notes' do it 'filters out "mentioned in" notes' do
...@@ -80,6 +81,20 @@ describe NotificationService, services: true do ...@@ -80,6 +81,20 @@ describe NotificationService, services: true do
expect(Notify).not_to receive(:note_issue_email) expect(Notify).not_to receive(:note_issue_email)
notification.new_note(mentioned_note) notification.new_note(mentioned_note)
end end
context 'participating' do
context 'by note' do
before do
ActionMailer::Base.deliveries.clear
note.author = @u_lazy_participant
note.save
notification.new_note(note)
end
it { should_not_email(@u_lazy_participant) }
end
end
end end
describe 'new note on issue in project that belongs to a group' do describe 'new note on issue in project that belongs to a group' do
...@@ -106,6 +121,7 @@ describe NotificationService, services: true do ...@@ -106,6 +121,7 @@ describe NotificationService, services: true do
should_not_email(note.author) should_not_email(note.author)
should_not_email(@u_participating) should_not_email(@u_participating)
should_not_email(@u_disabled) should_not_email(@u_disabled)
should_not_email(@u_lazy_participant)
end end
end end
end end
...@@ -116,12 +132,14 @@ describe NotificationService, services: true do ...@@ -116,12 +132,14 @@ describe NotificationService, services: true do
let(:assignee) { create(:user) } let(:assignee) { create(:user) }
let(:non_member) { create(:user) } let(:non_member) { create(:user) }
let(:member) { create(:user) } let(:member) { create(:user) }
let(:guest) { create(:user) }
let(:admin) { create(:admin) } let(:admin) { create(:admin) }
let(:confidential_issue) { create(:issue, :confidential, project: project, author: author, assignee: assignee) } let(:confidential_issue) { create(:issue, :confidential, project: project, author: author, assignee: assignee) }
let(:note) { create(:note_on_issue, noteable: confidential_issue, project: project, note: "#{author.to_reference} #{assignee.to_reference} #{non_member.to_reference} #{member.to_reference} #{admin.to_reference}") } let(:note) { create(:note_on_issue, noteable: confidential_issue, project: project, note: "#{author.to_reference} #{assignee.to_reference} #{non_member.to_reference} #{member.to_reference} #{admin.to_reference}") }
it 'filters out users that can not read the issue' do it 'filters out users that can not read the issue' do
project.team << [member, :developer] project.team << [member, :developer]
project.team << [guest, :guest]
expect(SentNotification).to receive(:record).with(confidential_issue, any_args).exactly(4).times expect(SentNotification).to receive(:record).with(confidential_issue, any_args).exactly(4).times
...@@ -130,6 +148,7 @@ describe NotificationService, services: true do ...@@ -130,6 +148,7 @@ describe NotificationService, services: true do
notification.new_note(note) notification.new_note(note)
should_not_email(non_member) should_not_email(non_member)
should_not_email(guest)
should_email(author) should_email(author)
should_email(assignee) should_email(assignee)
should_email(member) should_email(member)
...@@ -235,6 +254,7 @@ describe NotificationService, services: true do ...@@ -235,6 +254,7 @@ describe NotificationService, services: true do
should_not_email(note.author) should_not_email(note.author)
should_not_email(@u_participating) should_not_email(@u_participating)
should_not_email(@u_disabled) should_not_email(@u_disabled)
should_not_email(@u_lazy_participant)
end end
it do it do
...@@ -248,10 +268,11 @@ describe NotificationService, services: true do ...@@ -248,10 +268,11 @@ describe NotificationService, services: true do
should_not_email(note.author) should_not_email(note.author)
should_not_email(@u_participating) should_not_email(@u_participating)
should_not_email(@u_disabled) should_not_email(@u_disabled)
should_not_email(@u_lazy_participant)
end end
it do it do
@u_committer.update_attributes(notification_level: :mention) @u_committer = create_global_setting_for(@u_committer, :mention)
notification.new_note(note) notification.new_note(note)
should_not_email(@u_committer) should_not_email(@u_committer)
end end
...@@ -280,10 +301,11 @@ describe NotificationService, services: true do ...@@ -280,10 +301,11 @@ describe NotificationService, services: true do
should_not_email(@u_mentioned) should_not_email(@u_mentioned)
should_not_email(@u_participating) should_not_email(@u_participating)
should_not_email(@u_disabled) should_not_email(@u_disabled)
should_not_email(@u_lazy_participant)
end end
it do it do
issue.assignee.update_attributes(notification_level: :mention) create_global_setting_for(issue.assignee, :mention)
notification.new_issue(issue, @u_disabled) notification.new_issue(issue, @u_disabled)
should_not_email(issue.assignee) should_not_email(issue.assignee)
...@@ -303,17 +325,20 @@ describe NotificationService, services: true do ...@@ -303,17 +325,20 @@ describe NotificationService, services: true do
let(:assignee) { create(:user) } let(:assignee) { create(:user) }
let(:non_member) { create(:user) } let(:non_member) { create(:user) }
let(:member) { create(:user) } let(:member) { create(:user) }
let(:guest) { create(:user) }
let(:admin) { create(:admin) } let(:admin) { create(:admin) }
let(:confidential_issue) { create(:issue, :confidential, project: project, title: 'Confidential issue', author: author, assignee: assignee) } let(:confidential_issue) { create(:issue, :confidential, project: project, title: 'Confidential issue', author: author, assignee: assignee) }
it "emails subscribers of the issue's labels that can read the issue" do it "emails subscribers of the issue's labels that can read the issue" do
project.team << [member, :developer] project.team << [member, :developer]
project.team << [guest, :guest]
label = create(:label, issues: [confidential_issue]) label = create(:label, issues: [confidential_issue])
label.toggle_subscription(non_member) label.toggle_subscription(non_member)
label.toggle_subscription(author) label.toggle_subscription(author)
label.toggle_subscription(assignee) label.toggle_subscription(assignee)
label.toggle_subscription(member) label.toggle_subscription(member)
label.toggle_subscription(guest)
label.toggle_subscription(admin) label.toggle_subscription(admin)
ActionMailer::Base.deliveries.clear ActionMailer::Base.deliveries.clear
...@@ -322,6 +347,7 @@ describe NotificationService, services: true do ...@@ -322,6 +347,7 @@ describe NotificationService, services: true do
should_not_email(non_member) should_not_email(non_member)
should_not_email(author) should_not_email(author)
should_not_email(guest)
should_email(assignee) should_email(assignee)
should_email(member) should_email(member)
should_email(admin) should_email(admin)
...@@ -341,6 +367,7 @@ describe NotificationService, services: true do ...@@ -341,6 +367,7 @@ describe NotificationService, services: true do
should_not_email(@unsubscriber) should_not_email(@unsubscriber)
should_not_email(@u_participating) should_not_email(@u_participating)
should_not_email(@u_disabled) should_not_email(@u_disabled)
should_not_email(@u_lazy_participant)
end end
it 'emails previous assignee even if he has the "on mention" notif level' do it 'emails previous assignee even if he has the "on mention" notif level' do
...@@ -356,6 +383,7 @@ describe NotificationService, services: true do ...@@ -356,6 +383,7 @@ describe NotificationService, services: true do
should_not_email(@unsubscriber) should_not_email(@unsubscriber)
should_not_email(@u_participating) should_not_email(@u_participating)
should_not_email(@u_disabled) should_not_email(@u_disabled)
should_not_email(@u_lazy_participant)
end end
it 'emails new assignee even if he has the "on mention" notif level' do it 'emails new assignee even if he has the "on mention" notif level' do
...@@ -371,6 +399,7 @@ describe NotificationService, services: true do ...@@ -371,6 +399,7 @@ describe NotificationService, services: true do
should_not_email(@unsubscriber) should_not_email(@unsubscriber)
should_not_email(@u_participating) should_not_email(@u_participating)
should_not_email(@u_disabled) should_not_email(@u_disabled)
should_not_email(@u_lazy_participant)
end end
it 'emails new assignee' do it 'emails new assignee' do
...@@ -386,6 +415,7 @@ describe NotificationService, services: true do ...@@ -386,6 +415,7 @@ describe NotificationService, services: true do
should_not_email(@unsubscriber) should_not_email(@unsubscriber)
should_not_email(@u_participating) should_not_email(@u_participating)
should_not_email(@u_disabled) should_not_email(@u_disabled)
should_not_email(@u_lazy_participant)
end end
it 'does not email new assignee if they are the current user' do it 'does not email new assignee if they are the current user' do
...@@ -401,6 +431,35 @@ describe NotificationService, services: true do ...@@ -401,6 +431,35 @@ describe NotificationService, services: true do
should_not_email(@unsubscriber) should_not_email(@unsubscriber)
should_not_email(@u_participating) should_not_email(@u_participating)
should_not_email(@u_disabled) should_not_email(@u_disabled)
should_not_email(@u_lazy_participant)
end
context 'participating' do
context 'by assignee' do
before do
issue.update_attribute(:assignee, @u_lazy_participant)
notification.reassigned_issue(issue, @u_disabled)
end
it { should_email(@u_lazy_participant) }
end
context 'by note' do
let!(:note) { create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: 'anything', author: @u_lazy_participant) }
before { notification.reassigned_issue(issue, @u_disabled) }
it { should_email(@u_lazy_participant) }
end
context 'by author' do
before do
issue.author = @u_lazy_participant
notification.reassigned_issue(issue, @u_disabled)
end
it { should_email(@u_lazy_participant) }
end
end end
end end
...@@ -438,6 +497,7 @@ describe NotificationService, services: true do ...@@ -438,6 +497,7 @@ describe NotificationService, services: true do
let(:assignee) { create(:user) } let(:assignee) { create(:user) }
let(:non_member) { create(:user) } let(:non_member) { create(:user) }
let(:member) { create(:user) } let(:member) { create(:user) }
let(:guest) { create(:user) }
let(:admin) { create(:admin) } let(:admin) { create(:admin) }
let(:confidential_issue) { create(:issue, :confidential, project: project, title: 'Confidential issue', author: author, assignee: assignee) } let(:confidential_issue) { create(:issue, :confidential, project: project, title: 'Confidential issue', author: author, assignee: assignee) }
let!(:label_1) { create(:label, issues: [confidential_issue]) } let!(:label_1) { create(:label, issues: [confidential_issue]) }
...@@ -445,11 +505,13 @@ describe NotificationService, services: true do ...@@ -445,11 +505,13 @@ describe NotificationService, services: true do
it "emails subscribers of the issue's labels that can read the issue" do it "emails subscribers of the issue's labels that can read the issue" do
project.team << [member, :developer] project.team << [member, :developer]
project.team << [guest, :guest]
label_2.toggle_subscription(non_member) label_2.toggle_subscription(non_member)
label_2.toggle_subscription(author) label_2.toggle_subscription(author)
label_2.toggle_subscription(assignee) label_2.toggle_subscription(assignee)
label_2.toggle_subscription(member) label_2.toggle_subscription(member)
label_2.toggle_subscription(guest)
label_2.toggle_subscription(admin) label_2.toggle_subscription(admin)
ActionMailer::Base.deliveries.clear ActionMailer::Base.deliveries.clear
...@@ -457,6 +519,7 @@ describe NotificationService, services: true do ...@@ -457,6 +519,7 @@ describe NotificationService, services: true do
notification.relabeled_issue(confidential_issue, [label_2], @u_disabled) notification.relabeled_issue(confidential_issue, [label_2], @u_disabled)
should_not_email(non_member) should_not_email(non_member)
should_not_email(guest)
should_email(author) should_email(author)
should_email(assignee) should_email(assignee)
should_email(member) should_email(member)
...@@ -479,6 +542,35 @@ describe NotificationService, services: true do ...@@ -479,6 +542,35 @@ describe NotificationService, services: true do
should_not_email(@unsubscriber) should_not_email(@unsubscriber)
should_not_email(@u_participating) should_not_email(@u_participating)
should_not_email(@u_disabled) should_not_email(@u_disabled)
should_not_email(@u_lazy_participant)
end
context 'participating' do
context 'by assignee' do
before do
issue.update_attribute(:assignee, @u_lazy_participant)
notification.close_issue(issue, @u_disabled)
end
it { should_email(@u_lazy_participant) }
end
context 'by note' do
let!(:note) { create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: 'anything', author: @u_lazy_participant) }
before { notification.close_issue(issue, @u_disabled) }
it { should_email(@u_lazy_participant) }
end
context 'by author' do
before do
issue.author = @u_lazy_participant
notification.close_issue(issue, @u_disabled)
end
it { should_email(@u_lazy_participant) }
end
end end
end end
...@@ -495,6 +587,35 @@ describe NotificationService, services: true do ...@@ -495,6 +587,35 @@ describe NotificationService, services: true do
should_email(@watcher_and_subscriber) should_email(@watcher_and_subscriber)
should_not_email(@unsubscriber) should_not_email(@unsubscriber)
should_not_email(@u_participating) should_not_email(@u_participating)
should_not_email(@u_lazy_participant)
end
context 'participating' do
context 'by assignee' do
before do
issue.update_attribute(:assignee, @u_lazy_participant)
notification.reopen_issue(issue, @u_disabled)
end
it { should_email(@u_lazy_participant) }
end
context 'by note' do
let!(:note) { create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: 'anything', author: @u_lazy_participant) }
before { notification.reopen_issue(issue, @u_disabled) }
it { should_email(@u_lazy_participant) }
end
context 'by author' do
before do
issue.author = @u_lazy_participant
notification.reopen_issue(issue, @u_disabled)
end
it { should_email(@u_lazy_participant) }
end
end end
end end
end end
...@@ -520,6 +641,7 @@ describe NotificationService, services: true do ...@@ -520,6 +641,7 @@ describe NotificationService, services: true do
should_email(@u_guest_watcher) should_email(@u_guest_watcher)
should_not_email(@u_participating) should_not_email(@u_participating)
should_not_email(@u_disabled) should_not_email(@u_disabled)
should_not_email(@u_lazy_participant)
end end
it "emails subscribers of the merge request's labels" do it "emails subscribers of the merge request's labels" do
...@@ -530,6 +652,36 @@ describe NotificationService, services: true do ...@@ -530,6 +652,36 @@ describe NotificationService, services: true do
should_email(subscriber) should_email(subscriber)
end end
context 'participating' do
context 'by assignee' do
before do
merge_request.update_attribute(:assignee, @u_lazy_participant)
notification.new_merge_request(merge_request, @u_disabled)
end
it { should_email(@u_lazy_participant) }
end
context 'by note' do
let!(:note) { create(:note_on_issue, noteable: merge_request, project_id: project.id, note: 'anything', author: @u_lazy_participant) }
before { notification.new_merge_request(merge_request, @u_disabled) }
it { should_email(@u_lazy_participant) }
end
context 'by author' do
before do
merge_request.author = @u_lazy_participant
merge_request.save
notification.new_merge_request(merge_request, @u_disabled)
end
it { should_not_email(@u_lazy_participant) }
end
end
end end
describe '#reassigned_merge_request' do describe '#reassigned_merge_request' do
...@@ -545,6 +697,36 @@ describe NotificationService, services: true do ...@@ -545,6 +697,36 @@ describe NotificationService, services: true do
should_not_email(@unsubscriber) should_not_email(@unsubscriber)
should_not_email(@u_participating) should_not_email(@u_participating)
should_not_email(@u_disabled) should_not_email(@u_disabled)
should_not_email(@u_lazy_participant)
end
context 'participating' do
context 'by assignee' do
before do
merge_request.update_attribute(:assignee, @u_lazy_participant)
notification.reassigned_merge_request(merge_request, @u_disabled)
end
it { should_email(@u_lazy_participant) }
end
context 'by note' do
let!(:note) { create(:note_on_issue, noteable: merge_request, project_id: project.id, note: 'anything', author: @u_lazy_participant) }
before { notification.reassigned_merge_request(merge_request, @u_disabled) }
it { should_email(@u_lazy_participant) }
end
context 'by author' do
before do
merge_request.author = @u_lazy_participant
merge_request.save
notification.reassigned_merge_request(merge_request, @u_disabled)
end
it { should_email(@u_lazy_participant) }
end
end end
end end
...@@ -572,6 +754,7 @@ describe NotificationService, services: true do ...@@ -572,6 +754,7 @@ describe NotificationService, services: true do
should_not_email(@watcher_and_subscriber) should_not_email(@watcher_and_subscriber)
should_not_email(@unsubscriber) should_not_email(@unsubscriber)
should_not_email(@u_participating) should_not_email(@u_participating)
should_not_email(@u_lazy_participant)
should_not_email(subscriber_to_label) should_not_email(subscriber_to_label)
should_email(subscriber_to_label2) should_email(subscriber_to_label2)
end end
...@@ -590,6 +773,36 @@ describe NotificationService, services: true do ...@@ -590,6 +773,36 @@ describe NotificationService, services: true do
should_not_email(@unsubscriber) should_not_email(@unsubscriber)
should_not_email(@u_participating) should_not_email(@u_participating)
should_not_email(@u_disabled) should_not_email(@u_disabled)
should_not_email(@u_lazy_participant)
end
context 'participating' do
context 'by assignee' do
before do
merge_request.update_attribute(:assignee, @u_lazy_participant)
notification.close_mr(merge_request, @u_disabled)
end
it { should_email(@u_lazy_participant) }
end
context 'by note' do
let!(:note) { create(:note_on_issue, noteable: merge_request, project_id: project.id, note: 'anything', author: @u_lazy_participant) }
before { notification.close_mr(merge_request, @u_disabled) }
it { should_email(@u_lazy_participant) }
end
context 'by author' do
before do
merge_request.author = @u_lazy_participant
merge_request.save
notification.close_mr(merge_request, @u_disabled)
end
it { should_email(@u_lazy_participant) }
end
end end
end end
...@@ -606,6 +819,36 @@ describe NotificationService, services: true do ...@@ -606,6 +819,36 @@ describe NotificationService, services: true do
should_not_email(@unsubscriber) should_not_email(@unsubscriber)
should_not_email(@u_participating) should_not_email(@u_participating)
should_not_email(@u_disabled) should_not_email(@u_disabled)
should_not_email(@u_lazy_participant)
end
context 'participating' do
context 'by assignee' do
before do
merge_request.update_attribute(:assignee, @u_lazy_participant)
notification.merge_mr(merge_request, @u_disabled)
end
it { should_email(@u_lazy_participant) }
end
context 'by note' do
let!(:note) { create(:note_on_issue, noteable: merge_request, project_id: project.id, note: 'anything', author: @u_lazy_participant) }
before { notification.merge_mr(merge_request, @u_disabled) }
it { should_email(@u_lazy_participant) }
end
context 'by author' do
before do
merge_request.author = @u_lazy_participant
merge_request.save
notification.merge_mr(merge_request, @u_disabled)
end
it { should_email(@u_lazy_participant) }
end
end end
end end
...@@ -622,6 +865,36 @@ describe NotificationService, services: true do ...@@ -622,6 +865,36 @@ describe NotificationService, services: true do
should_not_email(@unsubscriber) should_not_email(@unsubscriber)
should_not_email(@u_participating) should_not_email(@u_participating)
should_not_email(@u_disabled) should_not_email(@u_disabled)
should_not_email(@u_lazy_participant)
end
context 'participating' do
context 'by assignee' do
before do
merge_request.update_attribute(:assignee, @u_lazy_participant)
notification.reopen_mr(merge_request, @u_disabled)
end
it { should_email(@u_lazy_participant) }
end
context 'by note' do
let!(:note) { create(:note_on_issue, noteable: merge_request, project_id: project.id, note: 'anything', author: @u_lazy_participant) }
before { notification.reopen_mr(merge_request, @u_disabled) }
it { should_email(@u_lazy_participant) }
end
context 'by author' do
before do
merge_request.author = @u_lazy_participant
merge_request.save
notification.reopen_mr(merge_request, @u_disabled)
end
it { should_email(@u_lazy_participant) }
end
end end
end end
end end
...@@ -640,6 +913,7 @@ describe NotificationService, services: true do ...@@ -640,6 +913,7 @@ describe NotificationService, services: true do
should_email(@u_watcher) should_email(@u_watcher)
should_email(@u_participating) should_email(@u_participating)
should_email(@u_lazy_participant)
should_not_email(@u_guest_watcher) should_not_email(@u_guest_watcher)
should_not_email(@u_disabled) should_not_email(@u_disabled)
end end
...@@ -647,14 +921,19 @@ describe NotificationService, services: true do ...@@ -647,14 +921,19 @@ describe NotificationService, services: true do
end end
def build_team(project) def build_team(project)
@u_watcher = create(:user, notification_level: :watch) @u_watcher = create_global_setting_for(create(:user), :watch)
@u_participating = create(:user, notification_level: :participating) @u_participating = create_global_setting_for(create(:user), :participating)
@u_participant_mentioned = create(:user, username: 'participant', notification_level: :participating) @u_participant_mentioned = create_global_setting_for(create(:user, username: 'participant'), :participating)
@u_disabled = create(:user, notification_level: :disabled) @u_disabled = create_global_setting_for(create(:user), :disabled)
@u_mentioned = create(:user, username: 'mention', notification_level: :mention) @u_mentioned = create_global_setting_for(create(:user, username: 'mention'), :mention)
@u_committer = create(:user, username: 'committer') @u_committer = create(:user, username: 'committer')
@u_not_mentioned = create(:user, username: 'regular', notification_level: :participating) @u_not_mentioned = create_global_setting_for(create(:user, username: 'regular'), :participating)
@u_outsider_mentioned = create(:user, username: 'outsider') @u_outsider_mentioned = create(:user, username: 'outsider')
# User to be participant by default
# This user does not contain any record in notification settings table
# It should be treated with a :participating notification_level
@u_lazy_participant = create(:user, username: 'lazy-participant')
create_guest_watcher create_guest_watcher
...@@ -665,6 +944,15 @@ describe NotificationService, services: true do ...@@ -665,6 +944,15 @@ describe NotificationService, services: true do
project.team << [@u_mentioned, :master] project.team << [@u_mentioned, :master]
project.team << [@u_committer, :master] project.team << [@u_committer, :master]
project.team << [@u_not_mentioned, :master] project.team << [@u_not_mentioned, :master]
project.team << [@u_lazy_participant, :master]
end
def create_global_setting_for(user, level)
setting = user.global_notification_setting
setting.level = level
setting.save
user
end end
def create_guest_watcher def create_guest_watcher
...@@ -677,8 +965,8 @@ describe NotificationService, services: true do ...@@ -677,8 +965,8 @@ describe NotificationService, services: true do
def add_users_with_subscription(project, issuable) def add_users_with_subscription(project, issuable)
@subscriber = create :user @subscriber = create :user
@unsubscriber = create :user @unsubscriber = create :user
@subscribed_participant = create(:user, username: 'subscribed_participant', notification_level: :participating) @subscribed_participant = create_global_setting_for(create(:user, username: 'subscribed_participant'), :participating)
@watcher_and_subscriber = create(:user, notification_level: :watch) @watcher_and_subscriber = create_global_setting_for(create(:user), :watch)
project.team << [@subscribed_participant, :master] project.team << [@subscribed_participant, :master]
project.team << [@subscriber, :master] project.team << [@subscriber, :master]
......
...@@ -33,6 +33,18 @@ describe Projects::AutocompleteService, services: true do ...@@ -33,6 +33,18 @@ describe Projects::AutocompleteService, services: true do
expect(issues.count).to eq 1 expect(issues.count).to eq 1
end end
it 'should not list project confidential issues for project members with guest role' do
project.team << [member, :guest]
autocomplete = described_class.new(project, non_member)
issues = autocomplete.issues.map(&:iid)
expect(issues).to include issue.iid
expect(issues).not_to include security_issue_1.iid
expect(issues).not_to include security_issue_2.iid
expect(issues.count).to eq 1
end
it 'should list project confidential issues for author' do it 'should list project confidential issues for author' do
autocomplete = described_class.new(project, author) autocomplete = described_class.new(project, author)
issues = autocomplete.issues.map(&:iid) issues = autocomplete.issues.map(&:iid)
......
...@@ -5,13 +5,15 @@ describe TodoService, services: true do ...@@ -5,13 +5,15 @@ describe TodoService, services: true do
let(:assignee) { create(:user) } let(:assignee) { create(:user) }
let(:non_member) { create(:user) } let(:non_member) { create(:user) }
let(:member) { create(:user) } let(:member) { create(:user) }
let(:guest) { create(:user) }
let(:admin) { create(:admin) } let(:admin) { create(:admin) }
let(:john_doe) { create(:user) } let(:john_doe) { create(:user) }
let(:project) { create(:project) } let(:project) { create(:project) }
let(:mentions) { [author, assignee, john_doe, member, non_member, admin].map(&:to_reference).join(' ') } let(:mentions) { [author, assignee, john_doe, member, guest, non_member, admin].map(&:to_reference).join(' ') }
let(:service) { described_class.new } let(:service) { described_class.new }
before do before do
project.team << [guest, :guest]
project.team << [author, :developer] project.team << [author, :developer]
project.team << [member, :developer] project.team << [member, :developer]
project.team << [john_doe, :developer] project.team << [john_doe, :developer]
...@@ -41,18 +43,20 @@ describe TodoService, services: true do ...@@ -41,18 +43,20 @@ describe TodoService, services: true do
service.new_issue(issue, author) service.new_issue(issue, author)
should_create_todo(user: member, target: issue, action: Todo::MENTIONED) should_create_todo(user: member, target: issue, action: Todo::MENTIONED)
should_create_todo(user: guest, target: issue, action: Todo::MENTIONED)
should_not_create_todo(user: author, target: issue, action: Todo::MENTIONED) should_not_create_todo(user: author, target: issue, action: Todo::MENTIONED)
should_not_create_todo(user: john_doe, target: issue, action: Todo::MENTIONED) should_not_create_todo(user: john_doe, target: issue, action: Todo::MENTIONED)
should_not_create_todo(user: non_member, target: issue, action: Todo::MENTIONED) should_not_create_todo(user: non_member, target: issue, action: Todo::MENTIONED)
end end
it 'does not create todo for non project members when issue is confidential' do it 'does not create todo if user can not see the issue when issue is confidential' do
service.new_issue(confidential_issue, john_doe) service.new_issue(confidential_issue, john_doe)
should_create_todo(user: assignee, target: confidential_issue, author: john_doe, action: Todo::ASSIGNED) should_create_todo(user: assignee, target: confidential_issue, author: john_doe, action: Todo::ASSIGNED)
should_create_todo(user: author, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) should_create_todo(user: author, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
should_create_todo(user: member, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) should_create_todo(user: member, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
should_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) should_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
should_not_create_todo(user: guest, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
should_not_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) should_not_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
end end
...@@ -81,6 +85,7 @@ describe TodoService, services: true do ...@@ -81,6 +85,7 @@ describe TodoService, services: true do
service.update_issue(issue, author) service.update_issue(issue, author)
should_create_todo(user: member, target: issue, action: Todo::MENTIONED) should_create_todo(user: member, target: issue, action: Todo::MENTIONED)
should_create_todo(user: guest, target: issue, action: Todo::MENTIONED)
should_create_todo(user: john_doe, target: issue, action: Todo::MENTIONED) should_create_todo(user: john_doe, target: issue, action: Todo::MENTIONED)
should_not_create_todo(user: author, target: issue, action: Todo::MENTIONED) should_not_create_todo(user: author, target: issue, action: Todo::MENTIONED)
should_not_create_todo(user: non_member, target: issue, action: Todo::MENTIONED) should_not_create_todo(user: non_member, target: issue, action: Todo::MENTIONED)
...@@ -92,13 +97,14 @@ describe TodoService, services: true do ...@@ -92,13 +97,14 @@ describe TodoService, services: true do
expect { service.update_issue(issue, author) }.not_to change(member.todos, :count) expect { service.update_issue(issue, author) }.not_to change(member.todos, :count)
end end
it 'does not create todo for non project members when issue is confidential' do it 'does not create todo if user can not see the issue when issue is confidential' do
service.update_issue(confidential_issue, john_doe) service.update_issue(confidential_issue, john_doe)
should_create_todo(user: author, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) should_create_todo(user: author, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
should_create_todo(user: assignee, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) should_create_todo(user: assignee, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
should_create_todo(user: member, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) should_create_todo(user: member, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
should_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) should_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
should_not_create_todo(user: guest, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
should_not_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) should_not_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
end end
...@@ -192,18 +198,20 @@ describe TodoService, services: true do ...@@ -192,18 +198,20 @@ describe TodoService, services: true do
service.new_note(note, john_doe) service.new_note(note, john_doe)
should_create_todo(user: member, target: issue, author: john_doe, action: Todo::MENTIONED, note: note) should_create_todo(user: member, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
should_create_todo(user: guest, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
should_create_todo(user: author, target: issue, author: john_doe, action: Todo::MENTIONED, note: note) should_create_todo(user: author, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
should_not_create_todo(user: john_doe, target: issue, author: john_doe, action: Todo::MENTIONED, note: note) should_not_create_todo(user: john_doe, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
should_not_create_todo(user: non_member, target: issue, author: john_doe, action: Todo::MENTIONED, note: note) should_not_create_todo(user: non_member, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
end end
it 'does not create todo for non project members when leaving a note on a confidential issue' do it 'does not create todo if user can not see the issue when leaving a note on a confidential issue' do
service.new_note(note_on_confidential_issue, john_doe) service.new_note(note_on_confidential_issue, john_doe)
should_create_todo(user: author, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue) should_create_todo(user: author, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue)
should_create_todo(user: assignee, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue) should_create_todo(user: assignee, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue)
should_create_todo(user: member, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue) should_create_todo(user: member, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue)
should_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue) should_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue)
should_not_create_todo(user: guest, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue)
should_not_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue) should_not_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue)
end end
...@@ -245,6 +253,7 @@ describe TodoService, services: true do ...@@ -245,6 +253,7 @@ describe TodoService, services: true do
service.new_merge_request(mr_assigned, author) service.new_merge_request(mr_assigned, author)
should_create_todo(user: member, target: mr_assigned, action: Todo::MENTIONED) should_create_todo(user: member, target: mr_assigned, action: Todo::MENTIONED)
should_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED)
should_not_create_todo(user: author, target: mr_assigned, action: Todo::MENTIONED) should_not_create_todo(user: author, target: mr_assigned, action: Todo::MENTIONED)
should_not_create_todo(user: john_doe, target: mr_assigned, action: Todo::MENTIONED) should_not_create_todo(user: john_doe, target: mr_assigned, action: Todo::MENTIONED)
should_not_create_todo(user: non_member, target: mr_assigned, action: Todo::MENTIONED) should_not_create_todo(user: non_member, target: mr_assigned, action: Todo::MENTIONED)
...@@ -256,6 +265,7 @@ describe TodoService, services: true do ...@@ -256,6 +265,7 @@ describe TodoService, services: true do
service.update_merge_request(mr_assigned, author) service.update_merge_request(mr_assigned, author)
should_create_todo(user: member, target: mr_assigned, action: Todo::MENTIONED) should_create_todo(user: member, target: mr_assigned, action: Todo::MENTIONED)
should_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED)
should_create_todo(user: john_doe, target: mr_assigned, action: Todo::MENTIONED) should_create_todo(user: john_doe, target: mr_assigned, action: Todo::MENTIONED)
should_not_create_todo(user: author, target: mr_assigned, action: Todo::MENTIONED) should_not_create_todo(user: author, target: mr_assigned, action: Todo::MENTIONED)
should_not_create_todo(user: non_member, target: mr_assigned, action: Todo::MENTIONED) should_not_create_todo(user: non_member, target: mr_assigned, action: Todo::MENTIONED)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment