Commit 170ca843 authored by Kamil Trzcinski's avatar Kamil Trzcinski

Merge branch 'master' into ci-permissions

# Conflicts:
#	db/schema.rb
parents a7c441aa 3a982792
...@@ -13,9 +13,12 @@ v 8.5.0 (unreleased) ...@@ -13,9 +13,12 @@ v 8.5.0 (unreleased)
set it up set it up
- Fix diff comments loaded by AJAX to load comment with diff in discussion tab - Fix diff comments loaded by AJAX to load comment with diff in discussion tab
- Whitelist raw "abbr" elements when parsing Markdown (Benedict Etzel) - Whitelist raw "abbr" elements when parsing Markdown (Benedict Etzel)
- Fix label links for a merge request pointing to issues list
- Don't vendor minified JS - Don't vendor minified JS
- Display 404 error on group not found - Display 404 error on group not found
- Track project import failure - Track project import failure
- Support Two-factor Authentication for LDAP users
- Display database type and version in Administration dashboard
- Fix visibility level text in admin area (Zeger-Jan van de Weg) - Fix visibility level text in admin area (Zeger-Jan van de Weg)
- Warn admin during OAuth of granting admin rights (Zeger-Jan van de Weg) - Warn admin during OAuth of granting admin rights (Zeger-Jan van de Weg)
- Update the ExternalIssue regex pattern (Blake Hitchcock) - Update the ExternalIssue regex pattern (Blake Hitchcock)
...@@ -24,6 +27,7 @@ v 8.5.0 (unreleased) ...@@ -24,6 +27,7 @@ v 8.5.0 (unreleased)
- Fix API to keep request parameters in Link header (Michael Potthoff) - Fix API to keep request parameters in Link header (Michael Potthoff)
- Deprecate API "merge_request/:merge_request_id/comments". Use "merge_requests/:merge_request_id/notes" instead - Deprecate API "merge_request/:merge_request_id/comments". Use "merge_requests/:merge_request_id/notes" instead
- Deprecate API "merge_request/:merge_request_id/...". Use "merge_requests/:merge_request_id/..." instead - Deprecate API "merge_request/:merge_request_id/...". Use "merge_requests/:merge_request_id/..." instead
- Prevent parse error when name of project ends with .atom and prevent path issues
- Mark inline difference between old and new paths when a file is renamed - Mark inline difference between old and new paths when a file is renamed
- Support Akismet spam checking for creation of issues via API (Stan Hu) - Support Akismet spam checking for creation of issues via API (Stan Hu)
- Improve UI consistency between projects and groups lists - Improve UI consistency between projects and groups lists
......
...@@ -211,8 +211,89 @@ $ -> ...@@ -211,8 +211,89 @@ $ ->
$this.attr 'value', $this.val() $this.attr 'value', $this.val()
return return
$(document).on 'keyup', 'input[type="search"]' , (e) -> $(document)
$this = $(this) .off 'keyup', 'input[type="search"]'
$this.attr 'value', $this.val() .on 'keyup', 'input[type="search"]' , (e) ->
$this = $(this)
$this.attr 'value', $this.val()
$(document)
.off 'breakpoint:change'
.on 'breakpoint:change', (e, breakpoint) ->
if breakpoint is 'sm' or breakpoint is 'xs'
$gutterIcon = $('.gutter-toggle').find('i')
if $gutterIcon.hasClass('fa-angle-double-right')
$gutterIcon.closest('a').trigger('click')
$(document)
.off 'click', 'aside .gutter-toggle'
.on 'click', 'aside .gutter-toggle', (e) ->
e.preventDefault()
$this = $(this)
$thisIcon = $this.find 'i'
if $thisIcon.hasClass('fa-angle-double-right')
$thisIcon
.removeClass('fa-angle-double-right')
.addClass('fa-angle-double-left')
$this
.closest('aside')
.removeClass('right-sidebar-expanded')
.addClass('right-sidebar-collapsed')
$('.page-with-sidebar')
.removeClass('right-sidebar-expanded')
.addClass('right-sidebar-collapsed')
else
$thisIcon
.removeClass('fa-angle-double-left')
.addClass('fa-angle-double-right')
$this
.closest('aside')
.removeClass('right-sidebar-collapsed')
.addClass('right-sidebar-expanded')
$('.page-with-sidebar')
.removeClass('right-sidebar-collapsed')
.addClass('right-sidebar-expanded')
$.cookie("collapsed_gutter",
$('.right-sidebar')
.hasClass('right-sidebar-collapsed'), { path: '/' })
bootstrapBreakpoint = undefined;
checkBootstrapBreakpoints = ->
if $('.device-xs').is(':visible')
bootstrapBreakpoint = "xs"
else if $('.device-sm').is(':visible')
bootstrapBreakpoint = "sm"
else if $('.device-md').is(':visible')
bootstrapBreakpoint = "md"
else if $('.device-lg').is(':visible')
bootstrapBreakpoint = "lg"
setBootstrapBreakpoints = ->
if $('.device-xs').length
return
$("body")
.append('<div class="device-xs visible-xs"></div>'+
'<div class="device-sm visible-sm"></div>'+
'<div class="device-md visible-md"></div>'+
'<div class="device-lg visible-lg"></div>')
checkBootstrapBreakpoints()
fitSidebarForSize = ->
oldBootstrapBreakpoint = bootstrapBreakpoint
checkBootstrapBreakpoints()
if bootstrapBreakpoint != oldBootstrapBreakpoint
$(document).trigger('breakpoint:change', [bootstrapBreakpoint])
checkInitialSidebarSize = ->
if bootstrapBreakpoint is "xs" or "sm"
$(document).trigger('breakpoint:change', [bootstrapBreakpoint])
$(window)
.off "resize"
.on "resize", (e) ->
fitSidebarForSize()
setBootstrapBreakpoints()
checkInitialSidebarSize()
new Aside() new Aside()
class @Dashboard @Dashboard =
constructor: -> init: ->
new ProjectsList() this.initSearch()
initSearch: ->
@timer = null
$("#project-filter-form-field").on('keyup', ->
clearTimeout(@timer)
@timer = setTimeout(Dashboard.filterResults, 500)
)
filterResults: =>
$('.projects-list-holder').fadeTo(250, 0.5)
form = null
form = $("#project-filter-form")
search = $("#project-filter-form-field").val()
project_filter_url = form.attr('action') + '?' + form.serialize()
$.ajax
type: "GET"
url: form.attr('action')
data: form.serialize()
complete: ->
$('.projects-list-holder').fadeTo(250, 1)
success: (data) ->
$('div.projects-list-holder').replaceWith(data.html)
# Change url so if user reload a page - search results are saved
history.replaceState {page: project_filter_url}, document.title, project_filter_url
dataType: "json"
...@@ -58,7 +58,7 @@ class Dispatcher ...@@ -58,7 +58,7 @@ class Dispatcher
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
MergeRequests.init() MergeRequests.init()
when 'dashboard:show', 'root:show' when 'dashboard:show', 'root:show'
new Dashboard() Dashboard.init()
when 'dashboard:activity' when 'dashboard:activity'
new Activities() new Activities()
when 'dashboard:projects:starred' when 'dashboard:projects:starred'
......
...@@ -10,19 +10,7 @@ class @IssuableContext ...@@ -10,19 +10,7 @@ class @IssuableContext
$(".issuable-sidebar .inline-update").on "change", ".js-assignee", -> $(".issuable-sidebar .inline-update").on "change", ".js-assignee", ->
$(this).submit() $(this).submit()
$('.issuable-details').waitForImages -> $(document).on "click",".edit-link", (e) ->
$('.issuable-affix').on 'affix.bs.affix', ->
$(@).width($(@).outerWidth())
.on 'affixed-top.bs.affix affixed-bottom.bs.affix', ->
$(@).width('')
$('.issuable-affix').affix offset:
top: ->
@top = ($('.issuable-affix').offset().top - 70)
bottom: ->
@bottom = $('.footer').outerHeight(true)
$(".edit-link").click (e) ->
block = $(@).parents('.block') block = $(@).parents('.block')
block.find('.selectbox').show() block.find('.selectbox').show()
block.find('.value').hide() block.find('.value').hide()
......
...@@ -22,5 +22,3 @@ class @ProjectsList ...@@ -22,5 +22,3 @@ class @ProjectsList
else else
$(this).show() $(this).show()
uiBox.find("ul.projects-list li.bottom").hide() uiBox.find("ul.projects-list li.bottom").hide()
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
&.s26 { width: 26px; height: 26px; margin-right: 8px; } &.s26 { width: 26px; height: 26px; margin-right: 8px; }
&.s32 { width: 32px; height: 32px; margin-right: 10px; } &.s32 { width: 32px; height: 32px; margin-right: 10px; }
&.s36 { width: 36px; height: 36px; margin-right: 10px; } &.s36 { width: 36px; height: 36px; margin-right: 10px; }
&.s40 { width: 40px; height: 40px; margin-right: 10px; }
&.s46 { width: 46px; height: 46px; margin-right: 15px; } &.s46 { width: 46px; height: 46px; margin-right: 15px; }
&.s48 { width: 48px; height: 48px; margin-right: 10px; } &.s48 { width: 48px; height: 48px; margin-right: 10px; }
&.s60 { width: 60px; height: 60px; margin-right: 12px; } &.s60 { width: 60px; height: 60px; margin-right: 12px; }
...@@ -40,7 +41,8 @@ ...@@ -40,7 +41,8 @@
&.s16 { font-size: 12px; line-height: 1.33; } &.s16 { font-size: 12px; line-height: 1.33; }
&.s24 { font-size: 14px; line-height: 1.8; } &.s24 { font-size: 14px; line-height: 1.8; }
&.s26 { font-size: 20px; line-height: 1.33; } &.s26 { font-size: 20px; line-height: 1.33; }
&.s32 { font-size: 22px; line-height: 32px; } &.s32 { font-size: 20px; line-height: 32px; }
&.s40 { font-size: 16px; line-height: 40px; }
&.s60 { font-size: 32px; line-height: 60px; } &.s60 { font-size: 32px; line-height: 60px; }
&.s90 { font-size: 36px; line-height: 90px; } &.s90 { font-size: 36px; line-height: 90px; }
&.s110 { font-size: 40px; line-height: 112px; font-weight: 300; } &.s110 { font-size: 40px; line-height: 112px; font-weight: 300; }
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
@include border-radius(3px); @include border-radius(3px);
font-size: $gl-font-size; font-size: $gl-font-size;
font-weight: 500; font-weight: 500;
padding: $gl-vert-padding $gl-padding; padding: $gl-vert-padding $gl-btn-padding;
&:focus, &:focus,
&:active { &:active {
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
display: block; display: block;
float: left; float: left;
padding: 0 $gl-padding; padding: 0 $gl-btn-padding;
font-weight: normal; font-weight: normal;
margin-right: 10px; margin-right: 10px;
font-size: $gl-font-size; font-size: $gl-font-size;
......
...@@ -109,7 +109,6 @@ ul.content-list { ...@@ -109,7 +109,6 @@ ul.content-list {
padding: 0; padding: 0;
> li { > li {
padding: $gl-padding 0;
border-color: $table-border-color; border-color: $table-border-color;
color: $gl-gray; color: $gl-gray;
......
...@@ -116,7 +116,7 @@ ...@@ -116,7 +116,7 @@
display: none; display: none;
} }
aside { aside:not(.right-sidebar){
display: none; display: none;
} }
......
...@@ -85,6 +85,10 @@ ...@@ -85,6 +85,10 @@
display: inline-block; display: inline-block;
} }
> form {
display: inline-block;
}
input { input {
height: 34px; height: 34px;
display: inline-block; display: inline-block;
......
...@@ -200,6 +200,14 @@ ...@@ -200,6 +200,14 @@
} }
} }
@mixin expanded-gutter {
padding-right: $gutter_width;
}
@mixin collapsed-gutter {
padding-right: $sidebar_collapsed_width;
}
@mixin collapsed-sidebar { @mixin collapsed-sidebar {
padding-left: $sidebar_collapsed_width; padding-left: $sidebar_collapsed_width;
...@@ -266,6 +274,7 @@ ...@@ -266,6 +274,7 @@
background: #f2f6f7; background: #f2f6f7;
} }
// page is small enough
@media (max-width: $screen-md-max) { @media (max-width: $screen-md-max) {
.page-sidebar-collapsed { .page-sidebar-collapsed {
@include collapsed-sidebar; @include collapsed-sidebar;
...@@ -275,12 +284,32 @@ ...@@ -275,12 +284,32 @@
@include collapsed-sidebar; @include collapsed-sidebar;
} }
.page-gutter {
&.right-sidebar-collapsed {
@include collapsed-gutter;
}
&.right-sidebar-expanded {
@include expanded-gutter;
}
}
.collapse-nav { .collapse-nav {
display: none; display: none;
} }
} }
// page is large enough
@media(min-width: $screen-md-max) { @media(min-width: $screen-md-max) {
.page-gutter {
&.right-sidebar-collapsed {
@include collapsed-gutter;
}
&.right-sidebar-expanded {
@include expanded-gutter;
}
}
.page-sidebar-collapsed { .page-sidebar-collapsed {
@include collapsed-sidebar; @include collapsed-sidebar;
} }
...@@ -288,4 +317,4 @@ ...@@ -288,4 +317,4 @@
.page-sidebar-expanded { .page-sidebar-expanded {
@include expanded-sidebar; @include expanded-sidebar;
} }
} }
\ No newline at end of file
...@@ -12,6 +12,9 @@ $gl-font-size: 15px; ...@@ -12,6 +12,9 @@ $gl-font-size: 15px;
$list-font-size: 15px; $list-font-size: 15px;
$sidebar_collapsed_width: 62px; $sidebar_collapsed_width: 62px;
$sidebar_width: 230px; $sidebar_width: 230px;
$gutter_collapsed_width: 62px;
$gutter_width: 312px;
$gutter_inner_width: 280px;
$avatar_radius: 50%; $avatar_radius: 50%;
$code_font_size: 13px; $code_font_size: 13px;
$code_line_height: 1.5; $code_line_height: 1.5;
...@@ -22,9 +25,10 @@ $header-height: 58px; ...@@ -22,9 +25,10 @@ $header-height: 58px;
$fixed-layout-width: 1280px; $fixed-layout-width: 1280px;
$gl-gray: #5a5a5a; $gl-gray: #5a5a5a;
$gl-padding: 16px; $gl-padding: 16px;
$gl-btn-padding: 10px;
$gl-vert-padding: 6px; $gl-vert-padding: 6px;
$gl-padding-top:10px; $gl-padding-top:10px;
$gl-avatar-size: 46px; $gl-avatar-size: 40px;
$secondary-text: #7f8fa4; $secondary-text: #7f8fa4;
$error-exclamation-point: #E62958; $error-exclamation-point: #E62958;
...@@ -36,11 +40,12 @@ $white-light: #FFFFFF; ...@@ -36,11 +40,12 @@ $white-light: #FFFFFF;
$white-normal: #ededed; $white-normal: #ededed;
$white-dark: #ededed; $white-dark: #ededed;
$gray-light: #f7f7f7; $gray-light: #faf9f9;
$gray-normal: #ededed; $gray-normal: #f5f5f5;
$gray-dark: #ededed; $gray-dark: #ededed;
$gray-darkest: #c9c9c9;
$green-light: #31AF64; $green-light: #38ae67;
$green-normal: #2FAA60; $green-normal: #2FAA60;
$green-dark: #2CA05B; $green-dark: #2CA05B;
...@@ -52,7 +57,7 @@ $blue-medium-light: #3498CB; ...@@ -52,7 +57,7 @@ $blue-medium-light: #3498CB;
$blue-medium: #2F8EBF; $blue-medium: #2F8EBF;
$blue-medium-dark: #2D86B4; $blue-medium-dark: #2D86B4;
$orange-light: #FC6443; $orange-light: rgba(252, 109, 38, 0.80);
$orange-normal: #E75E40; $orange-normal: #E75E40;
$orange-dark: #CE5237; $orange-dark: #CE5237;
...@@ -64,8 +69,8 @@ $border-white-light: #F1F2F4; ...@@ -64,8 +69,8 @@ $border-white-light: #F1F2F4;
$border-white-normal: #D6DAE2; $border-white-normal: #D6DAE2;
$border-white-dark: #C6CACF; $border-white-dark: #C6CACF;
$border-gray-light: #d1d1d1; $border-gray-light: rgba(0, 0, 0, 0.06);
$border-gray-normal: #D6DAE2; $border-gray-normal: rgba(0, 0, 0, 0.10);;
$border-gray-dark: #C6CACF; $border-gray-dark: #C6CACF;
$border-green-light: #2FAA60; $border-green-light: #2FAA60;
...@@ -76,7 +81,7 @@ $border-blue-light: #2D9FD8; ...@@ -76,7 +81,7 @@ $border-blue-light: #2D9FD8;
$border-blue-normal: #2897CE; $border-blue-normal: #2897CE;
$border-blue-dark: #258DC1; $border-blue-dark: #258DC1;
$border-orange-light: #ED5C3D; $border-orange-light: #fc6d26;
$border-orange-normal: #CE5237; $border-orange-normal: #CE5237;
$border-orange-dark: #C14E35; $border-orange-dark: #C14E35;
......
...@@ -40,10 +40,6 @@ ...@@ -40,10 +40,6 @@
.avatar { .avatar {
@include border-radius(50%); @include border-radius(50%);
} }
.identicon {
line-height: 46px;
}
} }
.dash-project-access-icon { .dash-project-access-icon {
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
*/ */
.event-item { .event-item {
font-size: $gl-font-size; font-size: $gl-font-size;
padding: $gl-padding 0 $gl-padding ($gl-avatar-size + 15px); padding: $gl-padding-top 0 $gl-padding-top ($gl-avatar-size + $gl-padding-top);
border-bottom: 1px solid $table-border-color; border-bottom: 1px solid $table-border-color;
color: #7f8fa4; color: #7f8fa4;
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
.event-title, .event-title,
.event-item-timestamp { .event-item-timestamp {
line-height: 44px; line-height: 40px;
} }
} }
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
} }
.avatar { .avatar {
margin-left: -($gl-avatar-size + 15px); margin-left: -($gl-avatar-size + $gl-padding-top);
} }
.event-title { .event-title {
...@@ -41,7 +41,6 @@ ...@@ -41,7 +41,6 @@
margin-right: 174px; margin-right: 174px;
.event-note { .event-note {
margin-top: 5px;
word-wrap: break-word; word-wrap: break-word;
.md { .md {
...@@ -98,8 +97,6 @@ ...@@ -98,8 +97,6 @@
&:last-child { border:none } &:last-child { border:none }
.event_commits { .event_commits {
margin-top: 9px;
li { li {
&.commit { &.commit {
background: transparent; background: transparent;
......
...@@ -29,21 +29,8 @@ ...@@ -29,21 +29,8 @@
} }
} }
.project-issuable-filter {
.controls {
float: right;
margin-top: 11px;
}
.nav-links {
text-align: left;
}
}
.issuable-details { .issuable-details {
section { section {
border-right: 1px solid $border-white-light;
.issuable-discussion { .issuable-discussion {
margin-right: 1px; margin-right: 1px;
} }
...@@ -73,11 +60,35 @@ ...@@ -73,11 +60,35 @@
.block { .block {
@include clearfix; @include clearfix;
padding: $gl-padding 0; padding: $gl-padding 0;
border-bottom: 1px solid #F0F0F0; border-bottom: 1px solid $border-gray-light;
// This prevents the mess when resizing the sidebar
// of elements repositioning themselves..
width: $gutter_inner_width;
overflow-x: hidden;
// --
&:first-child {
padding-top: 5px;
}
&:last-child { &:last-child {
border: none; border: none;
} }
span {
margin-top: 7px;
display: inline-block;
}
.issuable-count {
}
.gutter-toggle {
margin-left: 20px;
border-left: 1px solid $border-gray-light;
padding-left: 10px;
}
} }
.title { .title {
...@@ -133,3 +144,98 @@ ...@@ -133,3 +144,98 @@
margin-right: 2px; margin-right: 2px;
} }
} }
.right-sidebar {
position: fixed;
top: 58px;
right: 0;
height: 100%;
transition-duration: .3s;
background: $gray-light;
overflow: scroll;
padding: 10px 20px;
&.right-sidebar-expanded {
width: $gutter_width;
hr {
display: none;
}
}
.subscribe-button {
span {
margin-top: 0;
}
}
&.right-sidebar-collapsed {
width: $sidebar_collapsed_width;
padding-top: 0;
overflow-x: hidden;
hr {
margin: 0;
color: $gray-normal;
border-color: $gray-normal;
width: 62px;
margin-left: -20px
}
.block {
border-bottom: none;
padding: 15px 0 0 0;
}
}
.btn {
background: $gray-normal;
border: 1px solid $border-gray-normal;
}
&.right-sidebar-collapsed {
.issuable-count,
.issuable-nav,
.assignee > *,
.milestone > *,
.labels > *,
.participants > *,
.light > *,
.project-reference > * {
display: none;
}
.gutter-toggle {
margin-left: -$gutter_inner_width + 4;
}
.sidebar-collapsed-icon {
display: block;
float: left;
width: 62px;
text-align: center;
margin-left: -19px;
padding-bottom: 10px;
color: #999999;
span {
display: block;
margin-top: 0;
}
}
}
&.right-sidebar-expanded {
.sidebar-collapsed-icon {
display: none;
}
}
}
.detail-page-description {
small {
color: $gray-darkest;
}
}
\ No newline at end of file
...@@ -65,10 +65,6 @@ form.edit-issue { ...@@ -65,10 +65,6 @@ form.edit-issue {
width: 3em; width: 3em;
} }
.merge-request-info {
padding-left: 5px;
}
.merge-request-status { .merge-request-status {
color: $gl-gray; color: $gl-gray;
font-size: 15px; font-size: 15px;
...@@ -143,4 +139,4 @@ form.edit-issue { ...@@ -143,4 +139,4 @@ form.edit-issue {
.issue-closed-by-widget { .issue-closed-by-widget {
color: $secondary-text; color: $secondary-text;
margin-left: 52px; margin-left: 52px;
} }
\ No newline at end of file
...@@ -391,12 +391,11 @@ pre.light-well { ...@@ -391,12 +391,11 @@ pre.light-well {
@include basic-list; @include basic-list;
.project-row { .project-row {
padding: $gl-padding 0;
border-color: $table-border-color; border-color: $table-border-color;
&.no-description { &.no-description {
.project { .project {
line-height: 44px; line-height: 40px;
} }
} }
...@@ -409,7 +408,7 @@ pre.light-well { ...@@ -409,7 +408,7 @@ pre.light-well {
.project-controls { .project-controls {
float: right; float: right;
color: $gl-gray; color: $gl-gray;
line-height: 45px; line-height: 40px;
color: #7f8fa4; color: #7f8fa4;
a:hover { a:hover {
......
...@@ -2,7 +2,7 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController ...@@ -2,7 +2,7 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController
before_action :finder, only: [:edit, :update, :destroy] before_action :finder, only: [:edit, :update, :destroy]
def index def index
@broadcast_messages = BroadcastMessage.reorder("starts_at ASC").page(params[:page]) @broadcast_messages = BroadcastMessage.reorder("ends_at DESC").page(params[:page])
@broadcast_message = BroadcastMessage.new @broadcast_message = BroadcastMessage.new
end end
......
...@@ -277,9 +277,10 @@ class ApplicationController < ActionController::Base ...@@ -277,9 +277,10 @@ class ApplicationController < ActionController::Base
} }
end end
def view_to_html_string(partial) def view_to_html_string(partial, locals = {})
render_to_string( render_to_string(
partial, partial,
locals: locals,
layout: false, layout: false,
formats: [:html] formats: [:html]
) )
......
...@@ -5,6 +5,14 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController ...@@ -5,6 +5,14 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
@projects = current_user.authorized_projects.sorted_by_activity.non_archived @projects = current_user.authorized_projects.sorted_by_activity.non_archived
@projects = @projects.sort(@sort = params[:sort]) @projects = @projects.sort(@sort = params[:sort])
@projects = @projects.includes(:namespace) @projects = @projects.includes(:namespace)
terms = params['filter_projects']
if terms.present?
@projects = @projects.search(terms)
end
@projects = @projects.page(params[:page]).per(PER_PAGE)
@last_push = current_user.recent_push @last_push = current_user.recent_push
respond_to do |format| respond_to do |format|
...@@ -14,6 +22,11 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController ...@@ -14,6 +22,11 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
load_events load_events
render layout: false render layout: false
end end
format.json do
render json: {
html: view_to_html_string("dashboard/projects/_projects", locals: { projects: @projects })
}
end
end end
end end
...@@ -21,6 +34,14 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController ...@@ -21,6 +34,14 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
@projects = current_user.starred_projects @projects = current_user.starred_projects
@projects = @projects.includes(:namespace, :forked_from_project, :tags) @projects = @projects.includes(:namespace, :forked_from_project, :tags)
@projects = @projects.sort(@sort = params[:sort]) @projects = @projects.sort(@sort = params[:sort])
terms = params['filter_projects']
if terms.present?
@projects = @projects.search(terms)
end
@projects = @projects.page(params[:page]).per(PER_PAGE)
@last_push = current_user.recent_push @last_push = current_user.recent_push
@groups = [] @groups = []
...@@ -28,8 +49,9 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController ...@@ -28,8 +49,9 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
format.html format.html
format.json do format.json do
load_events render json: {
pager_json("events/_events", @events.count) html: view_to_html_string("dashboard/projects/_projects", locals: { projects: @projects })
}
end end
end end
end end
......
...@@ -11,14 +11,14 @@ class Explore::ProjectsController < Explore::ApplicationController ...@@ -11,14 +11,14 @@ class Explore::ProjectsController < Explore::ApplicationController
end end
def trending def trending
@trending_projects = TrendingProjectsFinder.new.execute(current_user) @projects = TrendingProjectsFinder.new.execute(current_user)
@trending_projects = @trending_projects.non_archived @projects = @projects.non_archived
@trending_projects = @trending_projects.page(params[:page]).per(PER_PAGE) @projects = @projects.page(params[:page]).per(PER_PAGE)
end end
def starred def starred
@starred_projects = ProjectsFinder.new.execute(current_user) @projects = ProjectsFinder.new.execute(current_user)
@starred_projects = @starred_projects.reorder('star_count DESC') @projects = @projects.reorder('star_count DESC')
@starred_projects = @starred_projects.page(params[:page]).per(PER_PAGE) @projects = @projects.page(params[:page]).per(PER_PAGE)
end end
end end
...@@ -41,6 +41,7 @@ class GroupsController < Groups::ApplicationController ...@@ -41,6 +41,7 @@ class GroupsController < Groups::ApplicationController
def show def show
@last_push = current_user.recent_push if current_user @last_push = current_user.recent_push if current_user
@projects = @projects.includes(:namespace) @projects = @projects.includes(:namespace)
@projects = @projects.page(params[:page]).per(PER_PAGE)
respond_to do |format| respond_to do |format|
format.html format.html
......
class OmniauthCallbacksController < Devise::OmniauthCallbacksController class OmniauthCallbacksController < Devise::OmniauthCallbacksController
include AuthenticatesWithTwoFactor
protect_from_forgery except: [:kerberos, :saml, :cas3] protect_from_forgery except: [:kerberos, :saml, :cas3]
...@@ -29,8 +30,12 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -29,8 +30,12 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
# Do additional LDAP checks for the user filter and EE features # Do additional LDAP checks for the user filter and EE features
if ldap_user.allowed? if ldap_user.allowed?
log_audit_event(@user, with: :ldap) if @user.two_factor_enabled?
sign_in_and_redirect(@user) prompt_for_two_factor(@user)
else
log_audit_event(@user, with: :ldap)
sign_in_and_redirect(@user)
end
else else
flash[:alert] = "Access denied for your LDAP account." flash[:alert] = "Access denied for your LDAP account."
redirect_to new_user_session_path redirect_to new_user_session_path
......
...@@ -4,8 +4,9 @@ class UsersController < ApplicationController ...@@ -4,8 +4,9 @@ class UsersController < ApplicationController
def show def show
@contributed_projects = contributed_projects.joined(@user).reject(&:forked?) @contributed_projects = contributed_projects.joined(@user).reject(&:forked?)
@projects = PersonalProjectsFinder.new(@user).execute(current_user) @projects = PersonalProjectsFinder.new(@user).execute(current_user)
@projects = @projects.page(params[:page]).per(PER_PAGE)
@groups = @user.groups.order_id_desc @groups = @user.groups.order_id_desc
......
...@@ -169,18 +169,6 @@ module ApplicationHelper ...@@ -169,18 +169,6 @@ module ApplicationHelper
Gitlab.config.extra Gitlab.config.extra
end end
def search_placeholder
if @project && @project.persisted?
'Search'
elsif @snippet || @snippets || @show_snippets
'Search snippets'
elsif @group && @group.persisted?
'Search in this group'
else
'Search'
end
end
# Render a `time` element with Javascript-based relative date and tooltip # Render a `time` element with Javascript-based relative date and tooltip
# #
# time - Time object # time - Time object
...@@ -293,6 +281,76 @@ module ApplicationHelper ...@@ -293,6 +281,76 @@ module ApplicationHelper
end end
end end
def issuable_link_next(project,issuable)
if project.nil?
nil
elsif current_controller?(:issues)
namespace_project_issue_path(project.namespace, project, next_issuable_for(project, issuable.id).try(:iid))
elsif current_controller?(:merge_requests)
namespace_project_merge_request_path(project.namespace, project, next_issuable_for(project, issuable.id).try(:iid))
end
end
def issuable_link_prev(project,issuable)
if project.nil?
nil
elsif current_controller?(:issues)
namespace_project_issue_path(project.namespace, project, prev_issuable_for(project, issuable.id).try(:iid))
elsif current_controller?(:merge_requests)
namespace_project_merge_request_path(project.namespace, project, prev_issuable_for(project, issuable.id).try(:iid))
end
end
def issuable_count(entity, project)
if project.nil?
0
elsif current_controller?(:issues)
project.issues.send(entity).count
elsif current_controller?(:merge_requests)
project.merge_requests.send(entity).count
end
end
def next_issuable_for(project, id)
if project.nil?
nil
elsif current_controller?(:issues)
project.issues.where("id > ?", id).last
elsif current_controller?(:merge_requests)
project.merge_requests.where("id > ?", id).last
end
end
def has_next_issuable?(project, id)
if project.nil?
nil
elsif current_controller?(:issues)
project.issues.where("id > ?", id).last
elsif current_controller?(:merge_requests)
project.merge_requests.where("id > ?", id).last
end
end
def prev_issuable_for(project, id)
if project.nil?
nil
elsif current_controller?(:issues)
project.issues.where("id < ?", id).first
elsif current_controller?(:merge_requests)
project.merge_requests.where("id < ?", id).first
end
end
def has_prev_issuable?(project, id)
if project.nil?
nil
elsif current_controller?(:issues)
project.issues.where("id < ?", id).first
elsif current_controller?(:merge_requests)
project.merge_requests.where("id < ?", id).first
end
end
def state_filters_text_for(entity, project) def state_filters_text_for(entity, project)
titles = { titles = {
opened: "Open" opened: "Open"
......
...@@ -7,6 +7,8 @@ module LabelsHelper ...@@ -7,6 +7,8 @@ module LabelsHelper
# project - Project object which will be used as the context for the label's # project - Project object which will be used as the context for the label's
# link. If omitted, defaults to `@project`, or the label's own # link. If omitted, defaults to `@project`, or the label's own
# project. # project.
# type - The type of item the link will point to (:issue or
# :merge_request). If omitted, defaults to :issue.
# block - An optional block that will be passed to `link_to`, forming the # block - An optional block that will be passed to `link_to`, forming the
# body of the link element. If omitted, defaults to # body of the link element. If omitted, defaults to
# `render_colored_label`. # `render_colored_label`.
...@@ -23,14 +25,19 @@ module LabelsHelper ...@@ -23,14 +25,19 @@ module LabelsHelper
# # Force the generated link to use a provided project # # Force the generated link to use a provided project
# link_to_label(label, project: Project.last) # link_to_label(label, project: Project.last)
# #
# # Force the generated link to point to merge requests instead of issues
# link_to_label(label, type: :merge_request)
#
# # Customize link body with a block # # Customize link body with a block
# link_to_label(label) { "My Custom Label Text" } # link_to_label(label) { "My Custom Label Text" }
# #
# Returns a String # Returns a String
def link_to_label(label, project: nil, &block) def link_to_label(label, project: nil, type: :issue, &block)
project ||= @project || label.project project ||= @project || label.project
link = namespace_project_issues_path(project.namespace, project, link = send("namespace_project_#{type.to_s.pluralize}_path",
label_name: label.name) project.namespace,
project,
label_name: label.name)
if block_given? if block_given?
link_to link, &block link_to link, &block
......
...@@ -3,6 +3,18 @@ module NavHelper ...@@ -3,6 +3,18 @@ module NavHelper
cookies[:collapsed_nav] == 'true' cookies[:collapsed_nav] == 'true'
end end
def sidebar_gutter_collapsed_class
if cookies[:collapsed_gutter] == 'true'
"right-sidebar-collapsed"
else
"right-sidebar-expanded"
end
end
def sidebar_gutter_collapsed?
cookies[:collapsed_gutter] == 'true'
end
def nav_sidebar_class def nav_sidebar_class
if nav_menu_collapsed? if nav_menu_collapsed?
"sidebar-collapsed" "sidebar-collapsed"
...@@ -19,6 +31,17 @@ module NavHelper ...@@ -19,6 +31,17 @@ module NavHelper
end end
end end
def page_gutter_class
if current_path?('merge_requests#show') || current_path?('issues#show')
if cookies[:collapsed_gutter] == 'true'
"page-gutter right-sidebar-collapsed"
else
"page-gutter right-sidebar-expanded"
end
end
end
def nav_header_class def nav_header_class
if nav_menu_collapsed? if nav_menu_collapsed?
"header-collapsed" "header-collapsed"
......
...@@ -20,6 +20,12 @@ module ProjectsHelper ...@@ -20,6 +20,12 @@ module ProjectsHelper
end end
end end
def link_to_member_avatar(author, opts = {})
default_opts = { avatar: true, name: true, size: 16, author_class: 'author', title: ":name" }
opts = default_opts.merge(opts)
image_tag(avatar_icon(author, opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}", alt:'') if opts[:avatar]
end
def link_to_member(project, author, opts = {}) def link_to_member(project, author, opts = {})
default_opts = { avatar: true, name: true, size: 16, author_class: 'author', title: ":name" } default_opts = { avatar: true, name: true, size: 16, author_class: 'author', title: ":name" }
opts = default_opts.merge(opts) opts = default_opts.merge(opts)
......
...@@ -49,7 +49,7 @@ class Event < ActiveRecord::Base ...@@ -49,7 +49,7 @@ class Event < ActiveRecord::Base
scope :code_push, -> { where(action: PUSHED) } scope :code_push, -> { where(action: PUSHED) }
scope :in_projects, ->(projects) do scope :in_projects, ->(projects) do
where(project_id: projects.select(:id).reorder(nil)).recent where(project_id: projects.map(&:id)).recent
end end
scope :with_associations, -> { includes(project: :namespace) } scope :with_associations, -> { includes(project: :namespace) }
......
...@@ -34,4 +34,4 @@ ...@@ -34,4 +34,4 @@
= link_to icon('pencil-square-o'), edit_admin_broadcast_message_path(message), title: 'Edit', class: 'btn btn-xs' = link_to icon('pencil-square-o'), edit_admin_broadcast_message_path(message), title: 'Edit', class: 'btn btn-xs'
= link_to icon('times'), admin_broadcast_message_path(message), method: :delete, remote: true, title: 'Remove', class: 'js-remove-tr btn btn-xs btn-danger' = link_to icon('times'), admin_broadcast_message_path(message), method: :delete, remote: true, title: 'Remove', class: 'js-remove-tr btn btn-xs btn-danger'
= paginate @broadcast_messages = paginate @broadcast_messages, theme: 'gitlab'
.project-issuable-filter .top-area
.controls
.pull-left.hidden-xs
- if @all_builds.running_or_pending.any?
= link_to 'Cancel all', cancel_all_admin_builds_path, data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
%ul.nav-links %ul.nav-links
%li{class: ('active' if @scope.nil?)} %li{class: ('active' if @scope.nil?)}
= link_to admin_builds_path do = link_to admin_builds_path do
...@@ -20,7 +15,11 @@ ...@@ -20,7 +15,11 @@
Finished Finished
%span.badge.js-running-count= number_with_delimiter(@all_builds.finished.count(:id)) %span.badge.js-running-count= number_with_delimiter(@all_builds.finished.count(:id))
.gray-content-block .nav-controls
- if @all_builds.running_or_pending.any?
= link_to 'Cancel all', cancel_all_admin_builds_path, data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
.gray-content-block.second-block
#{(@scope || 'running').capitalize} builds #{(@scope || 'running').capitalize} builds
%ul.content-list %ul.content-list
......
...@@ -92,6 +92,11 @@ ...@@ -92,6 +92,11 @@
Rails Rails
%span.pull-right %span.pull-right
#{Rails::VERSION::STRING} #{Rails::VERSION::STRING}
%p
= Gitlab::Database.adapter_name
%span.pull-right
= Gitlab::Database.version
%hr %hr
.row .row
.col-sm-4 .col-sm-4
......
...@@ -13,7 +13,8 @@ ...@@ -13,7 +13,8 @@
Explore Projects Explore Projects
.nav-controls .nav-controls
= search_field_tag :filter_projects, nil, placeholder: 'Filter by name...', class: 'projects-list-filter form-control hidden-xs input-short', spellcheck: false = form_tag request.original_url, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
= search_field_tag :filter_projects, params[:filter_projects], placeholder: 'Filter by name...', class: 'project-filter-form-field form-control input-short', spellcheck: false, id: 'project-filter-form-field'
= render 'explore/projects/dropdown' = render 'explore/projects/dropdown'
- if current_user.can_create_project? - if current_user.can_create_project?
= link_to new_project_path, class: 'btn btn-new' do = link_to new_project_path, class: 'btn btn-new' do
......
...@@ -4,17 +4,15 @@ ...@@ -4,17 +4,15 @@
- if current_user - if current_user
= auto_discovery_link_tag(:atom, issues_dashboard_url(format: :atom, private_token: current_user.private_token), title: "#{current_user.name} issues") = auto_discovery_link_tag(:atom, issues_dashboard_url(format: :atom, private_token: current_user.private_token), title: "#{current_user.name} issues")
.project-issuable-filter .top-area
.controls = render 'shared/issuable/nav', type: :issues
.pull-left .nav-controls
- if current_user - if current_user
.hidden-xs.pull-left = link_to issues_dashboard_url(format: :atom, private_token: current_user.private_token), class: 'btn' do
= link_to issues_dashboard_url(format: :atom, private_token: current_user.private_token), class: 'btn' do = icon('rss')
%i.fa.fa-rss
= render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue" = render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue"
= render 'shared/issuable/filter', type: :issues = render 'shared/issuable/filter', type: :issues
.prepend-top-default .prepend-top-default
= render 'shared/issues' = render 'shared/issues'
- page_title "Merge Requests" - page_title "Merge Requests"
- header_title "Merge Requests", merge_requests_dashboard_path(assignee_id: current_user.id) - header_title "Merge Requests", merge_requests_dashboard_path(assignee_id: current_user.id)
.project-issuable-filter .top-area
.controls = render 'shared/issuable/nav', type: :merge_requests
.nav-controls
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New Merge Request" = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New Merge Request"
= render 'shared/issuable/filter', type: :merge_requests = render 'shared/issuable/filter', type: :merge_requests
.prepend-top-default .prepend-top-default
= render 'shared/merge_requests' = render 'shared/merge_requests'
.projects-list-holder .projects-list-holder
= render 'shared/projects/list', projects: @projects, ci: true = render 'shared/projects/list', projects: @projects, ci: true
:javascript
Dashboard.init()
...@@ -3,8 +3,8 @@ ...@@ -3,8 +3,8 @@
.event-item-timestamp .event-item-timestamp
#{time_ago_with_tooltip(event.created_at)} #{time_ago_with_tooltip(event.created_at)}
= cache [event, current_application_settings, "v2.1"] do = cache [event, current_application_settings, "v2.2"] do
= image_tag avatar_icon(event.author_email, 46), class: "avatar s46", alt:'' = image_tag avatar_icon(event.author_email, 40), class: "avatar s40", alt:''
- if event.created_project? - if event.created_project?
= render "events/event/created_project", event: event = render "events/event/created_project", event: event
- elsif event.push? - elsif event.push?
......
...@@ -13,4 +13,3 @@ ...@@ -13,4 +13,3 @@
= render 'filter' = render 'filter'
= render 'projects', projects: @projects = render 'projects', projects: @projects
= paginate @projects, theme: "gitlab"
...@@ -7,5 +7,4 @@ ...@@ -7,5 +7,4 @@
= render 'explore/head' = render 'explore/head'
= render 'explore/projects/nav' = render 'explore/projects/nav'
= render 'projects', projects: @starred_projects = render 'projects', projects: @projects
= paginate @starred_projects, theme: 'gitlab'
...@@ -7,4 +7,4 @@ ...@@ -7,4 +7,4 @@
= render 'explore/head' = render 'explore/head'
= render 'explore/projects/nav' = render 'explore/projects/nav'
= render 'projects', projects: @trending_projects = render 'projects', projects: @projects
...@@ -4,17 +4,15 @@ ...@@ -4,17 +4,15 @@
- if current_user - if current_user
= auto_discovery_link_tag(:atom, issues_group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} issues") = auto_discovery_link_tag(:atom, issues_group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} issues")
.project-issuable-filter .top-area
.controls = render 'shared/issuable/nav', type: :issues
.pull-left .nav-controls
- if current_user - if current_user
.hidden-xs.pull-left = link_to issues_group_url(@group, format: :atom, private_token: current_user.private_token), class: 'btn' do
= link_to issues_group_url(@group, format: :atom, private_token: current_user.private_token), class: 'btn' do = icon('rss')
%i.fa.fa-rss
= render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue" = render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue"
= render 'shared/issuable/filter', type: :issues = render 'shared/issuable/filter', type: :issues
.gray-content-block.second-block .gray-content-block.second-block
Only issues from Only issues from
......
- page_title "Merge Requests" - page_title "Merge Requests"
- header_title group_title(@group, "Merge Requests", merge_requests_group_path(@group)) - header_title group_title(@group, "Merge Requests", merge_requests_group_path(@group))
.project-issuable-filter .top-area
.controls = render 'shared/issuable/nav', type: :merge_requests
.nav-controls
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New Merge Request" = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New Merge Request"
= render 'shared/issuable/filter', type: :merge_requests = render 'shared/issuable/filter', type: :merge_requests
.gray-content-block.second-block .gray-content-block.second-block
Only merge requests from Only merge requests from
......
...@@ -40,10 +40,6 @@ ...@@ -40,10 +40,6 @@
%td.shortcut %td.shortcut
.key enter .key enter
%td Open Selection %td Open Selection
%tr
%td.shortcut
.key t
%td Go to finding file
%tbody %tbody
%tr %tr
%th %th
...@@ -161,6 +157,10 @@ ...@@ -161,6 +157,10 @@
.key s .key s
%td %td
Go to snippets Go to snippets
%tr
%td.shortcut
.key t
%td Go to finding file
.col-lg-4 .col-lg-4
%table.shortcut-mappings %table.shortcut-mappings
%tbody{ class: 'hidden-shortcut network', style: 'display:none' } %tbody{ class: 'hidden-shortcut network', style: 'display:none' }
......
.page-with-sidebar{ class: page_sidebar_class } .page-with-sidebar{ class: "#{page_sidebar_class} #{page_gutter_class}" }
= render "layouts/broadcast" = render "layouts/broadcast"
.sidebar-wrapper.nicescroll{ class: nav_sidebar_class } .sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
.header-logo .header-logo
......
.search .search
= form_tag search_path, method: :get, class: 'navbar-form pull-left' do |f| = form_tag search_path, method: :get, class: 'navbar-form pull-left' do |f|
= search_field_tag "search", nil, placeholder: search_placeholder, class: "search-input form-control", spellcheck: false = search_field_tag "search", nil, placeholder: 'Search', class: "search-input form-control", spellcheck: false
= hidden_field_tag :group_id, @group.try(:id) = hidden_field_tag :group_id, @group.try(:id)
- if @project && @project.persisted? - if @project && @project.persisted?
= hidden_field_tag :project_id, @project.id = hidden_field_tag :project_id, @project.id
......
...@@ -31,34 +31,33 @@ ...@@ -31,34 +31,33 @@
- else - else
= f.submit 'Generate', class: "btn btn-default" = f.submit 'Generate', class: "btn btn-default"
- unless current_user.ldap_user? .panel.panel-default
.panel.panel-default .panel-heading
.panel-heading Two-factor Authentication
Two-factor Authentication .panel-body
.panel-body - if current_user.two_factor_enabled?
- if current_user.two_factor_enabled? .pull-right
.pull-right = link_to 'Disable Two-factor Authentication', profile_two_factor_auth_path, method: :delete, class: 'btn btn-close btn-sm',
= link_to 'Disable Two-factor Authentication', profile_two_factor_auth_path, method: :delete, class: 'btn btn-close btn-sm', data: { confirm: 'Are you sure?' }
data: { confirm: 'Are you sure?' } %p.text-success
%p.text-success %strong
%strong Two-factor Authentication is enabled
Two-factor Authentication is enabled %p
%p If you lose your recovery codes you can
If you lose your recovery codes you can %strong
%strong = succeed ',' do
= succeed ',' do = link_to 'generate new ones', codes_profile_two_factor_auth_path, method: :post, data: { confirm: 'Are you sure?' }
= link_to 'generate new ones', codes_profile_two_factor_auth_path, method: :post, data: { confirm: 'Are you sure?' } invalidating all previous codes.
invalidating all previous codes.
- else - else
%p %p
Increase your account's security by enabling two-factor authentication (2FA). Increase your account's security by enabling two-factor authentication (2FA).
%p %p
Each time you log in you’ll be required to provide your username and Each time you log in you’ll be required to provide your username and
password as usual, plus a randomly-generated code from your phone. password as usual, plus a randomly-generated code from your phone.
.form-actions .form-actions
= link_to 'Enable Two-factor Authentication', new_profile_two_factor_auth_path, class: 'btn btn-success' = link_to 'Enable Two-factor Authentication', new_profile_two_factor_auth_path, class: 'btn btn-success'
- if button_based_providers.any? - if button_based_providers.any?
.panel.panel-default .panel.panel-default
......
...@@ -5,22 +5,19 @@ ...@@ -5,22 +5,19 @@
- if current_user - if current_user
= auto_discovery_link_tag(:atom, namespace_project_issues_url(@project.namespace, @project, :atom, private_token: current_user.private_token), title: "#{@project.name} issues") = auto_discovery_link_tag(:atom, namespace_project_issues_url(@project.namespace, @project, :atom, private_token: current_user.private_token), title: "#{@project.name} issues")
.project-issuable-filter .top-area
.controls = render 'shared/issuable/nav', type: :issues
.pull-left .nav-controls
- if current_user - if current_user
.hidden-xs.pull-left = link_to namespace_project_issues_path(@project.namespace, @project, :atom, { private_token: current_user.private_token }), class: 'btn append-right-10' do
= link_to namespace_project_issues_path(@project.namespace, @project, :atom, { private_token: current_user.private_token }), class: 'btn append-right-10' do = icon('rss')
%i.fa.fa-rss
= render 'shared/issuable/search_form', path: namespace_project_issues_path(@project.namespace, @project) = render 'shared/issuable/search_form', path: namespace_project_issues_path(@project.namespace, @project)
- if can? current_user, :create_issue, @project - if can? current_user, :create_issue, @project
= link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { assignee_id: @issuable_finder.assignee.try(:id), milestone_id: @issuable_finder.milestones.try(:first).try(:id) }), class: "btn btn-new pull-left", title: "New Issue", id: "new_issue_link" do = link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { assignee_id: @issuable_finder.assignee.try(:id), milestone_id: @issuable_finder.milestones.try(:first).try(:id) }), class: "btn btn-new", title: "New Issue", id: "new_issue_link" do
%i.fa.fa-plus = icon('plus')
New Issue New Issue
= render 'shared/issuable/filter', type: :issues = render 'shared/issuable/filter', type: :issues
.issues-holder .issues-holder
= render "issues" = render "issues"
...@@ -15,11 +15,6 @@ ...@@ -15,11 +15,6 @@
opened by #{link_to_member(@project, @issue.author, size: 24)} opened by #{link_to_member(@project, @issue.author, size: 24)}
&middot; &middot;
= time_ago_with_tooltip(@issue.created_at, placement: 'bottom', html_class: 'issue_created_ago') = time_ago_with_tooltip(@issue.created_at, placement: 'bottom', html_class: 'issue_created_ago')
- if @issue.updated_at != @issue.created_at
%span
&middot;
= icon('edit', title: 'edited')
= time_ago_with_tooltip(@issue.updated_at, placement: 'bottom', html_class: 'issue_edited_ago')
.pull-right .pull-right
- if can?(current_user, :create_issue, @project) - if can?(current_user, :create_issue, @project)
...@@ -46,6 +41,10 @@ ...@@ -46,6 +41,10 @@
= markdown(@issue.description, cache_key: [@issue, "description"]) = markdown(@issue.description, cache_key: [@issue, "description"])
%textarea.hidden.js-task-list-field %textarea.hidden.js-task-list-field
= @issue.description = @issue.description
- if @issue.updated_at != @issue.created_at
%small
Edited
= time_ago_with_tooltip(@issue.updated_at, placement: 'bottom', html_class: 'issue_edited_ago')
.merge-requests .merge-requests
= render 'merge_requests' = render 'merge_requests'
...@@ -54,11 +53,8 @@ ...@@ -54,11 +53,8 @@
= render 'votes/votes_block', votable: @issue = render 'votes/votes_block', votable: @issue
.row .row
%section.col-md-9 %section.col-md-12
.issuable-discussion .issuable-discussion
= render 'projects/issues/discussion' = render 'projects/issues/discussion'
%aside.col-md-3 = render 'shared/issuable/sidebar', issuable: @issue
= render 'shared/issuable/sidebar', issuable: @issue \ No newline at end of file
= render 'shared/show_aside'
$('.issuable-sidebar').html("#{escape_javascript(render 'shared/issuable/sidebar', issuable: @issue)}"); $('aside.right-sidebar')[0].outerHTML = "#{escape_javascript(render 'shared/issuable/sidebar', issuable: @issue)}";
$('.issuable-sidebar').parent().effect('highlight') $('aside.right-sidebar').effect('highlight');
new Issue(); new Issue();
\ No newline at end of file
...@@ -53,7 +53,7 @@ ...@@ -53,7 +53,7 @@
- if merge_request.labels.any? - if merge_request.labels.any?
&nbsp; &nbsp;
- merge_request.labels.each do |label| - merge_request.labels.each do |label|
= link_to_label(label, project: merge_request.project) = link_to_label(label, project: merge_request.project, type: 'merge_request')
- if merge_request.tasks? - if merge_request.tasks?
&nbsp; &nbsp;
%span.task-status %span.task-status
......
...@@ -70,12 +70,9 @@ ...@@ -70,12 +70,9 @@
= render 'votes/votes_block', votable: @merge_request = render 'votes/votes_block', votable: @merge_request
.row .row
%section.col-md-9 %section.col-md-12
.issuable-discussion .issuable-discussion
= render "projects/merge_requests/discussion" = render "projects/merge_requests/discussion"
%aside.col-md-3
= render 'shared/issuable/sidebar', issuable: @merge_request
= render 'shared/show_aside'
#commits.commits.tab-pane #commits.commits.tab-pane
- # This tab is always loaded via AJAX - # This tab is always loaded via AJAX
...@@ -87,6 +84,8 @@ ...@@ -87,6 +84,8 @@
.mr-loading-status .mr-loading-status
= spinner = spinner
= render 'shared/issuable/sidebar', issuable: @merge_request
:javascript :javascript
var merge_request; var merge_request;
......
...@@ -2,16 +2,19 @@ ...@@ -2,16 +2,19 @@
= render "header_title" = render "header_title"
= render 'projects/last_push' = render 'projects/last_push'
.project-issuable-filter
.controls .top-area
= render 'shared/issuable/nav', type: :merge_requests
.nav-controls
= render 'shared/issuable/search_form', path: namespace_project_merge_requests_path(@project.namespace, @project) = render 'shared/issuable/search_form', path: namespace_project_merge_requests_path(@project.namespace, @project)
- merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project)) - merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
- if merge_project - if merge_project
.pull-left.hidden-xs = link_to new_namespace_project_merge_request_path(merge_project.namespace, merge_project), class: "btn btn-new", title: "New Merge Request" do
= link_to new_namespace_project_merge_request_path(merge_project.namespace, merge_project), class: "btn btn-new", title: "New Merge Request" do = icon('plus')
%i.fa.fa-plus New Merge Request
New Merge Request
= render 'shared/issuable/filter', type: :merge_requests = render 'shared/issuable/filter', type: :merge_requests
.merge-requests-holder .merge-requests-holder
= render 'merge_requests' = render 'merge_requests'
...@@ -10,3 +10,8 @@ ...@@ -10,3 +10,8 @@
= markdown(@merge_request.description, cache_key: [@merge_request, "description"]) = markdown(@merge_request.description, cache_key: [@merge_request, "description"])
%textarea.hidden.js-task-list-field %textarea.hidden.js-task-list-field
= @merge_request.description = @merge_request.description
- if @merge_request.updated_at != @merge_request.created_at
%small
Edited
= time_ago_with_tooltip(@merge_request.updated_at, placement: 'bottom')
...@@ -8,11 +8,6 @@ ...@@ -8,11 +8,6 @@
opened by #{link_to_member(@project, @merge_request.author, size: 24)} opened by #{link_to_member(@project, @merge_request.author, size: 24)}
&middot; &middot;
= time_ago_with_tooltip(@merge_request.created_at) = time_ago_with_tooltip(@merge_request.created_at)
- if @merge_request.updated_at != @merge_request.created_at
%span
&middot;
= icon('edit', title: 'edited')
= time_ago_with_tooltip(@merge_request.updated_at, placement: 'bottom')
.issue-btn-group.pull-right .issue-btn-group.pull-right
- if can?(current_user, :update_merge_request, @merge_request) - if can?(current_user, :update_merge_request, @merge_request)
......
$('.issuable-sidebar').html("#{escape_javascript(render 'shared/issuable/sidebar', issuable: @merge_request)}"); $('aside.right-sidebar')[0].outerHTML= "#{escape_javascript(render 'shared/issuable/sidebar', issuable: @merge_request)}";
$('.issuable-sidebar').parent().effect('highlight') $('aside.right-sidebar').effect('highlight')
merge_request = new MergeRequest(); merge_request = new MergeRequest();
...@@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear ...@@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.link href: namespace_project_url(@project.namespace, @project, format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml" xml.link href: namespace_project_url(@project.namespace, @project, format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml"
xml.link href: namespace_project_url(@project.namespace, @project), rel: "alternate", type: "text/html" xml.link href: namespace_project_url(@project.namespace, @project), rel: "alternate", type: "text/html"
xml.id namespace_project_url(@project.namespace, @project) xml.id namespace_project_url(@project.namespace, @project)
xml.updated @events[0].updated_at.xmlschema if @events[0? xml.updated @events[0].updated_at.xmlschema if @events[0]
@events.each do |event| @events.each do |event|
event_to_atom(xml, event) event_to_atom(xml, event)
......
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
= icon('users') = icon('users')
= number_with_delimiter(group.users.count) = number_with_delimiter(group.users.count)
= image_tag group_icon(group), class: "avatar s46 hidden-xs" = image_tag group_icon(group), class: "avatar s40 hidden-xs"
= link_to group, class: 'group-name' do = link_to group, class: 'group-name' do
%span.item-title= group.name %span.item-title= group.name
......
.issues-filters .issues-filters
.issues-state-filters .issues-details-filters.gray-content-block.second-block
%ul.nav-links
- if defined?(type) && type == :merge_requests
- page_context_word = 'merge requests'
- else
- page_context_word = 'issues'
%li{class: ("active" if params[:state] == 'opened')}
= link_to page_filter_path(state: 'opened'), title: "Filter by #{page_context_word} that are currently opened." do
#{state_filters_text_for(:opened, @project)}
- if defined?(type) && type == :merge_requests
%li{class: ("active" if params[:state] == 'merged')}
= link_to page_filter_path(state: 'merged'), title: 'Filter by merge requests that are currently merged.' do
#{state_filters_text_for(:merged, @project)}
%li{class: ("active" if params[:state] == 'closed')}
= link_to page_filter_path(state: 'closed'), title: 'Filter by merge requests that are currently closed and unmerged.' do
#{state_filters_text_for(:closed, @project)}
- else
%li{class: ("active" if params[:state] == 'closed')}
= link_to page_filter_path(state: 'closed'), title: 'Filter by issues that are currently closed.' do
#{state_filters_text_for(:closed, @project)}
%li{class: ("active" if params[:state] == 'all')}
= link_to page_filter_path(state: 'all'), title: "Show all #{page_context_word}." do
#{state_filters_text_for(:all, @project)}
.issues-details-filters.gray-content-block
= form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name]), method: :get, class: 'filter-form' do = form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name]), method: :get, class: 'filter-form' do
- if controller.controller_name == 'issues' && can?(current_user, :admin_issue, @project) - if controller.controller_name == 'issues' && can?(current_user, :admin_issue, @project)
.check-all-holder .check-all-holder
......
%ul.nav-links.issues-state-filters
- if defined?(type) && type == :merge_requests
- page_context_word = 'merge requests'
- else
- page_context_word = 'issues'
%li{class: ("active" if params[:state] == 'opened')}
= link_to page_filter_path(state: 'opened'), title: "Filter by #{page_context_word} that are currently opened." do
#{state_filters_text_for(:opened, @project)}
- if defined?(type) && type == :merge_requests
%li{class: ("active" if params[:state] == 'merged')}
= link_to page_filter_path(state: 'merged'), title: 'Filter by merge requests that are currently merged.' do
#{state_filters_text_for(:merged, @project)}
%li{class: ("active" if params[:state] == 'closed')}
= link_to page_filter_path(state: 'closed'), title: 'Filter by merge requests that are currently closed and unmerged.' do
#{state_filters_text_for(:closed, @project)}
- else
%li{class: ("active" if params[:state] == 'closed')}
= link_to page_filter_path(state: 'closed'), title: 'Filter by issues that are currently closed.' do
#{state_filters_text_for(:closed, @project)}
%li{class: ("active" if params[:state] == 'all')}
= link_to page_filter_path(state: 'all'), title: "Show all #{page_context_word}." do
#{state_filters_text_for(:all, @project)}
.block.participants .block.participants
.sidebar-collapsed-icon
= icon('users')
%span
= participants.count
.title .title
= pluralize participants.count, "participant" = pluralize participants.count, "participant"
- participants.each do |participant| - participants.each do |participant|
......
= form_tag(path, method: :get, id: "issue_search_form", class: 'pull-left issue-search-form') do = form_tag(path, method: :get, id: "issue_search_form", class: 'issue-search-form') do
.append-right-10.hidden-xs.hidden-sm = search_field_tag :issue_search, params[:issue_search], { placeholder: 'Filter by name ...', class: 'form-control issue_search search-text-input input-short', spellcheck: false }
= search_field_tag :issue_search, params[:issue_search], { placeholder: 'Filter by name ...', class: 'form-control issue_search search-text-input', spellcheck: false } = hidden_field_tag :state, params['state']
= hidden_field_tag :state, params['state'] = hidden_field_tag :scope, params['scope']
= hidden_field_tag :scope, params['scope'] = hidden_field_tag :assignee_id, params['assignee_id']
= hidden_field_tag :assignee_id, params['assignee_id'] = hidden_field_tag :author_id, params['author_id']
= hidden_field_tag :author_id, params['author_id'] = hidden_field_tag :milestone_id, params['milestone_id']
= hidden_field_tag :milestone_id, params['milestone_id'] = hidden_field_tag :label_id, params['label_id']
= hidden_field_tag :label_id, params['label_id']
.issuable-sidebar.issuable-affix %aside.right-sidebar{ class: sidebar_gutter_collapsed_class }
= form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f| .issuable-sidebar
.block.assignee .block
.title %span.issuable-count.pull-left
%label = issuable.iid
Assignee of
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project) = issuable_count(:all, @project)
.pull-right %span.pull-right
= link_to 'Edit', '#', class: 'edit-link' %a.gutter-toggle{href: '#'}
.value - if sidebar_gutter_collapsed?
- if issuable.assignee = icon('angle-double-left')
%strong= link_to_member(@project, issuable.assignee, size: 24) - else
- if issuable.instance_of?(MergeRequest) && !issuable.can_be_merged_by?(issuable.assignee) = icon('angle-double-right')
%a.pull-right.cannot-be-merged{href: '#', data: {toggle: 'tooltip'}, title: 'Not allowed to merge'} .issuable-nav.pull-right.btn-group{role: 'group', "aria-label" => '...'}
= icon('exclamation-triangle') - if has_prev_issuable?(@project, issuable.id)
= link_to 'Prev', issuable_link_prev(@project, issuable), class: 'btn btn-default'
- else - else
.light None %a.btn.btn-default.disabled{href: '#'}
Prev
- if has_next_issuable?(@project, issuable.id)
= link_to 'Next', issuable_link_next(@project, issuable), class: 'btn btn-default'
- else
%a.btn.btn-default.disabled{href: '#'}
Next
.selectbox = form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f|
= users_select_tag("#{issuable.class.table_name.singularize}[assignee_id]", placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: issuable.assignee_id, project: @target_project, null_user: true, current_user: true, first_user: true) .block.assignee
.sidebar-collapsed-icon
- if issuable.assignee
= link_to_member_avatar(issuable.assignee, size: 24)
- else
= icon('user')
.title
%label
Assignee
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
.pull-right
= link_to 'Edit', '#', class: 'edit-link'
.value
- if issuable.assignee
%strong= link_to_member(@project, issuable.assignee, size: 24)
- if issuable.instance_of?(MergeRequest) && !issuable.can_be_merged_by?(issuable.assignee)
%a.pull-right.cannot-be-merged{href: '#', data: {toggle: 'tooltip'}, title: 'Not allowed to merge'}
= icon('exclamation-triangle')
- else
.light None
.block.milestone .selectbox
.title = users_select_tag("#{issuable.class.table_name.singularize}[assignee_id]", placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: issuable.assignee_id, project: @target_project, null_user: true, current_user: true, first_user: true)
%label
Milestone
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
.pull-right
= link_to 'Edit', '#', class: 'edit-link'
.value
- if issuable.milestone
%span.back-to-milestone
= link_to namespace_project_milestone_path(@project.namespace, @project, issuable.milestone) do
%strong
= icon('clock-o')
= issuable.milestone.title
- else
.light None
.selectbox
= f.select(:milestone_id, milestone_options(issuable), { include_blank: true }, { class: 'select2 select2-compact js-select2 js-milestone', data: { placeholder: 'Select milestone' }})
= hidden_field_tag :issuable_context
= f.submit class: 'btn hide'
- if issuable.project.labels.any? .block.milestone
.block .sidebar-collapsed-icon
= icon('balance-scale')
%span
- if issuable.milestone
= issuable.milestone.title
- else
No
.title .title
%label Labels %label
Milestone
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project) - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
.pull-right .pull-right
= link_to 'Edit', '#', class: 'edit-link' = link_to 'Edit', '#', class: 'edit-link'
.value.issuable-show-labels .value
- if issuable.labels.any? - if issuable.milestone
- issuable.labels.each do |label| %span.back-to-milestone
= link_to_label(label) = link_to namespace_project_milestone_path(@project.namespace, @project, issuable.milestone) do
%strong
= icon('clock-o')
= issuable.milestone.title
- else - else
.light None .light None
.selectbox .selectbox
= f.collection_select :label_ids, issuable.project.labels.all, :id, :name, = f.select(:milestone_id, milestone_options(issuable), { include_blank: true }, { class: 'select2 select2-compact js-select2 js-milestone', data: { placeholder: 'Select milestone' }})
{ selected: issuable.label_ids }, multiple: true, class: 'select2 js-select2', data: { placeholder: "Select labels" } = hidden_field_tag :issuable_context
= f.submit class: 'btn hide'
= render "shared/issuable/participants", participants: issuable.participants(current_user) - if issuable.project.labels.any?
.block.labels
.sidebar-collapsed-icon
= icon('tags')
%span
= issuable.labels.count
.title
%label Labels
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
.pull-right
= link_to 'Edit', '#', class: 'edit-link'
.value.issuable-show-labels
- if issuable.labels.any?
- issuable.labels.each do |label|
= link_to_label(label, type: issuable.to_ability_name)
- else
.light None
.selectbox
= f.collection_select :label_ids, issuable.project.labels.all, :id, :name,
{ selected: issuable.label_ids }, multiple: true, class: 'select2 js-select2', data: { placeholder: "Select labels" }
- if current_user = render "shared/issuable/participants", participants: issuable.participants(current_user)
- subscribed = issuable.subscribed?(current_user) %hr
.block.light - if current_user
.title - subscribed = issuable.subscribed?(current_user)
%label.light Notifications .block.light
- subscribtion_status = subscribed ? 'subscribed' : 'unsubscribed' .sidebar-collapsed-icon
%button.btn.btn-block.btn-gray.subscribe-button{:type => 'button'} = icon('rss')
%span= subscribed ? 'Unsubscribe' : 'Subscribe' .title
.subscription-status{data: {status: subscribtion_status}} %label.light Notifications
.unsubscribed{class: ( 'hidden' if subscribed )} - subscribtion_status = subscribed ? 'subscribed' : 'unsubscribed'
You're not receiving notifications from this thread. %button.btn.btn-block.btn-gray.subscribe-button{:type => 'button'}
.subscribed{class: ( 'hidden' unless subscribed )} %span= subscribed ? 'Unsubscribe' : 'Subscribe'
You're receiving notifications because you're subscribed to this thread. .subscription-status{data: {status: subscribtion_status}}
.unsubscribed{class: ( 'hidden' if subscribed )}
You're not receiving notifications from this thread.
.subscribed{class: ( 'hidden' unless subscribed )}
You're receiving notifications because you're subscribed to this thread.
- project_ref = cross_project_reference(@project, issuable) - project_ref = cross_project_reference(@project, issuable)
.block .block.project-reference
.title .sidebar-collapsed-icon
.cross-project-reference = icon('clipboard')
%span .title
Reference: .cross-project-reference
%cite{title: project_ref} %span
= project_ref Reference:
= clipboard_button(clipboard_text: project_ref) %cite{title: project_ref}
= project_ref
= clipboard_button(clipboard_text: project_ref)
:javascript :javascript
new Subscription("#{toggle_subscription_path(issuable)}"); new Subscription("#{toggle_subscription_path(issuable)}");
new IssuableContext(); new IssuableContext();
\ No newline at end of file
...@@ -8,18 +8,22 @@ ...@@ -8,18 +8,22 @@
- show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true - show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true
%ul.projects-list %ul.projects-list
- projects.each_with_index do |project, i| - if projects.any?
- css_class = (i >= projects_limit) ? 'hide' : nil - projects.each_with_index do |project, i|
= render "shared/projects/project", project: project, skip_namespace: skip_namespace, - css_class = (i >= projects_limit) ? 'hide' : nil
avatar: avatar, stars: stars, css_class: css_class, ci: ci, use_creator_avatar: use_creator_avatar, = render "shared/projects/project", project: project, skip_namespace: skip_namespace,
forks: forks, show_last_commit_as_description: show_last_commit_as_description avatar: avatar, stars: stars, css_class: css_class, ci: ci, use_creator_avatar: use_creator_avatar,
forks: forks, show_last_commit_as_description: show_last_commit_as_description
- if projects.size > projects_limit - if projects.size > projects_limit && projects.kind_of?(Array)
%li.bottom.center %li.bottom.center
.light .light
#{projects_limit} of #{pluralize(projects.count, 'project')} displayed. #{projects_limit} of #{pluralize(projects.count, 'project')} displayed.
= link_to '#', class: 'js-expand' do = link_to '#', class: 'js-expand' do
Show all Show all
= paginate projects, theme: "gitlab" if !projects.kind_of?(Array)
- else
%h3 No projects found
:javascript :javascript
new ProjectsList(); new ProjectsList();
...@@ -16,9 +16,9 @@ ...@@ -16,9 +16,9 @@
- if avatar - if avatar
.dash-project-avatar .dash-project-avatar
- if use_creator_avatar - if use_creator_avatar
= image_tag avatar_icon(project.creator.email, 46), class: "avatar s46", alt:'' = image_tag avatar_icon(project.creator.email, 40), class: "avatar s40", alt:''
- else - else
= project_icon(project, alt: '', class: 'avatar project-avatar s46') = project_icon(project, alt: '', class: 'avatar project-avatar s40')
%span.project-full-name %span.project-full-name
%span.namespace-name %span.namespace-name
- if project.namespace && !skip_namespace - if project.namespace && !skip_namespace
......
class RemoveDotAtomPathEndingOfProjects < ActiveRecord::Migration
include Gitlab::ShellAdapter
class ProjectPath
attr_reader :old_path, :id, :namespace_path
def initialize(old_path, id, namespace_path, namespace_id)
@old_path = old_path
@id = id
@namespace_path = namespace_path
@namespace_id = namespace_id
end
def clean_path
@_clean_path ||= PathCleaner.clean(@old_path, @namespace_id)
end
end
class PathCleaner
def initialize(path, namespace_id)
@namespace_id = namespace_id
@path = path
end
def self.clean(*args)
new(*args).clean
end
def clean
path = cleaned_path
count = 0
while path_exists?(path)
path = "#{cleaned_path}#{count}"
count += 1
end
path
end
private
def cleaned_path
@_cleaned_path ||= @path.gsub(/\.atom\z/, '-atom')
end
def path_exists?(path)
Project.find_by_path_and_namespace_id(path, @namespace_id)
end
end
def projects_with_dot_atom
select_all("SELECT p.id, p.path, n.path as namespace_path, n.id as namespace_id FROM projects p inner join namespaces n on n.id = p.namespace_id WHERE lower(p.path) LIKE '%.atom'")
end
def up
projects_with_dot_atom.each do |project|
project_path = ProjectPath.new(project['path'], project['id'], project['namespace_path'], project['namespace_id'])
clean_path(project_path) if rename_project_repo(project_path)
end
end
private
def clean_path(project_path)
execute "UPDATE projects SET path = #{sanitize(project_path.clean_path)} WHERE id = #{project_path.id}"
end
def rename_project_repo(project_path)
old_path_with_namespace = File.join(project_path.namespace_path, project_path.old_path)
new_path_with_namespace = File.join(project_path.namespace_path, project_path.clean_path)
gitlab_shell.mv_repository("#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki")
gitlab_shell.mv_repository(old_path_with_namespace, new_path_with_namespace)
rescue
false
end
def sanitize(value)
ActiveRecord::Base.connection.quote(value)
end
end
# Adding deploy keys to multiple projects # Adding deploy keys to multiple projects
If you want to easily add the same deploy key to multiple projects in the same group, this can be achieved quite easily with the API. If you want to easily add the same deploy key to multiple projects in the same
group, this can be achieved quite easily with the API.
First, find the ID of the projects you're interested in, by either listing all projects: First, find the ID of the projects you're interested in, by either listing all
projects:
``` ```
curl --header 'PRIVATE-TOKEN: abcdef' https://gitlab.com/api/v3/projects curl -H 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' https://gitlab.example.com/api/v3/projects
``` ```
Or finding the id of a group and then listing all projects in that group: Or finding the ID of a group and then listing all projects in that group:
``` ```
curl --header 'PRIVATE-TOKEN: abcdef' https://gitlab.com/api/v3/groups curl -H 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' https://gitlab.example.com/api/v3/groups
# For group 1234: # For group 1234:
curl --header 'PRIVATE-TOKEN: abcdef' https://gitlab.com/api/v3/groups/1234 curl -H 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' https://gitlab.example.com/api/v3/groups/1234
``` ```
With those IDs, add the same deploy key to all: With those IDs, add the same deploy key to all:
``` ```
for project_id in 321 456 987; do for project_id in 321 456 987; do
curl -X POST --data '{"title": "my key", "key": "ssh-rsa AAAA..."}' --header 'PRIVATE-TOKEN: abcdef' https://gitlab.com/api/v3/projects/${project_id}/keys curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" -H "Content-Type: application/json" \
--data '{"title": "my key", "key": "ssh-rsa AAAA..."}' https://gitlab.example.com/api/v3/projects/${project_id}/keys
done done
``` ```
# Session # Session
Login to get private token You can login with both GitLab and LDAP credentials in order to obtain the
private token.
``` ```
POST /session POST /session
``` ```
Parameters: | Attribute | Type | Required | Description |
| ---------- | ------- | -------- | -------- |
| `login` | string | yes | The username of the user|
| `email` | string | yes if login is not provided | The email of the user |
| `password` | string | yes | The password of the user |
- `login` (required) - The login of user ```bash
- `email` (required if login missing) - The email of user curl -X POST "https://gitlab.example.com/api/v3/session?login=john_smith&password=strongpassw0rd"
- `password` (required) - Valid password ```
**You can login with both GitLab and LDAP credentials now**
Example response:
```json ```json
{ {
"id": 1,
"username": "john_smith",
"email": "john@example.com",
"name": "John Smith", "name": "John Smith",
"private_token": "dd34asd13as", "username": "john_smith",
"blocked": false, "id": 32,
"created_at": "2012-05-23T08:00:58Z", "state": "active",
"avatar_url": null,
"created_at": "2015-01-29T21:07:19.440Z",
"is_admin": true,
"bio": null, "bio": null,
"skype": "", "skype": "",
"linkedin": "", "linkedin": "",
"twitter": "", "twitter": "",
"website_url": "", "website_url": "",
"dark_scheme": false, "email": "john@example.com",
"theme_id": 1, "theme_id": 1,
"is_admin": false, "color_scheme_id": 1,
"projects_limit": 10,
"current_sign_in_at": "2015-07-07T07:10:58.392Z",
"identities": [],
"can_create_group": true, "can_create_group": true,
"can_create_team": true, "can_create_project": true,
"can_create_project": true "two_factor_enabled": false,
"private_token": "9koXpg98eAheJpvBs5tK"
} }
``` ```
...@@ -16,7 +16,7 @@ Here, `%{issue_ref}` is a complex regular expression defined inside GitLab, that ...@@ -16,7 +16,7 @@ Here, `%{issue_ref}` is a complex regular expression defined inside GitLab, that
For example: For example:
``` ```
git commit -m "Awesome commit message (Fix #20, Fixes #21 and Closes group/otherproject#2). This commit is also related to #17 and fixes #18, #19 and https://gitlab.example.com/group/otherproject/issues/23." git commit -m "Awesome commit message (Fix #20, Fixes #21 and Closes group/otherproject#22). This commit is also related to #17 and fixes #18, #19 and https://gitlab.example.com/group/otherproject/issues/23."
``` ```
will close `#18`, `#19`, `#20`, and `#21` in the project this commit is pushed to, as well as `#22` and `#23` in group/otherproject. `#17` won't be closed as it does not match the pattern. It also works with multiline commit messages. will close `#18`, `#19`, `#20`, and `#21` in the project this commit is pushed to, as well as `#22` and `#23` in group/otherproject. `#17` won't be closed as it does not match the pattern. It also works with multiline commit messages.
......
...@@ -120,6 +120,17 @@ Inside the document: ...@@ -120,6 +120,17 @@ Inside the document:
`http://doc.gitlab.com/ce/administration/restart_gitlab.html`. `http://doc.gitlab.com/ce/administration/restart_gitlab.html`.
Replace `reconfigure` with `restart` where appropriate. Replace `reconfigure` with `restart` where appropriate.
## Installation guide
- **Ruby:**
In [step 2 of the installation guide](../install/installation.md#2-ruby),
we install Ruby from source. Whenever there is a new version that needs to
be updated, remember to change it throughout the codeblock and also replace
the sha256sum (it can be found in the [downloads page][ruby-dl] of the Ruby
website).
[ruby-dl]: https://www.ruby-lang.org/en/downloads/ "Ruby download website"
## API ## API
Here is a list of must-have items. Use them in the exact order that appears Here is a list of must-have items. Use them in the exact order that appears
......
...@@ -124,7 +124,7 @@ Download Ruby and compile it: ...@@ -124,7 +124,7 @@ Download Ruby and compile it:
mkdir /tmp/ruby && cd /tmp/ruby mkdir /tmp/ruby && cd /tmp/ruby
curl -O --progress https://cache.ruby-lang.org/pub/ruby/2.2/ruby-2.2.4.tar.gz curl -O --progress https://cache.ruby-lang.org/pub/ruby/2.2/ruby-2.2.4.tar.gz
echo 'e2e195a4a58133e3ad33b955c829bb536fa3c075 ruby-2.2.4.tar.gz' | shasum -c - && tar xzf ruby-2.2.4.tar.gz echo 'b6eff568b48e0fda76e5a36333175df049b204e91217aa32a65153cc0cdcb761 ruby-2.2.4.tar.gz' | sha256sum -c - && tar xzf ruby-2.2.4.tar.gz
cd ruby-2.2.4 cd ruby-2.2.4
./configure --disable-install-rdoc ./configure --disable-install-rdoc
make make
...@@ -267,6 +267,9 @@ sudo usermod -aG redis git ...@@ -267,6 +267,9 @@ sudo usermod -aG redis git
sudo chmod -R u+rwX tmp/pids/ sudo chmod -R u+rwX tmp/pids/
sudo chmod -R u+rwX tmp/sockets/ sudo chmod -R u+rwX tmp/sockets/
# Create the public/uploads/ directory
sudo -u git -H mkdir public/uploads/
# Make sure GitLab can write to the public/uploads/ directory # Make sure GitLab can write to the public/uploads/ directory
sudo chmod -R u+rwX public/uploads sudo chmod -R u+rwX public/uploads
......
...@@ -47,7 +47,7 @@ module Gitlab ...@@ -47,7 +47,7 @@ module Gitlab
# new_path - new project path with namespace # new_path - new project path with namespace
# #
# Ex. # Ex.
# mv_repository("gitlab/gitlab-ci", "randx/gitlab-ci-new.git") # mv_repository("gitlab/gitlab-ci", "randx/gitlab-ci-new")
# #
def mv_repository(path, new_path) def mv_repository(path, new_path)
Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'mv-project', Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'mv-project',
......
module Gitlab module Gitlab
module Database module Database
def self.adapter_name
connection.adapter_name
end
def self.mysql? def self.mysql?
ActiveRecord::Base.connection.adapter_name.downcase == 'mysql2' adapter_name.downcase == 'mysql2'
end end
def self.postgresql? def self.postgresql?
ActiveRecord::Base.connection.adapter_name.downcase == 'postgresql' adapter_name.downcase == 'postgresql'
end
def self.version
database_version.match(/\A(?:PostgreSQL |)([^\s]+).*\z/)[1]
end end
def true_value def true_value
case ActiveRecord::Base.connection.adapter_name.downcase if self.class.postgresql?
when 'postgresql'
"'t'" "'t'"
else else
1 1
...@@ -18,12 +25,31 @@ module Gitlab ...@@ -18,12 +25,31 @@ module Gitlab
end end
def false_value def false_value
case ActiveRecord::Base.connection.adapter_name.downcase if self.class.postgresql?
when 'postgresql'
"'f'" "'f'"
else else
0 0
end end
end end
private
def self.connection
ActiveRecord::Base.connection
end
def self.database_version
row = connection.execute("SELECT VERSION()").first
if postgresql?
row['version']
else
row.first
end
end
def connection
self.class.connection
end
end end
end end
...@@ -34,12 +34,12 @@ module Gitlab ...@@ -34,12 +34,12 @@ module Gitlab
def project_path_regex def project_path_regex
@project_path_regex ||= /\A[a-zA-Z0-9_.][a-zA-Z0-9_\-\.]*(?<!\.git)\z/.freeze @project_path_regex ||= /\A[a-zA-Z0-9_.][a-zA-Z0-9_\-\.]*(?<!\.git|\.atom)\z/.freeze
end end
def project_path_regex_message def project_path_regex_message
"can contain only letters, digits, '_', '-' and '.'. " \ "can contain only letters, digits, '_', '-' and '.'. " \
"Cannot start with '-' or end in '.git'" \ "Cannot start with '-', end in '.git' or end in '.atom'" \
end end
......
...@@ -86,6 +86,14 @@ describe ProjectsController do ...@@ -86,6 +86,14 @@ describe ProjectsController do
end end
end end
end end
context "when the url contains .atom" do
let(:public_project_with_dot_atom) { build(:project, :public, name: 'my.atom', path: 'my.atom') }
it 'expect an error creating the project' do
expect(public_project_with_dot_atom).not_to be_valid
end
end
end end
describe "#destroy" do describe "#destroy" do
......
...@@ -18,7 +18,7 @@ describe 'Admin Builds' do ...@@ -18,7 +18,7 @@ describe 'Admin Builds' do
visit admin_builds_path visit admin_builds_path
expect(page).to have_selector('.project-issuable-filter li.active', text: 'All') expect(page).to have_selector('.nav-links li.active', text: 'All')
expect(page.all('.build-link').size).to eq(4) expect(page.all('.build-link').size).to eq(4)
expect(page).to have_link 'Cancel all' expect(page).to have_link 'Cancel all'
end end
...@@ -28,7 +28,7 @@ describe 'Admin Builds' do ...@@ -28,7 +28,7 @@ describe 'Admin Builds' do
it 'shows a message' do it 'shows a message' do
visit admin_builds_path visit admin_builds_path
expect(page).to have_selector('.project-issuable-filter li.active', text: 'All') expect(page).to have_selector('.nav-links li.active', text: 'All')
expect(page).to have_content 'No builds to show' expect(page).to have_content 'No builds to show'
expect(page).not_to have_link 'Cancel all' expect(page).not_to have_link 'Cancel all'
end end
...@@ -44,7 +44,7 @@ describe 'Admin Builds' do ...@@ -44,7 +44,7 @@ describe 'Admin Builds' do
visit admin_builds_path(scope: :running) visit admin_builds_path(scope: :running)
expect(page).to have_selector('.project-issuable-filter li.active', text: 'Running') expect(page).to have_selector('.nav-links li.active', text: 'Running')
expect(page.find('.build-link')).to have_content(build1.id) expect(page.find('.build-link')).to have_content(build1.id)
expect(page.find('.build-link')).not_to have_content(build2.id) expect(page.find('.build-link')).not_to have_content(build2.id)
expect(page.find('.build-link')).not_to have_content(build3.id) expect(page.find('.build-link')).not_to have_content(build3.id)
...@@ -58,7 +58,7 @@ describe 'Admin Builds' do ...@@ -58,7 +58,7 @@ describe 'Admin Builds' do
visit admin_builds_path(scope: :running) visit admin_builds_path(scope: :running)
expect(page).to have_selector('.project-issuable-filter li.active', text: 'Running') expect(page).to have_selector('.nav-links li.active', text: 'Running')
expect(page).to have_content 'No builds to show' expect(page).to have_content 'No builds to show'
expect(page).not_to have_link 'Cancel all' expect(page).not_to have_link 'Cancel all'
end end
...@@ -74,7 +74,7 @@ describe 'Admin Builds' do ...@@ -74,7 +74,7 @@ describe 'Admin Builds' do
visit admin_builds_path(scope: :finished) visit admin_builds_path(scope: :finished)
expect(page).to have_selector('.project-issuable-filter li.active', text: 'Finished') expect(page).to have_selector('.nav-links li.active', text: 'Finished')
expect(page.find('.build-link')).not_to have_content(build1.id) expect(page.find('.build-link')).not_to have_content(build1.id)
expect(page.find('.build-link')).not_to have_content(build2.id) expect(page.find('.build-link')).not_to have_content(build2.id)
expect(page.find('.build-link')).to have_content(build3.id) expect(page.find('.build-link')).to have_content(build3.id)
...@@ -88,7 +88,7 @@ describe 'Admin Builds' do ...@@ -88,7 +88,7 @@ describe 'Admin Builds' do
visit admin_builds_path(scope: :finished) visit admin_builds_path(scope: :finished)
expect(page).to have_selector('.project-issuable-filter li.active', text: 'Finished') expect(page).to have_selector('.nav-links li.active', text: 'Finished')
expect(page).to have_content 'No builds to show' expect(page).to have_content 'No builds to show'
expect(page).to have_link 'Cancel all' expect(page).to have_link 'Cancel all'
end end
......
...@@ -3,7 +3,7 @@ require 'spec_helper' ...@@ -3,7 +3,7 @@ require 'spec_helper'
describe LabelsHelper do describe LabelsHelper do
describe 'link_to_label' do describe 'link_to_label' do
let(:project) { create(:empty_project) } let(:project) { create(:empty_project) }
let(:label) { create(:label, project: project) } let(:label) { create(:label, project: project) }
context 'with @project set' do context 'with @project set' do
before do before do
...@@ -11,34 +11,31 @@ describe LabelsHelper do ...@@ -11,34 +11,31 @@ describe LabelsHelper do
end end
it 'uses the instance variable' do it 'uses the instance variable' do
expect(label).not_to receive(:project) expect(link_to_label(label)).to match %r{<a href="/#{@project.to_reference}/issues\?label_name=#{label.name}">.*</a>}
link_to_label(label)
end end
end end
context 'without @project set' do context 'without @project set' do
it "uses the label's project" do it "uses the label's project" do
expect(label).to receive(:project).and_return(project) expect(link_to_label(label)).to match %r{<a href="/#{label.project.to_reference}/issues\?label_name=#{label.name}">.*</a>}
link_to_label(label)
end end
end end
context 'with a named project argument' do context 'with a project argument' do
it 'uses the provided project' do let(:another_project) { double('project', namespace: 'foo3', to_param: 'bar3') }
arg = double('project')
expect(arg).to receive(:namespace).and_return('foo')
expect(arg).to receive(:to_param).and_return('foo')
link_to_label(label, project: arg) it 'links to merge requests page' do
expect(link_to_label(label, project: another_project)).to match %r{<a href="/foo3/bar3/issues\?label_name=#{label.name}">.*</a>}
end end
end
it 'takes precedence over other types' do context 'with a type argument' do
@project = project ['issue', :issue, 'merge_request', :merge_request].each do |type|
expect(@project).not_to receive(:namespace) context "set to #{type}" do
expect(label).not_to receive(:project) it 'links to correct page' do
expect(link_to_label(label, type: type)).to match %r{<a href="/#{label.project.to_reference}/#{type.to_s.pluralize}\?label_name=#{label.name}">.*</a>}
arg = double('project', namespace: 'foo', to_param: 'foo') end
link_to_label(label, project: arg) end
end end
end end
......
...@@ -42,9 +42,9 @@ describe SearchHelper do ...@@ -42,9 +42,9 @@ describe SearchHelper do
expect(search_autocomplete_opts(project.name).size).to eq(1) expect(search_autocomplete_opts(project.name).size).to eq(1)
end end
it "includes the public group" do it "should not include the public group" do
group = create(:group) group = create(:group)
expect(search_autocomplete_opts(group.name).size).to eq(1) expect(search_autocomplete_opts(group.name).size).to eq(0)
end end
context "with a current project" do context "with a current project" do
......
...@@ -14,4 +14,24 @@ describe Gitlab::Database, lib: true do ...@@ -14,4 +14,24 @@ describe Gitlab::Database, lib: true do
it { is_expected.to satisfy { |val| val == true || val == false } } it { is_expected.to satisfy { |val| val == true || val == false } }
end end
describe '.version' do
context "on mysql" do
it "extracts the version number" do
allow(described_class).to receive(:database_version).
and_return("5.7.12-standard")
expect(described_class.version).to eq '5.7.12-standard'
end
end
context "on postgresql" do
it "extracts the version number" do
allow(described_class).to receive(:database_version).
and_return("PostgreSQL 9.4.4 on x86_64-apple-darwin14.3.0")
expect(described_class.version).to eq '9.4.4'
end
end
end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment