Commit 17ab3730 authored by Robert Speicher's avatar Robert Speicher

Merge branch 'master' into 8-9-stable

parents 6c23107d 8d243f9b
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
v 8.9.0 (unreleased) v 8.9.0 (unreleased)
- Fix Error 500 when using closes_issues API with an external issue tracker
- Bulk assign/unassign labels to issues. - Bulk assign/unassign labels to issues.
- Ability to prioritize labels !4009 / !3205 (Thijs Wouters) - Ability to prioritize labels !4009 / !3205 (Thijs Wouters)
- Fix endless redirections when accessing user OAuth applications when they are disabled - Fix endless redirections when accessing user OAuth applications when they are disabled
- Allow enabling wiki page events from Webhook management UI - Allow enabling wiki page events from Webhook management UI
- Bump rouge to 1.11.0 - Bump rouge to 1.11.0
- Fix issue with arrow keys not working in search autocomplete dropdown
- Make EmailsOnPushWorker use Sidekiq mailers queue - Make EmailsOnPushWorker use Sidekiq mailers queue
- Fix wiki page events' webhook to point to the wiki repository - Fix wiki page events' webhook to point to the wiki repository
- Fix issue todo not remove when leave project !4150 (Long Nguyen) - Fix issue todo not remove when leave project !4150 (Long Nguyen)
...@@ -55,6 +57,7 @@ v 8.9.0 (unreleased) ...@@ -55,6 +57,7 @@ v 8.9.0 (unreleased)
- Remove duplicated notification settings - Remove duplicated notification settings
- Put project Files and Commits tabs under Code tab - Put project Files and Commits tabs under Code tab
- Replace Colorize with Rainbow for coloring console output in Rake tasks. - Replace Colorize with Rainbow for coloring console output in Rake tasks.
- Add workhorse controller and API helpers
- An indicator is now displayed at the top of the comment field for confidential issues. - An indicator is now displayed at the top of the comment field for confidential issues.
- RepositoryCheck::SingleRepositoryWorker public and private methods are now instrumented - RepositoryCheck::SingleRepositoryWorker public and private methods are now instrumented
- Improve issuables APIs performance when accessing notes !4471 - Improve issuables APIs performance when accessing notes !4471
...@@ -63,10 +66,10 @@ v 8.9.0 (unreleased) ...@@ -63,10 +66,10 @@ v 8.9.0 (unreleased)
- Toggling a task list item in a issue/mr description does not creates a Todo for mentions - Toggling a task list item in a issue/mr description does not creates a Todo for mentions
- Improved UX of date pickers on issue & milestone forms - Improved UX of date pickers on issue & milestone forms
- Cache on the database if a project has an active external issue tracker. - Cache on the database if a project has an active external issue tracker.
- Put project Labels and Milestones pages links under Issues and Merge Requests tabs as subnav
v 8.8.5 (unreleased) v 8.8.5 (unreleased)
- Ensure branch cleanup regardless of whether the GitHub import process succeeds - Ensure branch cleanup regardless of whether the GitHub import process succeeds
- Fix issue with arrow keys not working in search autocomplete dropdown
- Fix todos page throwing errors when you have a project pending deletion - Fix todos page throwing errors when you have a project pending deletion
- Reduce number of SQL queries when rendering user references - Reduce number of SQL queries when rendering user references
- Import GitHub repositories respecting the API rate limit - Import GitHub repositories respecting the API rate limit
......
...@@ -162,19 +162,6 @@ $ -> ...@@ -162,19 +162,6 @@ $ ->
$el.data('placement') || 'bottom' $el.data('placement') || 'bottom'
) )
$('.header-logo .home').tooltip(
placement: (_, el) ->
$el = $(el)
if $('.page-with-sidebar').hasClass('page-sidebar-collapsed') then 'right' else 'bottom'
container: 'body'
)
$('.page-with-sidebar').tooltip(
selector: '.sidebar-collapsed .nav-sidebar a, .sidebar-collapsed a.sidebar-user'
placement: 'right'
container: 'body'
)
# Form submitter # Form submitter
$('.trigger-submit').on 'change', -> $('.trigger-submit').on 'change', ->
$(@).parents('form').submit() $(@).parents('form').submit()
...@@ -207,6 +194,7 @@ $ -> ...@@ -207,6 +194,7 @@ $ ->
$('.navbar-toggle').on 'click', -> $('.navbar-toggle').on 'click', ->
$('.header-content .title').toggle() $('.header-content .title').toggle()
$('.header-content .header-logo').toggle()
$('.header-content .navbar-collapse').toggle() $('.header-content .navbar-collapse').toggle()
$('.navbar-toggle').toggleClass('active') $('.navbar-toggle').toggleClass('active')
$('.navbar-toggle i').toggleClass("fa-angle-right fa-angle-left") $('.navbar-toggle i').toggleClass("fa-angle-right fa-angle-left")
...@@ -241,7 +229,6 @@ $ -> ...@@ -241,7 +229,6 @@ $ ->
$this.attr 'value', $this.val() $this.attr 'value', $this.val()
$sidebarGutterToggle = $('.js-sidebar-toggle') $sidebarGutterToggle = $('.js-sidebar-toggle')
$navIconToggle = $('.toggle-nav-collapse')
$(document) $(document)
.off 'breakpoint:change' .off 'breakpoint:change'
...@@ -251,10 +238,6 @@ $ -> ...@@ -251,10 +238,6 @@ $ ->
if $gutterIcon.hasClass('fa-angle-double-right') if $gutterIcon.hasClass('fa-angle-double-right')
$sidebarGutterToggle.trigger('click') $sidebarGutterToggle.trigger('click')
$navIcon = $navIconToggle.find('.fa')
if $navIcon.hasClass('fa-angle-left')
$navIconToggle.trigger('click')
fitSidebarForSize = -> fitSidebarForSize = ->
oldBootstrapBreakpoint = bootstrapBreakpoint oldBootstrapBreakpoint = bootstrapBreakpoint
bootstrapBreakpoint = bp.getBreakpointSize() bootstrapBreakpoint = bp.getBreakpointSize()
......
...@@ -47,4 +47,4 @@ $ -> ...@@ -47,4 +47,4 @@ $ ->
# Make logo clickable as part of a workaround for Safari visited # Make logo clickable as part of a workaround for Safari visited
# link behaviour (See !2690). # link behaviour (See !2690).
$('#logo').on 'click', -> $('#logo').on 'click', ->
$('#js-shortcuts-home').get(0).click() Turbolinks.visit('/')
...@@ -4,8 +4,6 @@ expanded = 'page-sidebar-expanded' ...@@ -4,8 +4,6 @@ expanded = 'page-sidebar-expanded'
toggleSidebar = -> toggleSidebar = ->
$('.page-with-sidebar').toggleClass("#{collapsed} #{expanded}") $('.page-with-sidebar').toggleClass("#{collapsed} #{expanded}")
$('header').toggleClass("header-collapsed header-expanded") $('header').toggleClass("header-collapsed header-expanded")
$('.toggle-nav-collapse i').toggleClass("fa-angle-right fa-angle-left")
$.cookie("collapsed_nav", $('.page-with-sidebar').hasClass(collapsed), { path: '/' })
setTimeout ( -> setTimeout ( ->
niceScrollBars = $('.nicescroll').niceScroll(); niceScrollBars = $('.nicescroll').niceScroll();
...@@ -17,10 +15,3 @@ $(document).on("click", '.toggle-nav-collapse, .side-nav-toggle', (e) -> ...@@ -17,10 +15,3 @@ $(document).on("click", '.toggle-nav-collapse, .side-nav-toggle', (e) ->
toggleSidebar() toggleSidebar()
) )
$ ->
size = bp.getBreakpointSize()
if size is "xs" or size is "sm"
if $('.page-with-sidebar').hasClass(expanded)
toggleSidebar()
...@@ -8,34 +8,16 @@ ...@@ -8,34 +8,16 @@
*/ */
@mixin gitlab-theme($color-light, $color, $color-darker, $color-dark) { @mixin gitlab-theme($color-light, $color, $color-darker, $color-dark) {
.page-with-sidebar { .page-with-sidebar {
.header-logo {
background: $color-darker;
a { .collapse-nav a {
color: $color-light; color: $color-light;
background: $color;
h3 {
color: $color-light;
}
}
&:hover { &:hover {
background-color: $color-dark; color: $white-light;
a {
color: $white-light;
h3 {
color: $white-light;
}
}
} }
} }
.collapse-nav a {
color: $white-light;
background: $color;
}
.sidebar-wrapper { .sidebar-wrapper {
background: $color-darker; background: $color-darker;
......
...@@ -109,10 +109,8 @@ header { ...@@ -109,10 +109,8 @@ header {
position: relative; position: relative;
height: $header-height; height: $header-height;
padding-right: 40px; padding-right: 40px;
padding-left: 30px;
@media (max-width: $screen-xs-min) { transition-duration: .3s;
padding-left: 40px;
}
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
padding-right: 0; padding-right: 0;
...@@ -122,9 +120,29 @@ header { ...@@ -122,9 +120,29 @@ header {
margin-top: -5px; margin-top: -5px;
} }
.header-logo {
position: absolute;
left: 50%;
margin-left: -18px;
top: 7px;
transition-duration: .3s;
z-index: 999;
&:hover {
cursor: pointer;
}
@media (max-width: $screen-xs-max) {
right: 25px;
left: auto;
}
}
.title { .title {
margin: 0; margin: 0;
font-size: 19px; font-size: 19px;
max-width: 400px;
display: inline-block;
line-height: $header-height; line-height: $header-height;
font-weight: normal; font-weight: normal;
color: $gl-text-color; color: $gl-text-color;
...@@ -133,6 +151,10 @@ header { ...@@ -133,6 +151,10 @@ header {
vertical-align: top; vertical-align: top;
white-space: nowrap; white-space: nowrap;
@media (max-width: $screen-sm-max) {
max-width: 190px;
}
a { a {
color: $gl-text-color; color: $gl-text-color;
&:hover { &:hover {
...@@ -160,6 +182,10 @@ header { ...@@ -160,6 +182,10 @@ header {
.navbar-collapse { .navbar-collapse {
float: right; float: right;
border-top: none; border-top: none;
@media (max-width: $screen-xs-max) {
float: none;
}
} }
} }
...@@ -176,17 +202,20 @@ header { ...@@ -176,17 +202,20 @@ header {
margin-left: 0; margin-left: 0;
.header-content { .header-content {
padding-left: 30px;
transition-duration: .3s; @media (min-width: $screen-sm-max) {
padding-left: 30px;
transition-duration: .3s;
}
} }
} }
.header-expanded { .tanuki-shape {
margin-left: 0; transition: all 0.8s;
.header-content { &:hover, &.highlight {
margin-left: $sidebar_width; fill: rgb(255, 255, 255);
transition-duration: .3s; transition: all 0.1s;
} }
} }
......
@mixin fade($gradient-direction, $rgba, $gradient-color) { @mixin fade($gradient-direction, $rgba, $gradient-color) {
visibility: visible; visibility: visible;
opacity: 1; opacity: 1;
z-index: 2;
position: absolute; position: absolute;
bottom: 12px; bottom: 12px;
width: 43px; width: 43px;
...@@ -68,6 +69,7 @@ ...@@ -68,6 +69,7 @@
} }
&.sub-nav { &.sub-nav {
text-align: center;
background-color: $background-color; background-color: $background-color;
.container-fluid { .container-fluid {
...@@ -171,7 +173,6 @@ ...@@ -171,7 +173,6 @@
> form { > form {
display: inline-block; display: inline-block;
margin-top: -1px; margin-top: -1px;
margin-bottom: 12px;
} }
.icon-label { .icon-label {
...@@ -250,6 +251,7 @@ ...@@ -250,6 +251,7 @@
background: $background-color; background: $background-color;
border-bottom: 1px solid $border-color; border-bottom: 1px solid $border-color;
transition-duration: .3s; transition-duration: .3s;
text-align: center;
.container-fluid { .container-fluid {
position: relative; position: relative;
...@@ -352,7 +354,7 @@ ...@@ -352,7 +354,7 @@
.fade-right { .fade-right {
@media (min-width: $screen-xs-max) { @media (min-width: $screen-xs-max) {
right: 67px; right: 68px;
} }
@media (max-width: $screen-xs-min) { @media (max-width: $screen-xs-min) {
right: 0; right: 0;
......
...@@ -35,24 +35,11 @@ ...@@ -35,24 +35,11 @@
} }
.sidebar-wrapper { .sidebar-wrapper {
.header-logo {
height: $header-height;
padding: 8px 26px;
width: $sidebar_width;
position: fixed;
z-index: 999;
overflow: hidden;
transition-duration: .3s;
&:hover {
background-color: #eee;
}
}
.sidebar-user { .sidebar-user {
padding: 15px 22px; padding: 15px 22px;
position: fixed; position: fixed;
bottom: 40px; bottom: 0;
width: $sidebar_width; width: $sidebar_width;
overflow: hidden; overflow: hidden;
transition-duration: .3s; transition-duration: .3s;
...@@ -97,10 +84,10 @@ ...@@ -97,10 +84,10 @@
} }
a { a {
text-align: center; width: $sidebar_width;
padding: 8px; padding: 7px 15px 7px 23px;
font-size: $gl-font-size; font-size: $gl-font-size;
color: $gray; line-height: 24px;
display: block; display: block;
text-decoration: none; text-decoration: none;
font-weight: normal; font-weight: normal;
...@@ -118,10 +105,9 @@ ...@@ -118,10 +105,9 @@
font-size: 16px; font-size: 16px;
} }
.nav-link-text { i,
margin-top: 3px; svg {
font-size: 13px; margin-right: 13px;
line-height: 18px;
} }
&.back-link i { &.back-link i {
...@@ -129,6 +115,12 @@ ...@@ -129,6 +115,12 @@
} }
} }
} }
.count {
float: right;
padding: 0 8px;
@include border-radius(6px);
}
} }
.sidebar-subnav { .sidebar-subnav {
...@@ -143,11 +135,12 @@ ...@@ -143,11 +135,12 @@
.collapse-nav a { .collapse-nav a {
width: $sidebar_width; width: $sidebar_width;
position: fixed; position: fixed;
bottom: 0; top: 0;
left: 0; left: 0;
font-size: 13px; padding: 5px 0;
font-size: 18px;
background: transparent; background: transparent;
height: 40px; height: 50px;
text-align: center; text-align: center;
line-height: 40px; line-height: 40px;
transition-duration: .3s; transition-duration: .3s;
...@@ -170,25 +163,8 @@ ...@@ -170,25 +163,8 @@
.sidebar-wrapper { .sidebar-wrapper {
width: 0; width: 0;
.header-logo {
width: 0;
padding: 8px 0;
a {
padding-left: ($sidebar_collapsed_width - 36) / 2;
.gitlab-text-container {
display: none;
}
}
}
#logo {
display: none;
}
.nav-sidebar { .nav-sidebar {
width: $sidebar_collapsed_width; width: 0;
li { li {
width: auto; width: auto;
...@@ -203,6 +179,10 @@ ...@@ -203,6 +179,10 @@
.collapse-nav a { .collapse-nav a {
width: 0; width: 0;
i {
display: none;
}
} }
.sidebar-user { .sidebar-user {
...@@ -218,9 +198,8 @@ ...@@ -218,9 +198,8 @@
} }
.page-sidebar-expanded { .page-sidebar-expanded {
padding-left: $sidebar_width;
@media (max-width: $screen-xs-min) { @media (max-width: $screen-sm-max) {
padding-left: 0; padding-left: 0;
} }
...@@ -241,20 +220,6 @@ ...@@ -241,20 +220,6 @@
} }
} }
} }
.layout-nav {
@media (max-width: $screen-xs-min) {
padding-right: 0;
}
@media (min-width: $screen-xs-min) and (max-width: $screen-md-min) {
padding-right: 90px;
}
@media (min-width: $screen-md-min) {
padding-right: $sidebar_width;
}
}
} }
.right-sidebar-collapsed { .right-sidebar-collapsed {
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
* Layout * Layout
*/ */
$sidebar_collapsed_width: 62px; $sidebar_collapsed_width: 62px;
$sidebar_width: 90px; $sidebar_width: 220px;
$gutter_collapsed_width: 62px; $gutter_collapsed_width: 62px;
$gutter_width: 290px; $gutter_width: 290px;
$gutter_inner_width: 258px; $gutter_inner_width: 258px;
......
...@@ -6,6 +6,7 @@ class ApplicationController < ActionController::Base ...@@ -6,6 +6,7 @@ class ApplicationController < ActionController::Base
include Gitlab::GonHelper include Gitlab::GonHelper
include GitlabRoutingHelper include GitlabRoutingHelper
include PageLayoutHelper include PageLayoutHelper
include WorkhorseHelper
before_action :authenticate_user_from_token! before_action :authenticate_user_from_token!
before_action :authenticate_user! before_action :authenticate_user!
......
...@@ -10,10 +10,7 @@ class Projects::AvatarsController < Projects::ApplicationController ...@@ -10,10 +10,7 @@ class Projects::AvatarsController < Projects::ApplicationController
return if cached_blob? return if cached_blob?
headers.store(*Gitlab::Workhorse.send_git_blob(@repository, @blob)) send_git_blob @repository, @blob
headers['Content-Disposition'] = 'inline'
headers['Content-Type'] = safe_content_type(@blob)
head :ok # 'render nothing: true' messes up the Content-Type
else else
render_404 render_404
end end
......
...@@ -61,12 +61,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -61,12 +61,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController
format.json { render json: @merge_request } format.json { render json: @merge_request }
format.patch { render text: @merge_request.to_patch } format.patch { render text: @merge_request.to_patch }
format.diff do format.diff do
headers.store(*Gitlab::Workhorse.send_git_diff(@project.repository, return render_404 unless @merge_request.diff_refs
@merge_request.diff_base_commit.id,
@merge_request.last_commit.id))
headers['Content-Disposition'] = 'inline'
head :ok send_git_diff @project.repository, @merge_request.diff_refs
end end
end end
end end
......
...@@ -18,10 +18,7 @@ class Projects::RawController < Projects::ApplicationController ...@@ -18,10 +18,7 @@ class Projects::RawController < Projects::ApplicationController
if @blob.lfs_pointer? if @blob.lfs_pointer?
send_lfs_object send_lfs_object
else else
headers.store(*Gitlab::Workhorse.send_git_blob(@repository, @blob)) send_git_blob @repository, @blob
headers['Content-Disposition'] = 'inline'
headers['Content-Type'] = safe_content_type(@blob)
head :ok # 'render nothing: true' messes up the Content-Type
end end
else else
render_404 render_404
......
...@@ -11,8 +11,7 @@ class Projects::RepositoriesController < Projects::ApplicationController ...@@ -11,8 +11,7 @@ class Projects::RepositoriesController < Projects::ApplicationController
end end
def archive def archive
headers.store(*Gitlab::Workhorse.send_git_archive(@project, params[:ref], params[:format])) send_git_archive @repository, ref: params[:ref], format: params[:format]
head :ok
rescue => ex rescue => ex
logger.error("#{self.class.name}: #{ex}") logger.error("#{self.class.name}: #{ex}")
return git_not_found! return git_not_found!
......
...@@ -36,13 +36,7 @@ module NavHelper ...@@ -36,13 +36,7 @@ module NavHelper
end end
def nav_header_class def nav_header_class
class_name = class_name = " with-horizontal-nav" if defined?(nav) && nav
if nav_menu_collapsed?
"header-collapsed"
else
"header-expanded"
end
class_name += " with-horizontal-nav" if defined?(nav) && nav
class_name class_name
end end
......
# Helpers to send Git blobs, diffs or archives through Workhorse.
# Workhorse will also serve files when using `send_file`.
module WorkhorseHelper
# Send a Git blob through Workhorse
def send_git_blob(repository, blob)
headers.store(*Gitlab::Workhorse.send_git_blob(repository, blob))
headers['Content-Disposition'] = 'inline'
headers['Content-Type'] = safe_content_type(blob)
head :ok # 'render nothing: true' messes up the Content-Type
end
# Send a Git diff through Workhorse
def send_git_diff(repository, diff_refs)
headers.store(*Gitlab::Workhorse.send_git_diff(repository, diff_refs))
headers['Content-Disposition'] = 'inline'
head :ok
end
# Archive a Git repository and send it through Workhorse
def send_git_archive(repository, ref:, format:)
headers.store(*Gitlab::Workhorse.send_git_archive(repository, ref: ref, format: format))
head :ok
end
end
...@@ -276,14 +276,6 @@ class MergeRequest < ActiveRecord::Base ...@@ -276,14 +276,6 @@ class MergeRequest < ActiveRecord::Base
true true
end end
def gitlab_merge_status
if work_in_progress?
"work_in_progress"
else
merge_status_name
end
end
def can_cancel_merge_when_build_succeeds?(current_user) def can_cancel_merge_when_build_succeeds?(current_user)
can_be_merged_by?(current_user) || self.author == current_user can_be_merged_by?(current_user) || self.author == current_user
end end
......
...@@ -253,20 +253,69 @@ class Project < ActiveRecord::Base ...@@ -253,20 +253,69 @@ class Project < ActiveRecord::Base
non_archived.where(table[:name].matches(pattern)) non_archived.where(table[:name].matches(pattern))
end end
def find_with_namespace(id) # Finds a single project for the given path.
namespace_path, project_path = id.split('/', 2) #
# path - The full project path (including namespace path).
#
# Returns a Project, or nil if no project could be found.
def find_with_namespace(path)
where_paths_in([path]).reorder(nil).take
end
return nil if !namespace_path || !project_path # Builds a relation to find multiple projects by their full paths.
#
# Each path must be in the following format:
#
# namespace_path/project_path
#
# For example:
#
# gitlab-org/gitlab-ce
#
# Usage:
#
# Project.where_paths_in(%w{gitlab-org/gitlab-ce gitlab-org/gitlab-ee})
#
# This would return the projects with the full paths matching the values
# given.
#
# paths - An Array of full paths (namespace path + project path) for which
# to find the projects.
#
# Returns an ActiveRecord::Relation.
def where_paths_in(paths)
wheres = []
cast_lower = Gitlab::Database.postgresql?
paths.each do |path|
namespace_path, project_path = path.split('/', 2)
next unless namespace_path && project_path
namespace_path = connection.quote(namespace_path)
project_path = connection.quote(project_path)
where = "(namespaces.path = #{namespace_path}
AND projects.path = #{project_path})"
if cast_lower
where = "(
#{where}
OR (
LOWER(namespaces.path) = LOWER(#{namespace_path})
AND LOWER(projects.path) = LOWER(#{project_path})
)
)"
end
# Use of unscoped ensures we're not secretly adding any ORDER BYs, which wheres << where
# have a negative impact on performance (and aren't needed for this end
# query).
projects = unscoped.
joins(:namespace).
iwhere('namespaces.path' => namespace_path)
projects.find_by('projects.path' => project_path) || if wheres.empty?
projects.iwhere('projects.path' => project_path).take none
else
joins(:namespace).where(wheres.join(' OR '))
end
end end
def visibility_levels def visibility_levels
......
- if nav_menu_collapsed? = link_to icon('bars'), '#', class: 'toggle-nav-collapse', title: "Open/Close"
= link_to icon('angle-right'), '#', class: 'toggle-nav-collapse', title: "Open/Close"
- else
= link_to icon('angle-left'), '#', class: 'toggle-nav-collapse', title: "Open/Close"
.page-with-sidebar{ class: "#{page_sidebar_class} #{page_gutter_class}" } .page-with-sidebar.page-sidebar-collapsed{ class: "#{page_gutter_class}" }
.sidebar-wrapper.nicescroll{ class: nav_sidebar_class } .sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
= link_to root_path, class: 'gitlab-text-container-link', title: 'Dashboard', id: 'js-shortcuts-home' do
.header-logo
#logo
= brand_header_logo
- if defined?(sidebar) && sidebar - if defined?(sidebar) && sidebar
= render "layouts/nav/#{sidebar}" = render "layouts/nav/#{sidebar}"
...@@ -16,7 +12,9 @@ ...@@ -16,7 +12,9 @@
= render partial: 'layouts/collapse_button' = render partial: 'layouts/collapse_button'
- if current_user - if current_user
= link_to current_user, class: 'sidebar-user', title: "Profile", data: {user: current_user.username} do = link_to current_user, class: 'sidebar-user', title: "Profile", data: {user: current_user.username} do
= image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s46' = image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s36'
.username
= current_user.username
- if defined?(nav) && nav - if defined?(nav) && nav
.layout-nav .layout-nav
.container-fluid .container-fluid
......
.page-with-sidebar{ class: page_sidebar_class } .page-with-sidebar{ class: page_sidebar_class }
= render "layouts/broadcast" = render "layouts/broadcast"
.sidebar-wrapper.nicescroll{ class: nav_sidebar_class } .sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
.header-logo
%a#logo
= brand_header_logo
= link_to root_path, class: 'gitlab-text-container-link', title: 'Dashboard', id: 'js-shortcuts-home' do
.gitlab-text-container
%h3 GitLab
- if defined?(sidebar) && sidebar - if defined?(sidebar) && sidebar
= render "layouts/ci/#{sidebar}" = render "layouts/ci/#{sidebar}"
......
%header.navbar.navbar-fixed-top.navbar-gitlab{ class: nav_header_class } %header.navbar.navbar-fixed-top.navbar-gitlab.header-collapsed{ class: nav_header_class }
%div{ class: fluid_layout ? "container-fluid" : "container-fluid" } %div{ class: fluid_layout ? "container-fluid" : "container-fluid" }
.header-content .header-content
%button.side-nav-toggle{type: 'button'} %button.side-nav-toggle{type: 'button'}
...@@ -50,6 +50,10 @@ ...@@ -50,6 +50,10 @@
%h1.title= title %h1.title= title
.header-logo
#logo
= brand_header_logo
= yield :header_content = yield :header_content
= render 'shared/outdated_browser' = render 'shared/outdated_browser'
......
...@@ -2,102 +2,106 @@ ...@@ -2,102 +2,106 @@
= nav_link(controller: :dashboard, html_options: {class: 'home'}) do = nav_link(controller: :dashboard, html_options: {class: 'home'}) do
= link_to admin_root_path, title: 'Overview' do = link_to admin_root_path, title: 'Overview' do
= icon('dashboard fw') = icon('dashboard fw')
.nav-link-text %span
Overview Overview
= nav_link(controller: [:admin, :projects]) do = nav_link(controller: [:admin, :projects]) do
= link_to admin_namespaces_projects_path, title: 'Projects' do = link_to admin_namespaces_projects_path, title: 'Projects' do
= icon('cube fw') = icon('cube fw')
.nav-link-text %span
Projects Projects
= nav_link(controller: :users) do = nav_link(controller: :users) do
= link_to admin_users_path, title: 'Users' do = link_to admin_users_path, title: 'Users' do
= icon('user fw') = icon('user fw')
.nav-link-text %span
Users Users
= nav_link(controller: :groups) do = nav_link(controller: :groups) do
= link_to admin_groups_path, title: 'Groups' do = link_to admin_groups_path, title: 'Groups' do
= icon('group fw') = icon('group fw')
.nav-link-text %span
Groups Groups
= nav_link(controller: :deploy_keys) do = nav_link(controller: :deploy_keys) do
= link_to admin_deploy_keys_path, title: 'Deploy Keys' do = link_to admin_deploy_keys_path, title: 'Deploy Keys' do
= icon('key fw') = icon('key fw')
.nav-link-text %span
Deploy Keys Deploy Keys
= nav_link path: ['runners#index', 'runners#show'] do = nav_link path: ['runners#index', 'runners#show'] do
= link_to admin_runners_path, title: 'Runners' do = link_to admin_runners_path, title: 'Runners' do
= icon('cog fw') = icon('cog fw')
.nav-link-text %span
Runners Runners
%span.count= number_with_delimiter(Ci::Runner.count(:all))
= nav_link path: 'builds#index' do = nav_link path: 'builds#index' do
= link_to admin_builds_path, title: 'Builds' do = link_to admin_builds_path, title: 'Builds' do
= icon('link fw') = icon('link fw')
.nav-link-text %span
Builds Builds
%span.count= number_with_delimiter(Ci::Build.count(:all))
= nav_link(controller: :logs) do = nav_link(controller: :logs) do
= link_to admin_logs_path, title: 'Logs' do = link_to admin_logs_path, title: 'Logs' do
= icon('file-text fw') = icon('file-text fw')
.nav-link-text %span
Logs Logs
= nav_link(controller: :health_check) do = nav_link(controller: :health_check) do
= link_to admin_health_check_path, title: 'Health Check' do = link_to admin_health_check_path, title: 'Health Check' do
= icon('medkit fw') = icon('medkit fw')
.nav-link-text %span
Health Check Health Check
= nav_link(controller: :broadcast_messages) do = nav_link(controller: :broadcast_messages) do
= link_to admin_broadcast_messages_path, title: 'Messages' do = link_to admin_broadcast_messages_path, title: 'Messages' do
= icon('bullhorn fw') = icon('bullhorn fw')
.nav-link-text %span
Messages Messages
= nav_link(controller: :hooks) do = nav_link(controller: :hooks) do
= link_to admin_hooks_path, title: 'Hooks' do = link_to admin_hooks_path, title: 'Hooks' do
= icon('external-link fw') = icon('external-link fw')
.nav-link-text %span
Hooks Hooks
= nav_link(controller: :background_jobs) do = nav_link(controller: :background_jobs) do
= link_to admin_background_jobs_path, title: 'Background Jobs' do = link_to admin_background_jobs_path, title: 'Background Jobs' do
= icon('cog fw') = icon('cog fw')
.nav-link-text %span
Background Jobs Background Jobs
= nav_link(controller: :appearances) do = nav_link(controller: :appearances) do
= link_to admin_appearances_path, title: 'Appearances' do = link_to admin_appearances_path, title: 'Appearances' do
= icon('image') = icon('image')
.nav-link-text %span
Appearance Appearance
= nav_link(controller: :applications) do = nav_link(controller: :applications) do
= link_to admin_applications_path, title: 'Applications' do = link_to admin_applications_path, title: 'Applications' do
= icon('cloud fw') = icon('cloud fw')
.nav-link-text %span
Applications Applications
= nav_link(controller: :services) do = nav_link(controller: :services) do
= link_to admin_application_settings_services_path, title: 'Service Templates' do = link_to admin_application_settings_services_path, title: 'Service Templates' do
= icon('copy fw') = icon('copy fw')
.nav-link-text %span
Service Templates Service Templates
= nav_link(controller: :labels) do = nav_link(controller: :labels) do
= link_to admin_labels_path, title: 'Labels' do = link_to admin_labels_path, title: 'Labels' do
= icon('tags fw') = icon('tags fw')
.nav-link-text %span
Labels Labels
= nav_link(controller: :abuse_reports) do = nav_link(controller: :abuse_reports) do
= link_to admin_abuse_reports_path, title: "Abuse Reports" do = link_to admin_abuse_reports_path, title: "Abuse Reports" do
= icon('exclamation-circle fw') = icon('exclamation-circle fw')
.nav-link-text %span
Abuse Reports Abuse Reports
%span.count= number_with_delimiter(AbuseReport.count(:all))
- if askimet_enabled? - if askimet_enabled?
= nav_link(controller: :spam_logs) do = nav_link(controller: :spam_logs) do
= link_to admin_spam_logs_path, title: "Spam Logs" do = link_to admin_spam_logs_path, title: "Spam Logs" do
= icon('exclamation-triangle fw') = icon('exclamation-triangle fw')
.nav-link-text %span
Spam Logs Spam Logs
%span.count= number_with_delimiter(SpamLog.count(:all))
= nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do = nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do
= link_to admin_application_settings_path, title: 'Settings' do = link_to admin_application_settings_path, title: 'Settings' do
= icon('cogs fw') = icon('cogs fw')
.nav-link-text %span
Settings Settings
...@@ -2,50 +2,53 @@ ...@@ -2,50 +2,53 @@
= nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: "#{project_tab_class} home"}) do = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: "#{project_tab_class} home"}) do
= link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do = link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do
= navbar_icon('project') = navbar_icon('project')
.nav-link-text %span
Projects Projects
= nav_link(controller: :todos) do = nav_link(controller: :todos) do
= link_to dashboard_todos_path, title: 'Todos' do = link_to dashboard_todos_path, title: 'Todos' do
= icon('bell fw') = icon('bell fw')
.nav-link-text %span
Todos Todos
%span.count= number_with_delimiter(todos_pending_count)
= nav_link(path: 'dashboard#activity') do = nav_link(path: 'dashboard#activity') do
= link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do = link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do
= navbar_icon('activity') = navbar_icon('activity')
.nav-link-text %span
Activity Activity
= nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do = nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do
= link_to dashboard_groups_path, title: 'Groups' do = link_to dashboard_groups_path, title: 'Groups' do
= navbar_icon('group') = navbar_icon('group')
.nav-link-text %span
Groups Groups
= nav_link(controller: 'dashboard/milestones') do = nav_link(controller: 'dashboard/milestones') do
= link_to dashboard_milestones_path, title: 'Milestones' do = link_to dashboard_milestones_path, title: 'Milestones' do
= navbar_icon('milestones') = navbar_icon('milestones')
.nav-link-text %span
Milestones Milestones
= nav_link(path: 'dashboard#issues') do = nav_link(path: 'dashboard#issues') do
= link_to assigned_issues_dashboard_path, title: 'Issues', class: 'dashboard-shortcuts-issues' do = link_to assigned_issues_dashboard_path, title: 'Issues', class: 'dashboard-shortcuts-issues' do
= navbar_icon('issues') = navbar_icon('issues')
.nav-link-text %span
Issues Issues
%span.count= number_with_delimiter(current_user.assigned_issues.opened.count)
= nav_link(path: 'dashboard#merge_requests') do = nav_link(path: 'dashboard#merge_requests') do
= link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'dashboard-shortcuts-merge_requests' do = link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'dashboard-shortcuts-merge_requests' do
= navbar_icon('mr') = navbar_icon('mr')
.nav-link-text %span
Merge Requests Merge Requests
%span.count= number_with_delimiter(current_user.assigned_merge_requests.opened.count)
= nav_link(controller: :snippets) do = nav_link(controller: :snippets) do
= link_to dashboard_snippets_path, title: 'Snippets' do = link_to dashboard_snippets_path, title: 'Snippets' do
= icon('clipboard fw') = icon('clipboard fw')
.nav-link-text %span
Snippets Snippets
= nav_link(controller: :help) do = nav_link(controller: :help) do
= link_to help_path, title: 'Help' do = link_to help_path, title: 'Help' do
= icon('question-circle fw') = icon('question-circle fw')
.nav-link-text %span
Help Help
= nav_link(html_options: {class: profile_tab_class}) do = nav_link(html_options: {class: profile_tab_class}) do
= link_to profile_path, title: 'Profile Settings', data: {placement: 'bottom'} do = link_to profile_path, title: 'Profile Settings', data: {placement: 'bottom'} do
= icon('user fw') = icon('user fw')
.nav-link-text %span
Profile Settings Profile Settings
...@@ -2,20 +2,20 @@ ...@@ -2,20 +2,20 @@
= nav_link(path: ['dashboard#show', 'root#show', 'projects#trending', 'projects#starred', 'projects#index'], html_options: {class: 'home'}) do = nav_link(path: ['dashboard#show', 'root#show', 'projects#trending', 'projects#starred', 'projects#index'], html_options: {class: 'home'}) do
= link_to explore_root_path, title: 'Projects' do = link_to explore_root_path, title: 'Projects' do
= icon('bookmark fw') = icon('bookmark fw')
.nav-link-text %span
Projects Projects
= nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do = nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do
= link_to explore_groups_path, title: 'Groups' do = link_to explore_groups_path, title: 'Groups' do
= icon('group fw') = icon('group fw')
.nav-link-text %span
Groups Groups
= nav_link(controller: :snippets) do = nav_link(controller: :snippets) do
= link_to explore_snippets_path, title: 'Snippets' do = link_to explore_snippets_path, title: 'Snippets' do
= icon('clipboard fw') = icon('clipboard fw')
.nav-link-text %span
Snippets Snippets
= nav_link(controller: :help) do = nav_link(controller: :help) do
= link_to help_path, title: 'Help' do = link_to help_path, title: 'Help' do
= icon('question-circle fw') = icon('question-circle fw')
.nav-link-text %span
Help Help
...@@ -5,36 +5,30 @@ ...@@ -5,36 +5,30 @@
.fade-left .fade-left
= nav_link(path: 'groups#show', html_options: {class: 'home'}) do = nav_link(path: 'groups#show', html_options: {class: 'home'}) do
= link_to group_path(@group), title: 'Home' do = link_to group_path(@group), title: 'Home' do
= navbar_icon('group')
%span %span
Group Group
= nav_link(path: 'groups#activity') do = nav_link(path: 'groups#activity') do
= link_to activity_group_path(@group), title: 'Activity' do = link_to activity_group_path(@group), title: 'Activity' do
= navbar_icon('activity')
%span %span
Activity Activity
= nav_link(controller: [:group, :milestones]) do = nav_link(controller: [:group, :milestones]) do
= link_to group_milestones_path(@group), title: 'Milestones' do = link_to group_milestones_path(@group), title: 'Milestones' do
= navbar_icon('milestones')
%span %span
Milestones Milestones
= nav_link(path: 'groups#issues') do = nav_link(path: 'groups#issues') do
= link_to issues_group_path(@group), title: 'Issues' do = link_to issues_group_path(@group), title: 'Issues' do
= navbar_icon('issues')
%span %span
Issues Issues
- issues = IssuesFinder.new(current_user, group_id: @group.id, state: 'opened').execute - issues = IssuesFinder.new(current_user, group_id: @group.id, state: 'opened').execute
%span.badge.count= number_with_delimiter(issues.count) %span.badge.count= number_with_delimiter(issues.count)
= nav_link(path: 'groups#merge_requests') do = nav_link(path: 'groups#merge_requests') do
= link_to merge_requests_group_path(@group), title: 'Merge Requests' do = link_to merge_requests_group_path(@group), title: 'Merge Requests' do
= navbar_icon('mr')
%span %span
Merge Requests Merge Requests
- merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened').execute - merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened').execute
%span.badge.count= number_with_delimiter(merge_requests.count) %span.badge.count= number_with_delimiter(merge_requests.count)
= nav_link(controller: [:group_members]) do = nav_link(controller: [:group_members]) do
= link_to group_group_members_path(@group), title: 'Members' do = link_to group_group_members_path(@group), title: 'Members' do
= navbar_icon('members')
%span %span
Members Members
.fade-right .fade-right
...@@ -2,51 +2,41 @@ ...@@ -2,51 +2,41 @@
.fade-left .fade-left
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
= link_to profile_path, title: 'Profile Settings' do = link_to profile_path, title: 'Profile Settings' do
= icon('user fw')
%span %span
Profile Profile
= nav_link(controller: [:accounts, :two_factor_auths]) do = nav_link(controller: [:accounts, :two_factor_auths]) do
= link_to profile_account_path, title: 'Account' do = link_to profile_account_path, title: 'Account' do
= icon('gear fw')
%span %span
Account Account
- if current_application_settings.user_oauth_applications? - if current_application_settings.user_oauth_applications?
= nav_link(controller: 'oauth/applications') do = nav_link(controller: 'oauth/applications') do
= link_to applications_profile_path, title: 'Applications' do = link_to applications_profile_path, title: 'Applications' do
= icon('cloud fw')
%span %span
Applications Applications
= nav_link(controller: :emails) do = nav_link(controller: :emails) do
= link_to profile_emails_path, title: 'Emails' do = link_to profile_emails_path, title: 'Emails' do
= icon('envelope-o fw')
%span %span
Emails Emails
- unless current_user.ldap_user? - unless current_user.ldap_user?
= nav_link(controller: :passwords) do = nav_link(controller: :passwords) do
= link_to edit_profile_password_path, title: 'Password' do = link_to edit_profile_password_path, title: 'Password' do
= icon('lock fw')
%span %span
Password Password
= nav_link(controller: :notifications) do = nav_link(controller: :notifications) do
= link_to profile_notifications_path, title: 'Notifications' do = link_to profile_notifications_path, title: 'Notifications' do
= icon('inbox fw')
%span %span
Notifications Notifications
= nav_link(controller: :keys) do = nav_link(controller: :keys) do
= link_to profile_keys_path, title: 'SSH Keys' do = link_to profile_keys_path, title: 'SSH Keys' do
= icon('key fw')
%span %span
SSH Keys SSH Keys
= nav_link(controller: :preferences) do = nav_link(controller: :preferences) do
= link_to profile_preferences_path, title: 'Preferences' do = link_to profile_preferences_path, title: 'Preferences' do
-# TODO (rspeicher): Better icon?
= icon('image fw')
%span %span
Preferences Preferences
= nav_link(path: 'profiles#audit_log') do = nav_link(path: 'profiles#audit_log') do
= link_to audit_log_profile_path, title: 'Audit Log' do = link_to audit_log_profile_path, title: 'Audit Log' do
= icon('history fw')
%span %span
Audit Log Audit Log
.fade-right .fade-right
...@@ -24,55 +24,41 @@ ...@@ -24,55 +24,41 @@
.fade-left .fade-left
= nav_link(path: 'projects#show', html_options: {class: 'home'}) do = nav_link(path: 'projects#show', html_options: {class: 'home'}) do
= link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do = link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do
= navbar_icon('project')
%span %span
Project Project
= nav_link(path: 'projects#activity') do = nav_link(path: 'projects#activity') do
= link_to activity_project_path(@project), title: 'Activity', class: 'shortcuts-project-activity' do = link_to activity_project_path(@project), title: 'Activity', class: 'shortcuts-project-activity' do
= navbar_icon('activity')
%span %span
Activity Activity
- if project_nav_tab? :files - if project_nav_tab? :files
= nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare repositories tags branches releases network)) do = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare repositories tags branches releases network)) do
= link_to project_files_path(@project), title: 'Code', class: 'shortcuts-tree' do = link_to project_files_path(@project), title: 'Code', class: 'shortcuts-tree' do
= icon('code fw')
%span %span
Code Code
- if project_nav_tab? :pipelines - if project_nav_tab? :pipelines
= nav_link(controller: :pipelines) do = nav_link(controller: :pipelines) do
= link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do
= navbar_icon('pipelines')
%span %span
Pipelines Pipelines
- if project_nav_tab? :container_registry - if project_nav_tab? :container_registry
= nav_link(controller: %w(container_registry)) do = nav_link(controller: %w(container_registry)) do
= link_to project_container_registry_path(@project), title: 'Container Registry', class: 'shortcuts-container-registry' do = link_to project_container_registry_path(@project), title: 'Container Registry', class: 'shortcuts-container-registry' do
= icon('hdd-o fw')
%span %span
Registry Registry
- if project_nav_tab? :graphs - if project_nav_tab? :graphs
= nav_link(controller: %w(graphs)) do = nav_link(controller: %w(graphs)) do
= link_to namespace_project_graph_path(@project.namespace, @project, current_ref), title: 'Graphs', class: 'shortcuts-graphs' do = link_to namespace_project_graph_path(@project.namespace, @project, current_ref), title: 'Graphs', class: 'shortcuts-graphs' do
= icon('area-chart fw')
%span %span
Graphs Graphs
- if project_nav_tab? :milestones
= nav_link(controller: :milestones) do
= link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones' do
= navbar_icon('milestones')
%span
Milestones
- if project_nav_tab? :issues - if project_nav_tab? :issues
= nav_link(controller: :issues) do = nav_link(controller: [:issues, :labels, :milestones]) do
= link_to url_for_project_issues(@project, only_path: true), title: 'Issues', class: 'shortcuts-issues' do = link_to url_for_project_issues(@project, only_path: true), title: 'Issues', class: 'shortcuts-issues' do
= navbar_icon('issues')
%span %span
Issues Issues
- if @project.default_issues_tracker? - if @project.default_issues_tracker?
...@@ -81,29 +67,19 @@ ...@@ -81,29 +67,19 @@
- if project_nav_tab? :merge_requests - if project_nav_tab? :merge_requests
= nav_link(controller: :merge_requests) do = nav_link(controller: :merge_requests) do
= link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do
= navbar_icon('mr')
%span %span
Merge Requests Merge Requests
%span.badge.count.merge_counter= number_with_delimiter(@project.merge_requests.opened.count) %span.badge.count.merge_counter= number_with_delimiter(@project.merge_requests.opened.count)
- if project_nav_tab? :labels
= nav_link(controller: :labels) do
= link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels' do
= icon('tags fw')
%span
Labels
- if project_nav_tab? :wiki - if project_nav_tab? :wiki
= nav_link(controller: :wikis) do = nav_link(controller: :wikis) do
= link_to get_project_wiki_path(@project), title: 'Wiki', class: 'shortcuts-wiki' do = link_to get_project_wiki_path(@project), title: 'Wiki', class: 'shortcuts-wiki' do
= navbar_icon('wiki')
%span %span
Wiki Wiki
- if project_nav_tab? :snippets - if project_nav_tab? :snippets
= nav_link(controller: :snippets) do = nav_link(controller: :snippets) do
= link_to namespace_project_snippets_path(@project.namespace, @project), title: 'Snippets', class: 'shortcuts-snippets' do = link_to namespace_project_snippets_path(@project.namespace, @project), title: 'Snippets', class: 'shortcuts-snippets' do
= icon('clipboard fw')
%span %span
Snippets Snippets
......
%ul.nav-links.sub-nav
%div{ class: (container_class) }
- if project_nav_tab?(:issues) && !current_controller?(:merge_requests)
= nav_link(controller: :issues) do
= link_to url_for_project_issues(@project, only_path: true), title: 'Issues' do
%span
Issues
- if project_nav_tab?(:merge_requests) && current_controller?(:merge_requests)
= nav_link(controller: :merge_requests) do
= link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests' do
%span
Merge Requests
- if project_nav_tab? :labels
= nav_link(controller: :labels) do
= link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels' do
%span
Labels
- if project_nav_tab? :milestones
= nav_link(controller: :milestones) do
= link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones' do
%span
Milestones
- @no_container = true
- page_title "Issues" - page_title "Issues"
= render "projects/issues/head"
= content_for :meta_tags do = content_for :meta_tags do
- 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")
.top-area %div{ class: (container_class) }
= render 'shared/issuable/nav', type: :issues .top-area
.nav-controls = render 'shared/issuable/nav', type: :issues
- if current_user .nav-controls
= link_to namespace_project_issues_path(@project.namespace, @project, :atom, { private_token: current_user.private_token }), class: 'btn append-right-10' do - if current_user
= icon('rss') = link_to namespace_project_issues_path(@project.namespace, @project, :atom, { private_token: current_user.private_token }), class: 'btn append-right-10' do
%span.icon-label = icon('rss')
Subscribe %span.icon-label
= render 'shared/issuable/search_form', path: namespace_project_issues_path(@project.namespace, @project) Subscribe
- if can? current_user, :create_issue, @project = render 'shared/issuable/search_form', path: namespace_project_issues_path(@project.namespace, @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", title: "New Issue", id: "new_issue_link" do - if can? current_user, :create_issue, @project
New Issue = 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
New Issue
= render 'shared/issuable/filter', type: :issues = render 'shared/issuable/filter', type: :issues
.issues-holder .issues-holder
= render "issues" = render "issues"
- @no_container = true
- page_title "Labels" - page_title "Labels"
- hide_class = '' - hide_class = ''
= render "projects/issues/head"
.top-area %div{ class: (container_class) }
.nav-text .top-area
Labels can be applied to issues and merge requests. .nav-text
.nav-controls Labels can be applied to issues and merge requests.
- if can?(current_user, :admin_label, @project) .nav-controls
= link_to new_namespace_project_label_path(@project.namespace, @project), class: "btn btn-new" do - if can?(current_user, :admin_label, @project)
New label = link_to new_namespace_project_label_path(@project.namespace, @project), class: "btn btn-new" do
New label
.labels .labels
- if can?(current_user, :admin_label, @project)
-# Only show it in the first page
- hide = @project.labels.empty? || (params[:page].present? && params[:page] != '1')
.prioritized-labels{ class: ('hide' if hide) }
%h5 Prioritized Labels
%ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_namespace_project_labels_path(@project.namespace, @project) }
- if @prioritized_labels.present?
= render @prioritized_labels
- else
%p.empty-message No prioritized labels yet
.other-labels
- if can?(current_user, :admin_label, @project) - if can?(current_user, :admin_label, @project)
%h5{ class: ('hide' if hide) } Other Labels -# Only show it in the first page
- if @labels.present? - hide = @project.labels.empty? || (params[:page].present? && params[:page] != '1')
%ul.content-list.manage-labels-list.js-other-labels .prioritized-labels{ class: ('hide' if hide) }
= render @labels %h5 Prioritized Labels
= paginate @labels, theme: 'gitlab' %ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_namespace_project_labels_path(@project.namespace, @project) }
- else - if @prioritized_labels.present?
.nothing-here-block = render @prioritized_labels
- if can?(current_user, :admin_label, @project) - else
Create a label or #{link_to 'generate a default set of labels', generate_namespace_project_labels_path(@project.namespace, @project), method: :post}. %p.empty-message No prioritized labels yet
- else .other-labels
No labels created - if can?(current_user, :admin_label, @project)
%h5{ class: ('hide' if hide) } Other Labels
- if @labels.present?
%ul.content-list.manage-labels-list.js-other-labels
= render @labels
= paginate @labels, theme: 'gitlab'
- else
.nothing-here-block
- if can?(current_user, :admin_label, @project)
Create a label or #{link_to 'generate a default set of labels', generate_namespace_project_labels_path(@project.namespace, @project), method: :post}.
- else
No labels created
.top-tabs
= link_to namespace_project_merge_requests_path(@project.namespace, @project), class: "tab #{'active' if current_page?(namespace_project_merge_requests_path(@project.namespace, @project)) }" do
%span
Merge Requests
- @no_container = true
- page_title "Merge Requests" - page_title "Merge Requests"
= render "projects/issues/head"
= render 'projects/last_push' = render 'projects/last_push'
.top-area %div{ class: (container_class) }
= render 'shared/issuable/nav', type: :merge_requests .top-area
.nav-controls = render 'shared/issuable/nav', type: :merge_requests
= render 'shared/issuable/search_form', path: namespace_project_merge_requests_path(@project.namespace, @project) .nav-controls
= 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
= 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
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'
- @no_container = true
- page_title "Milestones" - page_title "Milestones"
= render "projects/issues/head"
.top-area %div{ class: (container_class) }
= render 'shared/milestones_filter' .top-area
= render 'shared/milestones_filter'
.nav-controls .nav-controls
- if can?(current_user, :admin_milestone, @project) - if can?(current_user, :admin_milestone, @project)
= link_to new_namespace_project_milestone_path(@project.namespace, @project), class: "btn btn-new", title: "New Milestone" do = link_to new_namespace_project_milestone_path(@project.namespace, @project), class: "btn btn-new", title: "New Milestone" do
New Milestone New Milestone
.milestones .milestones
%ul.content-list %ul.content-list
= render @milestones = render @milestones
- if @milestones.blank? - if @milestones.blank?
%li %li
.nothing-here-block No milestones to show .nothing-here-block No milestones to show
= paginate @milestones, theme: "gitlab" = paginate @milestones, theme: "gitlab"
...@@ -572,7 +572,7 @@ GET /projects/:id/merge_requests/:merge_request_id/closes_issues ...@@ -572,7 +572,7 @@ GET /projects/:id/merge_requests/:merge_request_id/closes_issues
curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/76/merge_requests/1/closes_issues curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/76/merge_requests/1/closes_issues
``` ```
Example response: Example response when the GitLab issue tracker is used:
```json ```json
[ [
...@@ -618,6 +618,17 @@ Example response: ...@@ -618,6 +618,17 @@ Example response:
] ]
``` ```
Example response when an external issue tracker (e.g. JIRA) is used:
```json
[
{
"id" : "PROJECT-123",
"title" : "Title of this issue"
}
]
```
## Subscribe to a merge request ## Subscribe to a merge request
Subscribes the authenticated user to a merge request to receive notification. If Subscribes the authenticated user to a merge request to receive notification. If
......
...@@ -107,12 +107,16 @@ Feature: Project Active Tab ...@@ -107,12 +107,16 @@ Feature: Project Active Tab
Scenario: On Project Issues/Milestones Scenario: On Project Issues/Milestones
Given I visit my project's issues page Given I visit my project's issues page
And I click the "Milestones" tab And I click the "Milestones" sub tab
Then the active main tab should be Milestones Then the active main tab should be Issues
Then the active sub tab should be Milestones
And no other main tabs should be active And no other main tabs should be active
And no other sub tabs should be active
Scenario: On Project Issues/Labels Scenario: On Project Issues/Labels
Given I visit my project's issues page Given I visit my project's issues page
And I click the "Labels" tab And I click the "Labels" sub tab
Then the active main tab should be Labels Then the active main tab should be Issues
Then the active sub tab should be Labels
And no other main tabs should be active And no other main tabs should be active
And no other sub tabs should be active
...@@ -155,6 +155,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps ...@@ -155,6 +155,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
end end
step 'I click on my profile picture' do step 'I click on my profile picture' do
find(:css, '.side-nav-toggle').click
find(:css, '.sidebar-user').click find(:css, '.sidebar-user').click
end end
......
...@@ -77,14 +77,14 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps ...@@ -77,14 +77,14 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps
# Sub Tabs: Issues # Sub Tabs: Issues
step 'I click the "Milestones" tab' do step 'I click the "Milestones" sub tab' do
page.within('.layout-nav') do page.within('.sub-nav') do
click_link('Milestones') click_link('Milestones')
end end
end end
step 'I click the "Labels" tab' do step 'I click the "Labels" sub tab' do
page.within('.layout-nav') do page.within('.sub-nav') do
click_link('Labels') click_link('Labels')
end end
end end
...@@ -93,11 +93,11 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps ...@@ -93,11 +93,11 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps
ensure_active_sub_tab('Issues') ensure_active_sub_tab('Issues')
end end
step 'the active main tab should be Milestones' do step 'the active sub tab should be Milestones' do
ensure_active_main_tab('Milestones') ensure_active_sub_tab('Milestones')
end end
step 'the active main tab should be Labels' do step 'the active sub tab should be Labels' do
ensure_active_main_tab('Labels') ensure_active_sub_tab('Labels')
end end
end end
...@@ -6,7 +6,7 @@ module SharedActiveTab ...@@ -6,7 +6,7 @@ module SharedActiveTab
end end
def ensure_active_sub_tab(content) def ensure_active_sub_tab(content)
expect(find('div.content ul.nav-links li.active')).to have_content(content) expect(find('.sub-nav li.active')).to have_content(content)
end end
def ensure_active_sub_nav(content) def ensure_active_sub_nav(content)
...@@ -18,7 +18,7 @@ module SharedActiveTab ...@@ -18,7 +18,7 @@ module SharedActiveTab
end end
step 'no other sub tabs should be active' do step 'no other sub tabs should be active' do
expect(page).to have_selector('div.content ul.nav-links li.active', count: 1) expect(page).to have_selector('.sub-nav li.active', count: 1)
end end
step 'no other sub navs should be active' do step 'no other sub navs should be active' do
......
...@@ -179,6 +179,11 @@ module API ...@@ -179,6 +179,11 @@ module API
expose :upvotes, :downvotes expose :upvotes, :downvotes
end end
class ExternalIssue < Grape::Entity
expose :title
expose :id
end
class MergeRequest < ProjectEntity class MergeRequest < ProjectEntity
expose :target_branch, :source_branch expose :target_branch, :source_branch
expose :upvotes, :downvotes expose :upvotes, :downvotes
......
...@@ -408,5 +408,23 @@ module API ...@@ -408,5 +408,23 @@ module API
error!(errors[:access_level], 422) if errors[:access_level].any? error!(errors[:access_level], 422) if errors[:access_level].any?
not_found!(errors) not_found!(errors)
end end
def send_git_blob(repository, blob)
env['api.format'] = :txt
content_type 'text/plain'
header(*Gitlab::Workhorse.send_git_blob(repository, blob))
end
def send_git_archive(repository, ref:, format:)
header(*Gitlab::Workhorse.send_git_archive(repository, ref: ref, format: format))
end
def issue_entity(project)
if project.has_external_issue_tracker?
Entities::ExternalIssue
else
Entities::Issue
end
end
end end
end end
...@@ -329,7 +329,7 @@ module API ...@@ -329,7 +329,7 @@ module API
get "#{path}/closes_issues" do get "#{path}/closes_issues" do
merge_request = user_project.merge_requests.find(params[:merge_request_id]) merge_request = user_project.merge_requests.find(params[:merge_request_id])
issues = ::Kaminari.paginate_array(merge_request.closes_issues(current_user)) issues = ::Kaminari.paginate_array(merge_request.closes_issues(current_user))
present paginate(issues), with: Entities::Issue, current_user: current_user present paginate(issues), with: issue_entity(user_project), current_user: current_user
end end
end end
end end
......
...@@ -56,8 +56,7 @@ module API ...@@ -56,8 +56,7 @@ module API
blob = Gitlab::Git::Blob.find(repo, commit.id, params[:filepath]) blob = Gitlab::Git::Blob.find(repo, commit.id, params[:filepath])
not_found! "File" unless blob not_found! "File" unless blob
content_type 'text/plain' send_git_blob repo, blob
header(*Gitlab::Workhorse.send_git_blob(repo, blob))
end end
# Get a raw blob contents by blob sha # Get a raw blob contents by blob sha
...@@ -80,10 +79,7 @@ module API ...@@ -80,10 +79,7 @@ module API
not_found! 'Blob' unless blob not_found! 'Blob' unless blob
env['api.format'] = :txt send_git_blob repo, blob
content_type blob.mime_type
header(*Gitlab::Workhorse.send_git_blob(repo, blob))
end end
# Get a an archive of the repository # Get a an archive of the repository
...@@ -98,7 +94,7 @@ module API ...@@ -98,7 +94,7 @@ module API
authorize! :download_code, user_project authorize! :download_code, user_project
begin begin
header(*Gitlab::Workhorse.send_git_archive(user_project, params[:sha], params[:format])) send_git_archive user_project.repository, ref: params[:sha], format: params[:format]
rescue rescue
not_found!('File') not_found!('File')
end end
......
...@@ -38,7 +38,6 @@ module Backup ...@@ -38,7 +38,6 @@ module Backup
end end
def upload(tar_file) def upload(tar_file)
remote_directory = Gitlab.config.backup.upload.remote_directory
$progress.print "Uploading backup archive to remote storage #{remote_directory} ... " $progress.print "Uploading backup archive to remote storage #{remote_directory} ... "
connection_settings = Gitlab.config.backup.upload.connection connection_settings = Gitlab.config.backup.upload.connection
...@@ -47,8 +46,7 @@ module Backup ...@@ -47,8 +46,7 @@ module Backup
return return
end end
connection = ::Fog::Storage.new(connection_settings) directory = connect_to_remote_directory(connection_settings)
directory = connection.directories.create(key: remote_directory)
if directory.files.create(key: tar_file, body: File.open(tar_file), public: false, if directory.files.create(key: tar_file, body: File.open(tar_file), public: false,
multipart_chunk_size: Gitlab.config.backup.upload.multipart_chunk_size, multipart_chunk_size: Gitlab.config.backup.upload.multipart_chunk_size,
...@@ -155,6 +153,23 @@ module Backup ...@@ -155,6 +153,23 @@ module Backup
private private
def connect_to_remote_directory(connection_settings)
connection = ::Fog::Storage.new(connection_settings)
# We only attempt to create the directory for local backups. For AWS
# and other cloud providers, we cannot guarantee the user will have
# permission to create the bucket.
if connection.service == ::Fog::Storage::Local
connection.directories.create(key: remote_directory)
else
connection.directories.get(remote_directory)
end
end
def remote_directory
Gitlab.config.backup.upload.remote_directory
end
def backup_contents def backup_contents
folders_to_backup + archives_to_backup + ["backup_information.yml"] folders_to_backup + archives_to_backup + ["backup_information.yml"]
end end
......
...@@ -21,27 +21,29 @@ module Gitlab ...@@ -21,27 +21,29 @@ module Gitlab
[ [
SEND_DATA_HEADER, SEND_DATA_HEADER,
"git-blob:#{encode(params)}", "git-blob:#{encode(params)}"
] ]
end end
def send_git_archive(project, ref, format) def send_git_archive(repository, ref:, format:)
format ||= 'tar.gz' format ||= 'tar.gz'
format.downcase! format.downcase!
params = project.repository.archive_metadata(ref, Gitlab.config.gitlab.repository_downloads_path, format) params = repository.archive_metadata(ref, Gitlab.config.gitlab.repository_downloads_path, format)
raise "Repository or ref not found" if params.empty? raise "Repository or ref not found" if params.empty?
[ [
SEND_DATA_HEADER, SEND_DATA_HEADER,
"git-archive:#{encode(params)}", "git-archive:#{encode(params)}"
] ]
end end
def send_git_diff(repository, from, to) def send_git_diff(repository, diff_refs)
from, to = diff_refs
params = { params = {
'RepoPath' => repository.path_to_repo, 'RepoPath' => repository.path_to_repo,
'ShaFrom' => from, 'ShaFrom' => from.sha,
'ShaTo' => to 'ShaTo' => to.sha
} }
[ [
......
...@@ -91,7 +91,7 @@ describe Projects::MergeRequestsController do ...@@ -91,7 +91,7 @@ describe Projects::MergeRequestsController do
id: merge_request.iid, id: merge_request.iid,
format: :diff) format: :diff)
expect(response.headers['Gitlab-Workhorse-Send-Data']).to start_with("git-diff:") expect(response.headers[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-diff:")
end end
end end
......
...@@ -17,6 +17,7 @@ describe Projects::RawController do ...@@ -17,6 +17,7 @@ describe Projects::RawController do
expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8') expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8')
expect(response.header['Content-Disposition']). expect(response.header['Content-Disposition']).
to eq("inline") to eq("inline")
expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-blob:")
end end
end end
...@@ -31,6 +32,7 @@ describe Projects::RawController do ...@@ -31,6 +32,7 @@ describe Projects::RawController do
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(response.header['Content-Type']).to eq('image/jpeg') expect(response.header['Content-Type']).to eq('image/jpeg')
expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-blob:")
end end
end end
......
...@@ -20,10 +20,11 @@ describe Projects::RepositoriesController do ...@@ -20,10 +20,11 @@ describe Projects::RepositoriesController do
project.team << [user, :developer] project.team << [user, :developer]
sign_in(user) sign_in(user)
end end
it "uses Gitlab::Workhorse" do
expect(Gitlab::Workhorse).to receive(:send_git_archive).with(project, "master", "zip")
it "uses Gitlab::Workhorse" do
get :archive, namespace_id: project.namespace.path, project_id: project.path, ref: "master", format: "zip" get :archive, namespace_id: project.namespace.path, project_id: project.path, ref: "master", format: "zip"
expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-archive:")
end end
context "when the service raises an error" do context "when the service raises an error" do
......
...@@ -54,7 +54,7 @@ describe 'Profile > Preferences', feature: true do ...@@ -54,7 +54,7 @@ describe 'Profile > Preferences', feature: true do
end end
end end
describe 'User changes their default dashboard' do describe 'User changes their default dashboard', js: true do
it 'creates a flash message' do it 'creates a flash message' do
select 'Starred Projects', from: 'user_dashboard' select 'Starred Projects', from: 'user_dashboard'
click_button 'Save' click_button 'Save'
...@@ -66,8 +66,10 @@ describe 'Profile > Preferences', feature: true do ...@@ -66,8 +66,10 @@ describe 'Profile > Preferences', feature: true do
select 'Starred Projects', from: 'user_dashboard' select 'Starred Projects', from: 'user_dashboard'
click_button 'Save' click_button 'Save'
click_link 'Dashboard' allowing_for_delay do
expect(page.current_path).to eq starred_dashboard_projects_path find('#logo').click
expect(page.current_path).to eq starred_dashboard_projects_path
end
click_link 'Your Projects' click_link 'Your Projects'
expect(page.current_path).to eq dashboard_projects_path expect(page.current_path).to eq dashboard_projects_path
......
...@@ -11,7 +11,7 @@ describe Gitlab::Workhorse, lib: true do ...@@ -11,7 +11,7 @@ describe Gitlab::Workhorse, lib: true do
end end
it "raises an error" do it "raises an error" do
expect { subject.send_git_archive(project, "master", "zip") }.to raise_error(RuntimeError) expect { subject.send_git_archive(project.repository, ref: "master", format: "zip") }.to raise_error(RuntimeError)
end end
end end
end end
......
...@@ -922,4 +922,37 @@ describe Project, models: true do ...@@ -922,4 +922,37 @@ describe Project, models: true do
it { is_expected.to be_falsey } it { is_expected.to be_falsey }
end end
end end
describe '.where_paths_in' do
context 'without any paths' do
it 'returns an empty relation' do
expect(Project.where_paths_in([])).to eq([])
end
end
context 'without any valid paths' do
it 'returns an empty relation' do
expect(Project.where_paths_in(%w[foo])).to eq([])
end
end
context 'with valid paths' do
let!(:project1) { create(:project) }
let!(:project2) { create(:project) }
it 'returns the projects matching the paths' do
projects = Project.where_paths_in([project1.path_with_namespace,
project2.path_with_namespace])
expect(projects).to contain_exactly(project1, project2)
end
it 'returns projects regardless of the casing of paths' do
projects = Project.where_paths_in([project1.path_with_namespace.upcase,
project2.path_with_namespace.upcase])
expect(projects).to contain_exactly(project1, project2)
end
end
end
end end
...@@ -563,6 +563,21 @@ describe API::API, api: true do ...@@ -563,6 +563,21 @@ describe API::API, api: true do
expect(json_response).to be_an Array expect(json_response).to be_an Array
expect(json_response.length).to eq(0) expect(json_response.length).to eq(0)
end end
it 'handles external issues' do
jira_project = create(:jira_project, :public, name: 'JIR_EXT1')
issue = ExternalIssue.new("#{jira_project.name}-123", jira_project)
merge_request = create(:merge_request, :simple, author: user, assignee: user, source_project: jira_project)
merge_request.update_attribute(:description, "Closes #{issue.to_reference(jira_project)}")
get api("/projects/#{jira_project.id}/merge_requests/#{merge_request.id}/closes_issues", user)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['title']).to eq(issue.title)
expect(json_response.first['id']).to eq(issue.id)
end
end end
describe 'POST :id/merge_requests/:merge_request_id/subscription' do describe 'POST :id/merge_requests/:merge_request_id/subscription' do
......
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