Commit 66ebf8d8 authored by Lennart Rosam's avatar Lennart Rosam

Merge remote-tracking branch 'github/master'

parents dc13af90 bd948549
web: bundle exec unicorn_rails -p $PORT web: bundle exec unicorn_rails -p $PORT
worker: bundle exec sidekiq -q post_receive,mailer,system_hook,common,default worker: bundle exec sidekiq -q post_receive,mailer,system_hook,project_web_hook,common,default
class CommitFile
constructor: (file) ->
if $('.image', file).length
new ImageFile(file)
this.CommitFile = CommitFile
\ No newline at end of file
class ImageFile
# Width where images must fits in, for 2-up this gets divided by 2
@availWidth = 900
@viewModes = ['two-up', 'swipe']
constructor: (@file) ->
# Determine if old and new file has same dimensions, if not show 'two-up' view
this.requestImageInfo $('.two-up.view .frame.deleted img', @file), (deletedWidth, deletedHeight) =>
this.requestImageInfo $('.two-up.view .frame.added img', @file), (width, height) =>
if width == deletedWidth && height == deletedHeight
this.initViewModes()
else
this.initView('two-up')
initViewModes: ->
viewMode = ImageFile.viewModes[0]
$('.view-modes', @file).removeClass 'hide'
$('.view-modes-menu', @file).on 'click', 'li', (event) =>
unless $(event.currentTarget).hasClass('active')
this.activateViewMode(event.currentTarget.className)
this.activateViewMode(viewMode)
activateViewMode: (viewMode) ->
$('.view-modes-menu li', @file)
.removeClass('active')
.filter(".#{viewMode}").addClass 'active'
$(".view:visible:not(.#{viewMode})", @file).fadeOut 200, =>
$(".view.#{viewMode}", @file).fadeIn(200)
this.initView viewMode
initView: (viewMode) ->
this.views[viewMode].call(this)
prepareFrames = (view) ->
maxWidth = 0
maxHeight = 0
$('.frame', view).each (index, frame) =>
width = $(frame).width()
height = $(frame).height()
maxWidth = if width > maxWidth then width else maxWidth
maxHeight = if height > maxHeight then height else maxHeight
.css
width: maxWidth
height: maxHeight
[maxWidth, maxHeight]
views:
'two-up': ->
$('.two-up.view .wrap', @file).each (index, wrap) =>
$('img', wrap).each ->
currentWidth = $(this).width()
if currentWidth > ImageFile.availWidth / 2
$(this).width ImageFile.availWidth / 2
this.requestImageInfo $('img', wrap), (width, height) ->
$('.image-info .meta-width', wrap).text "#{width}px"
$('.image-info .meta-height', wrap).text "#{height}px"
$('.image-info', wrap).removeClass('hide')
'swipe': ->
maxWidth = 0
maxHeight = 0
$('.swipe.view', @file).each (index, view) =>
[maxWidth, maxHeight] = prepareFrames(view)
$('.swipe-frame', view).css
width: maxWidth + 16
height: maxHeight + 28
$('.swipe-wrap', view).css
width: maxWidth + 1
height: maxHeight + 2
$('.swipe-bar', view).css
left: 0
.draggable
axis: 'x'
containment: 'parent'
drag: (event) ->
$('.swipe-wrap', view).width (maxWidth + 1) - $(this).position().left
stop: (event) ->
$('.swipe-wrap', view).width (maxWidth + 1) - $(this).position().left
'onion-skin': ->
maxWidth = 0
maxHeight = 0
dragTrackWidth = $('.drag-track', @file).width() - $('.dragger', @file).width()
$('.onion-skin.view', @file).each (index, view) =>
[maxWidth, maxHeight] = prepareFrames(view)
$('.onion-skin-frame', view).css
width: maxWidth + 16
height: maxHeight + 28
$('.swipe-wrap', view).css
width: maxWidth + 1
height: maxHeight + 2
$('.dragger', view).css
left: dragTrackWidth
.draggable
axis: 'x'
containment: 'parent'
drag: (event) ->
$('.frame.added', view).css('opacity', $(this).position().left / dragTrackWidth)
stop: (event) ->
$('.frame.added', view).css('opacity', $(this).position().left / dragTrackWidth)
requestImageInfo: (img, callback) ->
domImg = img.get(0)
if domImg.complete
callback.call(this, domImg.naturalWidth, domImg.naturalHeight)
else
img.on 'load', =>
callback.call(this, domImg.naturalWidth, domImg.naturalHeight)
this.ImageFile = ImageFile
\ No newline at end of file
var CommitsList = {
ref:null,
limit:0,
offset:0,
disable:false,
init:
function(ref, limit) {
$(".day-commits-table li.commit").live('click', function(e){
if(e.target.nodeName != "A") {
location.href = $(this).attr("url");
e.stopPropagation();
return false;
}
});
this.ref=ref;
this.limit=limit;
this.offset=limit;
this.initLoadMore();
$('.loading').show();
},
getOld:
function() {
$('.loading').show();
$.ajax({
type: "GET",
url: location.href,
data: "limit=" + this.limit + "&offset=" + this.offset + "&ref=" + this.ref,
complete: function(){ $('.loading').hide()},
dataType: "script"});
},
append:
function(count, html) {
$("#commits_list").append(html);
if(count > 0) {
this.offset += count;
} else {
this.disable = true;
}
},
initLoadMore:
function() {
$(document).endlessScroll({
bottomPixels: 400,
fireDelay: 1000,
fireOnce:true,
ceaseFire: function() {
return CommitsList.disable;
},
callback: function(i) {
CommitsList.getOld();
}
});
}
}
class CommitsList
@data =
ref: null
limit: 0
offset: 0
@disable = false
@showProgress: ->
$('.loading').show()
@hideProgress: ->
$('.loading').hide()
@init: (ref, limit) ->
$(".day-commits-table li.commit").live 'click', (event) ->
if event.target.nodeName != "A"
location.href = $(this).attr("url")
e.stopPropagation()
return false
@data.ref = ref
@data.limit = limit
@data.offset = limit
this.initLoadMore()
this.showProgress();
@getOld: ->
this.showProgress()
$.ajax
type: "GET"
url: location.href
data: @data
complete: this.hideProgress
dataType: "script"
@append: (count, html) ->
$("#commits-list").append(html)
if count > 0
@data.offset += count
else
@disable = true
@initLoadMore: ->
$(document).endlessScroll
bottomPixels: 400
fireDelay: 1000
fireOnce: true
ceaseFire: =>
@disable
callback: =>
this.getOld()
this.CommitsList = CommitsList
\ No newline at end of file
...@@ -4,11 +4,11 @@ window.dashboardPage = -> ...@@ -4,11 +4,11 @@ window.dashboardPage = ->
event.preventDefault() event.preventDefault()
toggleFilter $(this) toggleFilter $(this)
reloadActivities() reloadActivities()
reloadActivities = -> reloadActivities = ->
$(".content_list").html '' $(".content_list").html ''
Pager.init 20, true Pager.init 20, true
toggleFilter = (sender) -> toggleFilter = (sender) ->
sender.parent().toggleClass "inactive" sender.parent().toggleClass "inactive"
event_filters = $.cookie("event_filter") event_filters = $.cookie("event_filter")
...@@ -17,11 +17,11 @@ toggleFilter = (sender) -> ...@@ -17,11 +17,11 @@ toggleFilter = (sender) ->
event_filters = event_filters.split(",") event_filters = event_filters.split(",")
else else
event_filters = new Array() event_filters = new Array()
index = event_filters.indexOf(filter) index = event_filters.indexOf(filter)
if index is -1 if index is -1
event_filters.push filter event_filters.push filter
else else
event_filters.splice index, 1 event_filters.splice index, 1
$.cookie "event_filter", event_filters.join(",") $.cookie "event_filter", event_filters.join(",")
# #
# * Filter merge requests # * Filter merge requests
# #
@merge_requestsPage = -> @merge_requestsPage = ->
$('#assignee_id').chosen() $('#assignee_id').chosen()
$('#milestone_id').chosen() $('#milestone_id').chosen()
...@@ -8,16 +8,16 @@ ...@@ -8,16 +8,16 @@
$(this).closest('form').submit() $(this).closest('form').submit()
class MergeRequest class MergeRequest
constructor: (@opts) -> constructor: (@opts) ->
this.$el = $('.merge-request') this.$el = $('.merge-request')
@diffs_loaded = false @diffs_loaded = false
@commits_loaded = false @commits_loaded = false
this.activateTab(@opts.action) this.activateTab(@opts.action)
this.bindEvents() this.bindEvents()
this.initMergeWidget() this.initMergeWidget()
this.$('.show-all-commits').on 'click', => this.$('.show-all-commits').on 'click', =>
this.showAllCommits() this.showAllCommits()
...@@ -28,7 +28,7 @@ class MergeRequest ...@@ -28,7 +28,7 @@ class MergeRequest
initMergeWidget: -> initMergeWidget: ->
this.showState( @opts.current_state ) this.showState( @opts.current_state )
if this.$('.automerge_widget').length and @opts.check_enable if this.$('.automerge_widget').length and @opts.check_enable
$.get @opts.url_to_automerge_check, (data) => $.get @opts.url_to_automerge_check, (data) =>
this.showState( data.state ) this.showState( data.state )
...@@ -42,12 +42,12 @@ class MergeRequest ...@@ -42,12 +42,12 @@ class MergeRequest
bindEvents: -> bindEvents: ->
this.$('.nav-tabs').on 'click', 'a', (event) => this.$('.nav-tabs').on 'click', 'a', (event) =>
a = $(event.currentTarget) a = $(event.currentTarget)
href = a.attr('href') href = a.attr('href')
History.replaceState {path: href}, document.title, href History.replaceState {path: href}, document.title, href
event.preventDefault() event.preventDefault()
this.$('.nav-tabs').on 'click', 'li', (event) => this.$('.nav-tabs').on 'click', 'li', (event) =>
this.activateTab($(event.currentTarget).data('action')) this.activateTab($(event.currentTarget).data('action'))
......
var Pager = {
limit:0,
offset:0,
disable:false,
init:
function(limit, preload) {
this.limit=limit;
if(preload) {
this.offset = 0;
this.getOld();
} else {
this.offset = limit;
}
this.initLoadMore();
},
getOld:
function() {
$('.loading').show();
$.ajax({
type: "GET",
url: location.href,
data: "limit=" + this.limit + "&offset=" + this.offset,
complete: function(){ $('.loading').hide()},
dataType: "script"});
},
append:
function(count, html) {
$(".content_list").append(html);
if(count > 0) {
this.offset += count;
} else {
this.disable = true;
}
},
initLoadMore:
function() {
$(document).endlessScroll({
bottomPixels: 400,
fireDelay: 1000,
fireOnce:true,
ceaseFire: function() {
return Pager.disable;
},
callback: function(i) {
$('.loading').show();
Pager.getOld();
}
});
}
}
@Pager =
limit: 0
offset: 0
disable: false
init: (limit, preload) ->
@limit = limit
if preload
@offset = 0
@getOld()
else
@offset = limit
@initLoadMore()
getOld: ->
$(".loading").show()
$.ajax
type: "GET"
url: location.href
data: "limit=" + @limit + "&offset=" + @offset
complete: ->
$(".loading").hide()
dataType: "script"
append: (count, html) ->
$(".content_list").append html
if count > 0
@offset += count
else
@disable = true
initLoadMore: ->
$(document).endlessScroll
bottomPixels: 400
fireDelay: 1000
fireOnce: true
ceaseFire: ->
Pager.disable
callback: (i) ->
$(".loading").show()
Pager.getOld()
html { html {
overflow-y: scroll; overflow-y: scroll;
} }
/** LAYOUT **/ /** LAYOUT **/
...@@ -277,8 +277,20 @@ p.time { ...@@ -277,8 +277,20 @@ p.time {
} }
} }
.search-holder {
label, input {
height: 30px;
padding: 0;
font-size: 14px;
}
label {
line-height: 30px;
color: #666;
}
}
.highlight_word { .highlight_word {
background: #EEDC94; border-bottom: 2px solid #F90;
} }
.status_info { .status_info {
...@@ -326,7 +338,7 @@ li.note { ...@@ -326,7 +338,7 @@ li.note {
li { li {
border-bottom:none !important; border-bottom:none !important;
} }
.file { .attachment {
padding-left: 20px; padding-left: 20px;
background:url("icon-attachment.png") no-repeat left center; background:url("icon-attachment.png") no-repeat left center;
} }
......
...@@ -135,7 +135,7 @@ ...@@ -135,7 +135,7 @@
pre { pre {
border: none; border: none;
border-radius: 0; border-radius: 0;
font-family: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono','lucida console',monospace; font-family: $monospace_font;
font-size: 12px !important; font-size: 12px !important;
line-height: 16px !important; line-height: 16px !important;
margin: 0; margin: 0;
......
...@@ -4,4 +4,4 @@ ...@@ -4,4 +4,4 @@
} }
/** Typo **/ /** Typo **/
$monospace: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono', 'lucida console', monospace; $monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono', 'lucida console', monospace;
...@@ -21,7 +21,7 @@ h6 { ...@@ -21,7 +21,7 @@ h6 {
/** CODE **/ /** CODE **/
pre { pre {
font-family:'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono','lucida console',monospace; font-family: $monospace_font;
&.dark { &.dark {
background: #333; background: #333;
...@@ -79,7 +79,7 @@ a:focus { ...@@ -79,7 +79,7 @@ a:focus {
} }
.monospace { .monospace {
font-family: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono','lucida console',monospace; font-family: $monospace_font;
} }
/** /**
......
/** Colors **/ /**
* General Colors
*/
$primary_color: #2FA0BB; $primary_color: #2FA0BB;
$link_color: #3A89A3; $link_color: #3A89A3;
$style_color: #474D57; $style_color: #474D57;
$hover: #D9EDF7; $hover: #D9EDF7;
/**
* Commit Diff Colors
*/
$added: #63c363;
$deleted: #f77;
/** /**
* * Commit file
* COMMIT SHOw
*
*/ */
.commit-committer-link, .commit-committer-link,
.commit-author-link { .commit-author-link {
...@@ -12,11 +10,11 @@ ...@@ -12,11 +10,11 @@
} }
} }
.diff_file { .file {
border: 1px solid #CCC; border: 1px solid #CCC;
margin-bottom: 1em; margin-bottom: 1em;
.diff_file_header { .header {
@extend .clearfix; @extend .clearfix;
padding: 5px 5px 5px 10px; padding: 5px 5px 5px 10px;
color: #555; color: #555;
...@@ -28,32 +26,35 @@ ...@@ -28,32 +26,35 @@
background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf);
background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf);
a{
color: $style_color;
}
> span { > span {
font-family: $monospace; font-family: $monospace_font;
font-size: 14px; font-size: 14px;
line-height: 30px; line-height: 30px;
} }
a.view-commit{ a.view-file{
font-weight: bold; font-weight: bold;
} }
.commit-short-id{ .commit-short-id{
font-family: $monospace; font-family: $monospace_font;
font-size: smaller; font-size: smaller;
} }
.file-mode{ .file-mode{
font-family: $monospace; font-family: $monospace_font;
} }
} }
.diff_file_content { .content {
overflow: auto; overflow: auto;
overflow-y: hidden; overflow-y: hidden;
background: #fff; background: #FFF;
color: #333; color: #333;
font-size: 12px; font-size: 12px;
font-family: $monospace;
.old{ .old{
span.idiff{ span.idiff{
background-color: #FAA; background-color: #FAA;
...@@ -66,114 +67,274 @@ ...@@ -66,114 +67,274 @@
} }
table { table {
font-family: $monospace_font;
border: none;
margin: 0px;
padding: 0px;
td { td {
line-height: 18px; line-height: 18px;
font-size: 12px;
}
}
.old_line, .new_line {
margin: 0px;
padding: 0px;
border: none;
background: #EEE;
color: #666;
padding: 0px 5px;
border-right: 1px solid #ccc;
text-align: right;
min-width: 35px;
max-width: 35px;
width: 35px;
@include user-select(none);
a {
float: left;
width: 35px;
font-weight: normal;
color: #666;
&:hover {
text-decoration: underline;
}
}
}
.line_content {
white-space: pre;
height: 14px;
margin: 0px;
padding: 0px;
border: none;
&.new {
background: #CFD;
}
&.old {
background: #FDD;
}
&.matched {
color: #ccc;
background: #fafafa;
} }
} }
} }
.diff_file_content_image { .image {
background: #eee; background: #ddd;
text-align: center; text-align: center;
.image { padding: 30px;
.wrap{
display: inline-block; display: inline-block;
margin: 50px; }
max-width: 400px;
.frame {
display: inline-block;
background-color: #fff;
line-height: 0;
img{ img{
border: 1px solid #FFF;
background: url('trans_bg.gif'); background: url('trans_bg.gif');
} }
&.deleted {
border: 1px solid $deleted;
}
&.diff_removed { &.added {
img{ border: 1px solid $added;
border: 1px solid #C00;
}
} }
}
.image-info{
font-size: 12px;
margin: 5px 0 0 0;
color: grey;
}
&.diff_added { .view.swipe{
img{ position: relative;
border: 1px solid #0C0;
.swipe-frame{
display: block;
margin: auto;
position: relative;
}
.swipe-wrap{
overflow: hidden;
border-left: 1px solid #999;
position: absolute;
display: block;
top: 13px;
right: 7px;
}
.frame{
top: 0;
right: 0;
position: absolute;
&.deleted{
margin: 0;
display: block;
top: 13px;
right: 7px;
} }
} }
.swipe-bar{
.image-info{ display: block;
margin: 5px 0 0 0; height: 100%;
width: 15px;
z-index: 100;
position: absolute;
cursor: pointer;
&:hover{
.top-handle{
background-position: -15px 3px;
}
.bottom-handle{
background-position: -15px -11px;
}
};
.top-handle{
display: block;
height: 14px;
width: 15px;
position: absolute;
top: 0px;
background: url('swipemode_sprites.gif') 0 3px no-repeat;
}
.bottom-handle{
display: block;
height: 14px;
width: 15px;
position: absolute;
bottom: 0px;
background: url('swipemode_sprites.gif') 0 -11px no-repeat;
}
} }
} } //.view.swipe
.view.onion-skin{
&.img_compared { .onion-skin-frame{
.image { display: block;
max-width: 300px; margin: auto;
position: relative;
} }
} .frame.added, .frame.deleted {
position: absolute;
display: block;
top: 0px;
left: 0px;
}
.controls{
display: block;
height: 14px;
width: 300px;
z-index: 100;
position: absolute;
bottom: 0px;
left: 50%;
margin-left: -150px;
.drag-track{
display: block;
position: absolute;
left: 12px;
height: 10px;
width: 276px;
background: url('onion_skin_sprites.gif') -4px -20px repeat-x;
}
.dragger {
display: block;
position: absolute;
left: 0px;
top: 0px;
height: 14px;
width: 14px;
background: url('onion_skin_sprites.gif') 0px -34px repeat-x;
cursor: pointer;
}
.transparent {
display: block;
position: absolute;
top: 2px;
right: 0px;
height: 10px;
width: 10px;
background: url('onion_skin_sprites.gif') -2px 0px no-repeat;
}
.opaque {
display: block;
position: absolute;
top: 2px;
left: 0px;
height: 10px;
width: 10px;
background: url('onion_skin_sprites.gif') -2px -10px no-repeat;
}
}
} //.view.onion-skin
} }
} .view-modes{
.diff_file_content{ padding: 10px;
table { text-align: center;
border: none;
margin: 0px; background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf));
padding: 0px; background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf);
tr { background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf);
td { background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf);
font-size: 12px;
} ul, li{
list-style: none;
margin: 0;
padding: 0;
display: inline-block;
} }
}
.new_line, li{
.old_line, color: grey;
.notes_line { border-left: 1px solid #c1c1c1;
margin:0px; padding: 0 12px 0 16px;
padding:0px; cursor: pointer;
border:none; &:first-child{
background:#EEE; border-left: none;
color:#666; }
padding: 0px 5px; &:hover{
border-right: 1px solid #ccc;
text-align: right;
min-width: 35px;
max-width: 35px;
width: 35px;
moz-user-select: none;
-khtml-user-select: none;
user-select: none;
a {
float: left;
width: 35px;
font-weight: normal;
color: #666;
&:hover {
text-decoration: underline; text-decoration: underline;
} }
} &.active{
} &:hover{
.line_content { text-decoration: none;
white-space: pre; }
height: 14px; cursor: default;
margin: 0px; color: #333;
padding: 0px; }
border: none; &.disabled{
&.new { display: none;
background: #CFD; }
}
&.old {
background: #FDD;
}
&.matched {
color: #ccc;
background: #fafafa;
} }
} }
} }
/** COMMIT BLOCK **/ /** COMMIT BLOCK **/
.commit-title{display: block;} .commit-title{
.commit-title{margin-bottom: 10px} display: block;
.commit-author, .commit-committer{display: block;color: #999; font-weight: normal; font-style: italic;} }
.commit-author strong, .commit-committer strong{font-weight: bold; font-style: normal;} .commit-title{
margin-bottom: 10px;
}
.commit-author, .commit-committer{
display: block;
color: #999;
font-weight: normal;
font-style: italic;
}
.commit-author strong, .commit-committer strong{
font-weight: bold;
font-style: normal;
}
/** COMMIT ROW **/ /**
* COMMIT ROW
*/
.commit { .commit {
.browse_code_link_holder { .browse_code_link_holder {
@extend .span2; @extend .span2;
...@@ -199,11 +360,10 @@ ...@@ -199,11 +360,10 @@
float: left; float: left;
@extend .lined; @extend .lined;
min-width: 65px; min-width: 65px;
font-family: $monospace; font-family: $monospace_font;
} }
} }
.diff_file_header a,
.file-stats a { .file-stats a {
color: $style_color; color: $style_color;
} }
...@@ -237,7 +397,7 @@ ...@@ -237,7 +397,7 @@
font-size: 13px; font-size: 13px;
background: #474D57; background: #474D57;
color: #fff; color: #fff;
font-family: $monospace; font-family: $monospace_font;
} }
......
...@@ -77,7 +77,7 @@ li.merge_request { ...@@ -77,7 +77,7 @@ li.merge_request {
font-size: 14px; font-size: 14px;
background: #474D57; background: #474D57;
color: #fff; color: #fff;
font-family: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono','lucida console',monospace; font-family: $monospace_font;
} }
.mr_source_commit, .mr_source_commit,
......
...@@ -40,13 +40,13 @@ ul.notes { ...@@ -40,13 +40,13 @@ ul.notes {
.discussion-body { .discussion-body {
margin-left: 50px; margin-left: 50px;
.diff_file, .file,
.discussion-hidden, .discussion-hidden,
.notes { .notes {
@extend .borders; @extend .borders;
background-color: #F9F9F9; background-color: #F9F9F9;
} }
.diff_file .notes { .file .notes {
/* reset */ /* reset */
background: inherit; background: inherit;
border: none; border: none;
...@@ -109,7 +109,7 @@ ul.notes { ...@@ -109,7 +109,7 @@ ul.notes {
} }
} }
.diff_file .notes_holder { .file .notes_holder {
font-family: $sansFontFamily; font-family: $sansFontFamily;
font-size: 13px; font-size: 13px;
line-height: 18px; line-height: 18px;
...@@ -134,8 +134,6 @@ ul.notes { ...@@ -134,8 +134,6 @@ ul.notes {
} }
} }
/** /**
* Actions for Discussions/Notes * Actions for Discussions/Notes
*/ */
...@@ -171,7 +169,7 @@ ul.notes { ...@@ -171,7 +169,7 @@ ul.notes {
} }
} }
} }
.diff_file .note .note-actions { .file .note .note-actions {
right: 0; right: 0;
top: 0; top: 0;
} }
...@@ -182,7 +180,7 @@ ul.notes { ...@@ -182,7 +180,7 @@ ul.notes {
* Line note button on the side of diffs * Line note button on the side of diffs
*/ */
.diff_file tr.line_holder { .file tr.line_holder {
.add-diff-note { .add-diff-note {
background: url("diff_note_add.png") no-repeat left 0; background: url("diff_note_add.png") no-repeat left 0;
height: 22px; height: 22px;
...@@ -212,8 +210,6 @@ ul.notes { ...@@ -212,8 +210,6 @@ ul.notes {
} }
} }
/** /**
* Note Form * Note Form
*/ */
...@@ -222,7 +218,12 @@ ul.notes { ...@@ -222,7 +218,12 @@ ul.notes {
.reply-btn { .reply-btn {
@extend .save-btn; @extend .save-btn;
} }
.diff_file, .file .content tr.line_holder:hover > td { background: $hover !important; }
.file .content tr.line_holder:hover > td .line_note_link {
opacity: 1.0;
filter: alpha(opacity=100);
}
.file,
.discussion { .discussion {
.new_note { .new_note {
margin: 8px 5px 8px 0; margin: 8px 5px 8px 0;
......
...@@ -6,7 +6,6 @@ ...@@ -6,7 +6,6 @@
.side { .side {
@extend .right; @extend .right;
.groups_box,
.projects_box { .projects_box {
> .title { > .title {
padding: 2px 15px; padding: 2px 15px;
......
# Provides a base class for Admin controllers to subclass # Provides a base class for Admin controllers to subclass
# #
# Automatically sets the layout and ensures an administrator is logged in # Automatically sets the layout and ensures an administrator is logged in
class AdminController < ApplicationController class Admin::ApplicationController < ApplicationController
layout 'admin' layout 'admin'
before_filter :authenticate_admin! before_filter :authenticate_admin!
......
class Admin::DashboardController < AdminController class Admin::DashboardController < Admin::ApplicationController
def index def index
@projects = Project.order("created_at DESC").limit(10) @projects = Project.order("created_at DESC").limit(10)
@users = User.order("created_at DESC").limit(10) @users = User.order("created_at DESC").limit(10)
......
class Admin::GroupsController < AdminController class Admin::GroupsController < Admin::ApplicationController
before_filter :group, only: [:edit, :show, :update, :destroy, :project_update, :project_teams_update] before_filter :group, only: [:edit, :show, :update, :destroy, :project_update, :project_teams_update]
def index def index
......
class Admin::HooksController < AdminController class Admin::HooksController < Admin::ApplicationController
def index def index
@hooks = SystemHook.all @hooks = SystemHook.all
@hook = SystemHook.new @hook = SystemHook.new
......
class Admin::LogsController < AdminController class Admin::LogsController < Admin::ApplicationController
end end
# Provides a base class for Admin controllers to subclass
#
# Automatically sets the layout and ensures an administrator is logged in
class Admin::Projects::ApplicationController < Admin::ApplicationController
protected
def project
@project ||= Project.find_with_namespace(params[:project_id])
end
end
class Admin::Projects::MembersController < Admin::Projects::ApplicationController
def edit
@member = team_member
@project = project
@team_member_relation = team_member_relation
end
def update
if team_member_relation.update_attributes(params[:team_member])
redirect_to [:admin, project], notice: 'Project Access was successfully updated.'
else
render action: "edit"
end
end
def destroy
team_member_relation.destroy
redirect_to :back
end
private
def team_member
@member ||= project.users.find_by_username(params[:id])
end
def team_member_relation
team_member.users_projects.find_by_project_id(project)
end
end
class Admin::ProjectsController < AdminController class Admin::ProjectsController < Admin::ApplicationController
before_filter :project, only: [:edit, :show, :update, :destroy, :team_update] before_filter :project, only: [:edit, :show, :update, :destroy, :team_update]
def index def index
...@@ -29,7 +29,9 @@ class Admin::ProjectsController < AdminController ...@@ -29,7 +29,9 @@ class Admin::ProjectsController < AdminController
end end
def update def update
status = Projects::UpdateContext.new(project, current_user, params).execute(:admin) project.creator = current_user unless project.creator
status = ::Projects::UpdateContext.new(project, current_user, params).execute(:admin)
if status if status
redirect_to [:admin, @project], notice: 'Project was successfully updated.' redirect_to [:admin, @project], notice: 'Project was successfully updated.'
......
class Admin::ResqueController < AdminController class Admin::ResqueController < Admin::ApplicationController
def show def show
end end
end end
class Admin::TeamMembersController < AdminController
def edit
@admin_team_member = UsersProject.find(params[:id])
end
def update
@admin_team_member = UsersProject.find(params[:id])
if @admin_team_member.update_attributes(params[:team_member])
redirect_to [:admin, @admin_team_member.project], notice: 'Project Access was successfully updated.'
else
render action: "edit"
end
end
def destroy
@admin_team_member = UsersProject.find(params[:id])
@admin_team_member.destroy
redirect_to :back
end
end
# Provides a base class for Admin controllers to subclass
#
# Automatically sets the layout and ensures an administrator is logged in
class Admin::Teams::ApplicationController < Admin::ApplicationController
private
def user_team
@team = UserTeam.find_by_path(params[:team_id])
end
end
class Admin::Teams::MembersController < Admin::Teams::ApplicationController
def new
@users = User.potential_team_members(user_team)
@users = UserDecorator.decorate @users
end
def create
unless params[:user_ids].blank?
user_ids = params[:user_ids]
access = params[:default_project_access]
is_admin = params[:group_admin]
user_team.add_members(user_ids, access, is_admin)
end
redirect_to admin_team_path(user_team), notice: 'Members was successfully added into Team of users.'
end
def edit
team_member
end
def update
options = {default_projects_access: params[:default_project_access], group_admin: params[:group_admin]}
if user_team.update_membership(team_member, options)
redirect_to admin_team_path(user_team), notice: "Membership for #{team_member.name} was successfully updated in Team of users."
else
render :edit
end
end
def destroy
user_team.remove_member(team_member)
redirect_to admin_team_path(user_team), notice: "Member #{team_member.name} was successfully removed from Team of users."
end
protected
def team_member
@member ||= user_team.members.find_by_username(params[:id])
end
end
class Admin::Teams::ProjectsController < Admin::Teams::ApplicationController
def new
@projects = Project.scoped
@projects = @projects.without_team(user_team) if user_team.projects.any?
#@projects.reject!(&:empty_repo?)
end
def create
unless params[:project_ids].blank?
project_ids = params[:project_ids]
access = params[:greatest_project_access]
user_team.assign_to_projects(project_ids, access)
end
redirect_to admin_team_path(user_team), notice: 'Team of users was successfully assgned to projects.'
end
def edit
team_project
end
def update
if user_team.update_project_access(team_project, params[:greatest_project_access])
redirect_to admin_team_path(user_team), notice: 'Access was successfully updated.'
else
render :edit
end
end
def destroy
user_team.resign_from_project(team_project)
redirect_to admin_team_path(user_team), notice: 'Team of users was successfully reassigned from project.'
end
protected
def team_project
@project ||= user_team.projects.find_with_namespace(params[:id])
end
end
class Admin::TeamsController < Admin::ApplicationController
def index
@teams = UserTeam.order('name ASC')
@teams = @teams.search(params[:name]) if params[:name].present?
@teams = @teams.page(params[:page]).per(20)
end
def show
user_team
end
def new
@team = UserTeam.new
end
def edit
user_team
end
def create
@team = UserTeam.new(params[:user_team])
@team.path = @team.name.dup.parameterize if @team.name
@team.owner = current_user
if @team.save
redirect_to admin_team_path(@team), notice: 'Team of users was successfully created.'
else
render action: "new"
end
end
def update
user_team_params = params[:user_team].dup
owner_id = user_team_params.delete(:owner_id)
if owner_id
user_team.owner = User.find(owner_id)
end
if user_team.update_attributes(user_team_params)
redirect_to admin_team_path(user_team), notice: 'Team of users was successfully updated.'
else
render action: "edit"
end
end
def destroy
user_team.destroy
redirect_to admin_teams_path, notice: 'Team of users was successfully deleted.'
end
protected
def user_team
@team ||= UserTeam.find_by_path(params[:id])
end
end
class Admin::UsersController < AdminController class Admin::UsersController < Admin::ApplicationController
before_filter :admin_user, only: [:show, :edit, :update, :destroy]
def index def index
@admin_users = User.scoped @admin_users = User.scoped
@admin_users = @admin_users.filter(params[:filter]) @admin_users = @admin_users.filter(params[:filter])
...@@ -7,25 +9,18 @@ class Admin::UsersController < AdminController ...@@ -7,25 +9,18 @@ class Admin::UsersController < AdminController
end end
def show def show
@admin_user = User.find(params[:id]) @projects = Project.scoped
@projects = @projects.without_user(admin_user) if admin_user.authorized_projects.present?
@projects = if @admin_user.authorized_projects.empty?
Project
else
Project.without_user(@admin_user)
end.all
end end
def team_update def team_update
@admin_user = User.find(params[:id])
UsersProject.add_users_into_projects( UsersProject.add_users_into_projects(
params[:project_ids], params[:project_ids],
[@admin_user.id], [admin_user.id],
params[:project_access] params[:project_access]
) )
redirect_to [:admin, @admin_user], notice: 'Teams were successfully updated.' redirect_to [:admin, admin_user], notice: 'Teams were successfully updated.'
end end
...@@ -34,13 +29,11 @@ class Admin::UsersController < AdminController ...@@ -34,13 +29,11 @@ class Admin::UsersController < AdminController
end end
def edit def edit
@admin_user = User.find(params[:id]) admin_user
end end
def block def block
@admin_user = User.find(params[:id]) if admin_user.block
if @admin_user.block
redirect_to :back, alert: "Successfully blocked" redirect_to :back, alert: "Successfully blocked"
else else
redirect_to :back, alert: "Error occured. User was not blocked" redirect_to :back, alert: "Error occured. User was not blocked"
...@@ -48,9 +41,7 @@ class Admin::UsersController < AdminController ...@@ -48,9 +41,7 @@ class Admin::UsersController < AdminController
end end
def unblock def unblock
@admin_user = User.find(params[:id]) if admin_user.update_attribute(:blocked, false)
if @admin_user.update_attribute(:blocked, false)
redirect_to :back, alert: "Successfully unblocked" redirect_to :back, alert: "Successfully unblocked"
else else
redirect_to :back, alert: "Error occured. User was not unblocked" redirect_to :back, alert: "Error occured. User was not unblocked"
...@@ -82,30 +73,34 @@ class Admin::UsersController < AdminController ...@@ -82,30 +73,34 @@ class Admin::UsersController < AdminController
params[:user].delete(:password_confirmation) params[:user].delete(:password_confirmation)
end end
@admin_user = User.find(params[:id]) admin_user.admin = (admin && admin.to_i > 0)
@admin_user.admin = (admin && admin.to_i > 0)
respond_to do |format| respond_to do |format|
if @admin_user.update_attributes(params[:user], as: :admin) if admin_user.update_attributes(params[:user], as: :admin)
format.html { redirect_to [:admin, @admin_user], notice: 'User was successfully updated.' } format.html { redirect_to [:admin, admin_user], notice: 'User was successfully updated.' }
format.json { head :ok } format.json { head :ok }
else else
format.html { render action: "edit" } format.html { render action: "edit" }
format.json { render json: @admin_user.errors, status: :unprocessable_entity } format.json { render json: admin_user.errors, status: :unprocessable_entity }
end end
end end
end end
def destroy def destroy
@admin_user = User.find(params[:id]) if admin_user.personal_projects.count > 0
if @admin_user.personal_projects.count > 0
redirect_to admin_users_path, alert: "User is a project owner and can't be removed." and return redirect_to admin_users_path, alert: "User is a project owner and can't be removed." and return
end end
@admin_user.destroy admin_user.destroy
respond_to do |format| respond_to do |format|
format.html { redirect_to admin_users_url } format.html { redirect_to admin_users_path }
format.json { head :ok } format.json { head :ok }
end end
end end
protected
def admin_user
@admin_user ||= User.find_by_username!(params[:id])
end
end end
...@@ -94,6 +94,18 @@ class ApplicationController < ActionController::Base ...@@ -94,6 +94,18 @@ class ApplicationController < ActionController::Base
return access_denied! unless can?(current_user, :download_code, project) return access_denied! unless can?(current_user, :download_code, project)
end end
def authorize_create_team!
return access_denied! unless can?(current_user, :create_team, nil)
end
def authorize_manage_user_team!
return access_denied! unless user_team.present? && can?(current_user, :manage_user_team, user_team)
end
def authorize_admin_user_team!
return access_denied! unless user_team.present? && can?(current_user, :admin_user_team, user_team)
end
def access_denied! def access_denied!
render "errors/access_denied", layout: "errors", status: 404 render "errors/access_denied", layout: "errors", status: 404
end end
...@@ -135,4 +147,5 @@ class ApplicationController < ActionController::Base ...@@ -135,4 +147,5 @@ class ApplicationController < ActionController::Base
def dev_tools def dev_tools
Rack::MiniProfiler.authorize_request Rack::MiniProfiler.authorize_request
end end
end end
class DashboardController < ApplicationController class DashboardController < ApplicationController
respond_to :html respond_to :html
before_filter :projects before_filter :load_projects
before_filter :event_filter, only: :index before_filter :event_filter, only: :show
def index def show
@groups = current_user.authorized_groups @groups = current_user.authorized_groups
@has_authorized_projects = @projects.count > 0 @has_authorized_projects = @projects.count > 0
@teams = current_user.authorized_teams
@projects = case params[:scope] @projects_count = @projects.count
when 'personal' then @projects = @projects.limit(20)
@projects.personal(current_user)
when 'joined' then
@projects.joined(current_user)
else
@projects
end
@projects = @projects.page(params[:page]).per(30)
@events = Event.in_projects(current_user.authorized_projects.pluck(:id)) @events = Event.in_projects(current_user.authorized_projects.pluck(:id))
@events = @event_filter.apply_filter(@events) @events = @event_filter.apply_filter(@events)
...@@ -33,6 +24,19 @@ class DashboardController < ApplicationController ...@@ -33,6 +24,19 @@ class DashboardController < ApplicationController
end end
end end
def projects
@projects = case params[:scope]
when 'personal' then
@projects.personal(current_user)
when 'joined' then
@projects.joined(current_user)
else
@projects
end
@projects = @projects.page(params[:page]).per(30)
end
# Get authored or assigned open merge requests # Get authored or assigned open merge requests
def merge_requests def merge_requests
@merge_requests = current_user.cared_merge_requests @merge_requests = current_user.cared_merge_requests
...@@ -55,7 +59,7 @@ class DashboardController < ApplicationController ...@@ -55,7 +59,7 @@ class DashboardController < ApplicationController
protected protected
def projects def load_projects
@projects = current_user.authorized_projects.sorted_by_activity @projects = current_user.authorized_projects.sorted_by_activity
end end
......
class GroupsController < ApplicationController class GroupsController < ApplicationController
respond_to :html respond_to :html
layout 'group' layout 'group', except: [:new, :create]
before_filter :group before_filter :group, except: [:new, :create]
before_filter :projects
# Authorize # Authorize
before_filter :authorize_read_group! before_filter :authorize_read_group!, except: [:new, :create]
before_filter :authorize_create_group!, only: [:new, :create]
# Load group projects
before_filter :projects, except: [:new, :create]
def new
@group = Group.new
end
def create
@group = Group.new(params[:group])
@group.path = @group.name.dup.parameterize if @group.name
@group.owner = current_user
if @group.save
redirect_to @group, notice: 'Group was successfully created.'
else
render action: "new"
end
end
def show def show
@events = Event.in_projects(project_ids).limit(20).offset(params[:offset] || 0) @events = Event.in_projects(project_ids).limit(20).offset(params[:offset] || 0)
...@@ -85,4 +104,8 @@ class GroupsController < ApplicationController ...@@ -85,4 +104,8 @@ class GroupsController < ApplicationController
return render_404 return render_404
end end
end end
def authorize_create_group!
can?(current_user, :create_group, nil)
end
end end
class Projects::ApplicationController < ApplicationController
before_filter :authorize_admin_team_member!
protected
def user_team
@team ||= UserTeam.find_by_path(params[:id])
end
end
class Projects::TeamsController < Projects::ApplicationController
def available
@teams = current_user.is_admin? ? UserTeam.scoped : current_user.user_teams
@teams = @teams.without_project(project)
unless @teams.any?
redirect_to project_team_index_path(project), notice: "No avaliable teams for assigment."
end
end
def assign
unless params[:team_id].blank?
team = UserTeam.find(params[:team_id])
access = params[:greatest_project_access]
team.assign_to_project(project, access)
end
redirect_to project_team_index_path(project)
end
def resign
team = project.user_teams.find_by_path(params[:id])
team.resign_from_project(project)
redirect_to project_team_index_path(project)
end
end
...@@ -19,7 +19,7 @@ class ProjectsController < ProjectResourceController ...@@ -19,7 +19,7 @@ class ProjectsController < ProjectResourceController
end end
def create def create
@project = Projects::CreateContext.new(current_user, params[:project]).execute @project = ::Projects::CreateContext.new(current_user, params[:project]).execute
respond_to do |format| respond_to do |format|
flash[:notice] = 'Project was successfully created.' if @project.saved? flash[:notice] = 'Project was successfully created.' if @project.saved?
...@@ -35,7 +35,7 @@ class ProjectsController < ProjectResourceController ...@@ -35,7 +35,7 @@ class ProjectsController < ProjectResourceController
end end
def update def update
status = Projects::UpdateContext.new(project, current_user, params).execute status = ::Projects::UpdateContext.new(project, current_user, params).execute
respond_to do |format| respond_to do |format|
if status if status
......
class SearchController < ApplicationController class SearchController < ApplicationController
def show def show
result = SearchContext.new(current_user.authorized_projects.map(&:id), params).execute project_id = params[:project_id]
group_id = params[:group_id]
project_ids = current_user.authorized_projects.map(&:id)
if group_id.present?
group_project_ids = Group.find(group_id).projects.map(&:id)
project_ids.select! { |id| group_project_ids.include?(id)}
elsif project_id.present?
project_ids.select! { |id| id == project_id.to_i}
end
result = SearchContext.new(project_ids, params).execute
@projects = result[:projects] @projects = result[:projects]
@merge_requests = result[:merge_requests] @merge_requests = result[:merge_requests]
......
...@@ -4,15 +4,16 @@ class TeamMembersController < ProjectResourceController ...@@ -4,15 +4,16 @@ class TeamMembersController < ProjectResourceController
before_filter :authorize_admin_project!, except: [:index, :show] before_filter :authorize_admin_project!, except: [:index, :show]
def index def index
@teams = UserTeam.scoped
end end
def show def show
@team_member = project.users_projects.find(params[:id]) @user_project_relation = project.users_projects.find_by_user_id(member)
@events = @team_member.user.recent_events.where(:project_id => @project.id).limit(7) @events = member.recent_events.in_projects(project).limit(7)
end end
def new def new
@team_member = project.users_projects.new @user_project_relation = project.users_projects.new
end end
def create def create
...@@ -28,18 +29,18 @@ class TeamMembersController < ProjectResourceController ...@@ -28,18 +29,18 @@ class TeamMembersController < ProjectResourceController
end end
def update def update
@team_member = project.users_projects.find(params[:id]) @user_project_relation = project.users_projects.find_by_user_id(member)
@team_member.update_attributes(params[:team_member]) @user_project_relation.update_attributes(params[:team_member])
unless @team_member.valid? unless @user_project_relation.valid?
flash[:alert] = "User should have at least one role" flash[:alert] = "User should have at least one role"
end end
redirect_to project_team_index_path(@project) redirect_to project_team_index_path(@project)
end end
def destroy def destroy
@team_member = project.users_projects.find(params[:id]) @user_project_relation = project.users_projects.find_by_user_id(member)
@team_member.destroy @user_project_relation.destroy
respond_to do |format| respond_to do |format|
format.html { redirect_to project_team_index_path(@project) } format.html { redirect_to project_team_index_path(@project) }
...@@ -54,4 +55,10 @@ class TeamMembersController < ProjectResourceController ...@@ -54,4 +55,10 @@ class TeamMembersController < ProjectResourceController
redirect_to project_team_members_path(project), notice: notice redirect_to project_team_members_path(project), notice: notice
end end
protected
def member
@member ||= User.find_by_username(params[:id])
end
end end
class Teams::ApplicationController < ApplicationController
layout 'user_team'
before_filter :authorize_manage_user_team!
protected
def user_team
@team ||= UserTeam.find_by_path(params[:team_id])
end
end
class Teams::MembersController < Teams::ApplicationController
skip_before_filter :authorize_manage_user_team!, only: [:index]
def index
@members = user_team.members
end
def new
@users = User.potential_team_members(user_team)
@users = UserDecorator.decorate @users
end
def create
unless params[:user_ids].blank?
user_ids = params[:user_ids]
access = params[:default_project_access]
is_admin = params[:group_admin]
user_team.add_members(user_ids, access, is_admin)
end
redirect_to team_members_path(user_team), notice: 'Members was successfully added into Team of users.'
end
def edit
team_member
end
def update
options = {default_projects_access: params[:default_project_access], group_admin: params[:group_admin]}
if user_team.update_membership(team_member, options)
redirect_to team_members_path(user_team), notice: "Membership for #{team_member.name} was successfully updated in Team of users."
else
render :edit
end
end
def destroy
user_team.remove_member(team_member)
redirect_to team_path(user_team), notice: "Member #{team_member.name} was successfully removed from Team of users."
end
protected
def team_member
@member ||= user_team.members.find_by_username(params[:id])
end
end
class Teams::ProjectsController < Teams::ApplicationController
skip_before_filter :authorize_manage_user_team!, only: [:index]
def index
@projects = user_team.projects
@avaliable_projects = current_user.admin? ? Project.without_team(user_team) : current_user.owned_projects.without_team(user_team)
end
def new
user_team
@avaliable_projects = current_user.owned_projects.scoped
@avaliable_projects = @avaliable_projects.without_team(user_team) if user_team.projects.any?
redirect_to team_projects_path(user_team), notice: "No avalible projects." unless @avaliable_projects.any?
end
def create
redirect_to :back if params[:project_ids].blank?
project_ids = params[:project_ids]
access = params[:greatest_project_access]
# Reject non-allowed projects
allowed_project_ids = current_user.owned_projects.map(&:id)
project_ids.select! { |id| allowed_project_ids.include?(id.to_i) }
# Assign projects to team
user_team.assign_to_projects(project_ids, access)
redirect_to team_projects_path(user_team), notice: 'Team of users was successfully assigned to projects.'
end
def edit
team_project
end
def update
if user_team.update_project_access(team_project, params[:greatest_project_access])
redirect_to team_projects_path(user_team), notice: 'Access was successfully updated.'
else
render :edit
end
end
def destroy
user_team.resign_from_project(team_project)
redirect_to team_projects_path(user_team), notice: 'Team of users was successfully reassigned from project.'
end
private
def team_project
@project ||= user_team.projects.find_with_namespace(params[:id])
end
end
class TeamsController < ApplicationController
# Authorize
before_filter :authorize_create_team!, only: [:new, :create]
before_filter :authorize_manage_user_team!, only: [:edit, :update]
before_filter :authorize_admin_user_team!, only: [:destroy]
before_filter :user_team, except: [:new, :create]
layout 'user_team', except: [:new, :create]
def show
user_team
projects
@events = Event.in_projects(user_team.project_ids).limit(20).offset(params[:offset] || 0)
end
def edit
user_team
end
def update
if user_team.update_attributes(params[:user_team])
redirect_to team_path(user_team)
else
render action: :edit
end
end
def destroy
user_team.destroy
redirect_to dashboard_path
end
def new
@team = UserTeam.new
end
def create
@team = UserTeam.new(params[:user_team])
@team.owner = current_user unless params[:owner]
@team.path = @team.name.dup.parameterize if @team.name
if @team.save
redirect_to team_path(@team)
else
render action: :new
end
end
# Get authored or assigned open merge requests
def merge_requests
projects
@merge_requests = MergeRequest.of_user_team(user_team)
@merge_requests = FilterContext.new(@merge_requests, params).execute
@merge_requests = @merge_requests.recent.page(params[:page]).per(20)
end
# Get only assigned issues
def issues
projects
@issues = Issue.of_user_team(user_team)
@issues = FilterContext.new(@issues, params).execute
@issues = @issues.recent.page(params[:page]).per(20)
@issues = @issues.includes(:author, :project)
end
protected
def projects
@projects ||= user_team.projects.sorted_by_activity
end
def user_team
@team ||= current_user.authorized_teams.find_by_path(params[:id])
end
end
...@@ -8,4 +8,8 @@ class UserDecorator < ApplicationDecorator ...@@ -8,4 +8,8 @@ class UserDecorator < ApplicationDecorator
def tm_of(project) def tm_of(project)
project.team_member_by_id(self.id) project.team_member_by_id(self.id)
end end
def name_with_email
"#{name} (#{email})"
end
end end
module Admin::Teams::MembersHelper
def member_since(team, member)
team.user_team_user_relationships.find_by_user_id(member).created_at
end
end
module Admin::Teams::ProjectsHelper
def assigned_since(team, project)
team.user_team_project_relationships.find_by_project_id(project).created_at
end
end
...@@ -72,8 +72,9 @@ module ApplicationHelper ...@@ -72,8 +72,9 @@ module ApplicationHelper
end end
def search_autocomplete_source def search_autocomplete_source
projects = current_user.authorized_projects.map { |p| { label: p.name_with_namespace, url: project_path(p) } } projects = current_user.authorized_projects.map { |p| { label: "project: #{p.name_with_namespace}", url: project_path(p) } }
groups = current_user.authorized_groups.map { |group| { label: "<group> #{group.name}", url: group_path(group) } } groups = current_user.authorized_groups.map { |group| { label: "group: #{group.name}", url: group_path(group) } }
teams = current_user.authorized_teams.map { |team| { label: "team: #{team.name}", url: team_path(team) } }
default_nav = [ default_nav = [
{ label: "My Profile", url: profile_path }, { label: "My Profile", url: profile_path },
...@@ -83,29 +84,29 @@ module ApplicationHelper ...@@ -83,29 +84,29 @@ module ApplicationHelper
] ]
help_nav = [ help_nav = [
{ label: "API Help", url: help_api_path }, { label: "help: API Help", url: help_api_path },
{ label: "Markdown Help", url: help_markdown_path }, { label: "help: Markdown Help", url: help_markdown_path },
{ label: "Permissions Help", url: help_permissions_path }, { label: "help: Permissions Help", url: help_permissions_path },
{ label: "Public Access Help", url: help_public_access_path }, { label: "help: Public Access Help", url: help_public_access_path },
{ label: "Rake Tasks Help", url: help_raketasks_path }, { label: "help: Rake Tasks Help", url: help_raketasks_path },
{ label: "SSH Keys Help", url: help_ssh_path }, { label: "help: SSH Keys Help", url: help_ssh_path },
{ label: "System Hooks Help", url: help_system_hooks_path }, { label: "help: System Hooks Help", url: help_system_hooks_path },
{ label: "Web Hooks Help", url: help_web_hooks_path }, { label: "help: Web Hooks Help", url: help_web_hooks_path },
{ label: "Workflow Help", url: help_workflow_path }, { label: "help: Workflow Help", url: help_workflow_path },
] ]
project_nav = [] project_nav = []
if @project && @project.repository && @project.repository.root_ref if @project && @project.repository && @project.repository.root_ref
project_nav = [ project_nav = [
{ label: "#{@project.name} Issues", url: project_issues_path(@project) }, { label: "#{@project.name_with_namespace} - Issues", url: project_issues_path(@project) },
{ label: "#{@project.name} Commits", url: project_commits_path(@project, @ref || @project.repository.root_ref) }, { label: "#{@project.name_with_namespace} - Commits", url: project_commits_path(@project, @ref || @project.repository.root_ref) },
{ label: "#{@project.name} Merge Requests", url: project_merge_requests_path(@project) }, { label: "#{@project.name_with_namespace} - Merge Requests", url: project_merge_requests_path(@project) },
{ label: "#{@project.name} Milestones", url: project_milestones_path(@project) }, { label: "#{@project.name_with_namespace} - Milestones", url: project_milestones_path(@project) },
{ label: "#{@project.name} Snippets", url: project_snippets_path(@project) }, { label: "#{@project.name_with_namespace} - Snippets", url: project_snippets_path(@project) },
{ label: "#{@project.name} Team", url: project_team_index_path(@project) }, { label: "#{@project.name_with_namespace} - Team", url: project_team_index_path(@project) },
{ label: "#{@project.name} Tree", url: project_tree_path(@project, @ref || @project.repository.root_ref) }, { label: "#{@project.name_with_namespace} - Tree", url: project_tree_path(@project, @ref || @project.repository.root_ref) },
{ label: "#{@project.name} Wall", url: wall_project_path(@project) }, { label: "#{@project.name_with_namespace} - Wall", url: wall_project_path(@project) },
{ label: "#{@project.name} Wiki", url: project_wikis_path(@project) }, { label: "#{@project.name_with_namespace} - Wiki", url: project_wikis_path(@project) },
] ]
end end
......
...@@ -59,9 +59,9 @@ module CommitsHelper ...@@ -59,9 +59,9 @@ module CommitsHelper
def image_diff_class(diff) def image_diff_class(diff)
if diff.deleted_file if diff.deleted_file
"diff_removed" "deleted"
elsif diff.new_file elsif diff.new_file
"diff_added" "added"
else else
nil nil
end end
......
...@@ -9,9 +9,9 @@ module DashboardHelper ...@@ -9,9 +9,9 @@ module DashboardHelper
case entity case entity
when 'issue' then when 'issue' then
dashboard_issues_path(options) issues_dashboard_path(options)
when 'merge_request' when 'merge_request'
dashboard_merge_requests_path(options) merge_requests_dashboard_path(options)
end end
end end
......
...@@ -3,8 +3,12 @@ module ProjectsHelper ...@@ -3,8 +3,12 @@ module ProjectsHelper
@project.users_projects.sort_by(&:project_access).reverse.group_by(&:project_access) @project.users_projects.sort_by(&:project_access).reverse.group_by(&:project_access)
end end
def remove_from_team_message(project, member) def grouper_project_teams(project)
"You are going to remove #{member.user_name} from #{project.name}. Are you sure?" @project.user_team_project_relationships.sort_by(&:greatest_access).reverse.group_by(&:greatest_access)
end
def remove_from_project_team_message(project, user)
"You are going to remove #{user.name} from #{project.name} project team. Are you sure?"
end end
def link_to_project project def link_to_project project
...@@ -51,7 +55,9 @@ module ProjectsHelper ...@@ -51,7 +55,9 @@ module ProjectsHelper
def project_title project def project_title project
if project.group if project.group
project.name_with_namespace content_tag :span do
link_to(project.group.name, group_path(project.group)) + " / " + project.name
end
else else
project.name project.name
end end
......
...@@ -39,7 +39,12 @@ module TabHelper ...@@ -39,7 +39,12 @@ module TabHelper
# Returns a list item element String # Returns a list item element String
def nav_link(options = {}, &block) def nav_link(options = {}, &block)
if path = options.delete(:path) if path = options.delete(:path)
c, a, _ = path.split('#') if path.respond_to?(:each)
c = path.map { |p| p.split('#').first }
a = path.map { |p| p.split('#').last }
else
c, a, _ = path.split('#')
end
else else
c = options.delete(:controller) c = options.delete(:controller)
a = options.delete(:action) a = options.delete(:action)
......
module UserTeamsHelper
def team_filter_path(entity, options={})
exist_opts = {
status: params[:status],
project_id: params[:project_id],
}
options = exist_opts.merge(options)
case entity
when 'issue' then
issues_team_path(@team, options)
when 'merge_request'
merge_requests_team_path(@team, options)
end
end
def grouped_user_team_members(team)
team.user_team_user_relationships.sort_by(&:permission).reverse.group_by(&:permission)
end
def remove_from_user_team_message(team, member)
"You are going to remove #{member.name} from #{team.name}. Are you sure?"
end
end
class Ability class Ability
class << self class << self
def allowed(object, subject) def allowed(user, subject)
return [] unless user.kind_of?(User)
case subject.class.name case subject.class.name
when "Project" then project_abilities(object, subject) when "Project" then project_abilities(user, subject)
when "Issue" then issue_abilities(object, subject) when "Issue" then issue_abilities(user, subject)
when "Note" then note_abilities(object, subject) when "Note" then note_abilities(user, subject)
when "Snippet" then snippet_abilities(object, subject) when "Snippet" then snippet_abilities(user, subject)
when "MergeRequest" then merge_request_abilities(object, subject) when "MergeRequest" then merge_request_abilities(user, subject)
when "Group", "Namespace" then group_abilities(object, subject) when "Group", "Namespace" then group_abilities(user, subject)
when "UserTeam" then user_team_abilities(user, subject)
else [] else []
end end.concat(global_abilities(user))
end
def global_abilities(user)
rules = []
rules << :create_group if user.can_create_group
rules << :create_team if user.can_create_team
rules
end end
def project_abilities(user, project) def project_abilities(user, project)
...@@ -110,6 +120,22 @@ class Ability ...@@ -110,6 +120,22 @@ class Ability
rules.flatten rules.flatten
end end
def user_team_abilities user, team
rules = []
# Only group owner and administrators can manage group
if team.owner == user || team.admin?(user) || user.admin?
rules << [ :manage_user_team ]
end
if team.owner == user || user.admin?
rules << [ :admin_user_team ]
end
rules.flatten
end
[:issue, :note, :snippet, :merge_request].each do |name| [:issue, :note, :snippet, :merge_request].each do |name|
define_method "#{name}_abilities" do |user, subject| define_method "#{name}_abilities" do |user, subject|
if subject.author == user if subject.author == user
......
...@@ -22,6 +22,7 @@ module Issuable ...@@ -22,6 +22,7 @@ module Issuable
scope :opened, where(closed: false) scope :opened, where(closed: false)
scope :closed, where(closed: true) scope :closed, where(closed: true)
scope :of_group, ->(group) { where(project_id: group.project_ids) } scope :of_group, ->(group) { where(project_id: group.project_ids) }
scope :of_user_team, ->(team) { where(project_id: team.project_ids, assignee_id: team.member_ids) }
scope :assigned, ->(u) { where(assignee_id: u.id)} scope :assigned, ->(u) { where(assignee_id: u.id)}
scope :recent, order("created_at DESC") scope :recent, order("created_at DESC")
......
...@@ -33,28 +33,31 @@ class Project < ActiveRecord::Base ...@@ -33,28 +33,31 @@ class Project < ActiveRecord::Base
attr_accessor :error_code attr_accessor :error_code
# Relations # Relations
belongs_to :group, foreign_key: "namespace_id", conditions: "type = 'Group'" belongs_to :creator, foreign_key: "creator_id", class_name: "User"
belongs_to :group, foreign_key: "namespace_id", conditions: "type = 'Group'"
belongs_to :namespace belongs_to :namespace
belongs_to :creator,
class_name: "User",
foreign_key: "creator_id"
has_many :users, through: :users_projects
has_many :events, dependent: :destroy
has_many :merge_requests, dependent: :destroy
has_many :issues, dependent: :destroy, order: "closed, created_at DESC"
has_many :milestones, dependent: :destroy
has_many :users_projects, dependent: :destroy
has_many :notes, dependent: :destroy
has_many :snippets, dependent: :destroy
has_many :deploy_keys, dependent: :destroy, foreign_key: "project_id", class_name: "Key"
has_many :hooks, dependent: :destroy, class_name: "ProjectHook"
has_many :wikis, dependent: :destroy
has_many :protected_branches, dependent: :destroy
has_one :last_event, class_name: 'Event', order: 'events.created_at DESC', foreign_key: 'project_id' has_one :last_event, class_name: 'Event', order: 'events.created_at DESC', foreign_key: 'project_id'
has_one :gitlab_ci_service, dependent: :destroy has_one :gitlab_ci_service, dependent: :destroy
has_many :events, dependent: :destroy
has_many :merge_requests, dependent: :destroy
has_many :issues, dependent: :destroy, order: "closed, created_at DESC"
has_many :milestones, dependent: :destroy
has_many :users_projects, dependent: :destroy
has_many :notes, dependent: :destroy
has_many :snippets, dependent: :destroy
has_many :deploy_keys, dependent: :destroy, class_name: "Key", foreign_key: "project_id"
has_many :hooks, dependent: :destroy, class_name: "ProjectHook"
has_many :wikis, dependent: :destroy
has_many :protected_branches, dependent: :destroy
has_many :user_team_project_relationships, dependent: :destroy
has_many :users, through: :users_projects
has_many :user_teams, through: :user_team_project_relationships
has_many :user_team_user_relationships, through: :user_teams
has_many :user_teams_members, through: :user_team_user_relationships
delegate :name, to: :owner, allow_nil: true, prefix: true delegate :name, to: :owner, allow_nil: true, prefix: true
# Validations # Validations
...@@ -77,6 +80,8 @@ class Project < ActiveRecord::Base ...@@ -77,6 +80,8 @@ class Project < ActiveRecord::Base
# Scopes # Scopes
scope :without_user, ->(user) { where("id NOT IN (:ids)", ids: user.authorized_projects.map(&:id) ) } scope :without_user, ->(user) { where("id NOT IN (:ids)", ids: user.authorized_projects.map(&:id) ) }
scope :not_in_group, ->(group) { where("id NOT IN (:ids)", ids: group.project_ids ) } scope :not_in_group, ->(group) { where("id NOT IN (:ids)", ids: group.project_ids ) }
scope :without_team, ->(team) { team.projects.present? ? where("id NOT IN (:ids)", ids: team.projects.map(&:id)) : scoped }
scope :in_team, ->(team) { where("id IN (:ids)", ids: team.projects.map(&:id)) }
scope :in_namespace, ->(namespace) { where(namespace_id: namespace.id) } scope :in_namespace, ->(namespace) { where(namespace_id: namespace.id) }
scope :sorted_by_activity, ->() { order("(SELECT max(events.created_at) FROM events WHERE events.project_id = projects.id) DESC") } scope :sorted_by_activity, ->() { order("(SELECT max(events.created_at) FROM events WHERE events.project_id = projects.id) DESC") }
scope :personal, ->(user) { where(namespace_id: user.namespace_id) } scope :personal, ->(user) { where(namespace_id: user.namespace_id) }
...@@ -122,7 +127,7 @@ class Project < ActiveRecord::Base ...@@ -122,7 +127,7 @@ class Project < ActiveRecord::Base
end end
def team def team
@team ||= Team.new(self) @team ||= ProjectTeam.new(self)
end end
def repository def repository
...@@ -335,7 +340,7 @@ class Project < ActiveRecord::Base ...@@ -335,7 +340,7 @@ class Project < ActiveRecord::Base
end end
def execute_hooks(data) def execute_hooks(data)
hooks.each { |hook| hook.execute(data) } hooks.each { |hook| hook.async_execute(data) }
end end
def execute_services(data) def execute_services(data)
...@@ -489,6 +494,11 @@ class Project < ActiveRecord::Base ...@@ -489,6 +494,11 @@ class Project < ActiveRecord::Base
http_url = [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('') http_url = [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('')
end end
def project_access_human(member)
project_user_relation = self.users_projects.find_by_user_id(member.id)
self.class.access_options.key(project_user_relation.project_access)
end
# Check if current branch name is marked as protected in the system # Check if current branch name is marked as protected in the system
def protected_branch? branch_name def protected_branch? branch_name
protected_branches.map(&:name).include?(branch_name) protected_branches.map(&:name).include?(branch_name)
......
class Team class ProjectTeam
attr_accessor :project attr_accessor :project
def initialize(project) def initialize(project)
......
...@@ -40,23 +40,32 @@ class User < ActiveRecord::Base ...@@ -40,23 +40,32 @@ class User < ActiveRecord::Base
attr_accessible :email, :password, :password_confirmation, :remember_me, :bio, :name, :username, attr_accessible :email, :password, :password_confirmation, :remember_me, :bio, :name, :username,
:skype, :linkedin, :twitter, :dark_scheme, :theme_id, :force_random_password, :skype, :linkedin, :twitter, :dark_scheme, :theme_id, :force_random_password,
:extern_uid, :provider, as: [:default, :admin] :extern_uid, :provider, as: [:default, :admin]
attr_accessible :projects_limit, as: :admin attr_accessible :projects_limit, :can_create_team, :can_create_group, as: :admin
attr_accessor :force_random_password attr_accessor :force_random_password
# Namespace for personal projects # Namespace for personal projects
has_one :namespace, class_name: "Namespace", foreign_key: :owner_id, conditions: 'type IS NULL', dependent: :destroy has_one :namespace, dependent: :destroy, foreign_key: :owner_id, class_name: "Namespace", conditions: 'type IS NULL'
has_many :groups, class_name: "Group", foreign_key: :owner_id
has_many :keys, dependent: :destroy
has_many :keys, dependent: :destroy has_many :users_projects, dependent: :destroy
has_many :users_projects, dependent: :destroy has_many :issues, dependent: :destroy, foreign_key: :author_id
has_many :issues, foreign_key: :author_id, dependent: :destroy has_many :notes, dependent: :destroy, foreign_key: :author_id
has_many :notes, foreign_key: :author_id, dependent: :destroy has_many :merge_requests, dependent: :destroy, foreign_key: :author_id
has_many :merge_requests, foreign_key: :author_id, dependent: :destroy has_many :events, dependent: :destroy, foreign_key: :author_id, class_name: "Event"
has_many :events, class_name: "Event", foreign_key: :author_id, dependent: :destroy has_many :assigned_issues, dependent: :destroy, foreign_key: :assignee_id, class_name: "Issue"
has_many :recent_events, class_name: "Event", foreign_key: :author_id, order: "id DESC" has_many :assigned_merge_requests, dependent: :destroy, foreign_key: :assignee_id, class_name: "MergeRequest"
has_many :assigned_issues, class_name: "Issue", foreign_key: :assignee_id, dependent: :destroy
has_many :assigned_merge_requests, class_name: "MergeRequest", foreign_key: :assignee_id, dependent: :destroy has_many :groups, class_name: "Group", foreign_key: :owner_id
has_many :recent_events, class_name: "Event", foreign_key: :author_id, order: "id DESC"
has_many :projects, through: :users_projects
has_many :user_team_user_relationships, dependent: :destroy
has_many :user_teams, through: :user_team_user_relationships
has_many :user_team_project_relationships, through: :user_teams
has_many :team_projects, through: :user_team_project_relationships
validates :name, presence: true validates :name, presence: true
validates :bio, length: { within: 0..255 } validates :bio, length: { within: 0..255 }
...@@ -80,6 +89,9 @@ class User < ActiveRecord::Base ...@@ -80,6 +89,9 @@ class User < ActiveRecord::Base
scope :blocked, where(blocked: true) scope :blocked, where(blocked: true)
scope :active, where(blocked: false) scope :active, where(blocked: false)
scope :alphabetically, order('name ASC') scope :alphabetically, order('name ASC')
scope :in_team, ->(team){ where(id: team.member_ids) }
scope :not_in_team, ->(team){ where('users.id NOT IN (:ids)', ids: team.member_ids) }
scope :potential_team_members, ->(team) { team.members.any? ? active.not_in_team(team) : active }
# #
# Class methods # Class methods
...@@ -131,6 +143,11 @@ class User < ActiveRecord::Base ...@@ -131,6 +143,11 @@ class User < ActiveRecord::Base
# #
# Instance methods # Instance methods
# #
def to_param
username
end
def generate_password def generate_password
if self.force_random_password if self.force_random_password
self.password = self.password_confirmation = Devise.friendly_token.first(8) self.password = self.password_confirmation = Devise.friendly_token.first(8)
...@@ -220,7 +237,7 @@ class User < ActiveRecord::Base ...@@ -220,7 +237,7 @@ class User < ActiveRecord::Base
end end
def can_create_group? def can_create_group?
is_admin? can?(:create_group, nil)
end end
def abilities def abilities
...@@ -283,4 +300,15 @@ class User < ActiveRecord::Base ...@@ -283,4 +300,15 @@ class User < ActiveRecord::Base
def namespace_id def namespace_id
namespace.try :id namespace.try :id
end end
def authorized_teams
@authorized_teams ||= begin
ids = []
ids << UserTeam.with_member(self).pluck('user_teams.id')
ids << UserTeam.created_by(self).pluck('user_teams.id')
ids.flatten
UserTeam.where(id: ids)
end
end
end end
class UserTeam < ActiveRecord::Base
attr_accessible :name, :owner_id, :path
belongs_to :owner, class_name: User
has_many :user_team_project_relationships, dependent: :destroy
has_many :user_team_user_relationships, dependent: :destroy
has_many :projects, through: :user_team_project_relationships
has_many :members, through: :user_team_user_relationships, source: :user
validates :name, presence: true, uniqueness: true
validates :owner, presence: true
validates :path, uniqueness: true, presence: true, length: { within: 1..255 },
format: { with: Gitlab::Regex.path_regex,
message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" }
scope :with_member, ->(user){ joins(:user_team_user_relationships).where(user_team_user_relationships: {user_id: user.id}) }
scope :with_project, ->(project){ joins(:user_team_project_relationships).where(user_team_project_relationships: {project_id: project})}
scope :without_project, ->(project){ where("user_teams.id NOT IN (:ids)", ids: (a = with_project(project); a.blank? ? 0 : a))}
scope :created_by, ->(user){ where(owner_id: user) }
class << self
def search query
where("name LIKE :query OR path LIKE :query", query: "%#{query}%")
end
def global_id
'GLN'
end
def access_roles
UsersProject.access_roles
end
end
def to_param
path
end
def assign_to_projects(projects, access)
projects.each do |project|
assign_to_project(project, access)
end
end
def assign_to_project(project, access)
Gitlab::UserTeamManager.assign(self, project, access)
end
def resign_from_project(project)
Gitlab::UserTeamManager.resign(self, project)
end
def add_members(users, access, group_admin)
users.each do |user|
add_member(user, access, group_admin)
end
end
def add_member(user, access, group_admin)
Gitlab::UserTeamManager.add_member_into_team(self, user, access, group_admin)
end
def remove_member(user)
Gitlab::UserTeamManager.remove_member_from_team(self, user)
end
def update_membership(user, options)
Gitlab::UserTeamManager.update_team_user_membership(self, user, options)
end
def update_project_access(project, permission)
Gitlab::UserTeamManager.update_project_greates_access(self, project, permission)
end
def max_project_access(project)
user_team_project_relationships.find_by_project_id(project).greatest_access
end
def human_max_project_access(project)
self.class.access_roles.invert[max_project_access(project)]
end
def default_projects_access(member)
user_team_user_relationships.find_by_user_id(member).permission
end
def human_default_projects_access(member)
self.class.access_roles.invert[default_projects_access(member)]
end
def admin?(member)
user_team_user_relationships.with_user(member).first.group_admin?
end
end
class UserTeamProjectRelationship < ActiveRecord::Base
attr_accessible :greatest_access, :project_id, :user_team_id
belongs_to :user_team
belongs_to :project
validates :project, presence: true
validates :user_team, presence: true
validate :check_greatest_access
scope :with_project, ->(project){ where(project_id: project.id) }
def team_name
user_team.name
end
private
def check_greatest_access
errors.add(:base, :incorrect_access_code) unless correct_access?
end
def correct_access?
return false if greatest_access.blank?
return true if UsersProject.access_roles.has_value?(greatest_access)
false
end
end
class UserTeamUserRelationship < ActiveRecord::Base
attr_accessible :group_admin, :permission, :user_id, :user_team_id
belongs_to :user_team
belongs_to :user
validates :user_team, presence: true
validates :user, presence: true
scope :with_user, ->(user) { where(user_id: user.id) }
def user_name
user.name
end
def access_human
UsersProject.access_roles.invert[permission]
end
end
...@@ -39,7 +39,10 @@ class UsersProject < ActiveRecord::Base ...@@ -39,7 +39,10 @@ class UsersProject < ActiveRecord::Base
scope :reporters, where(project_access: REPORTER) scope :reporters, where(project_access: REPORTER)
scope :developers, where(project_access: DEVELOPER) scope :developers, where(project_access: DEVELOPER)
scope :masters, where(project_access: MASTER) scope :masters, where(project_access: MASTER)
scope :in_project, ->(project) { where(project_id: project.id) } scope :in_project, ->(project) { where(project_id: project.id) }
scope :in_projects, ->(projects) { where(project_id: project_ids) }
scope :with_user, ->(user) { where(user_id: user.id) }
class << self class << self
......
...@@ -34,4 +34,8 @@ class WebHook < ActiveRecord::Base ...@@ -34,4 +34,8 @@ class WebHook < ActiveRecord::Base
basic_auth: {username: parsed_url.user, password: parsed_url.password}) basic_auth: {username: parsed_url.user, password: parsed_url.password})
end end
end end
def async_execute(data)
Sidekiq::Client.enqueue(ProjectWebHookWorker, id, data)
end
end end
...@@ -72,16 +72,17 @@ ...@@ -72,16 +72,17 @@
%th Users %th Users
%th Project Access: %th Project Access:
- @group.users.each do |u| - @group.users.each do |user|
%tr{class: "user_#{u.id}"} - next unless user
%td.name= link_to u.name, admin_user_path(u) %tr{class: "user_#{user.id}"}
%td.name= link_to user.name, admin_user_path(user)
%td.projects_access %td.projects_access
- u.authorized_projects.in_namespace(@group).each do |project| - user.authorized_projects.in_namespace(@group).each do |project|
- u_p = u.users_projects.in_project(project).first - u_p = user.users_projects.in_project(project).first
- next unless u_p - next unless u_p
%span %span
= project.name = project.name_with_namespace
= link_to "(#{ u_p.project_access_human })", edit_admin_team_member_path(u_p) = link_to "(#{ u_p.project_access_human })", edit_admin_project_member_path(project, user)
%tr %tr
%td.input= select_tag :user_ids, options_from_collection_for_select(@users , :id, :name), multiple: true, data: {placeholder: 'Select users'}, class: 'chosen span5' %td.input= select_tag :user_ids, options_from_collection_for_select(@users , :id, :name), multiple: true, data: {placeholder: 'Select users'}, class: 'chosen span5'
%td= select_tag :project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3"} %td= select_tag :project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3"}
......
= form_for @admin_team_member, as: :team_member, url: admin_team_member_path(@admin_team_member) do |f| = form_for @team_member_relation, as: :team_member, url: admin_project_member_path(@project, @member) do |f|
-if @admin_team_member.errors.any? -if @team_member_relation.errors.any?
.alert-message.block-message.error .alert-message.block-message.error
%ul %ul
- @admin_team_member.errors.full_messages.each do |msg| - @team_member_relation.errors.full_messages.each do |msg|
%li= msg %li= msg
.clearfix .clearfix
%label Project Access: %label Project Access:
.input .input
= f.select :project_access, options_for_select(Project.access_options, @admin_team_member.project_access), {}, class: "project-access-select chosen span3" = f.select :project_access, options_for_select(Project.access_options, @team_member_relation.project_access), {}, class: "project-access-select chosen span3"
%br %br
.actions .actions
......
%p.slead
Edit access for
= link_to @member.name, admin_user_path(@member)
in
= link_to @project.name_with_namespace, admin_project_path(@project)
%hr
= render 'form'
...@@ -114,9 +114,9 @@ ...@@ -114,9 +114,9 @@
%h5 %h5
Team Team
%small %small
(#{@project.users_projects.count}) (#{@project.users.count})
%br %br
%table.zebra-striped %table.zebra-striped.team_members
%thead %thead
%tr %tr
%th Name %th Name
...@@ -124,13 +124,13 @@ ...@@ -124,13 +124,13 @@
%th Repository Access %th Repository Access
%th %th
- @project.users_projects.each do |tm| - @project.users.each do |tm|
%tr %tr
%td %td
= link_to tm.user_name, admin_user_path(tm.user) = link_to tm.name, admin_user_path(tm)
%td= tm.project_access_human %td= @project.project_access_human(tm)
%td= link_to 'Edit Access', edit_admin_team_member_path(tm), class: "btn small" %td= link_to 'Edit Access', edit_admin_project_member_path(@project, tm), class: "btn small"
%td= link_to 'Remove from team', admin_team_member_path(tm), confirm: 'Are you sure?', method: :delete, class: "btn danger small" %td= link_to 'Remove from team', admin_project_member_path(@project, tm), confirm: 'Are you sure?', method: :delete, class: "btn danger small"
%br %br
%h5 Add new team member %h5 Add new team member
......
%p.slead
Edit access for
= link_to @admin_team_member.user_name, admin_user_path(@admin_team_member)
in
= link_to @admin_team_member.project.name_with_namespace, admin_project_path(@admin_team_member)
%hr
= render 'form'
%h3.page_title Rename Team
%hr
= form_for @team, url: admin_team_path(@team), method: :put do |f|
- if @team.errors.any?
.alert-message.block-message.error
%span= @team.errors.full_messages.first
.clearfix.team_name_holder
= f.label :name do
Team name is
.input
= f.text_field :name, placeholder: "Example Team", class: "xxlarge"
.clearfix.team_name_holder
= f.label :path do
%span.cred Team path is
.input
= f.text_field :path, placeholder: "example-team", class: "xxlarge danger"
%ul.cred
%li It will change web url for access team and team projects.
.form-actions
= f.submit 'Rename team', class: "btn danger"
= link_to 'Cancel', admin_teams_path, class: "btn cancel-btn"
%h3.page_title
Teams
%small
simple Teams description
= link_to 'New Team', new_admin_team_path, class: "btn small right"
%br
= form_tag admin_teams_path, method: :get, class: 'form-inline' do
= text_field_tag :name, params[:name], class: "xlarge"
= submit_tag "Search", class: "btn submit primary"
%table
%thead
%tr
%th
Name
%i.icon-sort-down
%th Path
%th Projects
%th Members
%th Owner
%th.cred Danger Zone!
- @teams.each do |team|
%tr
%td
%strong= link_to team.name, admin_team_path(team)
%td= team.path
%td= team.projects.count
%td= team.members.count
%td
= link_to team.owner.name, admin_user_path(team.owner_id)
%td.bgred
= link_to 'Rename', edit_admin_team_path(team), id: "edit_#{dom_id(team)}", class: "btn small"
= link_to 'Destroy', admin_team_path(team), confirm: "REMOVE #{team.name}? Are you sure?", method: :delete, class: "btn small danger"
= paginate @teams, theme: "admin"
= form_tag admin_team_member_path(@team, @member), method: :put do
-if @member.errors.any?
.alert-message.block-message.error
%ul
- @member.errors.full_messages.each do |msg|
%li= msg
.clearfix
%label Default access for Team projects:
.input
= select_tag :default_project_access, options_for_select(UserTeam.access_roles, @team.default_projects_access(@member)), class: "project-access-select chosen span3"
.clearfix
%label Team admin?
.input
= check_box_tag :group_admin, true, @team.admin?(@member)
%br
.actions
= submit_tag 'Save', class: "btn primary"
= link_to 'Cancel', :back, class: "btn"
%h3
Edit access #{@member.name} in #{@team.name} team
%hr
%table.zebra-striped
%tr
%td User:
%td= @member.name
%tr
%td Team:
%td= @team.name
%tr
%td Since:
%td= member_since(@team, @member).stamp("Nov 11, 2010")
= render 'form'
%h3.page_title
Team: #{@team.name}
%fieldset
%legend Members (#{@team.members.count})
= form_tag admin_team_members_path(@team), id: "team_members", class: "bulk_import", method: :post do
%table#members_list
%thead
%tr
%th User name
%th Default project access
%th Team access
%th
- @team.members.each do |member|
%tr.member
%td
= link_to [:admin, member] do
= member.name
%small= "(#{member.email})"
%td= @team.human_default_projects_access(member)
%td= @team.admin?(member) ? "Admin" : "Member"
%td
%tr
%td= select_tag :user_ids, options_from_collection_for_select(@users , :id, :name_with_email), multiple: true, data: {placeholder: 'Select users'}, class: 'chosen span5'
%td= select_tag :default_project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3" }
%td
%span= check_box_tag :group_admin
%span Admin?
%td= submit_tag 'Add', class: "btn primary", id: :add_members_to_team
%h3.page_title New Team
%hr
= form_for @team, url: admin_teams_path do |f|
- if @team.errors.any?
.alert-message.block-message.error
%span= @team.errors.full_messages.first
.clearfix
= f.label :name do
Team name is
.input
= f.text_field :name, placeholder: "Ex. OpenSource", class: "xxlarge left"
&nbsp;
= f.submit 'Create team', class: "btn primary"
%hr
.padded
%ul
%li All created teams are public (users can view who enter into team and which project are assigned for this team)
%li People within a team see only projects they have access to
%li You will be able to assign existing projects for team
= form_tag admin_team_project_path(@team, @project), method: :put do
-if @project.errors.any?
.alert-message.block-message.error
%ul
- @project.errors.full_messages.each do |msg|
%li= msg
.clearfix
%label Max access for Team members:
.input
= select_tag :greatest_project_access, options_for_select(UserTeam.access_roles, @team.max_project_access(@project)), class: "project-access-select chosen span3"
%br
.actions
= submit_tag 'Save', class: "btn primary"
= link_to 'Cancel', :back, class: "btn"
%h3
Edit max access in #{@project.name} for #{@team.name} team
%hr
%table.zebra-striped
%tr
%td Project:
%td= @project.name
%tr
%td Team:
%td= @team.name
%tr
%td Since:
%td= assigned_since(@team, @project).stamp("Nov 11, 2010")
= render 'form'
%h3.page_title
Team: #{@team.name}
%fieldset
%legend Projects (#{@team.projects.count})
= form_tag admin_team_projects_path(@team), id: "assign_projects", class: "bulk_import", method: :post do
%table#projects_list
%thead
%tr
%th Project name
%th Max access
%th
- @team.projects.each do |project|
%tr.project
%td
= link_to project.name_with_namespace, [:admin, project]
%td
%span= @team.human_max_project_access(project)
%td
%tr
%td= select_tag :project_ids, options_from_collection_for_select(@projects , :id, :name_with_namespace), multiple: true, data: {placeholder: 'Select projects'}, class: 'chosen span5'
%td= select_tag :greatest_project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3" }
%td= submit_tag 'Add', class: "btn primary", id: :assign_projects_to_team
%h3.page_title
Team: #{@team.name}
%br
%table.zebra-striped
%thead
%tr
%th Team
%th
%tr
%td
%b
Name:
%td
= @team.name
&nbsp;
= link_to edit_admin_team_path(@team), class: "btn btn-small right" do
%i.icon-edit
Rename
%tr
%td
%b
Owner:
%td
= @team.owner.name
.right
= link_to "#", class: "btn btn-small change-owner-link" do
%i.icon-edit
Change owner
%tr.change-owner-holder.hide
%td.bgred
%b.cred
New Owner:
%td.bgred
= form_for @team, url: admin_team_path(@team) do |f|
= f.select :owner_id, User.all.map { |user| [user.name, user.id] }, {}, {class: 'chosen'}
%div
= f.submit 'Change Owner', class: "btn danger"
= link_to "Cancel", "#", class: "btn change-owner-cancel-link"
%fieldset
%legend
Members (#{@team.members.count})
%span= link_to 'Add members', new_admin_team_member_path(@team), class: "btn success small right", id: :add_members_to_team
- if @team.members.any?
%table#members_list
%thead
%tr
%th User name
%th Default project access
%th Team access
%th.cred.span3 Danger Zone!
- @team.members.each do |member|
%tr.member{ class: "user_#{member.id}"}
%td
= link_to [:admin, member] do
= member.name
%small= "(#{member.email})"
%td= @team.human_default_projects_access(member)
%td= @team.admin?(member) ? "Admin" : "Member"
%td.bgred
= link_to 'Edit', edit_admin_team_member_path(@team, member), class: "btn small"
&nbsp;
= link_to 'Remove', admin_team_member_path(@team, member), confirm: 'Remove member from team. Are you sure?', method: :delete, class: "btn danger small", id: "remove_member_#{member.id}"
%fieldset
%legend
Projects (#{@team.projects.count})
%span= link_to 'Add projects', new_admin_team_project_path(@team), class: "btn success small right", id: :assign_projects_to_team
- if @team.projects.any?
%table#projects_list
%thead
%tr
%th Project name
%th Max access
%th.cred.span3 Danger Zone!
- @team.projects.each do |project|
%tr.project
%td
= link_to project.name_with_namespace, [:admin, project]
%td
%span= @team.human_max_project_access(project)
%td.bgred
= link_to 'Edit', edit_admin_team_project_path(@team, project), class: "btn small"
&nbsp;
= link_to 'Relegate', admin_team_project_path(@team, project), confirm: 'Remove project from team. Are you sure?', method: :delete, class: "btn danger small", id: "relegate_project_#{project.id}"
:javascript
$(function(){
var modal = $('.change-owner-holder');
$('.change-owner-link').bind("click", function(){
$(this).hide();
modal.show();
});
$('.change-owner-cancel-link').bind("click", function(){
modal.hide();
$('.change-owner-link').show();
})
})
...@@ -46,6 +46,14 @@ ...@@ -46,6 +46,14 @@
= f.label :projects_limit = f.label :projects_limit
.input= f.number_field :projects_limit .input= f.number_field :projects_limit
.clearfix
= f.label :can_create_group
.input= f.check_box :can_create_group
.clearfix
= f.label :can_create_team
.input= f.check_box :can_create_team
.clearfix .clearfix
= f.label :admin do = f.label :admin do
%strong.cred Administrator %strong.cred Administrator
......
...@@ -123,5 +123,5 @@ ...@@ -123,5 +123,5 @@
%tr %tr
%td= link_to project.name_with_namespace, admin_project_path(project) %td= link_to project.name_with_namespace, admin_project_path(project)
%td= tm.project_access_human %td= tm.project_access_human
%td= link_to 'Edit Access', edit_admin_team_member_path(tm), class: "btn small" %td= link_to 'Edit Access', edit_admin_project_member_path(project, tm.user), class: "btn small"
%td= link_to 'Remove from team', admin_team_member_path(tm), confirm: 'Are you sure?', method: :delete, class: "btn small danger" %td= link_to 'Remove from team', admin_project_member_path(project, tm.user), confirm: 'Are you sure?', method: :delete, class: "btn small danger"
...@@ -11,19 +11,7 @@ ...@@ -11,19 +11,7 @@
:javascript :javascript
$(function(){ $(function(){
var w, h; $('.files .file').each(function(){
$('.diff_file').each(function(){ new CommitFile(this);
$('.image.diff_removed img', this).on('load', $.proxy(function(event){
var w = event.currentTarget.naturalWidth
, h = event.currentTarget.naturalHeight;
$('.image.diff_removed .image-info', this).append(' | <b>W:</b> ' + w + 'px | <b>H:</b> ' + h + 'px');
}, this));
$('.image.diff_added img', this).on('load', $.proxy(function(event){
var w = event.currentTarget.naturalWidth
, h = event.currentTarget.naturalHeight;
$('.image.diff_added .image-info', this).append(' | <b>W:</b> ' + w + 'px | <b>H:</b> ' + h + 'px');
}, this));
}); });
}); });
...@@ -2,5 +2,5 @@ ...@@ -2,5 +2,5 @@
%div.ui-box %div.ui-box
%h5.title %h5.title
%i.icon-calendar %i.icon-calendar
= day.stamp("28 Aug, 2010") %span= day.stamp("28 Aug, 2010")
%ul.well-list= render commits %ul.well-list= render commits
...@@ -12,50 +12,38 @@ ...@@ -12,50 +12,38 @@
.file-stats .file-stats
= render "commits/diff_head", diffs: diffs = render "commits/diff_head", diffs: diffs
- unless @suppress_diff .files
- diffs.each_with_index do |diff, i| - unless @suppress_diff
- next if diff.diff.empty? - diffs.each_with_index do |diff, i|
- file = (@commit.tree / diff.new_path) - next if diff.diff.empty?
- file = (@commit.prev_commit.tree / diff.old_path) unless file - file = (@commit.tree / diff.new_path)
- next unless file - file = (@commit.prev_commit.tree / diff.old_path) unless file
.diff_file{id: "diff-#{i}"} - next unless file
.diff_file_header .file{id: "diff-#{i}"}
- if diff.deleted_file .header
%span= diff.old_path - if diff.deleted_file
%span= diff.old_path
- if @commit.prev_commit - if @commit.prev_commit
= link_to project_tree_path(@project, tree_join(@commit.prev_commit_id, diff.new_path)), {:class => 'btn right view-commit'} do = link_to project_tree_path(@project, tree_join(@commit.prev_commit_id, diff.new_path)), {:class => 'btn right view-file'} do
View file @
%span.commit-short-id= @commit.short_id(6)
- else
%span= diff.new_path
- if diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode
%span.file-mode= "#{diff.a_mode}#{diff.b_mode}"
= link_to project_tree_path(@project, tree_join(@commit.id, diff.new_path)), {:class => 'btn very_small right view-file'} do
View file @ View file @
%span.commit-short-id= @commit.short_id(6) %span.commit-short-id= @commit.short_id(6)
- else
%span= diff.new_path
- if diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode
%span.file-mode= "#{diff.a_mode}#{diff.b_mode}"
= link_to project_tree_path(@project, tree_join(@commit.id, diff.new_path)), {:class => 'btn very_small right view-commit'} do
View file @
%span.commit-short-id= @commit.short_id(6)
%br/ .content
.diff_file_content -# Skipp all non non-supported blobs
-# Skip all non-supported blobs - next unless file.respond_to?('text?')
- next unless file.respond_to?('text?') - if file.text?
- if file.text? = render "commits/text_file", diff: diff, index: i
= render "commits/text_diff", diff: diff, index: i - elsif file.image?
- elsif file.image? - old_file = (@commit.prev_commit.tree / diff.old_path) if !@commit.prev_commit.nil?
- old_file = (@commit.prev_commit.tree / diff.old_path) if !@commit.prev_commit.nil? = render "commits/image", diff: diff, old_file: old_file, file: file, index: i
- if diff.renamed_file || diff.new_file || diff.deleted_file
.diff_file_content_image
.image{class: image_diff_class(diff)}
%img{src: "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"}
%div.image-info= "#{number_to_human_size file.size}"
- else - else
.diff_file_content_image.img_compared %p.nothing_here_message No preview for this file type
.image.diff_removed
%img{src: "data:#{file.mime_type};base64,#{Base64.encode64(old_file.data)}"}
%div.image-info= "#{number_to_human_size file.size}"
.image.diff_added
%img{src: "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"}
%div.image-info= "#{number_to_human_size file.size}"
- else
%p.nothing_here_message No preview for this file type
- if diff.renamed_file || diff.new_file || diff.deleted_file
.image
%span.wrap
.frame{class: image_diff_class(diff)}
%img{src: "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"}
%p.image-info= "#{number_to_human_size file.size}"
- else
.image
%div.two-up.view
%span.wrap
.frame.deleted
%a{href: project_tree_path(@project, tree_join(@commit.id, diff.old_path))}
%img{src: "data:#{old_file.mime_type};base64,#{Base64.encode64(old_file.data)}"}
%p.image-info.hide
%span.meta-filesize= "#{number_to_human_size old_file.size}"
|
%b W:
%span.meta-width
|
%b H:
%span.meta-height
%span.wrap
.frame.added
%a{href: project_tree_path(@project, tree_join(@commit.id, diff.new_path))}
%img{src: "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"}
%p.image-info.hide
%span.meta-filesize= "#{number_to_human_size file.size}"
|
%b W:
%span.meta-width
|
%b H:
%span.meta-height
%div.swipe.view.hide
.swipe-frame
.frame.deleted
%img{src: "data:#{old_file.mime_type};base64,#{Base64.encode64(old_file.data)}"}
.swipe-wrap
.frame.added
%img{src: "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"}
%span.swipe-bar
%span.top-handle
%span.bottom-handle
%div.onion-skin.view.hide
.onion-skin-frame
.frame.deleted
%img{src: "data:#{old_file.mime_type};base64,#{Base64.encode64(old_file.data)}"}
.frame.added
%img{src: "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"}
.controls
.transparent
.drag-track
.dragger{:style => "left: 0px;"}
.opaque
.view-modes.hide
%ul.view-modes-menu
%li.two-up{data: {mode: 'two-up'}} 2-up
%li.swipe{data: {mode: 'swipe'}} Swipe
%li.onion-skin{data: {mode: 'onion-skin'}} Onion skin
\ No newline at end of file
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
- if too_big - if too_big
%a.supp_diff_link Diff suppressed. Click to show %a.supp_diff_link Diff suppressed. Click to show
%table{class: "#{'hide' if too_big}"} %table.text-file{class: "#{'hide' if too_big}"}
- each_diff_line(diff, index) do |line, type, line_code, line_new, line_old| - each_diff_line(diff, index) do |line, type, line_code, line_new, line_old|
%tr.line_holder{ id: line_code } %tr.line_holder{ id: line_code }
- if type == "match" - if type == "match"
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
= breadcrumbs = breadcrumbs
%div{id: dom_id(@project)} %div{id: dom_id(@project)}
#commits_list= render "commits" #commits-list= render "commits"
.clear .clear
.loading{ style: "display:none;"} .loading{ style: "display:none;"}
......
.groups_box .ui-box
%h5.title %h5.title
Groups Groups
%small %small
(#{groups.count}) (#{groups.count})
- if current_user.can_create_group? - if current_user.can_create_group?
%span.right %span.right
= link_to new_admin_group_path, class: "btn very_small info" do = link_to new_group_path, class: "btn very_small info" do
%i.icon-plus %i.icon-plus
New Group New Group
%ul.well-list %ul.well-list
...@@ -13,8 +13,6 @@ ...@@ -13,8 +13,6 @@
%li %li
= link_to group_path(id: group.path), class: dom_class(group) do = link_to group_path(id: group.path), class: dom_class(group) do
%strong.well-title= truncate(group.name, length: 35) %strong.well-title= truncate(group.name, length: 35)
%span.arrow %span.right.light
&rarr; - if group.owner == current_user
%span.last_activity %i.icon-wrench
%strong Projects:
%span= current_user.authorized_projects.where(namespace_id: group.id).count
...@@ -2,19 +2,12 @@ ...@@ -2,19 +2,12 @@
%h5.title %h5.title
Projects Projects
%small %small
(#{projects.total_count}) (#{@projects_count})
- if current_user.can_create_project? - if current_user.can_create_project?
%span.right %span.right
= link_to new_project_path, class: "btn very_small info" do = link_to new_project_path, class: "btn very_small info" do
%i.icon-plus %i.icon-plus
New Project New Project
%ul.nav.nav-projects-tabs
= nav_tab :scope, nil do
= link_to "All", dashboard_path
= nav_tab :scope, 'personal' do
= link_to "Personal", dashboard_path(scope: 'personal')
= nav_tab :scope, 'joined' do
= link_to "Joined", dashboard_path(scope: 'joined')
%ul.well-list %ul.well-list
- projects.each do |project| - projects.each do |project|
...@@ -33,4 +26,6 @@ ...@@ -33,4 +26,6 @@
- if projects.blank? - if projects.blank?
%li %li
%h3.nothing_here_message There are no projects here. %h3.nothing_here_message There are no projects here.
.bottom= paginate projects, theme: "gitlab" - if @projects_count > 20
%li.bottom
%strong= link_to "show all projects", projects_dashboard_path
- if @teams.present?
= render "teams", teams: @teams
- if @groups.present? - if @groups.present?
= render "groups", groups: @groups = render "groups", groups: @groups
= render "projects", projects: @projects = render "projects", projects: @projects
......
.ui-box.teams-box
%h5.title
Teams
%small
(#{@teams.count})
%span.right
= link_to new_team_path, class: "btn very_small info" do
%i.icon-plus
New Team
%ul.well-list
- @teams.each do |team|
%li
= link_to team_path(id: team.path), class: dom_class(team) do
%strong.well-title= truncate(team.name, length: 35)
%span.right.light
- if team.owner == current_user
%i.icon-wrench
- tm = current_user.user_team_user_relationships.find_by_user_team_id(team.id)
- if tm
= tm.access_human
xml.instruct! xml.instruct!
xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
xml.title "#{current_user.name} issues" xml.title "#{current_user.name} issues"
xml.link :href => dashboard_issues_url(:atom, :private_token => current_user.private_token), :rel => "self", :type => "application/atom+xml" xml.link :href => issues_dashboard_url(:atom, :private_token => current_user.private_token), :rel => "self", :type => "application/atom+xml"
xml.link :href => dashboard_issues_url(:private_token => current_user.private_token), :rel => "alternate", :type => "text/html" xml.link :href => issues_dashboard_url(:private_token => current_user.private_token), :rel => "alternate", :type => "text/html"
xml.id dashboard_issues_url(:private_token => current_user.private_token) xml.id issues_dashboard_url(:private_token => current_user.private_token)
xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any? xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any?
@issues.each do |issue| @issues.each do |issue|
......
%h3.page_title
Projects
%span
(#{@projects.total_count})
- if current_user.can_create_project?
%span.right
= link_to new_project_path, class: "btn very_small info" do
%i.icon-plus
New Project
%hr
.row
.span3
%ul.nav.nav-pills.nav-stacked
= nav_tab :scope, nil do
= link_to "All", projects_dashboard_path
= nav_tab :scope, 'personal' do
= link_to "Personal", projects_dashboard_path(scope: 'personal')
= nav_tab :scope, 'joined' do
= link_to "Joined", projects_dashboard_path(scope: 'joined')
.span9
= form_tag projects_dashboard_path, method: 'get' do
%fieldset.dashboard-search-filter
= hidden_field_tag "scope", params[:scope]
= search_field_tag "search", params[:search], { placeholder: 'Search', class: 'left input-xxlarge' }
= button_tag type: 'submit', class: 'btn' do
%i.icon-search
%ul.well-list
- @projects.each do |project|
%li.clearfix
.left
= link_to project_path(project), class: dom_class(project) do
- if project.namespace
= project.namespace.human_name
\/
%strong.well-title
= truncate(project.name, length: 25)
%br
%small.light
%strong Last activity:
%span= project_last_activity(project)
.right.light
- if project.owner == current_user
%i.icon-wrench
- tm = project.team.get_tm(current_user.id)
- if tm
= tm.project_access_human
- if @projects.blank?
%li
%h3.nothing_here_message There are no projects here.
.bottom= paginate @projects, theme: "gitlab"
xml.instruct! xml.instruct!
xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
xml.title "#{@user.name} issues" xml.title "#{@user.name} issues"
xml.link :href => dashboard_issues_url(:atom, :private_token => @user.private_token), :rel => "self", :type => "application/atom+xml" xml.link :href => issues_dashboard_url(:atom, :private_token => @user.private_token), :rel => "self", :type => "application/atom+xml"
xml.link :href => dashboard_issues_url(:private_token => @user.private_token), :rel => "alternate", :type => "text/html" xml.link :href => issues_dashboard_url(:private_token => @user.private_token), :rel => "alternate", :type => "text/html"
xml.id dashboard_issues_url(:private_token => @user.private_token) xml.id issues_dashboard_url(:private_token => @user.private_token)
xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any? xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any?
@issues.each do |issue| @issues.each do |issue|
......
%h3.page_title New Group
%hr
= form_for @group do |f|
- if @group.errors.any?
.alert-message.block-message.error
%span= @group.errors.full_messages.first
.clearfix
= f.label :name do
Group name is
.input
= f.text_field :name, placeholder: "Ex. OpenSource", class: "xxlarge left"
&nbsp;
= f.submit 'Create group', class: "btn primary"
%hr
.padded
%ul
%li Group is kind of directory for several projects
%li All created groups are private
%li People within a group see only projects they have access to
%li All projects of group will be stored in group directory
%li You will be able to move existing projects into group
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
= link_to new_project_path, title: "Create New Project", class: 'has_bottom_tooltip', 'data-original-title' => 'New project' do = link_to new_project_path, title: "Create New Project", class: 'has_bottom_tooltip', 'data-original-title' => 'New project' do
%i.icon-plus %i.icon-plus
%li %li
= link_to profile_path, title: "Your Profile", class: 'has_bottom_tooltip', 'data-original-title' => 'Your profile' do = link_to profile_path, title: "My Profile", class: 'has_bottom_tooltip', 'data-original-title' => 'Your profile' do
%i.icon-user %i.icon-user
%li.separator %li.separator
%li %li
......
.search .search
= form_tag search_path, method: :get, class: 'navbar-form pull-left' do |f| = form_tag search_path, method: :get, class: 'navbar-form pull-left' do |f|
= text_field_tag "search", nil, placeholder: "Search", class: "search-input" = text_field_tag "search", nil, placeholder: "Search", class: "search-input"
= hidden_field_tag :group_id, @group.try(:id)
= hidden_field_tag :project_id, @project.try(:id)
:javascript :javascript
$(function(){ $(function(){
......
...@@ -10,6 +10,8 @@ ...@@ -10,6 +10,8 @@
= link_to "Stats", admin_root_path = link_to "Stats", admin_root_path
= nav_link(controller: :projects) do = nav_link(controller: :projects) do
= link_to "Projects", admin_projects_path = link_to "Projects", admin_projects_path
= nav_link(controller: :teams) do
= link_to "Teams", admin_teams_path
= nav_link(controller: :groups) do = nav_link(controller: :groups) do
= link_to "Groups", admin_groups_path = link_to "Groups", admin_groups_path
= nav_link(controller: :users) do = nav_link(controller: :users) do
......
...@@ -6,14 +6,17 @@ ...@@ -6,14 +6,17 @@
= render "layouts/head_panel", title: "Dashboard" = render "layouts/head_panel", title: "Dashboard"
.container .container
%ul.main_menu %ul.main_menu
= nav_link(path: 'dashboard#index', html_options: {class: 'home'}) do = nav_link(path: 'dashboard#show', html_options: {class: 'home'}) do
= link_to "Home", root_path, title: "Home" = link_to "Home", root_path, title: "Home"
= nav_link(path: 'dashboard#projects') do
= link_to projects_dashboard_path do
Projects
= nav_link(path: 'dashboard#issues') do = nav_link(path: 'dashboard#issues') do
= link_to dashboard_issues_path do = link_to issues_dashboard_path do
Issues Issues
%span.count= current_user.assigned_issues.opened.count %span.count= current_user.assigned_issues.opened.count
= nav_link(path: 'dashboard#merge_requests') do = nav_link(path: 'dashboard#merge_requests') do
= link_to dashboard_merge_requests_path do = link_to merge_requests_dashboard_path do
Merge Requests Merge Requests
%span.count= current_user.cared_merge_requests.opened.count %span.count= current_user.cared_merge_requests.opened.count
= nav_link(path: 'search#show') do = nav_link(path: 'search#show') do
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
= render "layouts/head", title: "#{@group.name}" = render "layouts/head", title: "#{@group.name}"
%body{class: "#{app_theme} application"} %body{class: "#{app_theme} application"}
= render "layouts/flash" = render "layouts/flash"
= render "layouts/head_panel", title: "#{@group.name}" = render "layouts/head_panel", title: "group: #{@group.name}"
.container .container
%ul.main_menu %ul.main_menu
= nav_link(path: 'groups#show', html_options: {class: 'home'}) do = nav_link(path: 'groups#show', html_options: {class: 'home'}) do
......
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: "#{@team.name}"
%body{class: "#{app_theme} application"}
= render "layouts/flash"
= render "layouts/head_panel", title: "team: #{@team.name}"
.container
%ul.main_menu
= nav_link(path: 'teams#show', html_options: {class: 'home'}) do
= link_to "Home", team_path(@team), title: "Home"
= nav_link(path: 'teams#issues') do
= link_to issues_team_path(@team) do
Issues
%span.count= Issue.opened.of_user_team(@team).count
= nav_link(path: 'teams#merge_requests') do
= link_to merge_requests_team_path(@team) do
Merge Requests
%span.count= MergeRequest.opened.of_user_team(@team).count
= nav_link(controller: [:members]) do
= link_to team_members_path(@team), class: "team-tab tab" do
Members
%span.count= @team.members.count
- if can? current_user, :admin_user_team, @team
= nav_link(controller: [:projects]) do
= link_to team_projects_path(@team), class: "team-tab tab" do
Projects
%span.count= @team.projects.count
= nav_link(path: 'teams#edit') do
= link_to edit_team_path(@team), class: "stat-tab tab " do
%i.icon-edit
Edit Team
.content= yield
...@@ -38,7 +38,7 @@ ...@@ -38,7 +38,7 @@
- if note.for_diff_line? - if note.for_diff_line?
- if note.diff - if note.diff
.content .content
.diff_file= render "notes/discussion_diff", discussion_notes: discussion_notes, note: note .file= render "notes/discussion_diff", discussion_notes: discussion_notes, note: note
- else - else
= link_to 'show outdated discussion', '#', class: 'js-show-outdated-discussion' = link_to 'show outdated discussion', '#', class: 'js-show-outdated-discussion'
%div.hide.outdated-discussion %div.hide.outdated-discussion
......
- diff = note.diff - diff = note.diff
.diff_file_header .header
- if diff.deleted_file - if diff.deleted_file
%span= diff.old_path %span= diff.old_path
- else - else
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
- if diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode - if diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode
%span.file-mode= "#{diff.a_mode}#{diff.b_mode}" %span.file-mode= "#{diff.a_mode}#{diff.b_mode}"
%br/ %br/
.diff_file_content .content
%table %table
- each_diff_line(diff, note.diff_file_index) do |line, type, line_code, line_new, line_old| - each_diff_line(diff, note.diff_file_index) do |line, type, line_code, line_new, line_old|
%tr.line_holder{ id: line_code } %tr.line_holder{ id: line_code }
......
...@@ -31,6 +31,20 @@ ...@@ -31,6 +31,20 @@
.controls .controls
= f.text_field :email, class: "input-xlarge", required: true = f.text_field :email, class: "input-xlarge", required: true
%span.help-block We also use email for avatar detection. %span.help-block We also use email for avatar detection.
.control-group
= f.label :skype, class: "control-label"
.controls= f.text_field :skype, class: "input-xlarge"
.control-group
= f.label :linkedin, class: "control-label"
.controls= f.text_field :linkedin, class: "input-xlarge"
.control-group
= f.label :twitter, class: "control-label"
.controls= f.text_field :twitter, class: "input-xlarge"
.control-group
= f.label :bio, class: "control-label"
.controls
= f.text_area :bio, rows: 6, class: "input-xlarge", maxlength: 250
%span.help-block Tell us about yourself in fewer than 250 characters.
.span5.right .span5.right
%fieldset.tips %fieldset.tips
...@@ -47,24 +61,18 @@ ...@@ -47,24 +61,18 @@
%p %p
You can login through #{@user.provider.titleize}! You can login through #{@user.provider.titleize}!
= link_to "click here to change", account_profile_path = link_to "click here to change", account_profile_path
- if current_user.can_create_group?
.row %li
.span7 %p
.control-group Need a group for several dependent projects?
= f.label :skype, class: "control-label" = link_to new_group_path, class: "btn very_small" do
.controls= f.text_field :skype, class: "input-xlarge" Create a group
.control-group - if current_user.can_create_team?
= f.label :linkedin, class: "control-label" %li
.controls= f.text_field :linkedin, class: "input-xlarge" %p
.control-group Want to share a team between projects?
= f.label :twitter, class: "control-label" = link_to new_team_path, class: "btn very_small" do
.controls= f.text_field :twitter, class: "input-xlarge" Create a team
.control-group
= f.label :bio, class: "control-label"
.controls
= f.text_area :bio, rows: 6, class: "input-xlarge", maxlength: 250
%span.help-block Tell us about yourself in fewer than 250 characters.
.span5.right
%fieldset %fieldset
%legend %legend
Personal projects: Personal projects:
...@@ -79,7 +87,8 @@ ...@@ -79,7 +87,8 @@
%fieldset %fieldset
%legend %legend
SSH public keys: SSH public keys:
%strong.right= link_to current_user.keys.count, keys_path %span.right
= link_to pluralize(current_user.keys.count, 'key'), keys_path
.padded .padded
= link_to "Add Public Key", new_key_path, class: "btn small" = link_to "Add Public Key", new_key_path, class: "btn small"
......
...@@ -15,6 +15,20 @@ ...@@ -15,6 +15,20 @@
%span Namespace %span Namespace
.input .input
= f.select :namespace_id, namespaces_options(params[:namespace_id] || :current_user), {}, {class: 'chosen'} = f.select :namespace_id, namespaces_options(params[:namespace_id] || :current_user), {}, {class: 'chosen'}
%hr
%p.padded %p.padded
New projects are private by default. You choose who can see the project and commit to repository. New projects are private by default. You choose who can see the project and commit to repository.
%hr
- if current_user.can_create_group?
.clearfix
.input.light
Need a group for several dependent projects?
= link_to new_group_path, class: "btn very_small" do
Create a group
- if current_user.can_create_team?
.clearfix
.input.light
Want to share a project between team?
= link_to new_team_path, class: "btn very_small" do
Create a team
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
= link_to project_path(@project), class: "activities-tab tab" do = link_to project_path(@project), class: "activities-tab tab" do
%i.icon-home %i.icon-home
Show Show
= nav_link(controller: :team_members) do = nav_link(controller: [:team_members, :teams]) do
= link_to project_team_index_path(@project), class: "team-tab tab" do = link_to project_team_index_path(@project), class: "team-tab tab" do
%i.icon-user %i.icon-user
Team Team
......
%h3.page_title Project Network Graph %h3.page_title Project Network Graph
%br %br
.graph_holder .graph_holder
%h4 %h4
%small You can move around the graph by using the arrow keys. %small You can move around the graph by using the arrow keys.
...@@ -11,6 +12,6 @@ ...@@ -11,6 +12,6 @@
$(function(){ $(function(){
branch_graph = new BranchGraph($("#holder"), { branch_graph = new BranchGraph($("#holder"), {
url: '#{url_for controller: 'projects', action: 'graph', format: :json}', url: '#{url_for controller: 'projects', action: 'graph', format: :json}',
commit_url: '#{url_for controller: 'projects', action: 'show'}/commits/%s' commit_url: '#{project_commit_path(@project, 'ae45ca32').gsub("ae45ca32", "%s")}'
}); });
}); });
= render "projects/project_head"
%h3.page_title
= "Assign project to team of users"
%hr
%p.slead
Read more about assign to team of users #{link_to "here", '#', class: 'vlink'}.
= form_tag assign_project_teams_path(@project), method: 'post' do
%p.slead Choose Team of users you want to assign:
.padded
= label_tag :team_id, "Team"
.input= select_tag(:team_id, options_from_collection_for_select(@teams, :id, :name), prompt: "Select team", class: "chosen xxlarge", required: true)
%p.slead Choose greatest user acces in team you want to assign:
.padded
= label_tag :team_ids, "Permission"
.input= select_tag :greatest_project_access, options_for_select(UserTeam.access_roles), {class: "project-access-select chosen span3" }
.actions
= submit_tag 'Assign', class: "btn save-btn"
= link_to "Cancel", project_team_index_path(@project), class: "btn cancel-btn"
%fieldset
%legend Groups:
%ul.nav.nav-pills.nav-stacked
%li{class: ("active" if params[:group_id].blank?)}
= link_to search_path(group_id: nil, search: params[:search]) do
Any
- current_user.authorized_groups.each do |group|
%li{class: ("active" if params[:group_id] == group.id.to_s)}
= link_to search_path(group_id: group.id, search: params[:search]) do
= group.name
%fieldset
%legend Projects:
%ul.nav.nav-pills.nav-stacked
%li{class: ("active" if params[:project_id].blank?)}
= link_to search_path(project_id: nil, search: params[:search]) do
Any
- current_user.authorized_projects.each do |project|
%li{class: ("active" if params[:project_id] == project.id.to_s)}
= link_to search_path(project_id: project.id, search: params[:search]) do
= project.name_with_namespace
= hidden_field_tag :group_id, params[:group_id]
= hidden_field_tag :project_id, params[:project_id]
%br %fieldset
%h3.page_title %legend
Search results Search results
%span.cgray (#{@projects.count + @merge_requests.count + @issues.count + @wiki_pages.count}) %span.cgray (#{@projects.count + @merge_requests.count + @issues.count + @wiki_pages.count})
%hr
.search_results .search_results
.row %ul.well-list
.span6 - @projects.each do |project|
%table %li
%thead project:
%tr = link_to project do
%th Projects %strong.term= project.name_with_namespace
%tbody - @merge_requests.each do |merge_request|
- @projects.each do |project| %li
%tr merge request:
%td = link_to [merge_request.project, merge_request] do
= link_to project do %span ##{merge_request.id}
%strong.term= project.name_with_namespace %strong.term
%small.cgray = truncate merge_request.title, length: 50
last activity at %span.light (#{merge_request.project.name_with_namespace})
= project.last_activity_date.stamp("Aug 25, 2011") - @issues.each do |issue|
- if @projects.blank? %li
%tr issue:
%td = link_to [issue.project, issue] do
%h4.nothing_here_message No Projects %span ##{issue.id}
%br %strong.term
%table = truncate issue.title, length: 50
%thead %span.light (#{issue.project.name_with_namespace})
%tr - @wiki_pages.each do |wiki_page|
%th Merge Requests %li
%tbody wiki:
- @merge_requests.each do |merge_request| = link_to project_wiki_path(wiki_page.project, wiki_page) do
%tr %strong.term
%td = truncate wiki_page.title, length: 50
= link_to [merge_request.project, merge_request] do %span.light (#{wiki_page.project.name_with_namespace})
%span.badge.badge-info ##{merge_request.id}
&ndash;
%strong.term= truncate merge_request.title, length: 50
%strong.right
%span.label= merge_request.project.name
- if @merge_requests.blank?
%tr
%td
%h4.nothing_here_message No Merge Requests
.span6
%table
%thead
%tr
%th Issues
%tbody
- @issues.each do |issue|
%tr
%td
= link_to [issue.project, issue] do
%span.badge.badge-info ##{issue.id}
&ndash;
%strong.term= truncate issue.title, length: 40
%strong.right
%span.label= issue.project.name
- if @issues.blank?
%tr
%td
%h4.nothing_here_message No Issues
.span6
%table
%thead
%tr
%th Wiki
%tbody
- @wiki_pages.each do |wiki_page|
%tr
%td
= link_to project_wiki_path(wiki_page.project, wiki_page) do
%strong.term= truncate wiki_page.title, length: 40
%strong.right
%span.label= wiki_page.project.name
- if @wiki_pages.blank?
%tr
%td
%h4.nothing_here_message No wiki pages
:javascript :javascript
$(function() { $(function() {
$(".search_results .term").highlight("#{escape_javascript(params[:search])}"); $(".search_results .term").highlight("#{escape_javascript(params[:search])}");
......
= form_tag search_path, method: :get, class: 'form-inline' do |f| = form_tag search_path, method: :get, class: 'form-inline' do |f|
.padded .search-holder
= label_tag :search do = label_tag :search do
%strong Looking for %span Looking for
.input .input
= search_field_tag :search, params[:search], placeholder: "issue 143", class: "input-xxlarge search-text-input", id: "dashboard_search" = search_field_tag :search, params[:search], placeholder: "issue 143", class: "input-xxlarge search-text-input", id: "dashboard_search"
= submit_tag 'Search', class: "btn primary wide" = submit_tag 'Search', class: "btn primary wide"
- if params[:search].present? .clearfix
= render 'search/result' .row
.span3
= render 'filter', f: f
.span9
.results
- if params[:search].present?
= render 'search/result'
%h3.page_title %h3.page_title
= "New Team member(s)" = "New Team member(s)"
%hr %hr
= form_for @team_member, as: :team_member, url: project_team_members_path(@project, @team_member) do |f| = form_for @user_project_relation, as: :team_member, url: project_team_members_path(@project) do |f|
-if @team_member.errors.any? -if @user_project_relation.errors.any?
.alert-message.block-message.error .alert-message.block-message.error
%ul %ul
- @team_member.errors.full_messages.each do |msg| - @user_project_relation.errors.full_messages.each do |msg|
%li= msg %li= msg
%h6 1. Choose people you want in the team %h6 1. Choose people you want in the team
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
%h6 2. Set access level for them %h6 2. Set access level for them
.clearfix .clearfix
= f.label :project_access, "Project Access" = f.label :project_access, "Project Access"
.input= select_tag :project_access, options_for_select(Project.access_options, @team_member.project_access), class: "project-access-select chosen" .input= select_tag :project_access, options_for_select(Project.access_options, @user_project_relation.project_access), class: "project-access-select chosen"
.actions .actions
= f.submit 'Save', class: "btn save-btn" = f.submit 'Save', class: "btn save-btn"
......
- user = member.user - user = member.user
- allow_admin = can? current_user, :admin_project, @project - allow_admin = can? current_user, :admin_project, @project
%li{id: dom_id(member), class: "team_member_row user_#{user.id}"} %li{id: dom_id(user), class: "team_member_row user_#{user.id}"}
.row .row
.span6 .span6
= link_to project_team_member_path(@project, member), title: user.name, class: "dark" do = link_to project_team_member_path(@project, user), title: user.name, class: "dark" do
= image_tag gravatar_icon(user.email, 40), class: "avatar s32" = image_tag gravatar_icon(user.email, 40), class: "avatar s32"
= link_to project_team_member_path(@project, member), title: user.name, class: "dark" do = link_to project_team_member_path(@project, user), title: user.name, class: "dark" do
%strong= truncate(user.name, lenght: 40) %strong= truncate(user.name, lenght: 40)
%br %br
%small.cgray= user.email %small.cgray= user.email
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
.span5.right .span5.right
- if allow_admin - if allow_admin
.left .left
= form_for(member, as: :team_member, url: project_team_member_path(@project, member)) do |f| = form_for(member, as: :team_member, url: project_team_member_path(@project, member.user)) do |f|
= f.select :project_access, options_for_select(UsersProject.access_roles, member.project_access), {}, class: "medium project-access-select span2" = f.select :project_access, options_for_select(UsersProject.access_roles, member.project_access), {}, class: "medium project-access-select span2"
.right .right
- if current_user == user - if current_user == user
...@@ -23,6 +23,6 @@ ...@@ -23,6 +23,6 @@
- elsif user.blocked - elsif user.blocked
%span.btn.disabled.blocked Blocked %span.btn.disabled.blocked Blocked
- elsif allow_admin - elsif allow_admin
= link_to project_team_member_path(project_id: @project, id: member.id), confirm: remove_from_team_message(@project, member), method: :delete, class: "very_small btn danger" do = link_to project_team_member_path(@project, user), confirm: remove_from_project_team_message(@project, user), method: :delete, class: "very_small btn danger" do
%i.icon-minus.icon-white %i.icon-minus.icon-white
- team = team_rel.user_team
- allow_admin = can? current_user, :admin_team_member, @project
%li{id: dom_id(team), class: "user_team_row team_#{team.id}"}
.row
.span6
%strong= link_to team.name, team_path(team), title: team.name, class: "dark"
%br
%small.cgray Members: #{team.members.count}
.span5.right
.right
- if allow_admin
.left
= link_to resign_project_team_path(@project, team), method: :delete, confirm: "Are you shure?", class: "btn danger small" do
%i.icon-minus.icon-white
- grouper_project_teams(@project).each do |access, teams|
.ui-box
%h5.title
= UserTeam.access_roles.key(access).pluralize
%small= teams.size
%ul.well-list
- teams.sort_by(&:team_name).each do |tofr|
= render(partial: 'team_members/show_team', locals: {team_rel: tofr})
:javascript
$(function(){
$('.repo-access-select, .project-access-select').live("change", function() {
$(this.form).submit();
});
})
- if @team_member.valid? - if @user_project_relation.valid?
:plain :plain
$("#new_team_member").hide("slide", { direction: "right" }, 150, function(){ $("#new_team_member").hide("slide", { direction: "right" }, 150, function(){
$("#team-table").show("slide", { direction: "left" }, 150, function() { $("#team-table").show("slide", { direction: "left" }, 150, function() {
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
= "Import team from another project" = "Import team from another project"
%hr %hr
%p.slead %p.slead
Read more about team import #{link_to "here", '#', class: 'vlink'}. Read more about project team import #{link_to "here", '#', class: 'vlink'}.
= form_tag apply_import_project_team_members_path(@project), method: 'post' do = form_tag apply_import_project_team_members_path(@project), method: 'post' do
%p.slead Choose project you want to use as team source: %p.slead Choose project you want to use as team source:
.padded .padded
......
= render "projects/project_head" = render "projects/project_head"
%h3.page_title %h3.page_title
Team Members Team Members
(#{@project.users_projects.count}) (#{@project.users.count})
%small %small
Read more about project permissions Read more about project permissions
%strong= link_to "here", help_permissions_path, class: "vlink" %strong= link_to "here", help_permissions_path, class: "vlink"
...@@ -10,11 +10,24 @@ ...@@ -10,11 +10,24 @@
%span.right %span.right
= link_to import_project_team_members_path(@project), class: "btn small grouped", title: "Import team from another project" do = link_to import_project_team_members_path(@project), class: "btn small grouped", title: "Import team from another project" do
Import team from another project Import team from another project
= link_to available_project_teams_path(@project), class: "btn small grouped", title: "Assign project to team of users" do
Assign project to Team of users
= link_to new_project_team_member_path(@project), class: "btn success small grouped", title: "New Team Member" do = link_to new_project_team_member_path(@project), class: "btn success small grouped", title: "New Team Member" do
New Team Member New Team Member
%hr
%hr
.clearfix .clearfix
%div.team-table %div.team-table
= render partial: "team_members/team", locals: {project: @project} = render partial: "team_members/team", locals: {project: @project}
%h3.page_title
Assigned teams
(#{@project.user_teams.count})
%hr
.clearfix
%div.team-table
= render partial: "team_members/teams", locals: {project: @project}
- allow_admin = can? current_user, :admin_project, @project - allow_admin = can? current_user, :admin_project, @project
- user = @team_member.user
.team_member_show .team_member_show
- if can? current_user, :admin_project, @project - if can? current_user, :admin_project, @project
= link_to 'Remove from team', project_team_member_path(project_id: @project, id: @team_member.id), confirm: 'Are you sure?', method: :delete, class: "right btn danger" = link_to 'Remove from team', project_team_member_path(@project, @member), confirm: 'Are you sure?', method: :delete, class: "right btn danger"
.profile_avatar_holder .profile_avatar_holder
= image_tag gravatar_icon(user.email, 60), class: "borders" = image_tag gravatar_icon(@member.email, 60), class: "borders"
%h3.page_title %h3.page_title
= user.name = @member.name
%small (@#{user.username}) %small (@#{@member.username})
%hr %hr
.back_link .back_link
...@@ -21,34 +20,34 @@ ...@@ -21,34 +20,34 @@
%table.lite %table.lite
%tr %tr
%td Email %td Email
%td= mail_to user.email %td= mail_to @member.email
%tr %tr
%td Skype %td Skype
%td= user.skype %td= @member.skype
- unless user.linkedin.blank? - unless @member.linkedin.blank?
%tr %tr
%td LinkedIn %td LinkedIn
%td= user.linkedin %td= @member.linkedin
- unless user.twitter.blank? - unless @member.twitter.blank?
%tr %tr
%td Twitter %td Twitter
%td= user.twitter %td= @member.twitter
- unless user.bio.blank? - unless @member.bio.blank?
%tr %tr
%td Bio %td Bio
%td= user.bio %td= @member.bio
.span6 .span6
%table.lite %table.lite
%tr %tr
%td Member since %td Member since
%td= @team_member.created_at.stamp("Aug 21, 2011") %td= @user_project_relation.created_at.stamp("Aug 21, 2011")
%tr %tr
%td %td
Project Access: Project Access:
%small (#{link_to "read more", help_permissions_path, class: "vlink"}) %small (#{link_to "read more", help_permissions_path, class: "vlink"})
%td %td
= form_for(@team_member, as: :team_member, url: project_team_member_path(@project, @team_member)) do |f| = form_for(@user_project_relation, as: :team_member, url: project_team_member_path(@project, @member)) do |f|
= f.select :project_access, options_for_select(Project.access_options, @team_member.project_access), {}, class: "project-access-select", disabled: !allow_admin = f.select :project_access, options_for_select(Project.access_options, @user_project_relation.project_access), {}, class: "project-access-select", disabled: !allow_admin
%hr %hr
= render @events = render @events
:javascript :javascript
......
- if @team_member.valid? - if @user_project_relation.valid?
:plain :plain
$("##{dom_id(@team_member)}").effect("highlight", {color: "#529214"}, 1000);; $("##{dom_id(@user_project_relation)}").effect("highlight", {color: "#529214"}, 1000);;
- else - else
:plain :plain
$("##{dom_id(@team_member)}").effect("highlight", {color: "#D12F19"}, 1000);; $("##{dom_id(@user_project_relation)}").effect("highlight", {color: "#D12F19"}, 1000);;
= form_tag team_filter_path(entity), method: 'get' do
%fieldset.dashboard-search-filter
= search_field_tag "search", params[:search], { placeholder: 'Search', class: 'search-text-input' }
= button_tag type: 'submit', class: 'btn' do
%i.icon-search
%fieldset
%legend Status:
%ul.nav.nav-pills.nav-stacked
%li{class: ("active" if !params[:status])}
= link_to team_filter_path(entity, status: nil) do
Open
%li{class: ("active" if params[:status] == 'closed')}
= link_to team_filter_path(entity, status: 'closed') do
Closed
%li{class: ("active" if params[:status] == 'all')}
= link_to team_filter_path(entity, status: 'all') do
All
%fieldset
%legend Projects:
%ul.nav.nav-pills.nav-stacked
- @projects.each do |project|
- unless entities_per_project(project, entity).zero?
%li{class: ("active" if params[:project_id] == project.id.to_s)}
= link_to team_filter_path(entity, project_id: project.id) do
= project.name_with_namespace
%small.right= entities_per_project(project, entity)
%fieldset
%hr
= link_to "Reset", team_filter_path(entity), class: 'btn right'
.projects_box
%h5.title
Projects
%small
(#{projects.count})
- if can? current_user, :manage_user_team, @team
%span.right
= link_to new_team_project_path(@team), class: "btn very_small info" do
%i.icon-plus
Assign Project
%ul.well-list
- if projects.blank?
%p.nothing_here_message This team has no projects yet
- projects.each do |project|
%li
= link_to project_path(project), class: dom_class(project) do
%strong.well-title= truncate(project.name, length: 25)
%span.arrow
&rarr;
%span.last_activity
%strong Last activity:
%span= project_last_activity(project)
%h3.page_title= "Edit Team #{@team.name}"
%hr
= form_for @team, url: teams_path do |f|
- if @team.errors.any?
.alert-message.block-message.error
%span= @team.errors.full_messages.first
.clearfix
= f.label :name do
Team name is
.input
= f.text_field :name, placeholder: "Ex. OpenSource", class: "xxlarge left"
.clearfix
= f.label :path do
Team path is
.input
= f.text_field :path, placeholder: "opensource", class: "xxlarge left"
.clearfix
.input.span3.center
= f.submit 'Save team changes', class: "btn primary"
.input.span3.center
= link_to 'Delete team', team_path(@team), method: :delete, confirm: "You are shure?", class: "btn danger"
%h3.page_title
Issues
%small (in Team projects assigned to Team members)
%small.right #{@issues.total_count} issues
%hr
.row
.span3
= render 'filter', entity: 'issue'
.span9
- if @issues.any?
- @issues.group_by(&:project).each do |group|
%div.ui-box
- @project = group[0]
%h5.title
= link_to_project @project
%ul.well-list.issues_table
- group[1].each do |issue|
= render(partial: 'issues/show', locals: {issue: issue})
%hr
= paginate @issues, theme: "gitlab"
- else
%p.nothing_here_message Nothing to show here
= form_tag admin_team_member_path(@team, @member), method: :put do
-if @member.errors.any?
.alert-message.block-message.error
%ul
- @member.errors.full_messages.each do |msg|
%li= msg
.clearfix
%label Default access for Team projects:
.input
= select_tag :default_project_access, options_for_select(UserTeam.access_roles, @team.default_projects_access(@member)), class: "project-access-select chosen span3"
.clearfix
%label Team admin?
.input
= check_box_tag :group_admin, true, @team.admin?(@member)
%br
.actions
= submit_tag 'Save', class: "btn primary"
= link_to 'Cancel', :back, class: "btn"
- user = member.user
- allow_admin = can? current_user, :manage_user_team, @team
%li{id: dom_id(member), class: "team_member_row user_#{user.id}"}
.row
.span5
= link_to user_path(user.username), title: user.name, class: "dark" do
= image_tag gravatar_icon(user.email, 40), class: "avatar s32"
= link_to user_path(user.username), title: user.name, class: "dark" do
%strong= truncate(user.name, lenght: 40)
%br
%small.cgray= user.email
.span6.right
- if allow_admin
.left.span2
= form_for(member, as: :team_member, url: team_member_path(@team, user)) do |f|
= f.select :permission, options_for_select(UsersProject.access_roles, @team.default_projects_access(user)), {}, class: "medium project-access-select span2"
.left.span2
%span
Admin access
= check_box_tag :group_admin, true, @team.admin?(user)
.right
- if current_user == user
%span.btn.disabled This is you!
- if @team.owner == user
%span.btn.disabled.success Owner
- elsif user.blocked
%span.btn.disabled.blocked Blocked
- elsif allow_admin
= link_to team_member_path(@team, user), confirm: remove_from_user_team_message(@team, user), method: :delete, class: "very_small btn danger" do
%i.icon-minus.icon-white
- grouped_user_team_members(@team).each do |access, members|
.ui-box
%h5.title
= Project.access_options.key(access).pluralize
%small= members.size
%ul.well-list
- members.sort_by(&:user_name).each do |up|
= render(partial: 'teams/members/show', locals: {member: up})
:javascript
$(function(){
$('.repo-access-select, .project-access-select').live("change", function() {
$(this.form).submit();
});
})
%h3.page_title
Edit access #{@member.name} in #{@team.name} team
%hr
%table.zebra-striped
%tr
%td User:
%td= @member.name
%tr
%td Team:
%td= @team.name
%tr
%td Since:
%td= member_since(@team, @member).stamp("Nov 11, 2010")
= render 'form'
%h3.page_title
Team Members
(#{@members.count})
%small
Read more about project permissions
%strong= link_to "here", help_permissions_path, class: "vlink"
- if can? current_user, :manage_user_team, @team
%span.right
= link_to new_team_member_path(@team), class: "btn success small grouped", title: "New Team Member" do
New Team Member
%hr
.clearfix
%div.team-table
= render partial: "teams/members/team", locals: {project: @team}
%h3.page_title
Team: #{@team.name}
%fieldset
%legend Members (#{@team.members.count})
= form_tag team_members_path(@team), id: "team_members", class: "bulk_import", method: :post do
%table#members_list
%thead
%tr
%th User name
%th Default project access
%th Team access
%th
- @team.members.each do |member|
%tr.member
%td
= member.name
%small= "(#{member.email})"
%td= @team.human_default_projects_access(member)
%td= @team.admin?(member) ? "Admin" : "Member"
%td
%tr
%td= select_tag :user_ids, options_from_collection_for_select(@users , :id, :name_with_email), multiple: true, data: {placeholder: 'Select users'}, class: 'chosen span5'
%td= select_tag :default_project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3" }
%td
%span= check_box_tag :group_admin
%span Admin?
%td= submit_tag 'Add', class: "btn primary", id: :add_members_to_team
- allow_admin = can? current_user, :admin_project, @project
- user = @team_member.user
.team_member_show
- if can? current_user, :admin_project, @project
= link_to 'Remove from team', project_team_member_path(project_id: @project, id: @team_member.id), confirm: 'Are you sure?', method: :delete, class: "right btn danger"
.profile_avatar_holder
= image_tag gravatar_icon(user.email, 60), class: "borders"
%h3.page_title
= user.name
%small (@#{user.username})
%hr
.back_link
%br
= link_to project_team_index_path(@project), class: "" do
&larr; To team list
%br
.row
.span6
%table.lite
%tr
%td Email
%td= mail_to user.email
%tr
%td Skype
%td= user.skype
- unless user.linkedin.blank?
%tr
%td LinkedIn
%td= user.linkedin
- unless user.twitter.blank?
%tr
%td Twitter
%td= user.twitter
- unless user.bio.blank?
%tr
%td Bio
%td= user.bio
.span6
%table.lite
%tr
%td Member since
%td= @team_member.created_at.stamp("Aug 21, 2011")
%tr
%td
Project Access:
%small (#{link_to "read more", help_permissions_path, class: "vlink"})
%td
= form_for(@team_member, as: :team_member, url: project_team_member_path(@project, @team_member)) do |f|
= f.select :project_access, options_for_select(Project.access_options, @team_member.project_access), {}, class: "project-access-select", disabled: !allow_admin
%hr
= render @events
:javascript
$(function(){
$('.repo-access-select, .project-access-select').live("change", function() {
$(this.form).submit();
});
})
%h3.page_title
Merge Requests
%small (authored by or assigned to Team members)
%small.right #{@merge_requests.total_count} merge requests
%hr
.row
.span3
= render 'filter', entity: 'merge_request'
.span9
- if @merge_requests.any?
- @merge_requests.group_by(&:project).each do |group|
.ui-box
- @project = group[0]
%h5.title
= link_to_project @project
%ul.well-list
- group[1].each do |merge_request|
= render(partial: 'merge_requests/merge_request', locals: {merge_request: merge_request})
%hr
= paginate @merge_requests, theme: "gitlab"
- else
%h3.nothing_here_message Nothing to show here
%h3.page_title New Team
%hr
= form_for @team, url: teams_path do |f|
- if @team.errors.any?
.alert-message.block-message.error
%span= @team.errors.full_messages.first
.clearfix
= f.label :name do
Team name is
.input
= f.text_field :name, placeholder: "Ex. Ruby Developers", class: "xxlarge left"
&nbsp;
= f.submit 'Create team', class: "btn primary"
%hr
.padded
%ul
%li All created teams are public (users can view who enter into team and which project are assigned for this team)
%li People within a team see only projects they have access to
%li You will be able to assign existing projects for team
= form_tag team_project_path(@team, @project), method: :put do
-if @project.errors.any?
.alert-message.block-message.error
%ul
- @project.errors.full_messages.each do |msg|
%li= msg
.clearfix
%label Max access for Team members:
.input
= select_tag :greatest_project_access, options_for_select(UserTeam.access_roles, @team.max_project_access(@project)), class: "project-access-select chosen span3"
%br
.actions
= submit_tag 'Save', class: "btn primary"
= link_to 'Cancel', :back, class: "btn"
%h3
Edit max access in #{@project.name} for #{@team.name} team
%hr
%table.zebra-striped
%tr
%td Project:
%td= @project.name
%tr
%td Team:
%td= @team.name
%tr
%td Since:
%td= assigned_since(@team, @project).stamp("Nov 11, 2010")
= render 'form'
%h3.page_title
Assigned projects (#{@team.projects.count})
%small
Read more about project permissions
%strong= link_to "here", help_permissions_path, class: "vlink"
- if current_user.can?(:manage_user_team, @team) && @avaliable_projects.any?
%span.right
= link_to new_team_project_path(@team), class: "btn success small grouped", title: "New Team Member" do
Assign project to Team
%hr
- if @team.projects.present?
%table.projects-table
%thead
%tr
%th Project name
%th Max access
- if current_user.can?(:admin_user_team, @team)
%th.span3
- @team.projects.each do |project|
%tr.project
%td
= link_to project.name_with_namespace, project_path(project)
%td
%span= @team.human_max_project_access(project)
- if current_user.can?(:admin_user_team, @team)
%td.bgred
= link_to 'Edit max access', edit_team_project_path(@team, project), class: "btn small"
= link_to 'Relegate', team_project_path(@team, project), confirm: 'Remove project from team and move to global namespace. Are you sure?', method: :delete, class: "btn danger small"
- else
%p.nothing_here_message This team has no projects yet
%h3.page_title
Team: #{@team.name}
%fieldset
%legend Projects (#{@team.projects.count})
= form_tag team_projects_path(@team), id: "assign_projects", class: "bulk_import", method: :post do
%table#projects_list
%thead
%tr
%th Project name
%th Max access
%th
- @team.projects.each do |project|
%tr.project
%td
= link_to project.name_with_namespace, team_project_path(@team, project)
%td
%span= @team.human_max_project_access(project)
%td
%tr
%td= select_tag :project_ids, options_from_collection_for_select(@avaliable_projects , :id, :name_with_namespace), multiple: true, data: {placeholder: 'Select projects'}, class: 'chosen span5'
%td= select_tag :greatest_project_access, options_for_select(UserTeam.access_roles), {class: "project-access-select chosen span3" }
%td= submit_tag 'Add', class: "btn primary", id: :assign_projects_to_team
.projects
.activities.span8
= link_to dashboard_path, class: 'btn very_small' do
&larr; To dashboard
&nbsp;
%span.cgray Events and projects are filtered in scope of team
%hr
- if @events.any?
.content_list
- else
%p.nothing_here_message Projects activity will be displayed here
.loading.hide
.side.span4
= render "projects", projects: @projects
%div
%span.rss-icon
= link_to dashboard_path(:atom, { private_token: current_user.private_token }) do
= image_tag "rss_ui.png", title: "feed"
%strong News Feed
%hr
.gitlab-promo
= link_to "Homepage", "http://gitlabhq.com"
= link_to "Blog", "http://blog.gitlabhq.com"
= link_to "@gitlabhq", "https://twitter.com/gitlabhq"
:javascript
$(function(){ Pager.init(20, true); });
.ui-box
%h5.title
Profile
%ul.well-list
%li
%strong Email
%span.right= mail_to @user.email
- unless @user.skype.blank?
%li
%strong Skype
%span.right= @user.skype
- unless @user.linkedin.blank?
%li
%strong LinkedIn
%span.right= @user.linkedin
- unless @user.twitter.blank?
%li
%strong Twitter
%span.right= @user.twitter
- unless @user.bio.blank?
%li
%strong Bio
%span.right= @user.bio
...@@ -3,6 +3,11 @@ ...@@ -3,6 +3,11 @@
%h3.page_title %h3.page_title
= image_tag gravatar_icon(@user.email, 90), class: "avatar s90" = image_tag gravatar_icon(@user.email, 90), class: "avatar s90"
= @user.name = @user.name
- if @user == current_user
.right
= link_to profile_path, class: 'btn small' do
%i.icon-edit
Edit Profile
%br %br
%small @#{@user.username} %small @#{@user.username}
%br %br
...@@ -12,26 +17,5 @@ ...@@ -12,26 +17,5 @@
%h5 Recent events %h5 Recent events
= render @events = render @events
.span4 .span4
.ui-box = render 'profile'
%h5.title Profile
%ul.well-list
%li
%strong Email
%span.right= mail_to @user.email
- unless @user.skype.blank?
%li
%strong Skype
%span.right= @user.skype
- unless @user.linkedin.blank?
%li
%strong LinkedIn
%span.right= @user.linkedin
- unless @user.twitter.blank?
%li
%strong Twitter
%span.right= @user.twitter
- unless @user.bio.blank?
%li
%strong Bio
%span.right= @user.bio
= render 'projects' = render 'projects'
class ProjectWebHookWorker
include Sidekiq::Worker
sidekiq_options queue: :project_web_hook
def perform(hook_id, data)
WebHook.find(hook_id).execute data
end
end
...@@ -21,7 +21,7 @@ Gitlab::Application.routes.draw do ...@@ -21,7 +21,7 @@ Gitlab::Application.routes.draw do
project_root: Gitlab.config.gitolite.repos_path, project_root: Gitlab.config.gitolite.repos_path,
upload_pack: Gitlab.config.gitolite.upload_pack, upload_pack: Gitlab.config.gitolite.upload_pack,
receive_pack: Gitlab.config.gitolite.receive_pack receive_pack: Gitlab.config.gitolite.receive_pack
}), at: '/', constraints: lambda { |request| /[-\/\w\.-]+\.git\//.match(request.path_info) } }), at: '/', constraints: lambda { |request| /[-\/\w\.]+\.git\//.match(request.path_info) }
# #
# Help # Help
...@@ -49,13 +49,14 @@ Gitlab::Application.routes.draw do ...@@ -49,13 +49,14 @@ Gitlab::Application.routes.draw do
# Admin Area # Admin Area
# #
namespace :admin do namespace :admin do
resources :users do resources :users, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ } do
member do member do
put :team_update put :team_update
put :block put :block
put :unblock put :unblock
end end
end end
resources :groups, constraints: { id: /[^\/]+/ } do resources :groups, constraints: { id: /[^\/]+/ } do
member do member do
put :project_update put :project_update
...@@ -63,18 +64,31 @@ Gitlab::Application.routes.draw do ...@@ -63,18 +64,31 @@ Gitlab::Application.routes.draw do
delete :remove_project delete :remove_project
end end
end end
resources :projects, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ }, except: [:new, :create] do
member do resources :teams, constraints: { id: /[^\/]+/ } do
get :team scope module: :teams do
put :team_update resources :members, only: [:edit, :update, :destroy, :new, :create]
resources :projects, only: [:edit, :update, :destroy, :new, :create], constraints: { id: /[a-zA-Z.\/0-9_\-]+/ }
end end
end end
resources :team_members, only: [:edit, :update, :destroy]
resources :hooks, only: [:index, :create, :destroy] do resources :hooks, only: [:index, :create, :destroy] do
get :test get :test
end end
resource :logs, only: [:show] resource :logs, only: [:show]
resource :resque, controller: 'resque', only: [:show] resource :resque, controller: 'resque', only: [:show]
resources :projects, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ }, except: [:new, :create] do
member do
get :team
put :team_update
end
scope module: :projects, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ } do
resources :members, only: [:edit, :update, :destroy]
end
end
root to: "dashboard#index" root to: "dashboard#index"
end end
...@@ -104,15 +118,18 @@ Gitlab::Application.routes.draw do ...@@ -104,15 +118,18 @@ Gitlab::Application.routes.draw do
# #
# Dashboard Area # Dashboard Area
# #
get "dashboard" => "dashboard#index" resource :dashboard, controller: "dashboard" do
get "dashboard/issues" => "dashboard#issues" member do
get "dashboard/merge_requests" => "dashboard#merge_requests" get :projects
get :issues
get :merge_requests
end
end
# #
# Groups Area # Groups Area
# #
resources :groups, constraints: { id: /[^\/]+/ }, only: [:show] do resources :groups, constraints: { id: /[^\/]+/ }, only: [:show, :new, :create] do
member do member do
get :issues get :issues
get :merge_requests get :merge_requests
...@@ -122,6 +139,20 @@ Gitlab::Application.routes.draw do ...@@ -122,6 +139,20 @@ Gitlab::Application.routes.draw do
end end
end end
#
# Teams Area
#
resources :teams, constraints: { id: /[^\/]+/ } do
member do
get :issues
get :merge_requests
end
scope module: :teams do
resources :members, only: [:index, :new, :create, :edit, :update, :destroy]
resources :projects, only: [:index, :new, :create, :edit, :update, :destroy], constraints: { id: /[a-zA-Z.0-9_\-\/]+/ }
end
end
resources :projects, constraints: { id: /[^\/]+/ }, only: [:new, :create] resources :projects, constraints: { id: /[^\/]+/ }, only: [:new, :create]
devise_for :users, controllers: { omniauth_callbacks: :omniauth_callbacks, registrations: :registrations } devise_for :users, controllers: { omniauth_callbacks: :omniauth_callbacks, registrations: :registrations }
...@@ -238,6 +269,18 @@ Gitlab::Application.routes.draw do ...@@ -238,6 +269,18 @@ Gitlab::Application.routes.draw do
end end
end end
scope module: :projects do
resources :teams, only: [] do
collection do
get :available
post :assign
end
member do
delete :resign
end
end
end
resources :notes, only: [:index, :create, :destroy] do resources :notes, only: [:index, :create, :destroy] do
collection do collection do
post :preview post :preview
...@@ -245,5 +288,5 @@ Gitlab::Application.routes.draw do ...@@ -245,5 +288,5 @@ Gitlab::Application.routes.draw do
end end
end end
root to: "dashboard#index" root to: "dashboard#show"
end end
class CreateUserTeams < ActiveRecord::Migration
def change
create_table :user_teams do |t|
t.string :name
t.string :path
t.integer :owner_id
t.timestamps
end
end
end
class CreateUserTeamProjectRelationships < ActiveRecord::Migration
def change
create_table :user_team_project_relationships do |t|
t.integer :project_id
t.integer :user_team_id
t.integer :greatest_access
t.timestamps
end
end
end
class CreateUserTeamUserRelationships < ActiveRecord::Migration
def change
create_table :user_team_user_relationships do |t|
t.integer :user_id
t.integer :user_team_id
t.boolean :group_admin
t.integer :permission
t.timestamps
end
end
end
class AddUserPermissions < ActiveRecord::Migration
def up
add_column :users, :can_create_group, :boolean, default: true, null: false
add_column :users, :can_create_team, :boolean, default: true, null: false
end
def down
remove_column :users, :can_create_group
remove_column :users, :can_create_team
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended to check this file into your version control system. # It's strongly recommended to check this file into your version control system.
ActiveRecord::Schema.define(:version => 20130110172407) do ActiveRecord::Schema.define(:version => 20130125090214) do
create_table "events", :force => true do |t| create_table "events", :force => true do |t|
t.string "target_type" t.string "target_type"
...@@ -213,6 +213,31 @@ ActiveRecord::Schema.define(:version => 20130110172407) do ...@@ -213,6 +213,31 @@ ActiveRecord::Schema.define(:version => 20130110172407) do
t.string "name" t.string "name"
end end
create_table "user_team_project_relationships", :force => true do |t|
t.integer "project_id"
t.integer "user_team_id"
t.integer "greatest_access"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "user_team_user_relationships", :force => true do |t|
t.integer "user_id"
t.integer "user_team_id"
t.boolean "group_admin"
t.integer "permission"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "user_teams", :force => true do |t|
t.string "name"
t.string "path"
t.integer "owner_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "users", :force => true do |t| create_table "users", :force => true do |t|
t.string "email", :default => "", :null => false t.string "email", :default => "", :null => false
t.string "encrypted_password", :default => "", :null => false t.string "encrypted_password", :default => "", :null => false
...@@ -242,6 +267,8 @@ ActiveRecord::Schema.define(:version => 20130110172407) do ...@@ -242,6 +267,8 @@ ActiveRecord::Schema.define(:version => 20130110172407) do
t.string "extern_uid" t.string "extern_uid"
t.string "provider" t.string "provider"
t.string "username" t.string "username"
t.boolean "can_create_group", :default => true, :null => false
t.boolean "can_create_team", :default => true, :null => false
end end
add_index "users", ["admin"], :name => "index_users_on_admin" add_index "users", ["admin"], :name => "index_users_on_admin"
......
Feature: Admin Teams
Background:
Given I sign in as an admin
And Create gitlab user "John"
Scenario: Create a team
When I visit admin teams page
And I click new team link
And submit form with new team info
Then I should be redirected to team page
And I should see newly created team
Scenario: Add user to team
When I visit admin teams page
When I have clean "HardCoders" team
And I visit "HardCoders" team page
When I click to "Add members" link
When I select user "John" from user list as "Developer"
And submit form with new team member info
Then I should see "John" in teams members list as "Developer"
Scenario: Assign team to existing project
When I visit admin teams page
When I have "HardCoders" team with "John" member with "Developer" role
When I have "Shop" project
And I visit "HardCoders" team page
Then I should see empty projects table
When I click to "Add projects" link
When I select project "Shop" with max access "Reporter"
And submit form with new team project info
Then I should see "Shop" project in projects list
When I visit "Shop" project admin page
Then I should see "John" user with role "Reporter" in team table
Scenario: Add user to team with ptojects
When I visit admin teams page
When I have "HardCoders" team with "John" member with "Developer" role
And "HardCoders" team assigned to "Shop" project with "Developer" max role access
When I have gitlab user "Jimm"
And I visit "HardCoders" team page
Then I should see members table without "Jimm" member
When I click to "Add members" link
When I select user "Jimm" ub team members list as "Master"
And submit form with new team member info
Then I should see "Jimm" in teams members list as "Master"
Scenario: Remove member from team
Given I have users team "HardCoders"
And gitlab user "John" is a member "HardCoders" team
And gitlab user "Jimm" is a member "HardCoders" team
And "HardCoders" team is assigned to "Shop" project
When I visit admin teams page
When I visit "HardCoders" team admin page
Then I shoould see "John" in members list
And I should see "Jimm" in members list
And I should see "Shop" in projects list
When I click on remove "Jimm" user link
Then I should be redirected to "HardCoders" team admin page
And I should not to see "Jimm" user in members list
Scenario: Remove project from team
Given I have users team "HardCoders"
And gitlab user "John" is a member "HardCoders" team
And gitlab user "Jimm" is a member "HardCoders" team
And "HardCoders" team is assigned to "Shop" project
When I visit admin teams page
When I visit "HardCoders" team admin page
Then I should see "Shop" project in projects list
When I click on "Relegate" link on "Shop" project
Then I should see projects liston team page without "Shop" project
...@@ -16,12 +16,6 @@ Feature: Dashboard ...@@ -16,12 +16,6 @@ Feature: Dashboard
And I visit dashboard page And I visit dashboard page
Then I should see groups list Then I should see groups list
Scenario: I should see correct projects count
Given I have group with projects
And group has a projects that does not belongs to me
When I visit dashboard page
Then I should see 1 project at group list
Scenario: I should see last push widget Scenario: I should see last push widget
Then I should see last push widget Then I should see last push widget
And I click "Create Merge Request" link And I click "Create Merge Request" link
......
Feature: Dashboard
Background:
Given I sign in as a user
And I own project "Shop"
And I visit dashboard projects page
Scenario: I should see issues list
Then I should see projects list
Feature: Groups
Background:
Given I sign in as a user
Scenario: Create a group from dasboard
Given I have group with projects
And I visit dashboard page
When I click new group link
And submit form with new group info
Then I should be redirected to group page
And I should see newly created group
class AdminTeams < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedActiveTab
include SharedAdmin
And 'I have own project' do
create :project
end
And 'Create gitlab user "John"' do
@user = create(:user, :name => "John")
end
And 'I click new team link' do
click_link "New Team"
end
And 'submit form with new team info' do
fill_in 'user_team_name', with: 'gitlab'
click_button 'Create team'
end
Then 'I should be redirected to team page' do
current_path.should == admin_team_path(UserTeam.last)
end
And 'I should see newly created team' do
page.should have_content "Team: gitlab"
end
When 'I visit admin teams page' do
visit admin_teams_path
end
When 'I have clean "HardCoders" team' do
@team = create :user_team, name: "HardCoders", owner: current_user
end
And 'I visit "HardCoders" team page' do
visit admin_team_path(UserTeam.find_by_name("HardCoders"))
end
Then 'I should see only me in members table' do
members_list = find("#members_list .member")
members_list.should have_content(current_user.name)
members_list.should have_content(current_user.email)
end
When 'I select user "John" from user list as "Developer"' do
@user ||= User.find_by_name("John")
within "#team_members" do
select @user.name, :from => "user_ids"
select "Developer", :from => "default_project_access"
end
end
And 'submit form with new team member info' do
click_button 'add_members_to_team'
end
Then 'I should see "John" in teams members list as "Developer"' do
@user ||= User.find_by_name("John")
find_in_list("#members_list .member", @user).must_equal true
end
When 'I visit "John" user admin page' do
pending 'step not implemented'
end
Then 'I should see "HardCoders" team in teams table' do
pending 'step not implemented'
end
When 'I have "HardCoders" team with "John" member with "Developer" role' do
@team = create :user_team, name: "HardCoders", owner: current_user
@user ||= User.find_by_name("John")
@team.add_member(@user, UserTeam.access_roles["Developer"], group_admin: false)
end
When 'I have "Shop" project' do
@project = create :project, name: "Shop"
end
Then 'I should see empty projects table' do
page.has_no_css?("#projects_list").must_equal true
end
When 'I select project "Shop" with max access "Reporter"' do
@project ||= Project.find_by_name("Shop")
within "#assign_projects" do
select @project.name, :from => "project_ids"
select "Reporter", :from => "greatest_project_access"
end
end
And 'submit form with new team project info' do
click_button 'assign_projects_to_team'
end
Then 'I should see "Shop" project in projects list' do
project = Project.find_by_name("Shop")
find_in_list("#projects_list .project", project).must_equal true
end
When 'I visit "Shop" project admin page' do
project = Project.find_by_name("Shop")
visit admin_project_path(project)
end
And '"HardCoders" team assigned to "Shop" project with "Developer" max role access' do
@team = UserTeam.find_by_name("HardCoders")
@project = create :project, name: "Shop"
@team.assign_to_project(@project, UserTeam.access_roles["Developer"])
end
When 'I have gitlab user "Jimm"' do
create :user, name: "Jimm"
end
Then 'I should see members table without "Jimm" member' do
user = User.find_by_name("Jimm")
find_in_list("#members_list .member", user).must_equal false
end
When 'I select user "Jimm" ub team members list as "Master"' do
user = User.find_by_name("Jimm")
within "#team_members" do
select user.name, :from => "user_ids"
select "Developer", :from => "default_project_access"
end
end
Then 'I should see "Jimm" in teams members list as "Master"' do
user = User.find_by_name("Jimm")
find_in_list("#members_list .member", user).must_equal true
end
Given 'I have users team "HardCoders"' do
@team = create :user_team, name: "HardCoders"
end
And 'gitlab user "John" is a member "HardCoders" team' do
@team = UserTeam.find_by_name("HardCoders")
@user = User.find_by_name("John")
@user = create :user, name: "John" unless @user
@team.add_member(@user, UserTeam.access_roles["Master"], group_admin: false)
end
And 'gitlab user "Jimm" is a member "HardCoders" team' do
@team = UserTeam.find_by_name("HardCoders")
@user = User.find_by_name("Jimm")
@user = create :user, name: "Jimm" unless @user
@team.add_member(@user, UserTeam.access_roles["Master"], group_admin: false)
end
And '"HardCoders" team is assigned to "Shop" project' do
@team = UserTeam.find_by_name("HardCoders")
@project = create :project, name: "Shop"
@team.assign_to_project(@project, UserTeam.access_roles["Developer"])
end
When 'I visit "HardCoders" team admin page' do
visit admin_team_path(UserTeam.find_by_name("HardCoders"))
end
Then 'I shoould see "John" in members list' do
user = User.find_by_name("John")
find_in_list("#members_list .member", user).must_equal true
end
And 'I should see "Jimm" in members list' do
user = User.find_by_name("Jimm")
find_in_list("#members_list .member", user).must_equal true
end
And 'I should see "Shop" in projects list' do
project = Project.find_by_name("Shop")
find_in_list("#projects_list .project", project).must_equal true
end
When 'I click on remove "Jimm" user link' do
user = User.find_by_name("Jimm")
click_link "remove_member_#{user.id}"
end
Then 'I should be redirected to "HardCoders" team admin page' do
current_path.should == admin_team_path(UserTeam.find_by_name("HardCoders"))
end
And 'I should not to see "Jimm" user in members list' do
user = User.find_by_name("Jimm")
find_in_list("#members_list .member", user).must_equal false
end
When 'I click on "Relegate" link on "Shop" project' do
project = Project.find_by_name("Shop")
click_link "relegate_project_#{project.id}"
end
Then 'I should see projects liston team page without "Shop" project' do
project = Project.find_by_name("Shop")
find_in_list("#projects_list .project", project).must_equal false
end
Then 'I should see "John" user with role "Reporter" in team table' do
user = User.find_by_name("John")
find_in_list(".team_members", user).must_equal true
end
When 'I click to "Add members" link' do
click_link "Add members"
end
When 'I click to "Add projects" link' do
click_link "Add projects"
end
protected
def current_team
@team ||= Team.first
end
def find_in_list(selector, item)
members_list = all(selector)
entered = false
members_list.each do |member_item|
entered = true if member_item.has_content?(item.name)
end
entered
end
end
...@@ -63,6 +63,12 @@ class Dashboard < Spinach::FeatureSteps ...@@ -63,6 +63,12 @@ class Dashboard < Spinach::FeatureSteps
@project.team << [current_user, :master] @project.team << [current_user, :master]
end end
Then 'I should see projects list' do
@user.authorized_projects.all.each do |project|
page.should have_link project.name_with_namespace
end
end
Then 'I should see groups list' do Then 'I should see groups list' do
Group.all.each do |group| Group.all.each do |group|
page.should have_link group.name page.should have_link group.name
......
...@@ -64,6 +64,24 @@ class Groups < Spinach::FeatureSteps ...@@ -64,6 +64,24 @@ class Groups < Spinach::FeatureSteps
author: current_user author: current_user
end end
When 'I click new group link' do
click_link "New Group"
end
And 'submit form with new group info' do
fill_in 'group_name', :with => 'Samurai'
click_button "Create group"
end
Then 'I should see newly created group' do
page.should have_content "Samurai"
page.should have_content "You will only see events from projects in this group"
end
Then 'I should be redirected to group page' do
current_path.should == group_path(Group.last)
end
protected protected
def current_group def current_group
......
...@@ -2,27 +2,27 @@ module SharedDiffNote ...@@ -2,27 +2,27 @@ module SharedDiffNote
include Spinach::DSL include Spinach::DSL
Given 'I cancel the diff comment' do Given 'I cancel the diff comment' do
within(".diff_file") do within(".file") do
find(".js-close-discussion-note-form").trigger("click") find(".js-close-discussion-note-form").trigger("click")
end end
end end
Given 'I delete a diff comment' do Given 'I delete a diff comment' do
sleep 1 sleep 1
within(".diff_file") do within(".file") do
first(".js-note-delete").trigger("click") first(".js-note-delete").trigger("click")
end end
end end
Given 'I haven\'t written any diff comment text' do Given 'I haven\'t written any diff comment text' do
within(".diff_file") do within(".file") do
fill_in "note[note]", with: "" fill_in "note[note]", with: ""
end end
end end
Given 'I leave a diff comment like "Typo, please fix"' do Given 'I leave a diff comment like "Typo, please fix"' do
find("#586fb7c4e1add2d4d24e27566ed7064680098646_29_14.line_holder .js-add-diff-note-button").trigger("click") find("#586fb7c4e1add2d4d24e27566ed7064680098646_29_14.line_holder .js-add-diff-note-button").trigger("click")
within(".diff_file") do within(".file") do
fill_in "note[note]", with: "Typo, please fix" fill_in "note[note]", with: "Typo, please fix"
#click_button("Add Comment") #click_button("Add Comment")
find(".js-comment-button").trigger("click") find(".js-comment-button").trigger("click")
...@@ -32,7 +32,7 @@ module SharedDiffNote ...@@ -32,7 +32,7 @@ module SharedDiffNote
Given 'I preview a diff comment text like "Should fix it :smile:"' do Given 'I preview a diff comment text like "Should fix it :smile:"' do
find("#586fb7c4e1add2d4d24e27566ed7064680098646_29_14.line_holder .js-add-diff-note-button").trigger("click") find("#586fb7c4e1add2d4d24e27566ed7064680098646_29_14.line_holder .js-add-diff-note-button").trigger("click")
within(".diff_file") do within(".file") do
fill_in "note[note]", with: "Should fix it :smile:" fill_in "note[note]", with: "Should fix it :smile:"
find(".js-note-preview-button").trigger("click") find(".js-note-preview-button").trigger("click")
end end
...@@ -40,7 +40,7 @@ module SharedDiffNote ...@@ -40,7 +40,7 @@ module SharedDiffNote
Given 'I preview another diff comment text like "DRY this up"' do Given 'I preview another diff comment text like "DRY this up"' do
find("#586fb7c4e1add2d4d24e27566ed7064680098646_57_41.line_holder .js-add-diff-note-button").trigger("click") find("#586fb7c4e1add2d4d24e27566ed7064680098646_57_41.line_holder .js-add-diff-note-button").trigger("click")
within(".diff_file") do within(".file") do
fill_in "note[note]", with: "DRY this up" fill_in "note[note]", with: "DRY this up"
find(".js-note-preview-button").trigger("click") find(".js-note-preview-button").trigger("click")
end end
...@@ -55,13 +55,13 @@ module SharedDiffNote ...@@ -55,13 +55,13 @@ module SharedDiffNote
end end
Given 'I write a diff comment like ":-1: I don\'t like this"' do Given 'I write a diff comment like ":-1: I don\'t like this"' do
within(".diff_file") do within(".file") do
fill_in "note[note]", with: ":-1: I don\'t like this" fill_in "note[note]", with: ":-1: I don\'t like this"
end end
end end
Given 'I submit the diff comment' do Given 'I submit the diff comment' do
within(".diff_file") do within(".file") do
click_button("Add Comment") click_button("Add Comment")
end end
end end
...@@ -69,49 +69,49 @@ module SharedDiffNote ...@@ -69,49 +69,49 @@ module SharedDiffNote
Then 'I should not see the diff comment form' do Then 'I should not see the diff comment form' do
within(".diff_file") do within(".file") do
page.should_not have_css("form.new_note") page.should_not have_css("form.new_note")
end end
end end
Then 'I should not see the diff comment preview button' do Then 'I should not see the diff comment preview button' do
within(".diff_file") do within(".file") do
page.should have_css(".js-note-preview-button", visible: false) page.should have_css(".js-note-preview-button", visible: false)
end end
end end
Then 'I should not see the diff comment text field' do Then 'I should not see the diff comment text field' do
within(".diff_file") do within(".file") do
page.should have_css(".js-note-text", visible: false) page.should have_css(".js-note-text", visible: false)
end end
end end
Then 'I should only see one diff form' do Then 'I should only see one diff form' do
within(".diff_file") do within(".file") do
page.should have_css("form.new_note", count: 1) page.should have_css("form.new_note", count: 1)
end end
end end
Then 'I should see a diff comment form with ":-1: I don\'t like this"' do Then 'I should see a diff comment form with ":-1: I don\'t like this"' do
within(".diff_file") do within(".file") do
page.should have_field("note[note]", with: ":-1: I don\'t like this") page.should have_field("note[note]", with: ":-1: I don\'t like this")
end end
end end
Then 'I should see a diff comment saying "Typo, please fix"' do Then 'I should see a diff comment saying "Typo, please fix"' do
within(".diff_file .note") do within(".file .note") do
page.should have_content("Typo, please fix") page.should have_content("Typo, please fix")
end end
end end
Then 'I should see a discussion reply button' do Then 'I should see a discussion reply button' do
within(".diff_file") do within(".file") do
page.should have_link("Reply") page.should have_link("Reply")
end end
end end
Then 'I should see a temporary diff comment form' do Then 'I should see a temporary diff comment form' do
within(".diff_file") do within(".file") do
page.should have_css(".js-temp-notes-holder form.new_note") page.should have_css(".js-temp-notes-holder form.new_note")
end end
end end
...@@ -121,37 +121,37 @@ module SharedDiffNote ...@@ -121,37 +121,37 @@ module SharedDiffNote
end end
Then 'I should see an empty diff comment form' do Then 'I should see an empty diff comment form' do
within(".diff_file") do within(".file") do
page.should have_field("note[note]", with: "") page.should have_field("note[note]", with: "")
end end
end end
Then 'I should see the cancel comment button' do Then 'I should see the cancel comment button' do
within(".diff_file form") do within(".file form") do
page.should have_css(".js-close-discussion-note-form", text: "Cancel") page.should have_css(".js-close-discussion-note-form", text: "Cancel")
end end
end end
Then 'I should see the diff comment preview' do Then 'I should see the diff comment preview' do
within(".diff_file form") do within(".file form") do
page.should have_css(".js-note-preview", visible: false) page.should have_css(".js-note-preview", visible: false)
end end
end end
Then 'I should see the diff comment edit button' do Then 'I should see the diff comment edit button' do
within(".diff_file") do within(".file") do
page.should have_css(".js-note-edit-button", visible: true) page.should have_css(".js-note-edit-button", visible: true)
end end
end end
Then 'I should see the diff comment preview button' do Then 'I should see the diff comment preview button' do
within(".diff_file") do within(".file") do
page.should have_css(".js-note-preview-button", visible: true) page.should have_css(".js-note-preview-button", visible: true)
end end
end end
Then 'I should see two separate previews' do Then 'I should see two separate previews' do
within(".diff_file") do within(".file") do
page.should have_css(".js-note-preview", visible: true, count: 2) page.should have_css(".js-note-preview", visible: true, count: 2)
page.should have_content("Should fix it") page.should have_content("Should fix it")
page.should have_content("DRY this up") page.should have_content("DRY this up")
......
...@@ -33,12 +33,16 @@ module SharedPaths ...@@ -33,12 +33,16 @@ module SharedPaths
visit dashboard_path visit dashboard_path
end end
Given 'I visit dashboard projects page' do
visit projects_dashboard_path
end
Given 'I visit dashboard issues page' do Given 'I visit dashboard issues page' do
visit dashboard_issues_path visit issues_dashboard_path
end end
Given 'I visit dashboard merge requests page' do Given 'I visit dashboard merge requests page' do
visit dashboard_merge_requests_path visit merge_requests_dashboard_path
end end
Given 'I visit dashboard search page' do Given 'I visit dashboard search page' do
...@@ -105,6 +109,10 @@ module SharedPaths ...@@ -105,6 +109,10 @@ module SharedPaths
visit admin_groups_path visit admin_groups_path
end end
When 'I visit admin teams page' do
visit admin_teams_path
end
# ---------------------------------------- # ----------------------------------------
# Generic Project # Generic Project
# ---------------------------------------- # ----------------------------------------
......
class Userteams < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedProject
When 'I do not have teams with me' do
UserTeam.with_member(current_user).destroy_all
end
Then 'I should see dashboard page without teams info block' do
page.has_no_css?(".teams-box").must_equal true
end
When 'I have teams with my membership' do
team = create :user_team, owner: current_user
team.add_member(current_user, UserTeam.access_roles["Master"], true)
end
Then 'I should see dashboard page with teams information block' do
page.should have_css(".teams-box")
end
When 'exist user teams' do
team = create :user_team
team.add_member(current_user, UserTeam.access_roles["Master"], true)
end
And 'I click on "All teams" link' do
click_link("All Teams")
end
Then 'I should see "All teams" page' do
current_path.should == teams_path
end
And 'I should see exist teams in teams list' do
team = UserTeam.last
find_in_list(".teams_list tr", team).must_equal true
end
When 'I click to "New team" link' do
click_link("New Team")
end
And 'I submit form with new team info' do
fill_in 'name', with: 'gitlab'
click_button 'Create team'
end
Then 'I should be redirected to new team page' do
team = UserTeam.last
current_path.should == team_path(team)
end
When 'I have teams with projects and members' do
team = create :user_team, owner: current_user
@project = create :project
team.add_member(current_user, UserTeam.access_roles["Master"], true)
team.assign_to_project(@project, UserTeam.access_roles["Master"])
@event = create(:closed_issue_event, project: @project)
end
When 'I visit team page' do
visit team_path(UserTeam.last)
end
Then 'I should see projects list' do
page.should have_css(".projects_box")
projects_box = find(".projects_box")
projects_box.should have_content(@project.name)
end
And 'project from team has issues assigned to me' do
team = UserTeam.last
team.projects.each do |project|
project.issues << create(:issue, assignee: current_user)
end
end
When 'I visit team issues page' do
team = UserTeam.last
visit issues_team_path(team)
end
Then 'I should see issues from this team assigned to me' do
team = UserTeam.last
team.projects.each do |project|
project.issues.assigned(current_user).each do |issue|
page.should have_content issue.title
end
end
end
Given 'I have team with projects and members' do
team = create :user_team, owner: current_user
project = create :project
user = create :user
team.add_member(current_user, UserTeam.access_roles["Master"], true)
team.add_member(user, UserTeam.access_roles["Developer"], false)
team.assign_to_project(project, UserTeam.access_roles["Master"])
end
Given 'project from team has issues assigned to teams members' do
team = UserTeam.last
team.projects.each do |project|
team.members.each do |member|
project.issues << create(:issue, assignee: member)
end
end
end
Then 'I should see issues from this team assigned to teams members' do
team = UserTeam.last
team.projects.each do |project|
team.members.each do |member|
project.issues.assigned(member).each do |issue|
page.should have_content issue.title
end
end
end
end
Given 'project from team has merge requests assigned to me' do
team = UserTeam.last
team.projects.each do |project|
team.members.each do |member|
3.times { project.merge_requests << create(:merge_request, assignee: member) }
end
end
end
When 'I visit team merge requests page' do
team = UserTeam.last
visit merge_requests_team_path(team)
end
Then 'I should see merge requests from this team assigned to me' do
team = UserTeam.last
team.projects.each do |project|
team.members.each do |member|
project.issues.assigned(member).each do |merge_request|
page.should have_content merge_request.title
end
end
end
end
Given 'project from team has merge requests assigned to team members' do
team = UserTeam.last
team.projects.each do |project|
team.members.each do |member|
3.times { project.merge_requests << create(:merge_request, assignee: member) }
end
end
end
Then 'I should see merge requests from this team assigned to me' do
team = UserTeam.last
team.projects.each do |project|
team.members.each do |member|
project.issues.assigned(member).each do |merge_request|
page.should have_content merge_request.title
end
end
end
end
Given 'I have new user "John"' do
create :user, name: "John"
end
When 'I visit team people page' do
team = UserTeam.last
visit team_members_path(team)
end
And 'I select user "John" from list with role "Reporter"' do
user = User.find_by_name("John")
within "#team_members" do
select user.name, :from => "user_ids"
select "Reporter", :from => "default_project_access"
end
click_button "Add"
end
Then 'I should see user "John" in team list' do
user = User.find_by_name("John")
team_members_list = find(".team-table")
team_members_list.should have_content user.name
end
And 'I have my own project without teams' do
@project = create :project, namespace: current_user.namespace
end
And 'I visit my team page' do
team = UserTeam.where(owner_id: current_user.id).last
visit team_path(team)
end
When 'I click on link "Projects"' do
click_link "Projects"
end
And 'I click link "Assign project to Team"' do
click_link "Assign project to Team"
end
Then 'I should see form with my own project in avaliable projects list' do
projects_select = find("#project_ids")
projects_select.should have_content(@project.name)
end
When 'I submit form with selected project and max access' do
within "#assign_projects" do
select @project.name_with_namespace, :from => "project_ids"
select "Reporter", :from => "greatest_project_access"
end
click_button "Add"
end
Then 'I should see my own project in team projects list' do
projects = find(".projects-table")
projects.should have_content(@project.name)
end
When 'I click link "New Team Member"' do
click_link "New Team Member"
end
protected
def current_team
@user_team ||= UserTeam.first
end
def project
current_team.projects.first
end
def assigned_to_user key, user
project.send(key).where(assignee_id: user)
end
def find_in_list(selector, item)
members_list = all(selector)
entered = false
members_list.each do |member_item|
entered = true if member_item.has_content?(item.name)
end
entered
end
end
...@@ -21,7 +21,6 @@ Dir["#{Rails.root}/features/steps/shared/*.rb"].each {|file| require file} ...@@ -21,7 +21,6 @@ Dir["#{Rails.root}/features/steps/shared/*.rb"].each {|file| require file}
include GitoliteStub include GitoliteStub
WebMock.allow_net_connect! WebMock.allow_net_connect!
# #
# JS driver # JS driver
# #
......
Feature: UserTeams
Background:
Given I sign in as a user
And I own project "Shop"
And project "Shop" has push event
Scenario: No teams, no dashboard info block
When I do not have teams with me
And I visit dashboard page
Then I should see dashboard page without teams info block
Scenario: I should see teams info block
When I have teams with my membership
And I visit dashboard page
Then I should see dashboard page with teams information block
Scenario: I should can create new team
When I have teams with my membership
And I visit dashboard page
When I click to "New team" link
And I submit form with new team info
Then I should be redirected to new team page
Scenario: I should see team dashboard list
When I have teams with projects and members
When I visit team page
Then I should see projects list
Scenario: I should see team issues list
Given I have team with projects and members
And project from team has issues assigned to me
When I visit team issues page
Then I should see issues from this team assigned to me
Scenario: I should see teams members issues list
Given I have team with projects and members
Given project from team has issues assigned to teams members
When I visit team issues page
Then I should see issues from this team assigned to teams members
Scenario: I should see team merge requests list
Given I have team with projects and members
Given project from team has merge requests assigned to me
When I visit team merge requests page
Then I should see merge requests from this team assigned to me
Scenario: I should see teams members merge requests list
Given I have team with projects and members
Given project from team has merge requests assigned to team members
When I visit team merge requests page
Then I should see merge requests from this team assigned to me
Scenario: I should add user to projects in Team
Given I have team with projects and members
Given I have new user "John"
When I visit team people page
When I click link "New Team Member"
And I select user "John" from list with role "Reporter"
Then I should see user "John" in team list
Scenario: I should assign my team to my own project
Given I have team with projects and members
And I have my own project without teams
And I visit my team page
When I click on link "Projects"
And I click link "Assign project to Team"
Then I should see form with my own project in avaliable projects list
When I submit form with selected project and max access
Then I should see my own project in team projects list
...@@ -88,7 +88,10 @@ module Gitlab ...@@ -88,7 +88,10 @@ module Gitlab
end end
def destroy_project(project) def destroy_project(project)
FileUtils.rm_rf(project.repository.path_to_repo) # do rm-rf only if repository exists
if project.repository
FileUtils.rm_rf(project.repository.path_to_repo)
end
conf.rm_repo(project.path_with_namespace) conf.rm_repo(project.path_with_namespace)
end end
......
# UserTeamManager class
#
# Used for manage User teams with project repositories
module Gitlab
class UserTeamManager
class << self
def assign(team, project, access)
project = Project.find(project) unless project.is_a? Project
searched_project = team.user_team_project_relationships.find_by_project_id(project.id)
unless searched_project.present?
team.user_team_project_relationships.create(project_id: project.id, greatest_access: access)
update_team_users_access_in_project(team, project)
end
end
def resign(team, project)
project = Project.find(project) unless project.is_a? Project
team.user_team_project_relationships.with_project(project).destroy_all
update_team_users_access_in_project(team, project)
end
def update_team_user_membership(team, member, options)
updates = {}
if options[:default_projects_access] && options[:default_projects_access] != team.default_projects_access(member)
updates[:permission] = options[:default_projects_access]
end
if options[:group_admin].to_s != team.admin?(member).to_s
updates[:group_admin] = options[:group_admin].present?
end
unless updates.blank?
user_team_relationship = team.user_team_user_relationships.find_by_user_id(member)
if user_team_relationship.update_attributes(updates)
if updates[:permission]
rebuild_project_permissions_to_member(team, member)
end
true
else
false
end
else
true
end
end
def update_project_greates_access(team, project, permission)
project_relation = team.user_team_project_relationships.find_by_project_id(project)
if permission != team.max_project_access(project)
if project_relation.update_attributes(greatest_access: permission)
update_team_users_access_in_project(team, project)
true
else
false
end
else
true
end
end
def rebuild_project_permissions_to_member(team, member)
team.projects.each do |project|
update_team_user_access_in_project(team, member, project)
end
end
def update_team_users_access_in_project(team, project)
members = team.members
members.each do |member|
update_team_user_access_in_project(team, member, project)
end
end
def update_team_user_access_in_project(team, user, project)
granted_access = max_teams_member_permission_in_project(user, project)
project_team_user = UsersProject.find_by_user_id_and_project_id(user.id, project.id)
project_team_user.destroy if project_team_user.present?
# project_team_user.project_access != granted_access
project.team << [user, granted_access] if granted_access > 0
end
def max_teams_member_permission_in_project(user, project, teams = nil)
result_access = 0
user_teams = project.user_teams.with_member(user)
teams ||= user_teams
if teams.any?
teams.each do |team|
granted_access = max_team_member_permission_in_project(team, user, project)
result_access = [granted_access, result_access].max
end
end
result_access
end
def max_team_member_permission_in_project(team, user, project)
member_access = team.default_projects_access(user)
team_access = team.user_team_project_relationships.find_by_project_id(project.id).greatest_access
[team_access, member_access].min
end
def add_member_into_team(team, user, access, admin)
user = User.find(user) unless user.is_a? User
team.user_team_user_relationships.create(user_id: user.id, permission: access, group_admin: admin)
team.projects.each do |project|
update_team_user_access_in_project(team, user, project)
end
end
def remove_member_from_team(team, user)
user = User.find(user) unless user.is_a? User
team.user_team_user_relationships.with_user(user).destroy_all
other_teams = []
team.projects.each do |project|
other_teams << project.user_teams.with_member(user)
end
other_teams.uniq
unless other_teams.any?
UsersProject.in_projects(team.projects).with_user(user).destroy_all
end
end
end
end
end
...@@ -169,7 +169,7 @@ namespace :gitlab do ...@@ -169,7 +169,7 @@ namespace :gitlab do
else else
puts "no".red puts "no".red
try_fixing_it( try_fixing_it(
sudo_gitlab("bundle exec rake db:migrate") sudo_gitlab("bundle exec rake db:migrate RAILS_ENV=production")
) )
fix_and_rerun fix_and_rerun
end end
...@@ -194,7 +194,7 @@ namespace :gitlab do ...@@ -194,7 +194,7 @@ namespace :gitlab do
else else
puts "no".red puts "no".red
try_fixing_it( try_fixing_it(
sudo_gitlab("bundle exec rake gitlab:satellites:create"), sudo_gitlab("bundle exec rake gitlab:satellites:create RAILS_ENV=production"),
"If necessary, remove the tmp/repo_satellites directory ...", "If necessary, remove the tmp/repo_satellites directory ...",
"... and rerun the above command" "... and rerun the above command"
) )
...@@ -789,7 +789,7 @@ namespace :gitlab do ...@@ -789,7 +789,7 @@ namespace :gitlab do
else else
puts "wrong or missing".red puts "wrong or missing".red
try_fixing_it( try_fixing_it(
sudo_gitlab("bundle exec rake gitlab:gitolite:update_repos") sudo_gitlab("bundle exec rake gitlab:gitolite:update_repos RAILS_ENV=production")
) )
for_more_information( for_more_information(
"doc/raketasks/maintenance.md" "doc/raketasks/maintenance.md"
...@@ -895,7 +895,7 @@ namespace :gitlab do ...@@ -895,7 +895,7 @@ namespace :gitlab do
else else
puts "no".red puts "no".red
try_fixing_it( try_fixing_it(
sudo_gitlab("bundle exec rake sidekiq:start") sudo_gitlab("bundle exec rake sidekiq:start RAILS_ENV=production")
) )
for_more_information( for_more_information(
see_installation_guide_section("Install Init Script"), see_installation_guide_section("Install Init Script"),
......
...@@ -6,7 +6,7 @@ namespace :sidekiq do ...@@ -6,7 +6,7 @@ namespace :sidekiq do
desc "GITLAB | Start sidekiq" desc "GITLAB | Start sidekiq"
task :start do task :start do
run "nohup bundle exec sidekiq -q post_receive,mailer,system_hook,common,default -e #{Rails.env} -P #{pidfile} >> #{Rails.root.join("log", "sidekiq.log")} 2>&1 &" run "nohup bundle exec sidekiq -q post_receive,mailer,system_hook,project_web_hook,common,default -e #{Rails.env} -P #{pidfile} >> #{Rails.root.join("log", "sidekiq.log")} 2>&1 &"
end end
def pidfile def pidfile
......
<!DOCTYPE html>
<html>
<head>
<title>Deploy in progress. Please try again in few minutes</title>
<link href="/static.css" media="screen" rel="stylesheet" type="text/css" />
</head>
<body>
<h1>Deploy in progress</h1>
<h3>Please try again in few minutes or contact your administrator.</h3>
</body>
</html>
# Read about factories at https://github.com/thoughtbot/factory_girl
FactoryGirl.define do
factory :user_team_project_relationship do
project
user_team
greatest_access { UsersProject::MASTER }
end
end
# Read about factories at https://github.com/thoughtbot/factory_girl
FactoryGirl.define do
factory :user_team_user_relationship do
user
user_team
group_admin false
permission { UsersProject::MASTER }
end
end
# Read about factories at https://github.com/thoughtbot/factory_girl
FactoryGirl.define do
factory :user_team do
sequence(:name) { |n| "team#{n}" }
path { name.downcase.gsub(/\s/, '_') }
owner
end
end
...@@ -38,11 +38,14 @@ describe Project, "Hooks" do ...@@ -38,11 +38,14 @@ describe Project, "Hooks" do
@project_hook = create(:project_hook) @project_hook = create(:project_hook)
@project_hook_2 = create(:project_hook) @project_hook_2 = create(:project_hook)
project.hooks << [@project_hook, @project_hook_2] project.hooks << [@project_hook, @project_hook_2]
stub_request(:post, @project_hook.url)
stub_request(:post, @project_hook_2.url)
end end
it "executes multiple web hook" do it "executes multiple web hook" do
@project_hook.should_receive(:execute).once @project_hook.should_receive(:async_execute).once
@project_hook_2.should_receive(:execute).once @project_hook_2.should_receive(:async_execute).once
project.trigger_post_receive('oldrev', 'newrev', 'refs/heads/master', @user) project.trigger_post_receive('oldrev', 'newrev', 'refs/heads/master', @user)
end end
......
require "spec_helper" require "spec_helper"
describe Team do describe ProjectTeam do
let(:team) { create(:project).team } let(:team) { create(:project).team }
describe "Respond to" do describe "Respond to" do
......
...@@ -189,7 +189,7 @@ describe User do ...@@ -189,7 +189,7 @@ describe User do
it { user.is_admin?.should be_false } it { user.is_admin?.should be_false }
it { user.require_ssh_key?.should be_true } it { user.require_ssh_key?.should be_true }
it { user.can_create_group?.should be_false } it { user.can_create_group?.should be_true }
it { user.can_create_project?.should be_true } it { user.can_create_project?.should be_true }
it { user.first_name.should == 'John' } it { user.first_name.should == 'John' }
end end
......
require 'spec_helper'
describe UserTeamProjectRelationship do
pending "add some examples to (or delete) #{__FILE__}"
end
require 'spec_helper'
describe UserTeam do
pending "add some examples to (or delete) #{__FILE__}"
end
require 'spec_helper'
describe UserTeamUserRelationship do
pending "add some examples to (or delete) #{__FILE__}"
end
...@@ -10,7 +10,7 @@ describe "Dashboard Issues Feed" do ...@@ -10,7 +10,7 @@ describe "Dashboard Issues Feed" do
describe "atom feed" do describe "atom feed" do
it "should render atom feed via private token" do it "should render atom feed via private token" do
visit dashboard_issues_path(:atom, private_token: user.private_token) visit issues_dashboard_path(:atom, private_token: user.private_token)
page.response_headers['Content-Type'].should have_content("application/atom+xml") page.response_headers['Content-Type'].should have_content("application/atom+xml")
page.body.should have_selector("title", text: "#{user.name} issues") page.body.should have_selector("title", text: "#{user.name} issues")
......
...@@ -95,20 +95,20 @@ describe Admin::ProjectsController, "routing" do ...@@ -95,20 +95,20 @@ describe Admin::ProjectsController, "routing" do
end end
end end
# edit_admin_team_member GET /admin/team_members/:id/edit(.:format) admin/team_members#edit # edit_admin_project_member GET /admin/projects/:project_id/members/:id/edit(.:format) admin/projects/members#edit {:id=>/[^\/]+/, :project_id=>/[^\/]+/}
# admin_team_member PUT /admin/team_members/:id(.:format) admin/team_members#update # admin_project_member PUT /admin/projects/:project_id/members/:id(.:format) admin/projects/members#update {:id=>/[^\/]+/, :project_id=>/[^\/]+/}
# DELETE /admin/team_members/:id(.:format) admin/team_members#destroy # DELETE /admin/projects/:project_id/members/:id(.:format) admin/projects/members#destroy {:id=>/[^\/]+/, :project_id=>/[^\/]+/}
describe Admin::TeamMembersController, "routing" do describe Admin::Projects::MembersController, "routing" do
it "to #edit" do it "to #edit" do
get("/admin/team_members/1/edit").should route_to('admin/team_members#edit', id: '1') get("/admin/projects/test/members/1/edit").should route_to('admin/projects/members#edit', project_id: 'test', id: '1')
end end
it "to #update" do it "to #update" do
put("/admin/team_members/1").should route_to('admin/team_members#update', id: '1') put("/admin/projects/test/members/1").should route_to('admin/projects/members#update', project_id: 'test', id: '1')
end end
it "to #destroy" do it "to #destroy" do
delete("/admin/team_members/1").should route_to('admin/team_members#destroy', id: '1') delete("/admin/projects/test/members/1").should route_to('admin/projects/members#destroy', project_id: 'test', id: '1')
end end
end end
......
...@@ -146,14 +146,14 @@ describe KeysController, "routing" do ...@@ -146,14 +146,14 @@ describe KeysController, "routing" do
end end
end end
# dashboard GET /dashboard(.:format) dashboard#index # dashboard GET /dashboard(.:format) dashboard#show
# dashboard_issues GET /dashboard/issues(.:format) dashboard#issues # dashboard_issues GET /dashboard/issues(.:format) dashboard#issues
# dashboard_merge_requests GET /dashboard/merge_requests(.:format) dashboard#merge_requests # dashboard_merge_requests GET /dashboard/merge_requests(.:format) dashboard#merge_requests
# root / dashboard#index # root / dashboard#show
describe DashboardController, "routing" do describe DashboardController, "routing" do
it "to #index" do it "to #index" do
get("/dashboard").should route_to('dashboard#index') get("/dashboard").should route_to('dashboard#show')
get("/").should route_to('dashboard#index') get("/").should route_to('dashboard#show')
end end
it "to #issues" do it "to #issues" do
......
...@@ -65,15 +65,15 @@ ...@@ -65,15 +65,15 @@
BranchGraph.prototype.buildGraph = function(){ BranchGraph.prototype.buildGraph = function(){
var graphWidth = $(this.element).width() var graphWidth = $(this.element).width()
, ch = this.mspace * 20 + 20 , ch = this.mspace * 20 + 100
, cw = Math.max(graphWidth, this.mtime * 20 + 20) , cw = Math.max(graphWidth, this.mtime * 20 + 260)
, r = Raphael(this.element.get(0), cw, ch) , r = Raphael(this.element.get(0), cw, ch)
, top = r.set() , top = r.set()
, cuday = 0 , cuday = 0
, cumonth = "" , cumonth = ""
, offsetX = 20 , offsetX = 20
, offsetY = 60 , offsetY = 60
, barWidth = Math.max(graphWidth, this.dayCount * 20 + 80); , barWidth = Math.max(graphWidth, this.dayCount * 20 + 320);
this.raphael = r; this.raphael = r;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment