Commit 66ebf8d83f894ed361031eb9ede5a8b829fefd36

Authored by Lennart Rosam
2 parents dc13af90 bd948549

Merge remote-tracking branch 'github/master'

Showing 190 changed files with 4007 additions and 974 deletions   Show diff stats
1 web: bundle exec unicorn_rails -p $PORT 1 web: bundle exec unicorn_rails -p $PORT
2 -worker: bundle exec sidekiq -q post_receive,mailer,system_hook,common,default 2 +worker: bundle exec sidekiq -q post_receive,mailer,system_hook,project_web_hook,common,default
app/assets/images/onion_skin_sprites.gif 0 → 100644

1.55 KB

app/assets/images/swipemode_sprites.gif 0 → 100644

1.5 KB

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