Merge remote-tracking branch 'origin/master' into environments-and-deployments
This commit is contained in:
commit
7d9e8b04c0
|
@ -7,7 +7,8 @@ services:
|
|||
cache:
|
||||
key: "ruby21"
|
||||
paths:
|
||||
- vendor
|
||||
- vendor/apt
|
||||
- vendor/ruby
|
||||
|
||||
variables:
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: "1"
|
||||
|
@ -91,9 +92,7 @@ update-knapsack:
|
|||
- export KNAPSACK_REPORT_PATH=knapsack/spinach_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
|
||||
- export KNAPSACK_GENERATE_REPORT=true
|
||||
- cp knapsack/spinach_report.json ${KNAPSACK_REPORT_PATH}
|
||||
- knapsack spinach "-r rerun"
|
||||
# retry failed tests 3 times
|
||||
- retry '[ ! -e tmp/spinach-rerun.txt ] || bin/spinach -r rerun $(cat tmp/spinach-rerun.txt)'
|
||||
- knapsack spinach "-r rerun" || retry '[ ! -e tmp/spinach-rerun.txt ] || bundle exec spinach -r rerun $(cat tmp/spinach-rerun.txt)'
|
||||
artifacts:
|
||||
paths:
|
||||
- knapsack/
|
||||
|
|
|
@ -349,7 +349,7 @@ Style/MultilineArrayBraceLayout:
|
|||
|
||||
# Avoid multi-line chains of blocks.
|
||||
Style/MultilineBlockChain:
|
||||
Enabled: false
|
||||
Enabled: true
|
||||
|
||||
# Ensures newlines after multiline block do statements.
|
||||
Style/MultilineBlockLayout:
|
||||
|
|
11
CHANGELOG
11
CHANGELOG
|
@ -1,6 +1,8 @@
|
|||
Please view this file on the master branch, on stable branches it's out of date.
|
||||
|
||||
v 8.9.0 (unreleased)
|
||||
- Fix Error 500 when using closes_issues API with an external issue tracker
|
||||
- Add more information into RSS feed for issues (Alexander Matyushentsev)
|
||||
- Bulk assign/unassign labels to issues.
|
||||
- Ability to prioritize labels !4009 / !3205 (Thijs Wouters)
|
||||
- Fix endless redirections when accessing user OAuth applications when they are disabled
|
||||
|
@ -9,9 +11,11 @@ v 8.9.0 (unreleased)
|
|||
- Fix issue with arrow keys not working in search autocomplete dropdown
|
||||
- Make EmailsOnPushWorker use Sidekiq mailers queue
|
||||
- Fix wiki page events' webhook to point to the wiki repository
|
||||
- Don't show tags for revert and cherry-pick operations
|
||||
- Fix issue todo not remove when leave project !4150 (Long Nguyen)
|
||||
- Allow customisable text on the 'nearly there' page after a user signs up
|
||||
- Bump recaptcha gem to 3.0.0 to remove deprecated stoken support
|
||||
- Fix SVG sanitizer to allow more elements
|
||||
- Allow forking projects with restricted visibility level
|
||||
- Added descriptions to notification settings dropdown
|
||||
- Improve note validation to prevent errors when creating invalid note via API
|
||||
|
@ -34,6 +38,9 @@ v 8.9.0 (unreleased)
|
|||
- Links from a wiki page to other wiki pages should be rewritten as expected
|
||||
- Add option to project to only allow merge requests to be merged if the build succeeds (Rui Santos)
|
||||
- Fix issues filter when ordering by milestone
|
||||
- Added artifacts:when to .gitlab-ci.yml - this requires GitLab Runner 1.3
|
||||
- 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'
|
||||
- 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
|
||||
|
@ -55,6 +62,7 @@ v 8.9.0 (unreleased)
|
|||
- Improve error handling importing projects
|
||||
- Remove duplicated notification settings
|
||||
- Put project Files and Commits tabs under Code tab
|
||||
- Decouple global notification level from user model
|
||||
- Replace Colorize with Rainbow for coloring console output in Rake tasks.
|
||||
- Add workhorse controller and API helpers
|
||||
- An indicator is now displayed at the top of the comment field for confidential issues.
|
||||
|
@ -66,6 +74,9 @@ v 8.9.0 (unreleased)
|
|||
- Improved UX of date pickers on issue & milestone forms
|
||||
- Cache on the database if a project has an active external issue tracker.
|
||||
- Put project Labels and Milestones pages links under Issues and Merge Requests tabs as subnav
|
||||
- All classes in the Banzai::ReferenceParser namespace are now instrumented
|
||||
- Remove deprecated issues_tracker and issues_tracker_id from project model
|
||||
- Allow users to create confidential issues in private projects
|
||||
|
||||
v 8.8.5 (unreleased)
|
||||
- Ensure branch cleanup regardless of whether the GitHub import process succeeds
|
||||
|
|
2
Gemfile
2
Gemfile
|
@ -245,7 +245,7 @@ end
|
|||
|
||||
group :development do
|
||||
gem "foreman"
|
||||
gem 'brakeman', '~> 3.2.0', require: false
|
||||
gem 'brakeman', '~> 3.3.0', require: false
|
||||
|
||||
gem 'letter_opener_web', '~> 1.3.0'
|
||||
gem 'quiet_assets', '~> 1.0.2'
|
||||
|
|
28
Gemfile.lock
28
Gemfile.lock
|
@ -97,16 +97,7 @@ GEM
|
|||
bootstrap-sass (3.3.6)
|
||||
autoprefixer-rails (>= 5.2.1)
|
||||
sass (>= 3.3.4)
|
||||
brakeman (3.2.1)
|
||||
erubis (~> 2.6)
|
||||
haml (>= 3.0, < 5.0)
|
||||
highline (>= 1.6.20, < 2.0)
|
||||
ruby2ruby (~> 2.3.0)
|
||||
ruby_parser (~> 3.8.1)
|
||||
safe_yaml (>= 1.0)
|
||||
sass (~> 3.0)
|
||||
slim (>= 1.3.6, < 4.0)
|
||||
terminal-table (~> 1.4)
|
||||
brakeman (3.3.2)
|
||||
browser (2.0.3)
|
||||
builder (3.2.2)
|
||||
bullet (5.0.0)
|
||||
|
@ -338,7 +329,6 @@ GEM
|
|||
hashie (3.4.3)
|
||||
health_check (1.5.1)
|
||||
rails (>= 2.3.0)
|
||||
highline (1.7.8)
|
||||
hipchat (1.5.2)
|
||||
httparty
|
||||
mimemagic
|
||||
|
@ -642,10 +632,7 @@ GEM
|
|||
ruby-saml (1.1.2)
|
||||
nokogiri (>= 1.5.10)
|
||||
uuid (~> 2.3)
|
||||
ruby2ruby (2.3.0)
|
||||
ruby_parser (~> 3.1)
|
||||
sexp_processor (~> 4.0)
|
||||
ruby_parser (3.8.1)
|
||||
ruby_parser (3.8.2)
|
||||
sexp_processor (~> 4.1)
|
||||
rubyntlm (0.5.2)
|
||||
rubypants (0.2.0)
|
||||
|
@ -655,7 +642,7 @@ GEM
|
|||
safe_yaml (1.0.4)
|
||||
sanitize (2.1.0)
|
||||
nokogiri (>= 1.4.4)
|
||||
sass (3.4.21)
|
||||
sass (3.4.22)
|
||||
sass-rails (5.0.4)
|
||||
railties (>= 4.0.0, < 5.0)
|
||||
sass (~> 3.1)
|
||||
|
@ -704,9 +691,6 @@ GEM
|
|||
tilt (>= 1.3, < 3)
|
||||
six (0.2.0)
|
||||
slack-notifier (1.2.1)
|
||||
slim (3.0.6)
|
||||
temple (~> 0.7.3)
|
||||
tilt (>= 1.3.3, < 2.1)
|
||||
slop (3.6.0)
|
||||
spinach (0.8.10)
|
||||
colorize
|
||||
|
@ -747,10 +731,8 @@ GEM
|
|||
railties (>= 3.2.5, < 6)
|
||||
teaspoon-jasmine (2.2.0)
|
||||
teaspoon (>= 1.0.0)
|
||||
temple (0.7.6)
|
||||
term-ansicolor (1.3.2)
|
||||
tins (~> 1.0)
|
||||
terminal-table (1.5.2)
|
||||
test_after_commit (0.4.2)
|
||||
activerecord (>= 3.2)
|
||||
thin (1.6.4)
|
||||
|
@ -759,7 +741,7 @@ GEM
|
|||
rack (~> 1.0)
|
||||
thor (0.19.1)
|
||||
thread_safe (0.3.5)
|
||||
tilt (2.0.2)
|
||||
tilt (2.0.5)
|
||||
timecop (0.8.1)
|
||||
timfel-krb5-auth (0.8.3)
|
||||
tinder (1.10.1)
|
||||
|
@ -848,7 +830,7 @@ DEPENDENCIES
|
|||
better_errors (~> 1.0.1)
|
||||
binding_of_caller (~> 0.7.2)
|
||||
bootstrap-sass (~> 3.3.0)
|
||||
brakeman (~> 3.2.0)
|
||||
brakeman (~> 3.3.0)
|
||||
browser (~> 2.0.3)
|
||||
bullet
|
||||
bundler-audit
|
||||
|
|
|
@ -162,19 +162,6 @@ $ ->
|
|||
$el.data('placement') || 'bottom'
|
||||
)
|
||||
|
||||
$('.header-logo .home').tooltip(
|
||||
placement: (_, el) ->
|
||||
$el = $(el)
|
||||
if $('.page-with-sidebar').hasClass('page-sidebar-collapsed') then 'right' else 'bottom'
|
||||
container: 'body'
|
||||
)
|
||||
|
||||
$('.page-with-sidebar').tooltip(
|
||||
selector: '.sidebar-collapsed .nav-sidebar a, .sidebar-collapsed a.sidebar-user'
|
||||
placement: 'right'
|
||||
container: 'body'
|
||||
)
|
||||
|
||||
# Form submitter
|
||||
$('.trigger-submit').on 'change', ->
|
||||
$(@).parents('form').submit()
|
||||
|
@ -207,6 +194,7 @@ $ ->
|
|||
|
||||
$('.navbar-toggle').on 'click', ->
|
||||
$('.header-content .title').toggle()
|
||||
$('.header-content .header-logo').toggle()
|
||||
$('.header-content .navbar-collapse').toggle()
|
||||
$('.navbar-toggle').toggleClass('active')
|
||||
$('.navbar-toggle i').toggleClass("fa-angle-right fa-angle-left")
|
||||
|
@ -241,7 +229,6 @@ $ ->
|
|||
$this.attr 'value', $this.val()
|
||||
|
||||
$sidebarGutterToggle = $('.js-sidebar-toggle')
|
||||
$navIconToggle = $('.toggle-nav-collapse')
|
||||
|
||||
$(document)
|
||||
.off 'breakpoint:change'
|
||||
|
@ -251,10 +238,6 @@ $ ->
|
|||
if $gutterIcon.hasClass('fa-angle-double-right')
|
||||
$sidebarGutterToggle.trigger('click')
|
||||
|
||||
$navIcon = $navIconToggle.find('.fa')
|
||||
if $navIcon.hasClass('fa-angle-left')
|
||||
$navIconToggle.trigger('click')
|
||||
|
||||
fitSidebarForSize = ->
|
||||
oldBootstrapBreakpoint = bootstrapBreakpoint
|
||||
bootstrapBreakpoint = bp.getBreakpointSize()
|
||||
|
|
|
@ -97,13 +97,22 @@ class @IssuableBulkActions
|
|||
$labels = @form.find('.labels-filter input[name="update[label_ids][]"]')
|
||||
|
||||
$labels.each (k, label) ->
|
||||
labelIds.push $(label).val() if label
|
||||
labelIds.push parseInt($(label).val()) if label
|
||||
|
||||
labelIds
|
||||
|
||||
###*
|
||||
* Just an alias of @getUnmarkedIndeterminedLabels
|
||||
* @return {Array} Array of labels
|
||||
* Returns Label IDs that will be removed from issue selection
|
||||
* @return {Array} Array of labels IDs
|
||||
###
|
||||
getLabelsToRemove: ->
|
||||
@getUnmarkedIndeterminedLabels()
|
||||
result = []
|
||||
indeterminatedLabels = @getUnmarkedIndeterminedLabels()
|
||||
labelsToApply = @getLabelsToApply()
|
||||
|
||||
indeterminatedLabels.map (id) ->
|
||||
# We need to exclude label IDs that will be applied
|
||||
# By not doing this will cause issues from selection to not add labels at all
|
||||
result.push(id) if labelsToApply.indexOf(id) is -1
|
||||
|
||||
result
|
||||
|
|
|
@ -47,4 +47,4 @@ $ ->
|
|||
# Make logo clickable as part of a workaround for Safari visited
|
||||
# link behaviour (See !2690).
|
||||
$('#logo').on 'click', ->
|
||||
$('#js-shortcuts-home').get(0).click()
|
||||
Turbolinks.visit('/')
|
||||
|
|
|
@ -4,8 +4,6 @@ expanded = 'page-sidebar-expanded'
|
|||
toggleSidebar = ->
|
||||
$('.page-with-sidebar').toggleClass("#{collapsed} #{expanded}")
|
||||
$('header').toggleClass("header-collapsed header-expanded")
|
||||
$('.toggle-nav-collapse i').toggleClass("fa-angle-right fa-angle-left")
|
||||
$.cookie("collapsed_nav", $('.page-with-sidebar').hasClass(collapsed), { path: '/' })
|
||||
|
||||
setTimeout ( ->
|
||||
niceScrollBars = $('.nicescroll').niceScroll();
|
||||
|
@ -17,10 +15,3 @@ $(document).on("click", '.toggle-nav-collapse, .side-nav-toggle', (e) ->
|
|||
|
||||
toggleSidebar()
|
||||
)
|
||||
|
||||
$ ->
|
||||
size = bp.getBreakpointSize()
|
||||
|
||||
if size is "xs" or size is "sm"
|
||||
if $('.page-with-sidebar').hasClass(expanded)
|
||||
toggleSidebar()
|
||||
|
|
|
@ -8,32 +8,14 @@
|
|||
*/
|
||||
@mixin gitlab-theme($color-light, $color, $color-darker, $color-dark) {
|
||||
.page-with-sidebar {
|
||||
.header-logo {
|
||||
background: $color-darker;
|
||||
|
||||
a {
|
||||
color: $color-light;
|
||||
|
||||
h3 {
|
||||
color: $color-light;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: $color-dark;
|
||||
a {
|
||||
color: $white-light;
|
||||
|
||||
h3 {
|
||||
color: $white-light;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.collapse-nav a {
|
||||
color: $white-light;
|
||||
color: $color-light;
|
||||
background: $color;
|
||||
|
||||
&:hover {
|
||||
color: $white-light;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-wrapper {
|
||||
|
|
|
@ -109,10 +109,8 @@ header {
|
|||
position: relative;
|
||||
height: $header-height;
|
||||
padding-right: 40px;
|
||||
|
||||
@media (max-width: $screen-xs-min) {
|
||||
padding-left: 40px;
|
||||
}
|
||||
padding-left: 30px;
|
||||
transition-duration: .3s;
|
||||
|
||||
@media (min-width: $screen-sm-min) {
|
||||
padding-right: 0;
|
||||
|
@ -122,9 +120,29 @@ header {
|
|||
margin-top: -5px;
|
||||
}
|
||||
|
||||
.header-logo {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
margin-left: -18px;
|
||||
top: 7px;
|
||||
transition-duration: .3s;
|
||||
z-index: 999;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@media (max-width: $screen-xs-max) {
|
||||
right: 25px;
|
||||
left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
font-size: 19px;
|
||||
max-width: 400px;
|
||||
display: inline-block;
|
||||
line-height: $header-height;
|
||||
font-weight: normal;
|
||||
color: $gl-text-color;
|
||||
|
@ -133,6 +151,10 @@ header {
|
|||
vertical-align: top;
|
||||
white-space: nowrap;
|
||||
|
||||
@media (max-width: $screen-sm-max) {
|
||||
max-width: 190px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $gl-text-color;
|
||||
&:hover {
|
||||
|
@ -160,6 +182,10 @@ header {
|
|||
.navbar-collapse {
|
||||
float: right;
|
||||
border-top: none;
|
||||
|
||||
@media (max-width: $screen-xs-max) {
|
||||
float: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -176,17 +202,20 @@ header {
|
|||
margin-left: 0;
|
||||
|
||||
.header-content {
|
||||
padding-left: 30px;
|
||||
transition-duration: .3s;
|
||||
|
||||
@media (min-width: $screen-sm-max) {
|
||||
padding-left: 30px;
|
||||
transition-duration: .3s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.header-expanded {
|
||||
margin-left: 0;
|
||||
.tanuki-shape {
|
||||
transition: all 0.8s;
|
||||
|
||||
.header-content {
|
||||
margin-left: $sidebar_width;
|
||||
transition-duration: .3s;
|
||||
&:hover, &.highlight {
|
||||
fill: rgb(255, 255, 255);
|
||||
transition: all 0.1s;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
@mixin fade($gradient-direction, $rgba, $gradient-color) {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
z-index: 2;
|
||||
position: absolute;
|
||||
bottom: 12px;
|
||||
width: 43px;
|
||||
|
@ -68,6 +69,7 @@
|
|||
}
|
||||
|
||||
&.sub-nav {
|
||||
text-align: center;
|
||||
background-color: $background-color;
|
||||
|
||||
.container-fluid {
|
||||
|
@ -171,7 +173,6 @@
|
|||
> form {
|
||||
display: inline-block;
|
||||
margin-top: -1px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.icon-label {
|
||||
|
@ -250,6 +251,7 @@
|
|||
background: $background-color;
|
||||
border-bottom: 1px solid $border-color;
|
||||
transition-duration: .3s;
|
||||
text-align: center;
|
||||
|
||||
.container-fluid {
|
||||
position: relative;
|
||||
|
@ -352,7 +354,7 @@
|
|||
|
||||
.fade-right {
|
||||
@media (min-width: $screen-xs-max) {
|
||||
right: 67px;
|
||||
right: 68px;
|
||||
}
|
||||
@media (max-width: $screen-xs-min) {
|
||||
right: 0;
|
||||
|
|
|
@ -35,24 +35,11 @@
|
|||
}
|
||||
|
||||
.sidebar-wrapper {
|
||||
.header-logo {
|
||||
height: $header-height;
|
||||
padding: 8px 26px;
|
||||
width: $sidebar_width;
|
||||
position: fixed;
|
||||
z-index: 999;
|
||||
overflow: hidden;
|
||||
transition-duration: .3s;
|
||||
|
||||
&:hover {
|
||||
background-color: #eee;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-user {
|
||||
padding: 15px 22px;
|
||||
position: fixed;
|
||||
bottom: 40px;
|
||||
bottom: 0;
|
||||
width: $sidebar_width;
|
||||
overflow: hidden;
|
||||
transition-duration: .3s;
|
||||
|
@ -97,10 +84,10 @@
|
|||
}
|
||||
|
||||
a {
|
||||
text-align: center;
|
||||
padding: 8px;
|
||||
width: $sidebar_width;
|
||||
padding: 7px 15px 7px 23px;
|
||||
font-size: $gl-font-size;
|
||||
color: $gray;
|
||||
line-height: 24px;
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
font-weight: normal;
|
||||
|
@ -118,10 +105,9 @@
|
|||
font-size: 16px;
|
||||
}
|
||||
|
||||
.nav-link-text {
|
||||
margin-top: 3px;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
i,
|
||||
svg {
|
||||
margin-right: 13px;
|
||||
}
|
||||
|
||||
&.back-link i {
|
||||
|
@ -129,6 +115,12 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.count {
|
||||
float: right;
|
||||
padding: 0 8px;
|
||||
@include border-radius(6px);
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-subnav {
|
||||
|
@ -143,11 +135,12 @@
|
|||
.collapse-nav a {
|
||||
width: $sidebar_width;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
font-size: 13px;
|
||||
padding: 5px 0;
|
||||
font-size: 18px;
|
||||
background: transparent;
|
||||
height: 40px;
|
||||
height: 50px;
|
||||
text-align: center;
|
||||
line-height: 40px;
|
||||
transition-duration: .3s;
|
||||
|
@ -170,25 +163,8 @@
|
|||
.sidebar-wrapper {
|
||||
width: 0;
|
||||
|
||||
.header-logo {
|
||||
width: 0;
|
||||
padding: 8px 0;
|
||||
|
||||
a {
|
||||
padding-left: ($sidebar_collapsed_width - 36) / 2;
|
||||
|
||||
.gitlab-text-container {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#logo {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nav-sidebar {
|
||||
width: $sidebar_collapsed_width;
|
||||
width: 0;
|
||||
|
||||
li {
|
||||
width: auto;
|
||||
|
@ -203,6 +179,10 @@
|
|||
|
||||
.collapse-nav a {
|
||||
width: 0;
|
||||
|
||||
i {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-user {
|
||||
|
@ -218,9 +198,8 @@
|
|||
}
|
||||
|
||||
.page-sidebar-expanded {
|
||||
padding-left: $sidebar_width;
|
||||
|
||||
@media (max-width: $screen-xs-min) {
|
||||
@media (max-width: $screen-sm-max) {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
|
@ -241,20 +220,6 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.layout-nav {
|
||||
@media (max-width: $screen-xs-min) {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
@media (min-width: $screen-xs-min) and (max-width: $screen-md-min) {
|
||||
padding-right: 90px;
|
||||
}
|
||||
|
||||
@media (min-width: $screen-md-min) {
|
||||
padding-right: $sidebar_width;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.right-sidebar-collapsed {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
* Layout
|
||||
*/
|
||||
$sidebar_collapsed_width: 62px;
|
||||
$sidebar_width: 90px;
|
||||
$sidebar_width: 220px;
|
||||
$gutter_collapsed_width: 62px;
|
||||
$gutter_width: 290px;
|
||||
$gutter_inner_width: 258px;
|
||||
|
|
|
@ -42,7 +42,7 @@ class JwtController < ApplicationController
|
|||
end
|
||||
|
||||
def authenticate_user(login, password)
|
||||
user = Gitlab::Auth.find_in_gitlab_or_ldap(login, password)
|
||||
user = Gitlab::Auth.find_with_user_password(login, password)
|
||||
Gitlab::Auth.rate_limit!(request.ip, success: user.present?, login: login)
|
||||
user
|
||||
end
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
class Profiles::NotificationsController < Profiles::ApplicationController
|
||||
def show
|
||||
@user = current_user
|
||||
@group_notifications = current_user.notification_settings.for_groups
|
||||
@project_notifications = current_user.notification_settings.for_projects
|
||||
@user = current_user
|
||||
@group_notifications = current_user.notification_settings.for_groups
|
||||
@project_notifications = current_user.notification_settings.for_projects
|
||||
@global_notification_setting = current_user.global_notification_setting
|
||||
end
|
||||
|
||||
def update
|
||||
if current_user.update_attributes(user_params)
|
||||
if current_user.update_attributes(user_params) && update_notification_settings
|
||||
flash[:notice] = "Notification settings saved"
|
||||
else
|
||||
flash[:alert] = "Failed to save new settings"
|
||||
|
@ -16,6 +17,18 @@ class Profiles::NotificationsController < Profiles::ApplicationController
|
|||
end
|
||||
|
||||
def user_params
|
||||
params.require(:user).permit(:notification_email, :notification_level)
|
||||
params.require(:user).permit(:notification_email)
|
||||
end
|
||||
|
||||
def global_notification_setting_params
|
||||
params.require(:global_notification_setting).permit(:level)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def update_notification_settings
|
||||
return true unless global_notification_setting_params
|
||||
|
||||
current_user.global_notification_setting.update_attributes(global_notification_setting_params)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -43,7 +43,7 @@ class Projects::GitHttpController < Projects::ApplicationController
|
|||
return if project && project.public? && upload_pack?
|
||||
|
||||
authenticate_or_request_with_http_basic do |login, password|
|
||||
auth_result = Gitlab::Auth.find(login, password, project: project, ip: request.ip)
|
||||
auth_result = Gitlab::Auth.find_for_git_client(login, password, project: project, ip: request.ip)
|
||||
|
||||
if auth_result.type == :ci && upload_pack?
|
||||
@ci = true
|
||||
|
|
|
@ -51,7 +51,7 @@ class SnippetsFinder
|
|||
snippets = project.snippets.fresh
|
||||
|
||||
if current_user
|
||||
if project.team.member?(current_user.id) || current_user.admin?
|
||||
if project.team.member?(current_user) || current_user.admin?
|
||||
snippets
|
||||
else
|
||||
snippets.public_and_internal
|
||||
|
|
|
@ -14,4 +14,8 @@ module BranchesHelper
|
|||
|
||||
::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(branch_name)
|
||||
end
|
||||
|
||||
def project_branches
|
||||
options_for_select(@project.repository.branch_names, @project.default_branch)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -36,13 +36,7 @@ module NavHelper
|
|||
end
|
||||
|
||||
def nav_header_class
|
||||
class_name =
|
||||
if nav_menu_collapsed?
|
||||
"header-collapsed"
|
||||
else
|
||||
"header-expanded"
|
||||
end
|
||||
class_name += " with-horizontal-nav" if defined?(nav) && nav
|
||||
class_name = " with-horizontal-nav" if defined?(nav) && nav
|
||||
class_name
|
||||
end
|
||||
|
||||
|
|
|
@ -61,4 +61,23 @@ module NotificationsHelper
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
def notification_level_radio_buttons
|
||||
html = ""
|
||||
|
||||
NotificationSetting.levels.each_key do |level|
|
||||
level = level.to_sym
|
||||
next if level == :global
|
||||
|
||||
html << content_tag(:div, class: "radio") do
|
||||
content_tag(:label, { value: level }) do
|
||||
radio_button_tag(:"global_notification_setting[level]", level, @global_notification_setting.level.to_sym == level) +
|
||||
content_tag(:div, level.to_s.capitalize, class: "level-title") +
|
||||
content_tag(:p, notification_description(level))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
html.html_safe
|
||||
end
|
||||
end
|
||||
|
|
|
@ -543,7 +543,7 @@ class Ability
|
|||
def filter_confidential_issues_abilities(user, issue, rules)
|
||||
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(:read_issue)
|
||||
rules.delete(:update_issue)
|
||||
|
|
|
@ -51,10 +51,18 @@ class Issue < ActiveRecord::Base
|
|||
end
|
||||
|
||||
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?
|
||||
|
||||
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
|
||||
|
||||
def self.reference_prefix
|
||||
|
|
|
@ -88,22 +88,9 @@ class Note < ActiveRecord::Base
|
|||
table = arel_table
|
||||
pattern = "%#{query}%"
|
||||
|
||||
found_notes = joins('LEFT JOIN issues ON issues.id = noteable_id').
|
||||
where(table[:note].matches(pattern))
|
||||
|
||||
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
|
||||
Note.joins('LEFT JOIN issues ON issues.id = noteable_id').
|
||||
where(table[:note].matches(pattern)).
|
||||
merge(Issue.visible_to_user(as_user))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ class NotificationSetting < ActiveRecord::Base
|
|||
belongs_to :source, polymorphic: true
|
||||
|
||||
validates :user, presence: true
|
||||
validates :source, presence: true
|
||||
validates :level, presence: true
|
||||
validates :user_id, uniqueness: { scope: [:source_type, :source_id],
|
||||
message: "already exists in source",
|
||||
|
|
|
@ -148,7 +148,6 @@ class Project < ActiveRecord::Base
|
|||
message: Gitlab::Regex.project_path_regex_message }
|
||||
validates :issues_enabled, :merge_requests_enabled,
|
||||
:wiki_enabled, inclusion: { in: [true, false] }
|
||||
validates :issues_tracker_id, length: { maximum: 255 }, allow_blank: true
|
||||
validates :namespace, presence: true
|
||||
validates_uniqueness_of :name, scope: :namespace_id
|
||||
validates_uniqueness_of :path, scope: :namespace_id
|
||||
|
@ -591,10 +590,6 @@ class Project < ActiveRecord::Base
|
|||
update_column(:has_external_issue_tracker, services.external_issue_trackers.any?)
|
||||
end
|
||||
|
||||
def can_have_issues_tracker_id?
|
||||
self.issues_enabled && !self.default_issues_tracker?
|
||||
end
|
||||
|
||||
def build_missing_services
|
||||
services_templates = Service.where(template: true)
|
||||
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
class BambooService < CiService
|
||||
include HTTParty
|
||||
|
||||
prop_accessor :bamboo_url, :build_key, :username, :password
|
||||
|
||||
validates :bamboo_url, presence: true, url: true, if: :activated?
|
||||
|
@ -61,18 +59,7 @@ class BambooService < CiService
|
|||
end
|
||||
|
||||
def build_info(sha)
|
||||
url = URI.join(bamboo_url, "/rest/api/latest/result?label=#{sha}").to_s
|
||||
|
||||
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
|
||||
@response = get_path("rest/api/latest/result?label=#{sha}")
|
||||
end
|
||||
|
||||
def build_page(sha, ref)
|
||||
|
@ -80,11 +67,11 @@ class BambooService < CiService
|
|||
|
||||
if @response.code != 200 || @response['results']['results']['size'] == '0'
|
||||
# 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
|
||||
# If actual build link is available, go to build result page.
|
||||
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
|
||||
|
||||
|
@ -112,8 +99,27 @@ class BambooService < CiService
|
|||
def execute(data)
|
||||
return unless supported_events.include?(data[:object_kind])
|
||||
|
||||
# Bamboo requires a GET and does not take any data.
|
||||
url = URI.join(bamboo_url, "/updateAndBuild.action?buildKey=#{build_key}").to_s
|
||||
self.class.get(url, verify: false)
|
||||
get_path("updateAndBuild.action?buildKey=#{build_key}")
|
||||
end
|
||||
|
||||
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
|
||||
|
|
|
@ -38,9 +38,9 @@ class IssueTrackerService < Service
|
|||
if enabled_in_gitlab_config
|
||||
self.properties = {
|
||||
title: issues_tracker['title'],
|
||||
project_url: add_issues_tracker_id(issues_tracker['project_url']),
|
||||
issues_url: add_issues_tracker_id(issues_tracker['issues_url']),
|
||||
new_issue_url: add_issues_tracker_id(issues_tracker['new_issue_url'])
|
||||
project_url: issues_tracker['project_url'],
|
||||
issues_url: issues_tracker['issues_url'],
|
||||
new_issue_url: issues_tracker['new_issue_url']
|
||||
}
|
||||
else
|
||||
self.properties = {}
|
||||
|
@ -83,16 +83,4 @@ class IssueTrackerService < Service
|
|||
def issues_tracker
|
||||
Gitlab.config.issues_tracker[to_param]
|
||||
end
|
||||
|
||||
def add_issues_tracker_id(url)
|
||||
if self.project
|
||||
id = self.project.issues_tracker_id
|
||||
|
||||
if id
|
||||
url = url.gsub(":issues_tracker_id", id)
|
||||
end
|
||||
end
|
||||
|
||||
url
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
class TeamcityService < CiService
|
||||
include HTTParty
|
||||
|
||||
prop_accessor :teamcity_url, :build_type, :username, :password
|
||||
|
||||
validates :teamcity_url, presence: true, url: true, if: :activated?
|
||||
|
@ -64,15 +62,7 @@ class TeamcityService < CiService
|
|||
end
|
||||
|
||||
def build_info(sha)
|
||||
url = URI.join(
|
||||
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)
|
||||
@response = get_path("httpAuth/app/rest/builds/branch:unspecified:any,number:#{sha}")
|
||||
end
|
||||
|
||||
def build_page(sha, ref)
|
||||
|
@ -81,14 +71,11 @@ class TeamcityService < CiService
|
|||
if @response.code != 200
|
||||
# If actual build link can't be determined,
|
||||
# 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
|
||||
# If actual build link is available, go to build result page.
|
||||
built_id = @response['build']['id']
|
||||
URI.join(
|
||||
teamcity_url,
|
||||
"/viewLog.html?buildId=#{built_id}&buildTypeId=#{build_type}"
|
||||
).to_s
|
||||
build_url("viewLog.html?buildId=#{built_id}&buildTypeId=#{build_type}")
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -123,8 +110,8 @@ class TeamcityService < CiService
|
|||
|
||||
branch = Gitlab::Git.ref_name(data[:ref])
|
||||
|
||||
self.class.post(
|
||||
URI.join(teamcity_url, '/httpAuth/app/rest/buildQueue').to_s,
|
||||
HTTParty.post(
|
||||
build_url('httpAuth/app/rest/buildQueue'),
|
||||
body: "<build branchName=\"#{branch}\">"\
|
||||
"<buildType id=\"#{build_type}\"/>"\
|
||||
'</build>',
|
||||
|
@ -132,4 +119,18 @@ class TeamcityService < CiService
|
|||
basic_auth: auth
|
||||
)
|
||||
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
|
||||
|
|
|
@ -131,8 +131,14 @@ class ProjectTeam
|
|||
max_member_access(user.id) == Gitlab::Access::MASTER
|
||||
end
|
||||
|
||||
def member?(user_id)
|
||||
!!find_member(user_id)
|
||||
def member?(user, min_member_access = nil)
|
||||
member = !!find_member(user.id)
|
||||
|
||||
if min_member_access
|
||||
member && max_member_access(user.id) >= min_member_access
|
||||
else
|
||||
member
|
||||
end
|
||||
end
|
||||
|
||||
def human_max_access(user_id)
|
||||
|
|
|
@ -10,6 +10,8 @@ class User < ActiveRecord::Base
|
|||
include CaseSensitivity
|
||||
include TokenAuthenticatable
|
||||
|
||||
DEFAULT_NOTIFICATION_LEVEL = :participating
|
||||
|
||||
add_authentication_token_field :authentication_token
|
||||
|
||||
default_value_for :admin, false
|
||||
|
@ -99,7 +101,6 @@ class User < ActiveRecord::Base
|
|||
presence: true,
|
||||
uniqueness: { case_sensitive: false }
|
||||
|
||||
validates :notification_level, presence: true
|
||||
validate :namespace_uniq, if: ->(user) { user.username_changed? }
|
||||
validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? }
|
||||
validate :unique_email, if: ->(user) { user.email_changed? }
|
||||
|
@ -133,13 +134,6 @@ class User < ActiveRecord::Base
|
|||
# Note: When adding an option, it MUST go on the end of the array.
|
||||
enum project_view: [:readme, :activity, :files]
|
||||
|
||||
# Notification level
|
||||
# Note: When adding an option, it MUST go on the end of the array.
|
||||
#
|
||||
# TODO: Add '_prefix: :notification' to enum when update to Rails 5. https://github.com/rails/rails/pull/19813
|
||||
# Because user.notification_disabled? is much better than user.disabled?
|
||||
enum notification_level: [:disabled, :participating, :watch, :global, :mention]
|
||||
|
||||
alias_attribute :private_token, :authentication_token
|
||||
|
||||
delegate :path, to: :namespace, allow_nil: true, prefix: true
|
||||
|
@ -411,8 +405,8 @@ class User < ActiveRecord::Base
|
|||
end
|
||||
|
||||
# Returns projects user is authorized to access.
|
||||
def authorized_projects
|
||||
Project.where("projects.id IN (#{projects_union.to_sql})")
|
||||
def authorized_projects(min_access_level = nil)
|
||||
Project.where("projects.id IN (#{projects_union(min_access_level).to_sql})")
|
||||
end
|
||||
|
||||
def viewable_starred_projects
|
||||
|
@ -800,6 +794,17 @@ class User < ActiveRecord::Base
|
|||
notification_settings.find_or_initialize_by(source: source)
|
||||
end
|
||||
|
||||
# Lazy load global notification setting
|
||||
# Initializes User setting with Participating level if setting not persisted
|
||||
def global_notification_setting
|
||||
return @global_notification_setting if defined?(@global_notification_setting)
|
||||
|
||||
@global_notification_setting = notification_settings.find_or_initialize_by(source: nil)
|
||||
@global_notification_setting.update_attributes(level: NotificationSetting.levels[DEFAULT_NOTIFICATION_LEVEL]) unless @global_notification_setting.persisted?
|
||||
|
||||
@global_notification_setting
|
||||
end
|
||||
|
||||
def assigned_open_merge_request_count(force: false)
|
||||
Rails.cache.fetch(['users', id, 'assigned_open_merge_request_count'], force: force) do
|
||||
assigned_merge_requests.opened.count
|
||||
|
@ -819,11 +824,19 @@ class User < ActiveRecord::Base
|
|||
|
||||
private
|
||||
|
||||
def projects_union
|
||||
Gitlab::SQL::Union.new([personal_projects.select(:id),
|
||||
groups_projects.select(:id),
|
||||
projects.select(:id),
|
||||
groups.joins(:shared_projects).select(:project_id)])
|
||||
def projects_union(min_access_level = nil)
|
||||
relations = [personal_projects.select(:id),
|
||||
groups_projects.select(:id),
|
||||
projects.select(: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
|
||||
|
||||
def ci_projects_union
|
||||
|
|
|
@ -279,10 +279,11 @@ class NotificationService
|
|||
end
|
||||
|
||||
def users_with_global_level_watch(ids)
|
||||
User.where(
|
||||
id: ids,
|
||||
notification_level: NotificationSetting.levels[:watch]
|
||||
).pluck(:id)
|
||||
NotificationSetting.where(
|
||||
user_id: ids,
|
||||
source_type: nil,
|
||||
level: NotificationSetting.levels[:watch]
|
||||
).pluck(:user_id)
|
||||
end
|
||||
|
||||
# Build a list of users based on project notifcation settings
|
||||
|
@ -352,7 +353,9 @@ class NotificationService
|
|||
users = users.reject(&:blocked?)
|
||||
|
||||
users.reject do |user|
|
||||
next user.notification_level == level unless project
|
||||
global_notification_setting = user.global_notification_setting
|
||||
|
||||
next global_notification_setting.level == level unless project
|
||||
|
||||
setting = user.notification_settings_for(project)
|
||||
|
||||
|
@ -361,13 +364,13 @@ class NotificationService
|
|||
end
|
||||
|
||||
# reject users who globally set mention notification and has no setting per project/group
|
||||
next user.notification_level == level unless setting
|
||||
next global_notification_setting.level == level unless setting
|
||||
|
||||
# reject users who set mention notification in project
|
||||
next true if setting.level == level
|
||||
|
||||
# reject users who have mention level in project and disabled in global settings
|
||||
setting.global? && user.notification_level == level
|
||||
setting.global? && global_notification_setting.level == level
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -456,7 +459,6 @@ class NotificationService
|
|||
|
||||
def build_recipients(target, project, current_user, action: nil, previous_assignee: nil)
|
||||
recipients = target.participants(current_user)
|
||||
|
||||
recipients = add_project_watchers(recipients, project)
|
||||
recipients = reject_mention_users(recipients, project)
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
|
|||
xml.link href: issues_dashboard_url(format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml"
|
||||
xml.link href: issues_dashboard_url, rel: "alternate", type: "text/html"
|
||||
xml.id issues_dashboard_url
|
||||
xml.updated @issues.first.created_at.xmlschema if @issues.any?
|
||||
xml.updated @issues.first.created_at.xmlschema if @issues.reorder(nil).any?
|
||||
|
||||
xml << render(partial: 'issues/issue', collection: @issues) if @issues.any?
|
||||
xml << render(partial: 'issues/issue', collection: @issues) if @issues.reorder(nil).any?
|
||||
end
|
||||
|
|
|
@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
|
|||
xml.link href: issues_group_url(format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml"
|
||||
xml.link href: issues_group_url, rel: "alternate", type: "text/html"
|
||||
xml.id issues_group_url
|
||||
xml.updated @issues.first.created_at.xmlschema if @issues.any?
|
||||
xml.updated @issues.first.created_at.xmlschema if @issues.reorder(nil).any?
|
||||
|
||||
xml << render(partial: 'issues/issue', collection: @issues) if @issues.any?
|
||||
xml << render(partial: 'issues/issue', collection: @issues) if @issues.reorder(nil).any?
|
||||
end
|
||||
|
|
|
@ -5,10 +5,28 @@ xml.entry do
|
|||
xml.updated issue.created_at.xmlschema
|
||||
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.email issue.author_email
|
||||
end
|
||||
|
||||
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
|
||||
|
|
|
@ -1,4 +1 @@
|
|||
- if nav_menu_collapsed?
|
||||
= link_to icon('angle-right'), '#', class: 'toggle-nav-collapse', title: "Open/Close"
|
||||
- else
|
||||
= link_to icon('angle-left'), '#', class: 'toggle-nav-collapse', title: "Open/Close"
|
||||
= link_to icon('bars'), '#', class: 'toggle-nav-collapse', title: "Open/Close"
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
.page-with-sidebar{ class: "#{page_sidebar_class} #{page_gutter_class}" }
|
||||
.page-with-sidebar.page-sidebar-collapsed{ class: "#{page_gutter_class}" }
|
||||
.sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
|
||||
= link_to root_path, class: 'gitlab-text-container-link', title: 'Dashboard', id: 'js-shortcuts-home' do
|
||||
.header-logo
|
||||
#logo
|
||||
= brand_header_logo
|
||||
|
||||
- if defined?(sidebar) && sidebar
|
||||
= render "layouts/nav/#{sidebar}"
|
||||
|
@ -16,7 +12,9 @@
|
|||
= render partial: 'layouts/collapse_button'
|
||||
- if current_user
|
||||
= link_to current_user, class: 'sidebar-user', title: "Profile", data: {user: current_user.username} do
|
||||
= image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s46'
|
||||
= image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s36'
|
||||
.username
|
||||
= current_user.username
|
||||
- if defined?(nav) && nav
|
||||
.layout-nav
|
||||
.container-fluid
|
||||
|
|
|
@ -1,12 +1,6 @@
|
|||
.page-with-sidebar{ class: page_sidebar_class }
|
||||
= render "layouts/broadcast"
|
||||
.sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
|
||||
.header-logo
|
||||
%a#logo
|
||||
= brand_header_logo
|
||||
= link_to root_path, class: 'gitlab-text-container-link', title: 'Dashboard', id: 'js-shortcuts-home' do
|
||||
.gitlab-text-container
|
||||
%h3 GitLab
|
||||
|
||||
- if defined?(sidebar) && sidebar
|
||||
= render "layouts/ci/#{sidebar}"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
%header.navbar.navbar-fixed-top.navbar-gitlab{ class: nav_header_class }
|
||||
%header.navbar.navbar-fixed-top.navbar-gitlab.header-collapsed{ class: nav_header_class }
|
||||
%div{ class: fluid_layout ? "container-fluid" : "container-fluid" }
|
||||
.header-content
|
||||
%button.side-nav-toggle{type: 'button'}
|
||||
|
@ -50,6 +50,10 @@
|
|||
|
||||
%h1.title= title
|
||||
|
||||
.header-logo
|
||||
#logo
|
||||
= brand_header_logo
|
||||
|
||||
= yield :header_content
|
||||
|
||||
= render 'shared/outdated_browser'
|
||||
|
|
|
@ -2,102 +2,106 @@
|
|||
= nav_link(controller: :dashboard, html_options: {class: 'home'}) do
|
||||
= link_to admin_root_path, title: 'Overview' do
|
||||
= icon('dashboard fw')
|
||||
.nav-link-text
|
||||
%span
|
||||
Overview
|
||||
= nav_link(controller: [:admin, :projects]) do
|
||||
= link_to admin_namespaces_projects_path, title: 'Projects' do
|
||||
= icon('cube fw')
|
||||
.nav-link-text
|
||||
%span
|
||||
Projects
|
||||
= nav_link(controller: :users) do
|
||||
= link_to admin_users_path, title: 'Users' do
|
||||
= icon('user fw')
|
||||
.nav-link-text
|
||||
%span
|
||||
Users
|
||||
= nav_link(controller: :groups) do
|
||||
= link_to admin_groups_path, title: 'Groups' do
|
||||
= icon('group fw')
|
||||
.nav-link-text
|
||||
%span
|
||||
Groups
|
||||
= nav_link(controller: :deploy_keys) do
|
||||
= link_to admin_deploy_keys_path, title: 'Deploy Keys' do
|
||||
= icon('key fw')
|
||||
.nav-link-text
|
||||
%span
|
||||
Deploy Keys
|
||||
= nav_link path: ['runners#index', 'runners#show'] do
|
||||
= link_to admin_runners_path, title: 'Runners' do
|
||||
= icon('cog fw')
|
||||
.nav-link-text
|
||||
%span
|
||||
Runners
|
||||
%span.count= number_with_delimiter(Ci::Runner.count(:all))
|
||||
= nav_link path: 'builds#index' do
|
||||
= link_to admin_builds_path, title: 'Builds' do
|
||||
= icon('link fw')
|
||||
.nav-link-text
|
||||
%span
|
||||
Builds
|
||||
%span.count= number_with_delimiter(Ci::Build.count(:all))
|
||||
= nav_link(controller: :logs) do
|
||||
= link_to admin_logs_path, title: 'Logs' do
|
||||
= icon('file-text fw')
|
||||
.nav-link-text
|
||||
%span
|
||||
Logs
|
||||
= nav_link(controller: :health_check) do
|
||||
= link_to admin_health_check_path, title: 'Health Check' do
|
||||
= icon('medkit fw')
|
||||
.nav-link-text
|
||||
%span
|
||||
Health Check
|
||||
= nav_link(controller: :broadcast_messages) do
|
||||
= link_to admin_broadcast_messages_path, title: 'Messages' do
|
||||
= icon('bullhorn fw')
|
||||
.nav-link-text
|
||||
%span
|
||||
Messages
|
||||
= nav_link(controller: :hooks) do
|
||||
= link_to admin_hooks_path, title: 'Hooks' do
|
||||
= icon('external-link fw')
|
||||
.nav-link-text
|
||||
%span
|
||||
Hooks
|
||||
= nav_link(controller: :background_jobs) do
|
||||
= link_to admin_background_jobs_path, title: 'Background Jobs' do
|
||||
= icon('cog fw')
|
||||
.nav-link-text
|
||||
%span
|
||||
Background Jobs
|
||||
= nav_link(controller: :appearances) do
|
||||
= link_to admin_appearances_path, title: 'Appearances' do
|
||||
= icon('image')
|
||||
.nav-link-text
|
||||
%span
|
||||
Appearance
|
||||
|
||||
= nav_link(controller: :applications) do
|
||||
= link_to admin_applications_path, title: 'Applications' do
|
||||
= icon('cloud fw')
|
||||
.nav-link-text
|
||||
%span
|
||||
Applications
|
||||
|
||||
= nav_link(controller: :services) do
|
||||
= link_to admin_application_settings_services_path, title: 'Service Templates' do
|
||||
= icon('copy fw')
|
||||
.nav-link-text
|
||||
%span
|
||||
Service Templates
|
||||
|
||||
= nav_link(controller: :labels) do
|
||||
= link_to admin_labels_path, title: 'Labels' do
|
||||
= icon('tags fw')
|
||||
.nav-link-text
|
||||
%span
|
||||
Labels
|
||||
|
||||
= nav_link(controller: :abuse_reports) do
|
||||
= link_to admin_abuse_reports_path, title: "Abuse Reports" do
|
||||
= icon('exclamation-circle fw')
|
||||
.nav-link-text
|
||||
%span
|
||||
Abuse Reports
|
||||
%span.count= number_with_delimiter(AbuseReport.count(:all))
|
||||
|
||||
- if askimet_enabled?
|
||||
= nav_link(controller: :spam_logs) do
|
||||
= link_to admin_spam_logs_path, title: "Spam Logs" do
|
||||
= icon('exclamation-triangle fw')
|
||||
.nav-link-text
|
||||
%span
|
||||
Spam Logs
|
||||
%span.count= number_with_delimiter(SpamLog.count(:all))
|
||||
|
||||
= nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do
|
||||
= link_to admin_application_settings_path, title: 'Settings' do
|
||||
= icon('cogs fw')
|
||||
.nav-link-text
|
||||
%span
|
||||
Settings
|
||||
|
|
|
@ -2,50 +2,53 @@
|
|||
= nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: "#{project_tab_class} home"}) do
|
||||
= link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do
|
||||
= navbar_icon('project')
|
||||
.nav-link-text
|
||||
%span
|
||||
Projects
|
||||
= nav_link(controller: :todos) do
|
||||
= link_to dashboard_todos_path, title: 'Todos' do
|
||||
= icon('bell fw')
|
||||
.nav-link-text
|
||||
%span
|
||||
Todos
|
||||
%span.count= number_with_delimiter(todos_pending_count)
|
||||
= nav_link(path: 'dashboard#activity') do
|
||||
= link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do
|
||||
= navbar_icon('activity')
|
||||
.nav-link-text
|
||||
%span
|
||||
Activity
|
||||
= nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do
|
||||
= link_to dashboard_groups_path, title: 'Groups' do
|
||||
= navbar_icon('group')
|
||||
.nav-link-text
|
||||
%span
|
||||
Groups
|
||||
= nav_link(controller: 'dashboard/milestones') do
|
||||
= link_to dashboard_milestones_path, title: 'Milestones' do
|
||||
= navbar_icon('milestones')
|
||||
.nav-link-text
|
||||
%span
|
||||
Milestones
|
||||
= nav_link(path: 'dashboard#issues') do
|
||||
= link_to assigned_issues_dashboard_path, title: 'Issues', class: 'dashboard-shortcuts-issues' do
|
||||
= navbar_icon('issues')
|
||||
.nav-link-text
|
||||
%span
|
||||
Issues
|
||||
%span.count= number_with_delimiter(current_user.assigned_issues.opened.count)
|
||||
= nav_link(path: 'dashboard#merge_requests') do
|
||||
= link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'dashboard-shortcuts-merge_requests' do
|
||||
= navbar_icon('mr')
|
||||
.nav-link-text
|
||||
%span
|
||||
Merge Requests
|
||||
%span.count= number_with_delimiter(current_user.assigned_merge_requests.opened.count)
|
||||
= nav_link(controller: :snippets) do
|
||||
= link_to dashboard_snippets_path, title: 'Snippets' do
|
||||
= icon('clipboard fw')
|
||||
.nav-link-text
|
||||
%span
|
||||
Snippets
|
||||
= nav_link(controller: :help) do
|
||||
= link_to help_path, title: 'Help' do
|
||||
= icon('question-circle fw')
|
||||
.nav-link-text
|
||||
%span
|
||||
Help
|
||||
= nav_link(html_options: {class: profile_tab_class}) do
|
||||
= link_to profile_path, title: 'Profile Settings', data: {placement: 'bottom'} do
|
||||
= icon('user fw')
|
||||
.nav-link-text
|
||||
%span
|
||||
Profile Settings
|
||||
|
|
|
@ -2,20 +2,20 @@
|
|||
= nav_link(path: ['dashboard#show', 'root#show', 'projects#trending', 'projects#starred', 'projects#index'], html_options: {class: 'home'}) do
|
||||
= link_to explore_root_path, title: 'Projects' do
|
||||
= icon('bookmark fw')
|
||||
.nav-link-text
|
||||
%span
|
||||
Projects
|
||||
= nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do
|
||||
= link_to explore_groups_path, title: 'Groups' do
|
||||
= icon('group fw')
|
||||
.nav-link-text
|
||||
%span
|
||||
Groups
|
||||
= nav_link(controller: :snippets) do
|
||||
= link_to explore_snippets_path, title: 'Snippets' do
|
||||
= icon('clipboard fw')
|
||||
.nav-link-text
|
||||
%span
|
||||
Snippets
|
||||
= nav_link(controller: :help) do
|
||||
= link_to help_path, title: 'Help' do
|
||||
= icon('question-circle fw')
|
||||
.nav-link-text
|
||||
%span
|
||||
Help
|
||||
|
|
|
@ -5,36 +5,30 @@
|
|||
.fade-left
|
||||
= nav_link(path: 'groups#show', html_options: {class: 'home'}) do
|
||||
= link_to group_path(@group), title: 'Home' do
|
||||
= navbar_icon('group')
|
||||
%span
|
||||
Group
|
||||
= nav_link(path: 'groups#activity') do
|
||||
= link_to activity_group_path(@group), title: 'Activity' do
|
||||
= navbar_icon('activity')
|
||||
%span
|
||||
Activity
|
||||
= nav_link(controller: [:group, :milestones]) do
|
||||
= link_to group_milestones_path(@group), title: 'Milestones' do
|
||||
= navbar_icon('milestones')
|
||||
%span
|
||||
Milestones
|
||||
= nav_link(path: 'groups#issues') do
|
||||
= link_to issues_group_path(@group), title: 'Issues' do
|
||||
= navbar_icon('issues')
|
||||
%span
|
||||
Issues
|
||||
- issues = IssuesFinder.new(current_user, group_id: @group.id, state: 'opened').execute
|
||||
%span.badge.count= number_with_delimiter(issues.count)
|
||||
= nav_link(path: 'groups#merge_requests') do
|
||||
= link_to merge_requests_group_path(@group), title: 'Merge Requests' do
|
||||
= navbar_icon('mr')
|
||||
%span
|
||||
Merge Requests
|
||||
- merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened').execute
|
||||
%span.badge.count= number_with_delimiter(merge_requests.count)
|
||||
= nav_link(controller: [:group_members]) do
|
||||
= link_to group_group_members_path(@group), title: 'Members' do
|
||||
= navbar_icon('members')
|
||||
%span
|
||||
Members
|
||||
.fade-right
|
||||
|
|
|
@ -2,51 +2,41 @@
|
|||
.fade-left
|
||||
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
|
||||
= link_to profile_path, title: 'Profile Settings' do
|
||||
= icon('user fw')
|
||||
%span
|
||||
Profile
|
||||
= nav_link(controller: [:accounts, :two_factor_auths]) do
|
||||
= link_to profile_account_path, title: 'Account' do
|
||||
= icon('gear fw')
|
||||
%span
|
||||
Account
|
||||
- if current_application_settings.user_oauth_applications?
|
||||
= nav_link(controller: 'oauth/applications') do
|
||||
= link_to applications_profile_path, title: 'Applications' do
|
||||
= icon('cloud fw')
|
||||
%span
|
||||
Applications
|
||||
= nav_link(controller: :emails) do
|
||||
= link_to profile_emails_path, title: 'Emails' do
|
||||
= icon('envelope-o fw')
|
||||
%span
|
||||
Emails
|
||||
- unless current_user.ldap_user?
|
||||
= nav_link(controller: :passwords) do
|
||||
= link_to edit_profile_password_path, title: 'Password' do
|
||||
= icon('lock fw')
|
||||
%span
|
||||
Password
|
||||
= nav_link(controller: :notifications) do
|
||||
= link_to profile_notifications_path, title: 'Notifications' do
|
||||
= icon('inbox fw')
|
||||
%span
|
||||
Notifications
|
||||
|
||||
= nav_link(controller: :keys) do
|
||||
= link_to profile_keys_path, title: 'SSH Keys' do
|
||||
= icon('key fw')
|
||||
%span
|
||||
SSH Keys
|
||||
= nav_link(controller: :preferences) do
|
||||
= link_to profile_preferences_path, title: 'Preferences' do
|
||||
-# TODO (rspeicher): Better icon?
|
||||
= icon('image fw')
|
||||
%span
|
||||
Preferences
|
||||
= nav_link(path: 'profiles#audit_log') do
|
||||
= link_to audit_log_profile_path, title: 'Audit Log' do
|
||||
= icon('history fw')
|
||||
%span
|
||||
Audit Log
|
||||
.fade-right
|
||||
|
|
|
@ -24,48 +24,41 @@
|
|||
.fade-left
|
||||
= nav_link(path: 'projects#show', html_options: {class: 'home'}) do
|
||||
= link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do
|
||||
= navbar_icon('project')
|
||||
%span
|
||||
Project
|
||||
|
||||
= nav_link(path: 'projects#activity') do
|
||||
= link_to activity_project_path(@project), title: 'Activity', class: 'shortcuts-project-activity' do
|
||||
= navbar_icon('activity')
|
||||
%span
|
||||
Activity
|
||||
|
||||
- if project_nav_tab? :files
|
||||
= nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare repositories tags branches releases network)) do
|
||||
= link_to project_files_path(@project), title: 'Code', class: 'shortcuts-tree' do
|
||||
= icon('code fw')
|
||||
%span
|
||||
Code
|
||||
|
||||
- if project_nav_tab? :pipelines
|
||||
= nav_link(controller: :pipelines) do
|
||||
= link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do
|
||||
= navbar_icon('pipelines')
|
||||
%span
|
||||
Pipelines
|
||||
|
||||
- if project_nav_tab? :container_registry
|
||||
= nav_link(controller: %w(container_registry)) do
|
||||
= link_to project_container_registry_path(@project), title: 'Container Registry', class: 'shortcuts-container-registry' do
|
||||
= icon('hdd-o fw')
|
||||
%span
|
||||
Registry
|
||||
|
||||
- if project_nav_tab? :graphs
|
||||
= nav_link(controller: %w(graphs)) do
|
||||
= link_to namespace_project_graph_path(@project.namespace, @project, current_ref), title: 'Graphs', class: 'shortcuts-graphs' do
|
||||
= icon('area-chart fw')
|
||||
%span
|
||||
Graphs
|
||||
|
||||
- if project_nav_tab? :issues
|
||||
= nav_link(controller: [:issues, :labels, :milestones]) do
|
||||
= link_to url_for_project_issues(@project, only_path: true), title: 'Issues', class: 'shortcuts-issues' do
|
||||
= navbar_icon('issues')
|
||||
%span
|
||||
Issues
|
||||
- if @project.default_issues_tracker?
|
||||
|
@ -74,7 +67,6 @@
|
|||
- if project_nav_tab? :merge_requests
|
||||
= nav_link(controller: :merge_requests) do
|
||||
= link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do
|
||||
= navbar_icon('mr')
|
||||
%span
|
||||
Merge Requests
|
||||
%span.badge.count.merge_counter= number_with_delimiter(@project.merge_requests.opened.count)
|
||||
|
@ -82,14 +74,12 @@
|
|||
- if project_nav_tab? :wiki
|
||||
= nav_link(controller: :wikis) do
|
||||
= link_to get_project_wiki_path(@project), title: 'Wiki', class: 'shortcuts-wiki' do
|
||||
= navbar_icon('wiki')
|
||||
%span
|
||||
Wiki
|
||||
|
||||
- if project_nav_tab? :snippets
|
||||
= nav_link(controller: :snippets) do
|
||||
= link_to namespace_project_snippets_path(@project.namespace, @project), title: 'Snippets', class: 'shortcuts-snippets' do
|
||||
= icon('clipboard fw')
|
||||
%span
|
||||
Snippets
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
%li.notification-list-item
|
||||
%span.notification.fa.fa-holder.append-right-5
|
||||
- if setting.global?
|
||||
= notification_icon(current_user.notification_level)
|
||||
= notification_icon(current_user.global_notification_setting.level)
|
||||
- else
|
||||
= notification_icon(setting.level)
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
%li.notification-list-item
|
||||
%span.notification.fa.fa-holder.append-right-5
|
||||
- if setting.global?
|
||||
= notification_icon(current_user.notification_level)
|
||||
= notification_icon(current_user.global_notification_setting.level)
|
||||
- else
|
||||
= notification_icon(setting.level)
|
||||
|
||||
|
|
|
@ -26,33 +26,7 @@
|
|||
= f.select :notification_email, @user.all_emails, { include_blank: false }, class: "select2"
|
||||
.form-group
|
||||
= f.label :notification_level, class: 'label-light'
|
||||
.radio
|
||||
= f.label :notification_level, value: :disabled do
|
||||
= f.radio_button :notification_level, :disabled
|
||||
.level-title
|
||||
Disabled
|
||||
%p You will not get any notifications via email
|
||||
|
||||
.radio
|
||||
= f.label :notification_level, value: :mention do
|
||||
= f.radio_button :notification_level, :mention
|
||||
.level-title
|
||||
On Mention
|
||||
%p You will receive notifications only for comments in which you were @mentioned
|
||||
|
||||
.radio
|
||||
= f.label :notification_level, value: :participating do
|
||||
= f.radio_button :notification_level, :participating
|
||||
.level-title
|
||||
Participating
|
||||
%p You will only receive notifications from related resources (e.g. from your commits or assigned issues)
|
||||
|
||||
.radio
|
||||
= f.label :notification_level, value: :watch do
|
||||
= f.radio_button :notification_level, :watch
|
||||
.level-title
|
||||
Watch
|
||||
%p You will receive notifications for any activity
|
||||
= notification_level_radio_buttons
|
||||
|
||||
.prepend-top-default
|
||||
= f.submit 'Update settings', class: "btn btn-create"
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
.form-group.branch
|
||||
= label_tag 'target_branch', target_label, class: 'control-label'
|
||||
.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)
|
||||
.js-create-merge-request-container
|
||||
.checkbox
|
||||
|
|
|
@ -24,8 +24,6 @@
|
|||
MERGED
|
||||
- elsif merge_request.closed?
|
||||
CLOSED
|
||||
%li
|
||||
= render partial: 'projects/issues/closed_by_box', locals: {merge_request_count: @merge_requests.count}
|
||||
- if @closed_by_merge_requests.present?
|
||||
%li
|
||||
= render partial: 'projects/issues/closed_by_box', locals: {merge_request_count: @merge_requests.count}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
%li
|
||||
- sha = @project.repository.find_branch(branch).target
|
||||
- pipeline = @project.pipeline(sha, branch) if sha
|
||||
- if ci_copipelinemmit
|
||||
- if pipeline
|
||||
%span.related-branch-ci-status
|
||||
= render_pipeline_status(pipeline)
|
||||
%span.related-branch-info
|
||||
|
|
|
@ -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), rel: "alternate", type: "text/html"
|
||||
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
|
||||
|
|
|
@ -5,18 +5,15 @@
|
|||
= link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do
|
||||
%span
|
||||
Pipelines
|
||||
%span.badge.count.ci_counter= number_with_delimiter(@project.pipelines.running_or_pending.count)
|
||||
|
||||
- if project_nav_tab? :builds
|
||||
= nav_link(controller: %w(builds)) do
|
||||
= link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do
|
||||
%span
|
||||
Builds
|
||||
%span.badge.count.builds_counter= number_with_delimiter(@project.running_or_pending_build_count)
|
||||
|
||||
- if project_nav_tab? :environments
|
||||
= nav_link(controller: %w(environments)) do
|
||||
= link_to project_environments_path(@project), title: 'Environments', class: 'shortcuts-environments' do
|
||||
%span
|
||||
Environments
|
||||
%span.badge.count.environments_counter= number_with_delimiter(@project.environments.count)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
- if @merge_requests.any?
|
||||
- if @merge_requests.reorder(nil).any?
|
||||
- @merge_requests.group_by(&:target_project).each do |group|
|
||||
.panel.panel-default.panel-small
|
||||
- project = group[0]
|
||||
|
|
|
@ -35,13 +35,13 @@
|
|||
.clearfix
|
||||
.error-alert
|
||||
|
||||
- if issuable.is_a?(Issue) && !issuable.project.private?
|
||||
- if issuable.is_a?(Issue)
|
||||
.form-group
|
||||
.col-sm-offset-2.col-sm-10
|
||||
.checkbox
|
||||
= f.label :confidential do
|
||||
= 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)
|
||||
- has_due_date = issuable.has_attribute?(:due_date)
|
||||
|
|
|
@ -12,7 +12,7 @@ Doorkeeper.configure do
|
|||
end
|
||||
|
||||
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
|
||||
|
||||
# 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?
|
|||
config.instrument_instance_methods(const)
|
||||
end
|
||||
|
||||
# Instruments all Banzai filters
|
||||
Dir[Rails.root.join('lib', 'banzai', 'filter', '*.rb')].each do |file|
|
||||
klass = File.basename(file, File.extname(file)).camelize
|
||||
const = Banzai::Filter.const_get(klass)
|
||||
# Instruments all Banzai filters and reference parsers
|
||||
{
|
||||
Filter: Rails.root.join('lib', 'banzai', 'filter', '*.rb'),
|
||||
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_instance_methods(const)
|
||||
config.instrument_methods(const)
|
||||
config.instrument_instance_methods(const)
|
||||
end
|
||||
end
|
||||
|
||||
config.instrument_methods(Banzai::Renderer)
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
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
|
|
@ -0,0 +1,6 @@
|
|||
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
|
|
@ -0,0 +1,21 @@
|
|||
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
|
|
@ -0,0 +1,7 @@
|
|||
class RemoveNotificationLevelFromUsers < ActiveRecord::Migration
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
def change
|
||||
remove_column :users, :notification_level, :integer
|
||||
end
|
||||
end
|
|
@ -11,7 +11,7 @@
|
|||
#
|
||||
# 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
|
||||
enable_extension "plpgsql"
|
||||
|
@ -699,8 +699,8 @@ ActiveRecord::Schema.define(version: 20160610211845) do
|
|||
|
||||
create_table "notification_settings", force: :cascade do |t|
|
||||
t.integer "user_id", null: false
|
||||
t.integer "source_id", null: false
|
||||
t.string "source_type", null: false
|
||||
t.integer "source_id"
|
||||
t.string "source_type"
|
||||
t.integer "level", default: 0, null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
|
@ -780,8 +780,6 @@ ActiveRecord::Schema.define(version: 20160610211845) do
|
|||
t.boolean "merge_requests_enabled", default: true, null: false
|
||||
t.boolean "wiki_enabled", default: true, null: false
|
||||
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.datetime "last_activity_at"
|
||||
t.string "import_url"
|
||||
|
@ -1017,7 +1015,6 @@ ActiveRecord::Schema.define(version: 20160610211845) do
|
|||
t.boolean "can_create_team", default: true, null: false
|
||||
t.string "state"
|
||||
t.integer "color_scheme_id", default: 1, null: false
|
||||
t.integer "notification_level", default: 1, null: false
|
||||
t.datetime "password_expires_at"
|
||||
t.integer "created_by_id"
|
||||
t.datetime "last_credential_check_at"
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
- [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.
|
||||
- [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.
|
||||
- [Operations](operations/README.md) Keeping GitLab up and running
|
||||
- [Raketasks](raketasks/README.md) Backups, maintenance, automatic webhook setup and the importing of projects.
|
||||
|
|
|
@ -0,0 +1,137 @@
|
|||
## 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
|
|||
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
|
||||
[
|
||||
|
@ -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
|
||||
|
||||
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
|
|||
- [when](#when)
|
||||
- [artifacts](#artifacts)
|
||||
- [artifacts:name](#artifacts-name)
|
||||
- [artifacts:when](#artifacts-when)
|
||||
- [dependencies](#dependencies)
|
||||
- [before_script and after_script](#before_script-and-after_script)
|
||||
- [Hidden jobs](#hidden-jobs)
|
||||
|
@ -651,6 +652,32 @@ job:
|
|||
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
|
||||
|
||||
>**Note:**
|
||||
|
|
|
@ -103,14 +103,14 @@ Inside the document:
|
|||
|
||||
- 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
|
||||
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.
|
||||
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
|
||||
number
|
||||
- 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
|
||||
|
||||
## References
|
||||
|
@ -141,6 +141,48 @@ Inside the document:
|
|||
|
||||
[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
|
||||
|
||||
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.
|
|||
|
||||
#### Post data using JSON content
|
||||
|
||||
_**Note:** In this example we create a new group. Watch carefully the single
|
||||
and double quotes._
|
||||
> **Note:** In this example we create a new group. Watch carefully the single
|
||||
and double quotes.
|
||||
|
||||
```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
|
||||
|
|
|
@ -1,92 +1 @@
|
|||
## 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 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[0m [["id", 26]]
|
||||
[1m[35mCACHE (0.0ms)[0m 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"]]
|
||||
[1m[36mCACHE (0.0ms)[0m [1mSELECT "members".* FROM "members" WHERE "members"."source_type" = 'Project' AND "members".
|
||||
[1m[36m (1.4ms)[0m [1mSELECT COUNT(*) FROM "merge_requests" WHERE "merge_requests"."target_project_id" = $1 AND ("merge_requests"."state" IN ('opened','reopened'))[0m [["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
|
||||
```
|
||||
This document was moved to [administration/logs.md](../administration/logs.md).
|
||||
|
|
|
@ -155,6 +155,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
|
|||
end
|
||||
|
||||
step 'I click on my profile picture' do
|
||||
find(:css, '.side-nav-toggle').click
|
||||
find(:css, '.sidebar-user').click
|
||||
end
|
||||
|
||||
|
|
|
@ -179,6 +179,11 @@ module API
|
|||
expose :upvotes, :downvotes
|
||||
end
|
||||
|
||||
class ExternalIssue < Grape::Entity
|
||||
expose :title
|
||||
expose :id
|
||||
end
|
||||
|
||||
class MergeRequest < ProjectEntity
|
||||
expose :target_branch, :source_branch
|
||||
expose :upvotes, :downvotes
|
||||
|
|
|
@ -418,5 +418,13 @@ module API
|
|||
def send_git_archive(repository, ref:, format:)
|
||||
header(*Gitlab::Workhorse.send_git_archive(repository, ref: ref, format: format))
|
||||
end
|
||||
|
||||
def issue_entity(project)
|
||||
if project.has_external_issue_tracker?
|
||||
Entities::ExternalIssue
|
||||
else
|
||||
Entities::Issue
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -329,7 +329,7 @@ module API
|
|||
get "#{path}/closes_issues" do
|
||||
merge_request = user_project.merge_requests.find(params[:merge_request_id])
|
||||
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
|
||||
|
|
|
@ -11,7 +11,7 @@ module API
|
|||
# Example Request:
|
||||
# POST /session
|
||||
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
|
||||
present user, with: Entities::UserLogin
|
||||
|
|
|
@ -1,23 +1,16 @@
|
|||
module Banzai
|
||||
module Pipeline
|
||||
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)
|
||||
super(context).merge(
|
||||
# SanitizationFilter
|
||||
whitelist: whitelist
|
||||
whitelist: WHITELIST
|
||||
)
|
||||
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
|
||||
|
|
|
@ -9,6 +9,8 @@ module Ci
|
|||
:allow_failure, :type, :stage, :when, :artifacts, :cache,
|
||||
:dependencies, :before_script, :after_script, :variables,
|
||||
:environment]
|
||||
ALLOWED_CACHE_KEYS = [:key, :untracked, :paths]
|
||||
ALLOWED_ARTIFACTS_KEYS = [:name, :untracked, :paths, :when]
|
||||
|
||||
attr_reader :before_script, :after_script, :image, :services, :path, :cache
|
||||
|
||||
|
@ -137,6 +139,12 @@ module Ci
|
|||
end
|
||||
|
||||
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])
|
||||
raise ValidationError, "cache:key parameter should be a string"
|
||||
end
|
||||
|
@ -202,7 +210,7 @@ module Ci
|
|||
raise ValidationError, "#{name} job: allow_failure parameter should be an boolean"
|
||||
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"
|
||||
end
|
||||
|
||||
|
@ -239,6 +247,12 @@ module Ci
|
|||
end
|
||||
|
||||
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])
|
||||
raise ValidationError, "#{name} job: cache:key parameter should be a string"
|
||||
end
|
||||
|
@ -253,6 +267,12 @@ module Ci
|
|||
end
|
||||
|
||||
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])
|
||||
raise ValidationError, "#{name} job: artifacts:name parameter should be a string"
|
||||
end
|
||||
|
@ -264,6 +284,10 @@ module Ci
|
|||
if job[:artifacts][:paths] && !validate_array_of_strings(job[:artifacts][:paths])
|
||||
raise ValidationError, "#{name} job: artifacts:paths parameter should be an array of strings"
|
||||
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
|
||||
|
||||
def validate_job_dependencies!(name, job)
|
||||
|
|
|
@ -3,14 +3,14 @@ module Gitlab
|
|||
Result = Struct.new(:user, :type)
|
||||
|
||||
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?
|
||||
|
||||
result = Result.new
|
||||
|
||||
if valid_ci_request?(login, password, project)
|
||||
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
|
||||
elsif result.user = oauth_access_token_check(login, password)
|
||||
result.type = :oauth
|
||||
|
@ -20,7 +20,7 @@ module Gitlab
|
|||
result
|
||||
end
|
||||
|
||||
def find_in_gitlab_or_ldap(login, password)
|
||||
def find_with_user_password(login, password)
|
||||
user = User.by_login(login)
|
||||
|
||||
# If no user is found, or it's an LDAP server, try LDAP.
|
||||
|
|
|
@ -95,7 +95,7 @@ module Grack
|
|||
end
|
||||
|
||||
def authenticate_user(login, password)
|
||||
user = Gitlab::Auth.find_in_gitlab_or_ldap(login, password)
|
||||
user = Gitlab::Auth.find_with_user_password(login, password)
|
||||
|
||||
unless user
|
||||
user = oauth_access_token_check(login, password)
|
||||
|
|
|
@ -31,8 +31,6 @@ module Gitlab
|
|||
# Any data inserted while running this method (or after it has finished
|
||||
# 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.
|
||||
# column - The name of the column to update.
|
||||
# value - The value for the column.
|
||||
|
@ -55,10 +53,10 @@ module Gitlab
|
|||
first['count'].
|
||||
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
|
||||
|
||||
while processed < total
|
||||
loop do
|
||||
start_row = exec_query(%Q{
|
||||
SELECT id
|
||||
FROM #{quoted_table}
|
||||
|
@ -66,6 +64,9 @@ module Gitlab
|
|||
LIMIT 1 OFFSET #{processed}
|
||||
}).to_hash.first
|
||||
|
||||
# There are no more rows to process
|
||||
break unless start_row
|
||||
|
||||
stop_row = exec_query(%Q{
|
||||
SELECT id
|
||||
FROM #{quoted_table}
|
||||
|
@ -126,6 +127,8 @@ module Gitlab
|
|||
begin
|
||||
transaction do
|
||||
update_column_in_batches(table, column, default)
|
||||
|
||||
change_column_null(table, column, false) unless allow_null
|
||||
end
|
||||
# We want to rescue _all_ exceptions here, even those that don't inherit
|
||||
# from StandardError.
|
||||
|
@ -134,8 +137,6 @@ module Gitlab
|
|||
|
||||
raise error
|
||||
end
|
||||
|
||||
change_column_null(table, column, false) unless allow_null
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -12,23 +12,45 @@ module Gitlab
|
|||
def scrub(node)
|
||||
unless Whitelist::ALLOWED_ELEMENTS.include?(node.name)
|
||||
node.unlink
|
||||
else
|
||||
node.attributes.each do |attr_name, attr|
|
||||
valid_attributes = Whitelist::ALLOWED_ATTRIBUTES[node.name]
|
||||
return
|
||||
end
|
||||
|
||||
unless valid_attributes && valid_attributes.include?(attr_name)
|
||||
if Whitelist::ALLOWED_DATA_ATTRIBUTES_IN_ELEMENTS.include?(node.name) &&
|
||||
attr_name.start_with?('data-')
|
||||
# Arbitrary data attributes are allowed. Verify that the attribute
|
||||
# is a valid data attribute.
|
||||
attr.unlink unless attr_name =~ DATA_ATTR_PATTERN
|
||||
else
|
||||
attr.unlink
|
||||
end
|
||||
valid_attributes = Whitelist::ALLOWED_ATTRIBUTES[node.name]
|
||||
return unless valid_attributes
|
||||
|
||||
node.attribute_nodes.each do |attr|
|
||||
attr_name = attribute_name_with_namespace(attr)
|
||||
|
||||
if valid_attributes.include?(attr_name)
|
||||
attr.unlink if unsafe_href?(attr)
|
||||
else
|
||||
# Arbitrary data attributes are allowed.
|
||||
unless allows_data_attribute?(node) && data_attribute?(attr)
|
||||
attr.unlink
|
||||
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
|
||||
|
|
|
@ -16,10 +16,10 @@ retry() {
|
|||
}
|
||||
|
||||
if [ -f /.dockerenv ] || [ -f ./dockerinit ]; then
|
||||
mkdir -p vendor
|
||||
mkdir -p vendor/apt
|
||||
|
||||
# Install phantomjs package
|
||||
pushd vendor
|
||||
pushd vendor/apt
|
||||
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
|
||||
fi
|
||||
|
|
|
@ -105,6 +105,15 @@ describe Projects::IssuesController do
|
|||
expect(assigns(:issues)).to eq [issue]
|
||||
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
|
||||
sign_in(author)
|
||||
get_issues
|
||||
|
@ -148,7 +157,7 @@ describe Projects::IssuesController do
|
|||
|
||||
shared_examples_for 'restricted action' do |http_status|
|
||||
it 'returns 404 for guests' do
|
||||
sign_out :user
|
||||
sign_out(:user)
|
||||
go(id: unescaped_parameter_value.to_param)
|
||||
|
||||
expect(response).to have_http_status :not_found
|
||||
|
@ -161,6 +170,14 @@ describe Projects::IssuesController do
|
|||
expect(response).to have_http_status :not_found
|
||||
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
|
||||
sign_in(author)
|
||||
go(id: unescaped_parameter_value.to_param)
|
||||
|
|
|
@ -67,9 +67,6 @@ FactoryGirl.define do
|
|||
'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
|
||||
|
||||
|
@ -84,9 +81,6 @@ FactoryGirl.define do
|
|||
'new_issue_url' => 'http://jira.example/secure/CreateIssue.jspa'
|
||||
}
|
||||
)
|
||||
|
||||
project.issues_tracker = 'jira'
|
||||
project.issues_tracker_id = 'project_name_in_jira'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,8 +5,6 @@ describe "Dashboard Issues Feed", feature: true do
|
|||
let!(:user) { create(:user) }
|
||||
let!(:project1) { 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
|
||||
project1.team << [user, :master]
|
||||
|
@ -14,16 +12,51 @@ describe "Dashboard Issues Feed", feature: true do
|
|||
end
|
||||
|
||||
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)
|
||||
|
||||
expect(response_headers['Content-Type']).
|
||||
to have_content('application/atom+xml')
|
||||
expect(response_headers['Content-Type']).to have_content('application/atom+xml')
|
||||
expect(body).to have_selector('title', text: "#{user.name} issues")
|
||||
expect(body).to have_selector('author email', text: issue1.author_email)
|
||||
expect(body).to have_selector('entry summary', text: issue1.title)
|
||||
expect(body).to have_selector('author email', text: issue2.author_email)
|
||||
expect(body).to have_selector('entry summary', text: issue2.title)
|
||||
end
|
||||
|
||||
context "issue with basic fields" do
|
||||
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
|
||||
|
|
|
@ -83,6 +83,23 @@ feature 'Issues > Labels bulk assignment', feature: true do
|
|||
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 'all labels to all issues' do
|
||||
before do
|
||||
|
|
|
@ -54,7 +54,7 @@ describe 'Profile > Preferences', feature: true do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'User changes their default dashboard' do
|
||||
describe 'User changes their default dashboard', js: true do
|
||||
it 'creates a flash message' do
|
||||
select 'Starred Projects', from: 'user_dashboard'
|
||||
click_button 'Save'
|
||||
|
@ -66,8 +66,10 @@ describe 'Profile > Preferences', feature: true do
|
|||
select 'Starred Projects', from: 'user_dashboard'
|
||||
click_button 'Save'
|
||||
|
||||
click_link 'Dashboard'
|
||||
expect(page.current_path).to eq starred_dashboard_projects_path
|
||||
allowing_for_delay do
|
||||
find('#logo').click
|
||||
expect(page.current_path).to eq starred_dashboard_projects_path
|
||||
end
|
||||
|
||||
click_link 'Your Projects'
|
||||
expect(page.current_path).to eq dashboard_projects_path
|
||||
|
|
|
@ -16,6 +16,7 @@ describe 'Cherry-pick Commits' do
|
|||
it do
|
||||
visit namespace_project_commit_path(project.namespace, project, master_pickable_commit.id)
|
||||
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
|
||||
uncheck 'create_merge_request'
|
||||
click_button 'Cherry-pick'
|
||||
|
|
|
@ -7,10 +7,7 @@ describe IssuesHelper do
|
|||
|
||||
describe "url_for_project_issues" do
|
||||
let(:project_url) { ext_project.external_issue_tracker.project_url }
|
||||
let(:ext_expected) do
|
||||
project_url.gsub(':project_id', ext_project.id.to_s)
|
||||
.gsub(':issues_tracker_id', ext_project.issues_tracker_id.to_s)
|
||||
end
|
||||
let(:ext_expected) { project_url.gsub(':project_id', ext_project.id.to_s) }
|
||||
let(:int_expected) { polymorphic_path([@project.namespace, project]) }
|
||||
|
||||
it "should return internal path if used internal tracker" do
|
||||
|
@ -56,11 +53,7 @@ describe IssuesHelper do
|
|||
|
||||
describe "url_for_issue" do
|
||||
let(:issues_url) { ext_project.external_issue_tracker.issues_url}
|
||||
let(:ext_expected) do
|
||||
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(:ext_expected) { issues_url.gsub(':id', issue.iid.to_s).gsub(':project_id', ext_project.id.to_s) }
|
||||
let(:int_expected) { polymorphic_path([@project.namespace, project, issue]) }
|
||||
|
||||
it "should return internal path if used internal tracker" do
|
||||
|
@ -106,10 +99,7 @@ describe IssuesHelper do
|
|||
|
||||
describe 'url_for_new_issue' do
|
||||
let(:issues_url) { ext_project.external_issue_tracker.new_issue_url }
|
||||
let(:ext_expected) do
|
||||
issues_url.gsub(':project_id', ext_project.id.to_s)
|
||||
.gsub(':issues_tracker_id', ext_project.issues_tracker_id.to_s)
|
||||
end
|
||||
let(:ext_expected) { issues_url.gsub(':project_id', ext_project.id.to_s) }
|
||||
let(:int_expected) { new_namespace_project_issue_path(project.namespace, project) }
|
||||
|
||||
it "should return internal path if used internal tracker" do
|
||||
|
|
|
@ -69,6 +69,18 @@ describe Banzai::Filter::RedactorFilter, lib: true do
|
|||
expect(doc.css('a').length).to eq 0
|
||||
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
|
||||
author = create(:user)
|
||||
project = create(:empty_project, :public)
|
||||
|
|
|
@ -501,6 +501,7 @@ module Ci
|
|||
})
|
||||
|
||||
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[:when]).to eq(when_state)
|
||||
|
@ -601,6 +602,23 @@ module Ci
|
|||
allow_failure: false
|
||||
})
|
||||
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
|
||||
|
||||
describe "Dependencies" do
|
||||
|
@ -967,6 +985,13 @@ EOT
|
|||
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: artifacts:name parameter should be a string")
|
||||
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
|
||||
config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", artifacts: { untracked: "string" } } })
|
||||
expect do
|
||||
|
|
|
@ -3,7 +3,7 @@ require 'spec_helper'
|
|||
describe Gitlab::Auth, lib: true do
|
||||
let(:gl_auth) { described_class }
|
||||
|
||||
describe 'find' do
|
||||
describe 'find_for_git_client' do
|
||||
it 'recognizes CI' do
|
||||
token = '123'
|
||||
project = create(:empty_project)
|
||||
|
@ -11,7 +11,7 @@ describe Gitlab::Auth, lib: true do
|
|||
ip = 'ip'
|
||||
|
||||
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
|
||||
|
||||
it 'recognizes master passwords' do
|
||||
|
@ -19,7 +19,7 @@ describe Gitlab::Auth, lib: true do
|
|||
ip = 'ip'
|
||||
|
||||
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
|
||||
|
||||
it 'recognizes OAuth tokens' do
|
||||
|
@ -29,7 +29,7 @@ describe Gitlab::Auth, lib: true do
|
|||
ip = 'ip'
|
||||
|
||||
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
|
||||
|
||||
it 'returns double nil for invalid credentials' do
|
||||
|
@ -37,11 +37,11 @@ describe Gitlab::Auth, lib: true do
|
|||
ip = 'ip'
|
||||
|
||||
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
|
||||
|
||||
describe 'find_in_gitlab_or_ldap' do
|
||||
describe 'find_with_user_password' do
|
||||
let!(:user) do
|
||||
create(:user,
|
||||
username: username,
|
||||
|
@ -52,25 +52,25 @@ describe Gitlab::Auth, lib: true do
|
|||
let(:password) { 'my-secret' }
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
it "should not find user with invalid password" do
|
||||
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
|
||||
|
||||
it "should not find user with invalid login" do
|
||||
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
|
||||
|
||||
context "with ldap enabled" do
|
||||
|
@ -81,13 +81,13 @@ describe Gitlab::Auth, lib: true do
|
|||
it "tries to autheticate with db before ldap" do
|
||||
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
|
||||
|
||||
it "uses ldap as fallback to for authentication" do
|
||||
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
|
||||
|
|
|
@ -120,6 +120,19 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
|
|||
model.add_column_with_default(:projects, :foo, :integer, default: 10)
|
||||
end.to raise_error(RuntimeError)
|
||||
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
|
||||
|
||||
context 'inside a transaction' do
|
||||
|
|
|
@ -43,6 +43,18 @@ describe Gitlab::ProjectSearchResults, lib: true do
|
|||
expect(results.issues_count).to eq 1
|
||||
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
|
||||
results = described_class.new(author, project, query)
|
||||
issues = results.objects('issues')
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
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
|
|||
expect(results.issues_count).to eq 1
|
||||
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
|
||||
results = described_class.new(author, limit_projects, query)
|
||||
issues = results.objects('issues')
|
||||
|
|
|
@ -5,6 +5,7 @@ describe Milestone, 'Milestoneish' do
|
|||
let(:assignee) { create(:user) }
|
||||
let(:non_member) { create(:user) }
|
||||
let(:member) { create(:user) }
|
||||
let(:guest) { create(:user) }
|
||||
let(:admin) { create(:admin) }
|
||||
let(:project) { create(:project, :public) }
|
||||
let(:milestone) { create(:milestone, project: project) }
|
||||
|
@ -21,6 +22,7 @@ describe Milestone, 'Milestoneish' do
|
|||
|
||||
before do
|
||||
project.team << [member, :developer]
|
||||
project.team << [guest, :guest]
|
||||
end
|
||||
|
||||
describe '#closed_items_count' do
|
||||
|
@ -28,6 +30,10 @@ describe Milestone, 'Milestoneish' do
|
|||
expect(milestone.closed_items_count(non_member)).to eq 2
|
||||
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
|
||||
expect(milestone.closed_items_count(author)).to eq 4
|
||||
end
|
||||
|
@ -50,6 +56,10 @@ describe Milestone, 'Milestoneish' do
|
|||
expect(milestone.total_items_count(non_member)).to eq 4
|
||||
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
|
||||
expect(milestone.total_items_count(author)).to eq 7
|
||||
end
|
||||
|
@ -85,6 +95,10 @@ describe Milestone, 'Milestoneish' do
|
|||
expect(milestone.percent_complete(non_member)).to eq 50
|
||||
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
|
||||
expect(milestone.percent_complete(author)).to eq 57
|
||||
end
|
||||
|
|
|
@ -50,6 +50,7 @@ describe Event, models: true do
|
|||
let(:project) { create(:empty_project, :public) }
|
||||
let(:non_member) { create(:user) }
|
||||
let(:member) { create(:user) }
|
||||
let(:guest) { create(:user) }
|
||||
let(:author) { create(:author) }
|
||||
let(:assignee) { create(:user) }
|
||||
let(:admin) { create(:admin) }
|
||||
|
@ -61,6 +62,7 @@ describe Event, models: true do
|
|||
|
||||
before do
|
||||
project.team << [member, :developer]
|
||||
project.team << [guest, :guest]
|
||||
end
|
||||
|
||||
context 'issue event' 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?(assignee)).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 }
|
||||
end
|
||||
|
||||
|
@ -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?(assignee)).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 }
|
||||
end
|
||||
end
|
||||
|
@ -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?(assignee)).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 }
|
||||
end
|
||||
|
||||
|
@ -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?(assignee)).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 }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -162,16 +162,23 @@ describe Note, models: true do
|
|||
end
|
||||
|
||||
context "confidential issues" do
|
||||
let(:user) { create :user }
|
||||
let(:confidential_issue) { create(:issue, :confidential, author: user) }
|
||||
let(:confidential_note) { create :note, note: "Random", noteable: confidential_issue, project: confidential_issue.project }
|
||||
let(:user) { create(:user) }
|
||||
let(:project) { create(: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
|
||||
expect(described_class.search(confidential_note.note, as_user: user)).to eq([confidential_note])
|
||||
end
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@ RSpec.describe NotificationSetting, type: :model do
|
|||
subject { NotificationSetting.new(source_id: 1, source_type: 'Project') }
|
||||
|
||||
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_uniqueness_of(:user_id).scoped_to([:source_id, :source_type]).with_message(/already exists in source/) }
|
||||
end
|
||||
|
|
|
@ -126,25 +126,25 @@ describe BambooService, models: true do
|
|||
it 'returns a specific URL when status is 500' do
|
||||
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
|
||||
|
||||
it 'returns a specific URL when response has no results' do
|
||||
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
|
||||
|
||||
it 'returns a build URL when bamboo_url has no trailing slash' do
|
||||
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
|
||||
|
||||
it 'returns a build URL when bamboo_url has a trailing slash' do
|
||||
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
|
||||
|
||||
|
@ -192,7 +192,7 @@ describe BambooService, models: true do
|
|||
end
|
||||
end
|
||||
|
||||
def service(bamboo_url: 'http://gitlab.com')
|
||||
def service(bamboo_url: 'http://gitlab.com/bamboo')
|
||||
described_class.create(
|
||||
project: create(:empty_project),
|
||||
properties: {
|
||||
|
@ -205,7 +205,7 @@ describe BambooService, models: true do
|
|||
end
|
||||
|
||||
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}"}}}})
|
||||
|
||||
WebMock.stub_request(:get, bamboo_full_url).to_return(
|
||||
|
|
|
@ -126,19 +126,19 @@ describe TeamcityService, models: true do
|
|||
it 'returns a specific URL when status is 500' do
|
||||
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
|
||||
|
||||
it 'returns a build URL when teamcity_url has no trailing slash' do
|
||||
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
|
||||
|
||||
it 'returns a build URL when teamcity_url has a trailing slash' do
|
||||
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
|
||||
|
||||
|
@ -180,7 +180,7 @@ describe TeamcityService, models: true do
|
|||
end
|
||||
end
|
||||
|
||||
def service(teamcity_url: 'http://gitlab.com')
|
||||
def service(teamcity_url: 'http://gitlab.com/teamcity')
|
||||
described_class.create(
|
||||
project: create(:empty_project),
|
||||
properties: {
|
||||
|
@ -193,7 +193,7 @@ describe TeamcityService, models: true do
|
|||
end
|
||||
|
||||
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"}})
|
||||
|
||||
WebMock.stub_request(:get, teamcity_full_url).to_return(
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue