Commit 66ebf8d83f894ed361031eb9ede5a8b829fefd36
Exists in
master
and in
4 other branches
Merge remote-tracking branch 'github/master'
Showing
190 changed files
with
4007 additions
and
974 deletions
Show diff stats
Procfile
1.55 KB
1.5 KB
... | ... | @@ -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 | 129 | \ No newline at end of file | ... | ... |
app/assets/javascripts/commits.js
... | ... | @@ -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 | -} |
... | ... | @@ -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 | 55 | \ No newline at end of file | ... | ... |
app/assets/javascripts/dashboard.js.coffee
... | ... | @@ -4,11 +4,11 @@ window.dashboardPage = -> |
4 | 4 | event.preventDefault() |
5 | 5 | toggleFilter $(this) |
6 | 6 | reloadActivities() |
7 | - | |
7 | + | |
8 | 8 | reloadActivities = -> |
9 | 9 | $(".content_list").html '' |
10 | 10 | Pager.init 20, true |
11 | - | |
11 | + | |
12 | 12 | toggleFilter = (sender) -> |
13 | 13 | sender.parent().toggleClass "inactive" |
14 | 14 | event_filters = $.cookie("event_filter") |
... | ... | @@ -17,11 +17,11 @@ toggleFilter = (sender) -> |
17 | 17 | event_filters = event_filters.split(",") |
18 | 18 | else |
19 | 19 | event_filters = new Array() |
20 | - | |
20 | + | |
21 | 21 | index = event_filters.indexOf(filter) |
22 | 22 | if index is -1 |
23 | 23 | event_filters.push filter |
24 | 24 | else |
25 | 25 | event_filters.splice index, 1 |
26 | - | |
26 | + | |
27 | 27 | $.cookie "event_filter", event_filters.join(",") | ... | ... |
app/assets/javascripts/merge_requests.js.coffee
1 | 1 | # |
2 | 2 | # * Filter merge requests |
3 | -# | |
3 | +# | |
4 | 4 | @merge_requestsPage = -> |
5 | 5 | $('#assignee_id').chosen() |
6 | 6 | $('#milestone_id').chosen() |
... | ... | @@ -8,16 +8,16 @@ |
8 | 8 | $(this).closest('form').submit() |
9 | 9 | |
10 | 10 | class MergeRequest |
11 | - | |
11 | + | |
12 | 12 | constructor: (@opts) -> |
13 | 13 | this.$el = $('.merge-request') |
14 | 14 | @diffs_loaded = false |
15 | 15 | @commits_loaded = false |
16 | - | |
16 | + | |
17 | 17 | this.activateTab(@opts.action) |
18 | - | |
18 | + | |
19 | 19 | this.bindEvents() |
20 | - | |
20 | + | |
21 | 21 | this.initMergeWidget() |
22 | 22 | this.$('.show-all-commits').on 'click', => |
23 | 23 | this.showAllCommits() |
... | ... | @@ -28,7 +28,7 @@ class MergeRequest |
28 | 28 | |
29 | 29 | initMergeWidget: -> |
30 | 30 | this.showState( @opts.current_state ) |
31 | - | |
31 | + | |
32 | 32 | if this.$('.automerge_widget').length and @opts.check_enable |
33 | 33 | $.get @opts.url_to_automerge_check, (data) => |
34 | 34 | this.showState( data.state ) |
... | ... | @@ -42,12 +42,12 @@ class MergeRequest |
42 | 42 | bindEvents: -> |
43 | 43 | this.$('.nav-tabs').on 'click', 'a', (event) => |
44 | 44 | a = $(event.currentTarget) |
45 | - | |
45 | + | |
46 | 46 | href = a.attr('href') |
47 | 47 | History.replaceState {path: href}, document.title, href |
48 | - | |
48 | + | |
49 | 49 | event.preventDefault() |
50 | - | |
50 | + | |
51 | 51 | this.$('.nav-tabs').on 'click', 'li', (event) => |
52 | 52 | this.activateTab($(event.currentTarget).data('action')) |
53 | 53 | ... | ... |
app/assets/javascripts/pager.js
... | ... | @@ -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 | -} |
... | ... | @@ -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 | 1 | html { |
2 | - overflow-y: scroll; | |
2 | + overflow-y: scroll; | |
3 | 3 | } |
4 | 4 | |
5 | 5 | /** LAYOUT **/ |
... | ... | @@ -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 | 292 | .highlight_word { |
281 | - background: #EEDC94; | |
293 | + border-bottom: 2px solid #F90; | |
282 | 294 | } |
283 | 295 | |
284 | 296 | .status_info { |
... | ... | @@ -326,7 +338,7 @@ li.note { |
326 | 338 | li { |
327 | 339 | border-bottom:none !important; |
328 | 340 | } |
329 | - .file { | |
341 | + .attachment { | |
330 | 342 | padding-left: 20px; |
331 | 343 | background:url("icon-attachment.png") no-repeat left center; |
332 | 344 | } | ... | ... |
app/assets/stylesheets/gitlab_bootstrap/files.scss
... | ... | @@ -135,7 +135,7 @@ |
135 | 135 | pre { |
136 | 136 | border: none; |
137 | 137 | border-radius: 0; |
138 | - font-family: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono','lucida console',monospace; | |
138 | + font-family: $monospace_font; | |
139 | 139 | font-size: 12px !important; |
140 | 140 | line-height: 16px !important; |
141 | 141 | margin: 0; | ... | ... |
app/assets/stylesheets/gitlab_bootstrap/fonts.scss
app/assets/stylesheets/gitlab_bootstrap/typography.scss
... | ... | @@ -21,7 +21,7 @@ h6 { |
21 | 21 | |
22 | 22 | /** CODE **/ |
23 | 23 | pre { |
24 | - font-family:'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono','lucida console',monospace; | |
24 | + font-family: $monospace_font; | |
25 | 25 | |
26 | 26 | &.dark { |
27 | 27 | background: #333; |
... | ... | @@ -79,7 +79,7 @@ a:focus { |
79 | 79 | } |
80 | 80 | |
81 | 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
app/assets/stylesheets/sections/commits.scss
1 | 1 | /** |
2 | - * | |
3 | - * COMMIT SHOw | |
4 | - * | |
2 | + * Commit file | |
5 | 3 | */ |
6 | 4 | .commit-committer-link, |
7 | 5 | .commit-author-link { |
... | ... | @@ -12,11 +10,11 @@ |
12 | 10 | } |
13 | 11 | } |
14 | 12 | |
15 | -.diff_file { | |
13 | +.file { | |
16 | 14 | border: 1px solid #CCC; |
17 | 15 | margin-bottom: 1em; |
18 | 16 | |
19 | - .diff_file_header { | |
17 | + .header { | |
20 | 18 | @extend .clearfix; |
21 | 19 | padding: 5px 5px 5px 10px; |
22 | 20 | color: #555; |
... | ... | @@ -28,32 +26,35 @@ |
28 | 26 | background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); |
29 | 27 | background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); |
30 | 28 | |
29 | + a{ | |
30 | + color: $style_color; | |
31 | + } | |
32 | + | |
31 | 33 | > span { |
32 | - font-family: $monospace; | |
34 | + font-family: $monospace_font; | |
33 | 35 | font-size: 14px; |
34 | 36 | line-height: 30px; |
35 | 37 | } |
36 | 38 | |
37 | - a.view-commit{ | |
39 | + a.view-file{ | |
38 | 40 | font-weight: bold; |
39 | 41 | } |
40 | 42 | |
41 | 43 | .commit-short-id{ |
42 | - font-family: $monospace; | |
44 | + font-family: $monospace_font; | |
43 | 45 | font-size: smaller; |
44 | 46 | } |
45 | 47 | |
46 | 48 | .file-mode{ |
47 | - font-family: $monospace; | |
49 | + font-family: $monospace_font; | |
48 | 50 | } |
49 | 51 | } |
50 | - .diff_file_content { | |
52 | + .content { | |
51 | 53 | overflow: auto; |
52 | 54 | overflow-y: hidden; |
53 | - background: #fff; | |
55 | + background: #FFF; | |
54 | 56 | color: #333; |
55 | 57 | font-size: 12px; |
56 | - font-family: $monospace; | |
57 | 58 | .old{ |
58 | 59 | span.idiff{ |
59 | 60 | background-color: #FAA; |
... | ... | @@ -66,114 +67,274 @@ |
66 | 67 | } |
67 | 68 | |
68 | 69 | table { |
70 | + font-family: $monospace_font; | |
71 | + border: none; | |
72 | + margin: 0px; | |
73 | + padding: 0px; | |
69 | 74 | td { |
70 | 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 | 122 | text-align: center; |
77 | - .image { | |
123 | + padding: 30px; | |
124 | + .wrap{ | |
78 | 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 | 132 | img{ |
133 | + border: 1px solid #FFF; | |
83 | 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 | 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 | 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 | 338 | .commit { |
178 | 339 | .browse_code_link_holder { |
179 | 340 | @extend .span2; |
... | ... | @@ -199,11 +360,10 @@ |
199 | 360 | float: left; |
200 | 361 | @extend .lined; |
201 | 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 | 367 | .file-stats a { |
208 | 368 | color: $style_color; |
209 | 369 | } |
... | ... | @@ -237,7 +397,7 @@ |
237 | 397 | font-size: 13px; |
238 | 398 | background: #474D57; |
239 | 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 | 77 | font-size: 14px; |
78 | 78 | background: #474D57; |
79 | 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 | 83 | .mr_source_commit, | ... | ... |
app/assets/stylesheets/sections/notes.scss
... | ... | @@ -40,13 +40,13 @@ ul.notes { |
40 | 40 | .discussion-body { |
41 | 41 | margin-left: 50px; |
42 | 42 | |
43 | - .diff_file, | |
43 | + .file, | |
44 | 44 | .discussion-hidden, |
45 | 45 | .notes { |
46 | 46 | @extend .borders; |
47 | 47 | background-color: #F9F9F9; |
48 | 48 | } |
49 | - .diff_file .notes { | |
49 | + .file .notes { | |
50 | 50 | /* reset */ |
51 | 51 | background: inherit; |
52 | 52 | border: none; |
... | ... | @@ -109,7 +109,7 @@ ul.notes { |
109 | 109 | } |
110 | 110 | } |
111 | 111 | |
112 | -.diff_file .notes_holder { | |
112 | +.file .notes_holder { | |
113 | 113 | font-family: $sansFontFamily; |
114 | 114 | font-size: 13px; |
115 | 115 | line-height: 18px; |
... | ... | @@ -134,8 +134,6 @@ ul.notes { |
134 | 134 | } |
135 | 135 | } |
136 | 136 | |
137 | - | |
138 | - | |
139 | 137 | /** |
140 | 138 | * Actions for Discussions/Notes |
141 | 139 | */ |
... | ... | @@ -171,7 +169,7 @@ ul.notes { |
171 | 169 | } |
172 | 170 | } |
173 | 171 | } |
174 | -.diff_file .note .note-actions { | |
172 | +.file .note .note-actions { | |
175 | 173 | right: 0; |
176 | 174 | top: 0; |
177 | 175 | } |
... | ... | @@ -182,7 +180,7 @@ ul.notes { |
182 | 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 | 184 | .add-diff-note { |
187 | 185 | background: url("diff_note_add.png") no-repeat left 0; |
188 | 186 | height: 22px; |
... | ... | @@ -212,8 +210,6 @@ ul.notes { |
212 | 210 | } |
213 | 211 | } |
214 | 212 | |
215 | - | |
216 | - | |
217 | 213 | /** |
218 | 214 | * Note Form |
219 | 215 | */ |
... | ... | @@ -222,7 +218,12 @@ ul.notes { |
222 | 218 | .reply-btn { |
223 | 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 | 227 | .discussion { |
227 | 228 | .new_note { |
228 | 229 | margin: 8px 5px 8px 0; | ... | ... |
app/assets/stylesheets/sections/projects.scss
... | ... | @@ -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
app/controllers/admin/groups_controller.rb
app/controllers/admin/hooks_controller.rb
app/controllers/admin/logs_controller.rb
app/controllers/admin/projects/application_controller.rb
0 → 100644
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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 | 2 | before_filter :project, only: [:edit, :show, :update, :destroy, :team_update] |
3 | 3 | |
4 | 4 | def index |
... | ... | @@ -29,7 +29,9 @@ class Admin::ProjectsController < AdminController |
29 | 29 | end |
30 | 30 | |
31 | 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 | 36 | if status |
35 | 37 | redirect_to [:admin, @project], notice: 'Project was successfully updated.' | ... | ... |
app/controllers/admin/resque_controller.rb
app/controllers/admin/team_members_controller.rb
... | ... | @@ -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 |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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 | 4 | def index |
3 | 5 | @admin_users = User.scoped |
4 | 6 | @admin_users = @admin_users.filter(params[:filter]) |
... | ... | @@ -7,25 +9,18 @@ class Admin::UsersController < AdminController |
7 | 9 | end |
8 | 10 | |
9 | 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 | 14 | end |
18 | 15 | |
19 | 16 | def team_update |
20 | - @admin_user = User.find(params[:id]) | |
21 | - | |
22 | 17 | UsersProject.add_users_into_projects( |
23 | 18 | params[:project_ids], |
24 | - [@admin_user.id], | |
19 | + [admin_user.id], | |
25 | 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 | 24 | end |
30 | 25 | |
31 | 26 | |
... | ... | @@ -34,13 +29,11 @@ class Admin::UsersController < AdminController |
34 | 29 | end |
35 | 30 | |
36 | 31 | def edit |
37 | - @admin_user = User.find(params[:id]) | |
32 | + admin_user | |
38 | 33 | end |
39 | 34 | |
40 | 35 | def block |
41 | - @admin_user = User.find(params[:id]) | |
42 | - | |
43 | - if @admin_user.block | |
36 | + if admin_user.block | |
44 | 37 | redirect_to :back, alert: "Successfully blocked" |
45 | 38 | else |
46 | 39 | redirect_to :back, alert: "Error occured. User was not blocked" |
... | ... | @@ -48,9 +41,7 @@ class Admin::UsersController < AdminController |
48 | 41 | end |
49 | 42 | |
50 | 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 | 45 | redirect_to :back, alert: "Successfully unblocked" |
55 | 46 | else |
56 | 47 | redirect_to :back, alert: "Error occured. User was not unblocked" |
... | ... | @@ -82,30 +73,34 @@ class Admin::UsersController < AdminController |
82 | 73 | params[:user].delete(:password_confirmation) |
83 | 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 | 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 | 81 | format.json { head :ok } |
92 | 82 | else |
93 | 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 | 85 | end |
96 | 86 | end |
97 | 87 | end |
98 | 88 | |
99 | 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 | 91 | redirect_to admin_users_path, alert: "User is a project owner and can't be removed." and return |
103 | 92 | end |
104 | - @admin_user.destroy | |
93 | + admin_user.destroy | |
105 | 94 | |
106 | 95 | respond_to do |format| |
107 | - format.html { redirect_to admin_users_url } | |
96 | + format.html { redirect_to admin_users_path } | |
108 | 97 | format.json { head :ok } |
109 | 98 | end |
110 | 99 | end |
100 | + | |
101 | + protected | |
102 | + | |
103 | + def admin_user | |
104 | + @admin_user ||= User.find_by_username!(params[:id]) | |
105 | + end | |
111 | 106 | end | ... | ... |
app/controllers/admin_controller.rb
... | ... | @@ -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 < ActionController::Base |
94 | 94 | return access_denied! unless can?(current_user, :download_code, project) |
95 | 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 | 109 | def access_denied! |
98 | 110 | render "errors/access_denied", layout: "errors", status: 404 |
99 | 111 | end |
... | ... | @@ -135,4 +147,5 @@ class ApplicationController < ActionController::Base |
135 | 147 | def dev_tools |
136 | 148 | Rack::MiniProfiler.authorize_request |
137 | 149 | end |
150 | + | |
138 | 151 | end | ... | ... |
app/controllers/dashboard_controller.rb
1 | 1 | class DashboardController < ApplicationController |
2 | 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 | 8 | @groups = current_user.authorized_groups |
9 | - | |
10 | 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 | 14 | @events = Event.in_projects(current_user.authorized_projects.pluck(:id)) |
24 | 15 | @events = @event_filter.apply_filter(@events) |
... | ... | @@ -33,6 +24,19 @@ class DashboardController < ApplicationController |
33 | 24 | end |
34 | 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 | 40 | # Get authored or assigned open merge requests |
37 | 41 | def merge_requests |
38 | 42 | @merge_requests = current_user.cared_merge_requests |
... | ... | @@ -55,7 +59,7 @@ class DashboardController < ApplicationController |
55 | 59 | |
56 | 60 | protected |
57 | 61 | |
58 | - def projects | |
62 | + def load_projects | |
59 | 63 | @projects = current_user.authorized_projects.sorted_by_activity |
60 | 64 | end |
61 | 65 | ... | ... |
app/controllers/groups_controller.rb
1 | 1 | class GroupsController < ApplicationController |
2 | 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 | 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 | 30 | def show |
12 | 31 | @events = Event.in_projects(project_ids).limit(20).offset(params[:offset] || 0) |
... | ... | @@ -85,4 +104,8 @@ class GroupsController < ApplicationController |
85 | 104 | return render_404 |
86 | 105 | end |
87 | 106 | end |
107 | + | |
108 | + def authorize_create_group! | |
109 | + can?(current_user, :create_group, nil) | |
110 | + end | |
88 | 111 | end | ... | ... |
... | ... | @@ -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 < ProjectResourceController |
19 | 19 | end |
20 | 20 | |
21 | 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 | 24 | respond_to do |format| |
25 | 25 | flash[:notice] = 'Project was successfully created.' if @project.saved? |
... | ... | @@ -35,7 +35,7 @@ class ProjectsController < ProjectResourceController |
35 | 35 | end |
36 | 36 | |
37 | 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 | 40 | respond_to do |format| |
41 | 41 | if status | ... | ... |
app/controllers/search_controller.rb
1 | 1 | class SearchController < ApplicationController |
2 | 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 | 17 | @projects = result[:projects] |
6 | 18 | @merge_requests = result[:merge_requests] | ... | ... |
app/controllers/team_members_controller.rb
... | ... | @@ -4,15 +4,16 @@ class TeamMembersController < ProjectResourceController |
4 | 4 | before_filter :authorize_admin_project!, except: [:index, :show] |
5 | 5 | |
6 | 6 | def index |
7 | + @teams = UserTeam.scoped | |
7 | 8 | end |
8 | 9 | |
9 | 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 | 13 | end |
13 | 14 | |
14 | 15 | def new |
15 | - @team_member = project.users_projects.new | |
16 | + @user_project_relation = project.users_projects.new | |
16 | 17 | end |
17 | 18 | |
18 | 19 | def create |
... | ... | @@ -28,18 +29,18 @@ class TeamMembersController < ProjectResourceController |
28 | 29 | end |
29 | 30 | |
30 | 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 | 36 | flash[:alert] = "User should have at least one role" |
36 | 37 | end |
37 | 38 | redirect_to project_team_index_path(@project) |
38 | 39 | end |
39 | 40 | |
40 | 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 | 45 | respond_to do |format| |
45 | 46 | format.html { redirect_to project_team_index_path(@project) } |
... | ... | @@ -54,4 +55,10 @@ class TeamMembersController < ProjectResourceController |
54 | 55 | |
55 | 56 | redirect_to project_team_members_path(project), notice: notice |
56 | 57 | end |
58 | + | |
59 | + protected | |
60 | + | |
61 | + def member | |
62 | + @member ||= User.find_by_username(params[:id]) | |
63 | + end | |
57 | 64 | end | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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
app/helpers/application_helper.rb
... | ... | @@ -72,8 +72,9 @@ module ApplicationHelper |
72 | 72 | end |
73 | 73 | |
74 | 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 | 79 | default_nav = [ |
79 | 80 | { label: "My Profile", url: profile_path }, |
... | ... | @@ -83,29 +84,29 @@ module ApplicationHelper |
83 | 84 | ] |
84 | 85 | |
85 | 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 | 98 | project_nav = [] |
98 | 99 | if @project && @project.repository && @project.repository.root_ref |
99 | 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 | 111 | end |
111 | 112 | ... | ... |
app/helpers/commits_helper.rb
app/helpers/dashboard_helper.rb
... | ... | @@ -9,9 +9,9 @@ module DashboardHelper |
9 | 9 | |
10 | 10 | case entity |
11 | 11 | when 'issue' then |
12 | - dashboard_issues_path(options) | |
12 | + issues_dashboard_path(options) | |
13 | 13 | when 'merge_request' |
14 | - dashboard_merge_requests_path(options) | |
14 | + merge_requests_dashboard_path(options) | |
15 | 15 | end |
16 | 16 | end |
17 | 17 | ... | ... |
app/helpers/projects_helper.rb
... | ... | @@ -3,8 +3,12 @@ module ProjectsHelper |
3 | 3 | @project.users_projects.sort_by(&:project_access).reverse.group_by(&:project_access) |
4 | 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 | 12 | end |
9 | 13 | |
10 | 14 | def link_to_project project |
... | ... | @@ -51,7 +55,9 @@ module ProjectsHelper |
51 | 55 | |
52 | 56 | def project_title project |
53 | 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 | 61 | else |
56 | 62 | project.name |
57 | 63 | end | ... | ... |
app/helpers/tab_helper.rb
... | ... | @@ -39,7 +39,12 @@ module TabHelper |
39 | 39 | # Returns a list item element String |
40 | 40 | def nav_link(options = {}, &block) |
41 | 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 | 48 | else |
44 | 49 | c = options.delete(:controller) |
45 | 50 | a = options.delete(:action) | ... | ... |
... | ... | @@ -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 | 1 | class Ability |
2 | 2 | class << self |
3 | - def allowed(object, subject) | |
3 | + def allowed(user, subject) | |
4 | + return [] unless user.kind_of?(User) | |
5 | + | |
4 | 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 | 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 | 23 | end |
14 | 24 | |
15 | 25 | def project_abilities(user, project) |
... | ... | @@ -110,6 +120,22 @@ class Ability |
110 | 120 | rules.flatten |
111 | 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 | 139 | [:issue, :note, :snippet, :merge_request].each do |name| |
114 | 140 | define_method "#{name}_abilities" do |user, subject| |
115 | 141 | if subject.author == user | ... | ... |
app/models/concerns/issuable.rb
... | ... | @@ -22,6 +22,7 @@ module Issuable |
22 | 22 | scope :opened, where(closed: false) |
23 | 23 | scope :closed, where(closed: true) |
24 | 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 | 26 | scope :assigned, ->(u) { where(assignee_id: u.id)} |
26 | 27 | scope :recent, order("created_at DESC") |
27 | 28 | ... | ... |
app/models/project.rb
... | ... | @@ -33,28 +33,31 @@ class Project < ActiveRecord::Base |
33 | 33 | attr_accessor :error_code |
34 | 34 | |
35 | 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 | 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 | 40 | has_one :last_event, class_name: 'Event', order: 'events.created_at DESC', foreign_key: 'project_id' |
56 | 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 | 61 | delegate :name, to: :owner, allow_nil: true, prefix: true |
59 | 62 | |
60 | 63 | # Validations |
... | ... | @@ -77,6 +80,8 @@ class Project < ActiveRecord::Base |
77 | 80 | # Scopes |
78 | 81 | scope :without_user, ->(user) { where("id NOT IN (:ids)", ids: user.authorized_projects.map(&:id) ) } |
79 | 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 | 85 | scope :in_namespace, ->(namespace) { where(namespace_id: namespace.id) } |
81 | 86 | scope :sorted_by_activity, ->() { order("(SELECT max(events.created_at) FROM events WHERE events.project_id = projects.id) DESC") } |
82 | 87 | scope :personal, ->(user) { where(namespace_id: user.namespace_id) } |
... | ... | @@ -122,7 +127,7 @@ class Project < ActiveRecord::Base |
122 | 127 | end |
123 | 128 | |
124 | 129 | def team |
125 | - @team ||= Team.new(self) | |
130 | + @team ||= ProjectTeam.new(self) | |
126 | 131 | end |
127 | 132 | |
128 | 133 | def repository |
... | ... | @@ -335,7 +340,7 @@ class Project < ActiveRecord::Base |
335 | 340 | end |
336 | 341 | |
337 | 342 | def execute_hooks(data) |
338 | - hooks.each { |hook| hook.execute(data) } | |
343 | + hooks.each { |hook| hook.async_execute(data) } | |
339 | 344 | end |
340 | 345 | |
341 | 346 | def execute_services(data) |
... | ... | @@ -489,6 +494,11 @@ class Project < ActiveRecord::Base |
489 | 494 | http_url = [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('') |
490 | 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 | 502 | # Check if current branch name is marked as protected in the system |
493 | 503 | def protected_branch? branch_name |
494 | 504 | protected_branches.map(&:name).include?(branch_name) | ... | ... |
... | ... | @@ -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 | -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 < ActiveRecord::Base |
40 | 40 | attr_accessible :email, :password, :password_confirmation, :remember_me, :bio, :name, :username, |
41 | 41 | :skype, :linkedin, :twitter, :dark_scheme, :theme_id, :force_random_password, |
42 | 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 | 45 | attr_accessor :force_random_password |
46 | 46 | |
47 | 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 | 70 | validates :name, presence: true |
62 | 71 | validates :bio, length: { within: 0..255 } |
... | ... | @@ -80,6 +89,9 @@ class User < ActiveRecord::Base |
80 | 89 | scope :blocked, where(blocked: true) |
81 | 90 | scope :active, where(blocked: false) |
82 | 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 | 97 | # Class methods |
... | ... | @@ -131,6 +143,11 @@ class User < ActiveRecord::Base |
131 | 143 | # |
132 | 144 | # Instance methods |
133 | 145 | # |
146 | + | |
147 | + def to_param | |
148 | + username | |
149 | + end | |
150 | + | |
134 | 151 | def generate_password |
135 | 152 | if self.force_random_password |
136 | 153 | self.password = self.password_confirmation = Devise.friendly_token.first(8) |
... | ... | @@ -220,7 +237,7 @@ class User < ActiveRecord::Base |
220 | 237 | end |
221 | 238 | |
222 | 239 | def can_create_group? |
223 | - is_admin? | |
240 | + can?(:create_group, nil) | |
224 | 241 | end |
225 | 242 | |
226 | 243 | def abilities |
... | ... | @@ -283,4 +300,15 @@ class User < ActiveRecord::Base |
283 | 300 | def namespace_id |
284 | 301 | namespace.try :id |
285 | 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 | 314 | end | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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 < ActiveRecord::Base |
39 | 39 | scope :reporters, where(project_access: REPORTER) |
40 | 40 | scope :developers, where(project_access: DEVELOPER) |
41 | 41 | scope :masters, where(project_access: MASTER) |
42 | + | |
42 | 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 | 47 | class << self |
45 | 48 | ... | ... |
app/models/web_hook.rb
app/views/admin/groups/show.html.haml
... | ... | @@ -72,16 +72,17 @@ |
72 | 72 | %th Users |
73 | 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 | 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 | 82 | - next unless u_p |
82 | 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 | 86 | %tr |
86 | 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 | 88 | %td= select_tag :project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3"} | ... | ... |
... | ... | @@ -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/show.html.haml
... | ... | @@ -114,9 +114,9 @@ |
114 | 114 | %h5 |
115 | 115 | Team |
116 | 116 | %small |
117 | - (#{@project.users_projects.count}) | |
117 | + (#{@project.users.count}) | |
118 | 118 | %br |
119 | -%table.zebra-striped | |
119 | +%table.zebra-striped.team_members | |
120 | 120 | %thead |
121 | 121 | %tr |
122 | 122 | %th Name |
... | ... | @@ -124,13 +124,13 @@ |
124 | 124 | %th Repository Access |
125 | 125 | %th |
126 | 126 | |
127 | - - @project.users_projects.each do |tm| | |
127 | + - @project.users.each do |tm| | |
128 | 128 | %tr |
129 | 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 | 135 | %br |
136 | 136 | %h5 Add new team member | ... | ... |
app/views/admin/team_members/_form.html.haml
... | ... | @@ -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
... | ... | @@ -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" | ... | ... |
... | ... | @@ -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" | ... | ... |
... | ... | @@ -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" | ... | ... |
... | ... | @@ -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' | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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 | + | |
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 | ... | ... |
... | ... | @@ -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" | ... | ... |
... | ... | @@ -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' | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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 | + | |
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 | + | |
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 | + | |
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 | 47 | .input= f.number_field :projects_limit |
48 | 48 | |
49 | 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 | 58 | = f.label :admin do |
51 | 59 | %strong.cred Administrator |
52 | 60 | .input= f.check_box :admin | ... | ... |
app/views/admin/users/show.html.haml
... | ... | @@ -123,5 +123,5 @@ |
123 | 123 | %tr |
124 | 124 | %td= link_to project.name_with_namespace, admin_project_path(project) |
125 | 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 | 11 | |
12 | 12 | :javascript |
13 | 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
app/views/commits/_diffs.html.haml
... | ... | @@ -12,50 +12,38 @@ |
12 | 12 | .file-stats |
13 | 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 | 37 | View file @ |
29 | 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 | 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 | ... | ... |
... | ... | @@ -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 | 64 | \ No newline at end of file | ... | ... |
app/views/commits/_text_diff.html.haml
... | ... | @@ -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" ? " " : 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" ? " " : 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 |
... | ... | @@ -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" ? " " : 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" ? " " : 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
app/views/dashboard/_groups.html.haml
1 | -.groups_box | |
1 | +.ui-box | |
2 | 2 | %h5.title |
3 | 3 | Groups |
4 | 4 | %small |
5 | 5 | (#{groups.count}) |
6 | 6 | - if current_user.can_create_group? |
7 | 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 | 9 | %i.icon-plus |
10 | 10 | New Group |
11 | 11 | %ul.well-list |
... | ... | @@ -13,8 +13,6 @@ |
13 | 13 | %li |
14 | 14 | = link_to group_path(id: group.path), class: dom_class(group) do |
15 | 15 | %strong.well-title= truncate(group.name, length: 35) |
16 | - %span.arrow | |
17 | - → | |
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 | 2 | %h5.title |
3 | 3 | Projects |
4 | 4 | %small |
5 | - (#{projects.total_count}) | |
5 | + (#{@projects_count}) | |
6 | 6 | - if current_user.can_create_project? |
7 | 7 | %span.right |
8 | 8 | = link_to new_project_path, class: "btn very_small info" do |
9 | 9 | %i.icon-plus |
10 | 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 | 12 | %ul.well-list |
20 | 13 | - projects.each do |project| |
... | ... | @@ -33,4 +26,6 @@ |
33 | 26 | - if projects.blank? |
34 | 27 | %li |
35 | 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
... | ... | @@ -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 | -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
app/views/dashboard/index.js.haml
app/views/dashboard/issues.atom.builder
1 | 1 | xml.instruct! |
2 | 2 | xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do |
3 | 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 | 7 | xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any? |
8 | 8 | |
9 | 9 | @issues.each do |issue| | ... | ... |
... | ... | @@ -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 | + | ... | ... |
... | ... | @@ -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/groups/issues.atom.builder
1 | 1 | xml.instruct! |
2 | 2 | xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do |
3 | 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 | 7 | xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any? |
8 | 8 | |
9 | 9 | @issues.each do |issue| | ... | ... |
... | ... | @@ -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 | + | |
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 | 17 | = link_to new_project_path, title: "Create New Project", class: 'has_bottom_tooltip', 'data-original-title' => 'New project' do |
18 | 18 | %i.icon-plus |
19 | 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 | 21 | %i.icon-user |
22 | 22 | %li.separator |
23 | 23 | %li | ... | ... |
app/views/layouts/_search.html.haml
1 | 1 | .search |
2 | 2 | = form_tag search_path, method: :get, class: 'navbar-form pull-left' do |f| |
3 | 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 | 7 | :javascript |
6 | 8 | $(function(){ | ... | ... |
app/views/layouts/admin.html.haml
... | ... | @@ -10,6 +10,8 @@ |
10 | 10 | = link_to "Stats", admin_root_path |
11 | 11 | = nav_link(controller: :projects) do |
12 | 12 | = link_to "Projects", admin_projects_path |
13 | + = nav_link(controller: :teams) do | |
14 | + = link_to "Teams", admin_teams_path | |
13 | 15 | = nav_link(controller: :groups) do |
14 | 16 | = link_to "Groups", admin_groups_path |
15 | 17 | = nav_link(controller: :users) do | ... | ... |
app/views/layouts/application.html.haml
... | ... | @@ -6,14 +6,17 @@ |
6 | 6 | = render "layouts/head_panel", title: "Dashboard" |
7 | 7 | .container |
8 | 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 | 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 | 14 | = nav_link(path: 'dashboard#issues') do |
12 | - = link_to dashboard_issues_path do | |
15 | + = link_to issues_dashboard_path do | |
13 | 16 | Issues |
14 | 17 | %span.count= current_user.assigned_issues.opened.count |
15 | 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 | 20 | Merge Requests |
18 | 21 | %span.count= current_user.cared_merge_requests.opened.count |
19 | 22 | = nav_link(path: 'search#show') do | ... | ... |
app/views/layouts/group.html.haml
... | ... | @@ -3,7 +3,7 @@ |
3 | 3 | = render "layouts/head", title: "#{@group.name}" |
4 | 4 | %body{class: "#{app_theme} application"} |
5 | 5 | = render "layouts/flash" |
6 | - = render "layouts/head_panel", title: "#{@group.name}" | |
6 | + = render "layouts/head_panel", title: "group: #{@group.name}" | |
7 | 7 | .container |
8 | 8 | %ul.main_menu |
9 | 9 | = nav_link(path: 'groups#show', html_options: {class: 'home'}) do | ... | ... |
... | ... | @@ -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 | 38 | - if note.for_diff_line? |
39 | 39 | - if note.diff |
40 | 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 | 42 | - else |
43 | 43 | = link_to 'show outdated discussion', '#', class: 'js-show-outdated-discussion' |
44 | 44 | %div.hide.outdated-discussion | ... | ... |
app/views/notes/_discussion_diff.html.haml
1 | 1 | - diff = note.diff |
2 | -.diff_file_header | |
2 | +.header | |
3 | 3 | - if diff.deleted_file |
4 | 4 | %span= diff.old_path |
5 | 5 | - else |
... | ... | @@ -7,7 +7,7 @@ |
7 | 7 | - if diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode |
8 | 8 | %span.file-mode= "#{diff.a_mode} → #{diff.b_mode}" |
9 | 9 | %br/ |
10 | -.diff_file_content | |
10 | +.content | |
11 | 11 | %table |
12 | 12 | - each_diff_line(diff, note.diff_file_index) do |line, type, line_code, line_new, line_old| |
13 | 13 | %tr.line_holder{ id: line_code } | ... | ... |
app/views/profiles/show.html.haml
... | ... | @@ -31,6 +31,20 @@ |
31 | 31 | .controls |
32 | 32 | = f.text_field :email, class: "input-xlarge", required: true |
33 | 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 | 49 | .span5.right |
36 | 50 | %fieldset.tips |
... | ... | @@ -47,24 +61,18 @@ |
47 | 61 | %p |
48 | 62 | You can login through #{@user.provider.titleize}! |
49 | 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 | 76 | %fieldset |
69 | 77 | %legend |
70 | 78 | Personal projects: |
... | ... | @@ -79,7 +87,8 @@ |
79 | 87 | %fieldset |
80 | 88 | %legend |
81 | 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 | 92 | .padded |
84 | 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 | 15 | %span Namespace |
16 | 16 | .input |
17 | 17 | = f.select :namespace_id, namespaces_options(params[:namespace_id] || :current_user), {}, {class: 'chosen'} |
18 | - %hr | |
18 | + | |
19 | 19 | %p.padded |
20 | 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 | 3 | = link_to project_path(@project), class: "activities-tab tab" do |
4 | 4 | %i.icon-home |
5 | 5 | Show |
6 | - = nav_link(controller: :team_members) do | |
6 | + = nav_link(controller: [:team_members, :teams]) do | |
7 | 7 | = link_to project_team_index_path(@project), class: "team-tab tab" do |
8 | 8 | %i.icon-user |
9 | 9 | Team | ... | ... |
app/views/projects/graph.html.haml
1 | 1 | %h3.page_title Project Network Graph |
2 | 2 | %br |
3 | + | |
3 | 4 | .graph_holder |
4 | 5 | %h4 |
5 | 6 | %small You can move around the graph by using the arrow keys. |
... | ... | @@ -11,6 +12,6 @@ |
11 | 12 | $(function(){ |
12 | 13 | branch_graph = new BranchGraph($("#holder"), { |
13 | 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 | }); | ... | ... |
... | ... | @@ -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 | + | ... | ... |
... | ... | @@ -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 | 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 | - – | |
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 | - – | |
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 | 36 | :javascript |
82 | 37 | $(function() { |
83 | 38 | $(".search_results .term").highlight("#{escape_javascript(params[:search])}"); | ... | ... |
app/views/search/show.html.haml
1 | 1 | = form_tag search_path, method: :get, class: 'form-inline' do |f| |
2 | - .padded | |
2 | + .search-holder | |
3 | 3 | = label_tag :search do |
4 | - %strong Looking for | |
4 | + %span Looking for | |
5 | 5 | .input |
6 | 6 | = search_field_tag :search, params[:search], placeholder: "issue 143", class: "input-xxlarge search-text-input", id: "dashboard_search" |
7 | 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 | 1 | %h3.page_title |
2 | 2 | = "New Team member(s)" |
3 | 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 | 6 | .alert-message.block-message.error |
7 | 7 | %ul |
8 | - - @team_member.errors.full_messages.each do |msg| | |
8 | + - @user_project_relation.errors.full_messages.each do |msg| | |
9 | 9 | %li= msg |
10 | 10 | |
11 | 11 | %h6 1. Choose people you want in the team |
... | ... | @@ -16,7 +16,7 @@ |
16 | 16 | %h6 2. Set access level for them |
17 | 17 | .clearfix |
18 | 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 | 21 | .actions |
22 | 22 | = f.submit 'Save', class: "btn save-btn" | ... | ... |
app/views/team_members/_show.html.haml
1 | 1 | - user = member.user |
2 | 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 | 4 | .row |
5 | 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 | 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 | 9 | %strong= truncate(user.name, lenght: 40) |
10 | 10 | %br |
11 | 11 | %small.cgray= user.email |
... | ... | @@ -13,7 +13,7 @@ |
13 | 13 | .span5.right |
14 | 14 | - if allow_admin |
15 | 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 | 17 | = f.select :project_access, options_for_select(UsersProject.access_roles, member.project_access), {}, class: "medium project-access-select span2" |
18 | 18 | .right |
19 | 19 | - if current_user == user |
... | ... | @@ -23,6 +23,6 @@ |
23 | 23 | - elsif user.blocked |
24 | 24 | %span.btn.disabled.blocked Blocked |
25 | 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 | 27 | %i.icon-minus.icon-white |
28 | 28 | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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
app/views/team_members/import.html.haml
... | ... | @@ -4,7 +4,7 @@ |
4 | 4 | = "Import team from another project" |
5 | 5 | %hr |
6 | 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 | 8 | = form_tag apply_import_project_team_members_path(@project), method: 'post' do |
9 | 9 | %p.slead Choose project you want to use as team source: |
10 | 10 | .padded | ... | ... |
app/views/team_members/index.html.haml
1 | 1 | = render "projects/project_head" |
2 | 2 | %h3.page_title |
3 | 3 | Team Members |
4 | - (#{@project.users_projects.count}) | |
4 | + (#{@project.users.count}) | |
5 | 5 | %small |
6 | 6 | Read more about project permissions |
7 | 7 | %strong= link_to "here", help_permissions_path, class: "vlink" |
... | ... | @@ -10,11 +10,24 @@ |
10 | 10 | %span.right |
11 | 11 | = link_to import_project_team_members_path(@project), class: "btn small grouped", title: "Import team from another project" do |
12 | 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 | 15 | = link_to new_project_team_member_path(@project), class: "btn success small grouped", title: "New Team Member" do |
14 | 16 | New Team Member |
15 | -%hr | |
16 | 17 | |
18 | +%hr | |
17 | 19 | |
18 | 20 | .clearfix |
19 | 21 | %div.team-table |
20 | 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 | 1 | - allow_admin = can? current_user, :admin_project, @project |
2 | -- user = @team_member.user | |
3 | 2 | |
4 | 3 | .team_member_show |
5 | 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 | 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 | 8 | %h3.page_title |
10 | - = user.name | |
11 | - %small (@#{user.username}) | |
9 | + = @member.name | |
10 | + %small (@#{@member.username}) | |
12 | 11 | |
13 | 12 | %hr |
14 | 13 | .back_link |
... | ... | @@ -21,34 +20,34 @@ |
21 | 20 | %table.lite |
22 | 21 | %tr |
23 | 22 | %td Email |
24 | - %td= mail_to user.email | |
23 | + %td= mail_to @member.email | |
25 | 24 | %tr |
26 | 25 | %td Skype |
27 | - %td= user.skype | |
28 | - - unless user.linkedin.blank? | |
26 | + %td= @member.skype | |
27 | + - unless @member.linkedin.blank? | |
29 | 28 | %tr |
30 | 29 | %td LinkedIn |
31 | - %td= user.linkedin | |
32 | - - unless user.twitter.blank? | |
30 | + %td= @member.linkedin | |
31 | + - unless @member.twitter.blank? | |
33 | 32 | %tr |
34 | 33 | %td Twitter |
35 | - %td= user.twitter | |
36 | - - unless user.bio.blank? | |
34 | + %td= @member.twitter | |
35 | + - unless @member.bio.blank? | |
37 | 36 | %tr |
38 | 37 | %td Bio |
39 | - %td= user.bio | |
38 | + %td= @member.bio | |
40 | 39 | .span6 |
41 | 40 | %table.lite |
42 | 41 | %tr |
43 | 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 | 44 | %tr |
46 | 45 | %td |
47 | 46 | Project Access: |
48 | 47 | %small (#{link_to "read more", help_permissions_path, class: "vlink"}) |
49 | 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 | 51 | %hr |
53 | 52 | = render @events |
54 | 53 | :javascript | ... | ... |
app/views/team_members/update.js.haml
1 | -- if @team_member.valid? | |
1 | +- if @user_project_relation.valid? | |
2 | 2 | :plain |
3 | - $("##{dom_id(@team_member)}").effect("highlight", {color: "#529214"}, 1000);; | |
3 | + $("##{dom_id(@user_project_relation)}").effect("highlight", {color: "#529214"}, 1000);; | |
4 | 4 | - else |
5 | 5 | :plain |
6 | - $("##{dom_id(@team_member)}").effect("highlight", {color: "#D12F19"}, 1000);; | |
6 | + $("##{dom_id(@user_project_relation)}").effect("highlight", {color: "#D12F19"}, 1000);; | ... | ... |
... | ... | @@ -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 | + | ... | ... |
... | ... | @@ -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 | + → | |
20 | + %span.last_activity | |
21 | + %strong Last activity: | |
22 | + %span= project_last_activity(project) | ... | ... |
... | ... | @@ -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" | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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" | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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 | + }) | ... | ... |
... | ... | @@ -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' | ... | ... |
... | ... | @@ -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} | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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 | + ← 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 | + | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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 | + | |
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 | ... | ... |
... | ... | @@ -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" | ... | ... |
... | ... | @@ -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' | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -0,0 +1,28 @@ |
1 | +.projects | |
2 | + .activities.span8 | |
3 | + = link_to dashboard_path, class: 'btn very_small' do | |
4 | + ← To dashboard | |
5 | + | |
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); }); | ... | ... |
... | ... | @@ -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 | 3 | %h3.page_title |
4 | 4 | = image_tag gravatar_icon(@user.email, 90), class: "avatar s90" |
5 | 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 | 11 | %br |
7 | 12 | %small @#{@user.username} |
8 | 13 | %br |
... | ... | @@ -12,26 +17,5 @@ |
12 | 17 | %h5 Recent events |
13 | 18 | = render @events |
14 | 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 | 21 | = render 'projects' | ... | ... |
config/routes.rb
... | ... | @@ -21,7 +21,7 @@ Gitlab::Application.routes.draw do |
21 | 21 | project_root: Gitlab.config.gitolite.repos_path, |
22 | 22 | upload_pack: Gitlab.config.gitolite.upload_pack, |
23 | 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 | 27 | # Help |
... | ... | @@ -49,13 +49,14 @@ Gitlab::Application.routes.draw do |
49 | 49 | # Admin Area |
50 | 50 | # |
51 | 51 | namespace :admin do |
52 | - resources :users do | |
52 | + resources :users, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ } do | |
53 | 53 | member do |
54 | 54 | put :team_update |
55 | 55 | put :block |
56 | 56 | put :unblock |
57 | 57 | end |
58 | 58 | end |
59 | + | |
59 | 60 | resources :groups, constraints: { id: /[^\/]+/ } do |
60 | 61 | member do |
61 | 62 | put :project_update |
... | ... | @@ -63,18 +64,31 @@ Gitlab::Application.routes.draw do |
63 | 64 | delete :remove_project |
64 | 65 | end |
65 | 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 | 72 | end |
71 | 73 | end |
72 | - resources :team_members, only: [:edit, :update, :destroy] | |
74 | + | |
73 | 75 | resources :hooks, only: [:index, :create, :destroy] do |
74 | 76 | get :test |
75 | 77 | end |
78 | + | |
76 | 79 | resource :logs, only: [:show] |
77 | 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 | 92 | root to: "dashboard#index" |
79 | 93 | end |
80 | 94 | |
... | ... | @@ -104,15 +118,18 @@ Gitlab::Application.routes.draw do |
104 | 118 | # |
105 | 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 | 130 | # Groups Area |
114 | 131 | # |
115 | - resources :groups, constraints: { id: /[^\/]+/ }, only: [:show] do | |
132 | + resources :groups, constraints: { id: /[^\/]+/ }, only: [:show, :new, :create] do | |
116 | 133 | member do |
117 | 134 | get :issues |
118 | 135 | get :merge_requests |
... | ... | @@ -122,6 +139,20 @@ Gitlab::Application.routes.draw do |
122 | 139 | end |
123 | 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 | 156 | resources :projects, constraints: { id: /[^\/]+/ }, only: [:new, :create] |
126 | 157 | |
127 | 158 | devise_for :users, controllers: { omniauth_callbacks: :omniauth_callbacks, registrations: :registrations } |
... | ... | @@ -238,6 +269,18 @@ Gitlab::Application.routes.draw do |
238 | 269 | end |
239 | 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 | 284 | resources :notes, only: [:index, :create, :destroy] do |
242 | 285 | collection do |
243 | 286 | post :preview |
... | ... | @@ -245,5 +288,5 @@ Gitlab::Application.routes.draw do |
245 | 288 | end |
246 | 289 | end |
247 | 290 | |
248 | - root to: "dashboard#index" | |
291 | + root to: "dashboard#show" | |
249 | 292 | end | ... | ... |
db/migrate/20121220064104_create_user_team_project_relationships.rb
0 → 100644
... | ... | @@ -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 @@ |
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 | ... | ... |
... | ... | @@ -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 | ... | ... |
db/schema.rb
... | ... | @@ -11,7 +11,7 @@ |
11 | 11 | # |
12 | 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 | 16 | create_table "events", :force => true do |t| |
17 | 17 | t.string "target_type" |
... | ... | @@ -213,6 +213,31 @@ ActiveRecord::Schema.define(:version => 20130110172407) do |
213 | 213 | t.string "name" |
214 | 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 | 241 | create_table "users", :force => true do |t| |
217 | 242 | t.string "email", :default => "", :null => false |
218 | 243 | t.string "encrypted_password", :default => "", :null => false |
... | ... | @@ -242,6 +267,8 @@ ActiveRecord::Schema.define(:version => 20130110172407) do |
242 | 267 | t.string "extern_uid" |
243 | 268 | t.string "provider" |
244 | 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 | 272 | end |
246 | 273 | |
247 | 274 | add_index "users", ["admin"], :name => "index_users_on_admin" | ... | ... |
... | ... | @@ -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 | 16 | And I visit dashboard page |
17 | 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 | 19 | Scenario: I should see last push widget |
26 | 20 | Then I should see last push widget |
27 | 21 | And I click "Create Merge Request" link | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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 < Spinach::FeatureSteps |
63 | 63 | @project.team << [current_user, :master] |
64 | 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 | 72 | Then 'I should see groups list' do |
67 | 73 | Group.all.each do |group| |
68 | 74 | page.should have_link group.name | ... | ... |
features/steps/group/group.rb
... | ... | @@ -64,6 +64,24 @@ class Groups < Spinach::FeatureSteps |
64 | 64 | author: current_user |
65 | 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 | 85 | protected |
68 | 86 | |
69 | 87 | def current_group | ... | ... |
features/steps/shared/diff_note.rb
... | ... | @@ -2,27 +2,27 @@ module SharedDiffNote |
2 | 2 | include Spinach::DSL |
3 | 3 | |
4 | 4 | Given 'I cancel the diff comment' do |
5 | - within(".diff_file") do | |
5 | + within(".file") do | |
6 | 6 | find(".js-close-discussion-note-form").trigger("click") |
7 | 7 | end |
8 | 8 | end |
9 | 9 | |
10 | 10 | Given 'I delete a diff comment' do |
11 | 11 | sleep 1 |
12 | - within(".diff_file") do | |
12 | + within(".file") do | |
13 | 13 | first(".js-note-delete").trigger("click") |
14 | 14 | end |
15 | 15 | end |
16 | 16 | |
17 | 17 | Given 'I haven\'t written any diff comment text' do |
18 | - within(".diff_file") do | |
18 | + within(".file") do | |
19 | 19 | fill_in "note[note]", with: "" |
20 | 20 | end |
21 | 21 | end |
22 | 22 | |
23 | 23 | Given 'I leave a diff comment like "Typo, please fix"' do |
24 | 24 | find("#586fb7c4e1add2d4d24e27566ed7064680098646_29_14.line_holder .js-add-diff-note-button").trigger("click") |
25 | - within(".diff_file") do | |
25 | + within(".file") do | |
26 | 26 | fill_in "note[note]", with: "Typo, please fix" |
27 | 27 | #click_button("Add Comment") |
28 | 28 | find(".js-comment-button").trigger("click") |
... | ... | @@ -32,7 +32,7 @@ module SharedDiffNote |
32 | 32 | |
33 | 33 | Given 'I preview a diff comment text like "Should fix it :smile:"' do |
34 | 34 | find("#586fb7c4e1add2d4d24e27566ed7064680098646_29_14.line_holder .js-add-diff-note-button").trigger("click") |
35 | - within(".diff_file") do | |
35 | + within(".file") do | |
36 | 36 | fill_in "note[note]", with: "Should fix it :smile:" |
37 | 37 | find(".js-note-preview-button").trigger("click") |
38 | 38 | end |
... | ... | @@ -40,7 +40,7 @@ module SharedDiffNote |
40 | 40 | |
41 | 41 | Given 'I preview another diff comment text like "DRY this up"' do |
42 | 42 | find("#586fb7c4e1add2d4d24e27566ed7064680098646_57_41.line_holder .js-add-diff-note-button").trigger("click") |
43 | - within(".diff_file") do | |
43 | + within(".file") do | |
44 | 44 | fill_in "note[note]", with: "DRY this up" |
45 | 45 | find(".js-note-preview-button").trigger("click") |
46 | 46 | end |
... | ... | @@ -55,13 +55,13 @@ module SharedDiffNote |
55 | 55 | end |
56 | 56 | |
57 | 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 | 59 | fill_in "note[note]", with: ":-1: I don\'t like this" |
60 | 60 | end |
61 | 61 | end |
62 | 62 | |
63 | 63 | Given 'I submit the diff comment' do |
64 | - within(".diff_file") do | |
64 | + within(".file") do | |
65 | 65 | click_button("Add Comment") |
66 | 66 | end |
67 | 67 | end |
... | ... | @@ -69,49 +69,49 @@ module SharedDiffNote |
69 | 69 | |
70 | 70 | |
71 | 71 | Then 'I should not see the diff comment form' do |
72 | - within(".diff_file") do | |
72 | + within(".file") do | |
73 | 73 | page.should_not have_css("form.new_note") |
74 | 74 | end |
75 | 75 | end |
76 | 76 | |
77 | 77 | Then 'I should not see the diff comment preview button' do |
78 | - within(".diff_file") do | |
78 | + within(".file") do | |
79 | 79 | page.should have_css(".js-note-preview-button", visible: false) |
80 | 80 | end |
81 | 81 | end |
82 | 82 | |
83 | 83 | Then 'I should not see the diff comment text field' do |
84 | - within(".diff_file") do | |
84 | + within(".file") do | |
85 | 85 | page.should have_css(".js-note-text", visible: false) |
86 | 86 | end |
87 | 87 | end |
88 | 88 | |
89 | 89 | Then 'I should only see one diff form' do |
90 | - within(".diff_file") do | |
90 | + within(".file") do | |
91 | 91 | page.should have_css("form.new_note", count: 1) |
92 | 92 | end |
93 | 93 | end |
94 | 94 | |
95 | 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 | 97 | page.should have_field("note[note]", with: ":-1: I don\'t like this") |
98 | 98 | end |
99 | 99 | end |
100 | 100 | |
101 | 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 | 103 | page.should have_content("Typo, please fix") |
104 | 104 | end |
105 | 105 | end |
106 | 106 | |
107 | 107 | Then 'I should see a discussion reply button' do |
108 | - within(".diff_file") do | |
108 | + within(".file") do | |
109 | 109 | page.should have_link("Reply") |
110 | 110 | end |
111 | 111 | end |
112 | 112 | |
113 | 113 | Then 'I should see a temporary diff comment form' do |
114 | - within(".diff_file") do | |
114 | + within(".file") do | |
115 | 115 | page.should have_css(".js-temp-notes-holder form.new_note") |
116 | 116 | end |
117 | 117 | end |
... | ... | @@ -121,37 +121,37 @@ module SharedDiffNote |
121 | 121 | end |
122 | 122 | |
123 | 123 | Then 'I should see an empty diff comment form' do |
124 | - within(".diff_file") do | |
124 | + within(".file") do | |
125 | 125 | page.should have_field("note[note]", with: "") |
126 | 126 | end |
127 | 127 | end |
128 | 128 | |
129 | 129 | Then 'I should see the cancel comment button' do |
130 | - within(".diff_file form") do | |
130 | + within(".file form") do | |
131 | 131 | page.should have_css(".js-close-discussion-note-form", text: "Cancel") |
132 | 132 | end |
133 | 133 | end |
134 | 134 | |
135 | 135 | Then 'I should see the diff comment preview' do |
136 | - within(".diff_file form") do | |
136 | + within(".file form") do | |
137 | 137 | page.should have_css(".js-note-preview", visible: false) |
138 | 138 | end |
139 | 139 | end |
140 | 140 | |
141 | 141 | Then 'I should see the diff comment edit button' do |
142 | - within(".diff_file") do | |
142 | + within(".file") do | |
143 | 143 | page.should have_css(".js-note-edit-button", visible: true) |
144 | 144 | end |
145 | 145 | end |
146 | 146 | |
147 | 147 | Then 'I should see the diff comment preview button' do |
148 | - within(".diff_file") do | |
148 | + within(".file") do | |
149 | 149 | page.should have_css(".js-note-preview-button", visible: true) |
150 | 150 | end |
151 | 151 | end |
152 | 152 | |
153 | 153 | Then 'I should see two separate previews' do |
154 | - within(".diff_file") do | |
154 | + within(".file") do | |
155 | 155 | page.should have_css(".js-note-preview", visible: true, count: 2) |
156 | 156 | page.should have_content("Should fix it") |
157 | 157 | page.should have_content("DRY this up") | ... | ... |
features/steps/shared/paths.rb
... | ... | @@ -33,12 +33,16 @@ module SharedPaths |
33 | 33 | visit dashboard_path |
34 | 34 | end |
35 | 35 | |
36 | + Given 'I visit dashboard projects page' do | |
37 | + visit projects_dashboard_path | |
38 | + end | |
39 | + | |
36 | 40 | Given 'I visit dashboard issues page' do |
37 | - visit dashboard_issues_path | |
41 | + visit issues_dashboard_path | |
38 | 42 | end |
39 | 43 | |
40 | 44 | Given 'I visit dashboard merge requests page' do |
41 | - visit dashboard_merge_requests_path | |
45 | + visit merge_requests_dashboard_path | |
42 | 46 | end |
43 | 47 | |
44 | 48 | Given 'I visit dashboard search page' do |
... | ... | @@ -105,6 +109,10 @@ module SharedPaths |
105 | 109 | visit admin_groups_path |
106 | 110 | end |
107 | 111 | |
112 | + When 'I visit admin teams page' do | |
113 | + visit admin_teams_path | |
114 | + end | |
115 | + | |
108 | 116 | # ---------------------------------------- |
109 | 117 | # Generic Project |
110 | 118 | # ---------------------------------------- | ... | ... |
... | ... | @@ -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
... | ... | @@ -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 | 88 | end |
89 | 89 | |
90 | 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 | 95 | conf.rm_repo(project.path_with_namespace) |
93 | 96 | end |
94 | 97 | ... | ... |
... | ... | @@ -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 | 169 | else |
170 | 170 | puts "no".red |
171 | 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 | 174 | fix_and_rerun |
175 | 175 | end |
... | ... | @@ -194,7 +194,7 @@ namespace :gitlab do |
194 | 194 | else |
195 | 195 | puts "no".red |
196 | 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 | 198 | "If necessary, remove the tmp/repo_satellites directory ...", |
199 | 199 | "... and rerun the above command" |
200 | 200 | ) |
... | ... | @@ -789,7 +789,7 @@ namespace :gitlab do |
789 | 789 | else |
790 | 790 | puts "wrong or missing".red |
791 | 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 | 794 | for_more_information( |
795 | 795 | "doc/raketasks/maintenance.md" |
... | ... | @@ -895,7 +895,7 @@ namespace :gitlab do |
895 | 895 | else |
896 | 896 | puts "no".red |
897 | 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 | 900 | for_more_information( |
901 | 901 | see_installation_guide_section("Install Init Script"), | ... | ... |
lib/tasks/sidekiq.rake
... | ... | @@ -6,7 +6,7 @@ namespace :sidekiq do |
6 | 6 | |
7 | 7 | desc "GITLAB | Start sidekiq" |
8 | 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 | 10 | end |
11 | 11 | |
12 | 12 | def pidfile | ... | ... |
... | ... | @@ -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/models/project_hooks_spec.rb
... | ... | @@ -38,11 +38,14 @@ describe Project, "Hooks" do |
38 | 38 | @project_hook = create(:project_hook) |
39 | 39 | @project_hook_2 = create(:project_hook) |
40 | 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 | 44 | end |
42 | 45 | |
43 | 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 | 50 | project.trigger_post_receive('oldrev', 'newrev', 'refs/heads/master', @user) |
48 | 51 | end | ... | ... |
... | ... | @@ -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 | -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 | 189 | |
190 | 190 | it { user.is_admin?.should be_false } |
191 | 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 | 193 | it { user.can_create_project?.should be_true } |
194 | 194 | it { user.first_name.should == 'John' } |
195 | 195 | end | ... | ... |
spec/requests/atom/dashboard_issues_spec.rb
... | ... | @@ -10,7 +10,7 @@ describe "Dashboard Issues Feed" do |
10 | 10 | |
11 | 11 | describe "atom feed" do |
12 | 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 | 15 | page.response_headers['Content-Type'].should have_content("application/atom+xml") |
16 | 16 | page.body.should have_selector("title", text: "#{user.name} issues") | ... | ... |
spec/routing/admin_routing_spec.rb
... | ... | @@ -95,20 +95,20 @@ describe Admin::ProjectsController, "routing" do |
95 | 95 | end |
96 | 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 | 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 | 104 | end |
105 | 105 | |
106 | 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 | 108 | end |
109 | 109 | |
110 | 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 | 112 | end |
113 | 113 | end |
114 | 114 | ... | ... |
spec/routing/routing_spec.rb
... | ... | @@ -146,14 +146,14 @@ describe KeysController, "routing" do |
146 | 146 | end |
147 | 147 | end |
148 | 148 | |
149 | -# dashboard GET /dashboard(.:format) dashboard#index | |
149 | +# dashboard GET /dashboard(.:format) dashboard#show | |
150 | 150 | # dashboard_issues GET /dashboard/issues(.:format) dashboard#issues |
151 | 151 | # dashboard_merge_requests GET /dashboard/merge_requests(.:format) dashboard#merge_requests |
152 | -# root / dashboard#index | |
152 | +# root / dashboard#show | |
153 | 153 | describe DashboardController, "routing" do |
154 | 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 | 157 | end |
158 | 158 | |
159 | 159 | it "to #issues" do | ... | ... |
vendor/assets/javascripts/branch-graph.js
... | ... | @@ -65,15 +65,15 @@ |
65 | 65 | |
66 | 66 | BranchGraph.prototype.buildGraph = function(){ |
67 | 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 | 70 | , r = Raphael(this.element.get(0), cw, ch) |
71 | 71 | , top = r.set() |
72 | 72 | , cuday = 0 |
73 | 73 | , cumonth = "" |
74 | 74 | , offsetX = 20 |
75 | 75 | , offsetY = 60 |
76 | - , barWidth = Math.max(graphWidth, this.dayCount * 20 + 80); | |
76 | + , barWidth = Math.max(graphWidth, this.dayCount * 20 + 320); | |
77 | 77 | |
78 | 78 | this.raphael = r; |
79 | 79 | ... | ... |