Commit 962b97d8 authored by Tomasz Maczukin's avatar Tomasz Maczukin

Merge branch 'master' into ci/api-builds

* master: (75 commits)
  Fix grammar
  Clarify the key generation step
  Remove misleading `ssh-dsa`
  markdown fixes
  markdown fixes
  Add `AbuseReport#notify`
  Make AbuseReportMailer responsible for knowing if it should deliver
  Redirect back to user profile page after abuse report
  Redesign the AbuseReports index
  Don't notify users twice if they are both project watchers and subscribers
  Restructure logo JS to use `setInterval`
  Decrease the logo sweep delay
  Correct the logo ID names
  Update CHANGELOG
  Merge pull request GH-9938 from huacnlee/hotfix/note_mail_with_notification
  Remove jquery.blockUI.js plugin
  rempves tests for "you have master access" text
  Revert "Merge branch 'rs-remove-jquery-blockui' into 'master'"
  removes footer message about access to project
  remove public field from namespace and refactoring
  ...
parents 628297fe 9b127028
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.4.0 (unreleased) v 8.4.0 (unreleased)
- Expire view caches when application settings change (e.g. Gravatar disabled) (Stan Hu)
- Don't notify users twice if they are both project watchers and subscribers (Stan Hu)
- Implement new UI for group page - Implement new UI for group page
- Implement search inside emoji picker - Implement search inside emoji picker
- Add API support for looking up a user by username (Stan Hu) - Add API support for looking up a user by username (Stan Hu)
- Add project permissions to all project API endpoints (Stan Hu) - Add project permissions to all project API endpoints (Stan Hu)
- Only allow group/project members to mention `@all` - Only allow group/project members to mention `@all`
- Expose Git's version in the admin area - Expose Git's version in the admin area (Trey Davis)
- Add "Frequently used" category to emoji picker - Add "Frequently used" category to emoji picker
- Add CAS support (tduehr) - Add CAS support (tduehr)
- Add link to merge request on build detail page - Add link to merge request on build detail page
- Revert back upvote and downvote button to the issue and MR pages - Revert back upvote and downvote button to the issue and MR pages
- Enable "Add key" button when user fills in a proper key (Stan Hu) - Swap position of Assignee and Author selector on Issuables (Zeger-Jan van de Weg)
- Add system hook messages for project rename and transfer (Steve Norman)
- Fix version check image in Safari
- Show 'All' tab by default in the builds page
- Fix API project lookups when querying with a namespace with dots (Stan Hu)
v 8.3.3 (unreleased) v 8.3.3 (unreleased)
- Fix project transfer e-mail sending incorrect paths in e-mail notification (Stan Hu) - Fix project transfer e-mail sending incorrect paths in e-mail notification (Stan Hu)
- Enable "Add key" button when user fills in a proper key (Stan Hu)
v 8.3.2 v 8.3.2
- Disable --follow in `git log` to avoid loading duplicate commit data in infinite scroll (Stan Hu) - Disable --follow in `git log` to avoid loading duplicate commit data in infinite scroll (Stan Hu)
......
...@@ -169,10 +169,10 @@ gem 'asana', '~> 0.4.0' ...@@ -169,10 +169,10 @@ gem 'asana', '~> 0.4.0'
gem 'ruby-fogbugz', '~> 0.2.1' gem 'ruby-fogbugz', '~> 0.2.1'
# d3 # d3
gem 'd3_rails', '~> 3.5.5' gem 'd3_rails', '~> 3.5.0'
#cal-heatmap #cal-heatmap
gem "cal-heatmap-rails", "~> 0.0.1" gem 'cal-heatmap-rails', '~> 3.5.0'
# underscore-rails # underscore-rails
gem "underscore-rails", "~> 1.8.0" gem "underscore-rails", "~> 1.8.0"
...@@ -200,7 +200,7 @@ gem 'turbolinks', '~> 2.5.0' ...@@ -200,7 +200,7 @@ gem 'turbolinks', '~> 2.5.0'
gem 'jquery-turbolinks', '~> 2.1.0' gem 'jquery-turbolinks', '~> 2.1.0'
gem 'addressable', '~> 2.3.8' gem 'addressable', '~> 2.3.8'
gem 'bootstrap-sass', '~> 3.0' gem 'bootstrap-sass', '~> 3.3.0'
gem 'font-awesome-rails', '~> 4.2' gem 'font-awesome-rails', '~> 4.2'
gem 'gitlab_emoji', '~> 0.2.0' gem 'gitlab_emoji', '~> 0.2.0'
gem 'gon', '~> 6.0.1' gem 'gon', '~> 6.0.1'
......
...@@ -49,7 +49,7 @@ GEM ...@@ -49,7 +49,7 @@ GEM
addressable (2.3.8) addressable (2.3.8)
after_commit_queue (1.3.0) after_commit_queue (1.3.0)
activerecord (>= 3.0) activerecord (>= 3.0)
allocations (1.0.1) allocations (1.0.3)
annotate (2.6.10) annotate (2.6.10)
activerecord (>= 3.2, <= 4.3) activerecord (>= 3.2, <= 4.3)
rake (~> 10.4) rake (~> 10.4)
...@@ -66,7 +66,7 @@ GEM ...@@ -66,7 +66,7 @@ GEM
attr_encrypted (1.3.4) attr_encrypted (1.3.4)
encryptor (>= 1.3.0) encryptor (>= 1.3.0)
attr_required (1.0.0) attr_required (1.0.0)
autoprefixer-rails (6.1.2) autoprefixer-rails (6.2.3)
execjs execjs
json json
awesome_print (1.2.0) awesome_print (1.2.0)
...@@ -82,9 +82,9 @@ GEM ...@@ -82,9 +82,9 @@ GEM
erubis (>= 2.6.6) erubis (>= 2.6.6)
binding_of_caller (0.7.2) binding_of_caller (0.7.2)
debug_inspector (>= 0.0.1) debug_inspector (>= 0.0.1)
bootstrap-sass (3.3.5) bootstrap-sass (3.3.6)
autoprefixer-rails (>= 5.0.0.1) autoprefixer-rails (>= 5.2.1)
sass (>= 3.2.19) sass (>= 3.3.4)
brakeman (3.1.4) brakeman (3.1.4)
erubis (~> 2.6) erubis (~> 2.6)
fastercsv (~> 1.5) fastercsv (~> 1.5)
...@@ -106,7 +106,7 @@ GEM ...@@ -106,7 +106,7 @@ GEM
bundler (~> 1.2) bundler (~> 1.2)
thor (~> 0.18) thor (~> 0.18)
byebug (8.2.1) byebug (8.2.1)
cal-heatmap-rails (0.0.1) cal-heatmap-rails (3.5.1)
capybara (2.4.4) capybara (2.4.4)
mime-types (>= 1.16) mime-types (>= 1.16)
nokogiri (>= 1.3.3) nokogiri (>= 1.3.3)
...@@ -843,13 +843,13 @@ DEPENDENCIES ...@@ -843,13 +843,13 @@ DEPENDENCIES
benchmark-ips benchmark-ips
better_errors (~> 1.0.1) better_errors (~> 1.0.1)
binding_of_caller (~> 0.7.2) binding_of_caller (~> 0.7.2)
bootstrap-sass (~> 3.0) bootstrap-sass (~> 3.3.0)
brakeman (~> 3.1.0) brakeman (~> 3.1.0)
browser (~> 1.0.0) browser (~> 1.0.0)
bullet bullet
bundler-audit bundler-audit
byebug byebug
cal-heatmap-rails (~> 0.0.1) cal-heatmap-rails (~> 3.5.0)
capybara (~> 2.4.0) capybara (~> 2.4.0)
capybara-screenshot (~> 1.0.0) capybara-screenshot (~> 1.0.0)
carrierwave (~> 0.9.0) carrierwave (~> 0.9.0)
...@@ -859,7 +859,7 @@ DEPENDENCIES ...@@ -859,7 +859,7 @@ DEPENDENCIES
connection_pool (~> 2.0) connection_pool (~> 2.0)
coveralls (~> 0.8.2) coveralls (~> 0.8.2)
creole (~> 0.5.0) creole (~> 0.5.0)
d3_rails (~> 3.5.5) d3_rails (~> 3.5.0)
database_cleaner (~> 1.4.0) database_cleaner (~> 1.4.0)
default_value_for (~> 3.0.0) default_value_for (~> 3.0.0)
devise (~> 3.5.3) devise (~> 3.5.3)
......
Copyright (c) 2011-2015 GitLab B.V. Copyright (c) 2011-2016 GitLab B.V.
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
......
...@@ -10,12 +10,12 @@ ...@@ -10,12 +10,12 @@
#= require jquery.cookie #= require jquery.cookie
#= require jquery.endless-scroll #= require jquery.endless-scroll
#= require jquery.highlight #= require jquery.highlight
#= require jquery.history
#= require jquery.waitforimages #= require jquery.waitforimages
#= require jquery.atwho #= require jquery.atwho
#= require jquery.scrollTo #= require jquery.scrollTo
#= require jquery.blockUI
#= require jquery.turbolinks #= require jquery.turbolinks
#= require d3
#= require cal-heatmap
#= require turbolinks #= require turbolinks
#= require autosave #= require autosave
#= require bootstrap #= require bootstrap
...@@ -27,7 +27,6 @@ ...@@ -27,7 +27,6 @@
#= require branch-graph #= require branch-graph
#= require ace/ace #= require ace/ace
#= require ace/ext-searchbox #= require ace/ext-searchbox
#= require d3
#= require underscore #= require underscore
#= require nprogress #= require nprogress
#= require nprogress-turbolinks #= require nprogress-turbolinks
...@@ -39,7 +38,6 @@ ...@@ -39,7 +38,6 @@
#= require shortcuts_dashboard_navigation #= require shortcuts_dashboard_navigation
#= require shortcuts_issuable #= require shortcuts_issuable
#= require shortcuts_network #= require shortcuts_network
#= require cal-heatmap
#= require jquery.nicescroll.min #= require jquery.nicescroll.min
#= require_tree . #= require_tree .
......
class @Calendar class @Calendar
options =
month: "short"
day: "numeric"
year: "numeric"
constructor: (timestamps, starting_year, starting_month, calendar_activities_path) -> constructor: (timestamps, starting_year, starting_month, calendar_activities_path) ->
cal = new CalHeatMap() cal = new CalHeatMap()
cal.init cal.init
......
...@@ -15,13 +15,6 @@ ...@@ -15,13 +15,6 @@
$(this).html totalIssues + 1 $(this).html totalIssues + 1
else else
$(this).html totalIssues - 1 $(this).html totalIssues - 1
$("body").on "click", ".issues-other-filters .dropdown-menu a", ->
$('.issues-list').block(
message: null,
overlayCSS:
backgroundColor: '#DDD'
opacity: .4
)
reload: -> reload: ->
Issues.initSelects() Issues.initSelects()
...@@ -54,7 +47,7 @@ ...@@ -54,7 +47,7 @@
form = $("#issue_search_form") form = $("#issue_search_form")
search = $("#issue_search").val() search = $("#issue_search").val()
$('.issues-holder').css("opacity", '0.5') $('.issues-holder').css("opacity", '0.5')
issues_url = form.attr('action') + '? '+ form.serialize() issues_url = form.attr('action') + '?' + form.serialize()
$.ajax $.ajax
type: "GET" type: "GET"
...@@ -65,7 +58,7 @@ ...@@ -65,7 +58,7 @@
success: (data) -> success: (data) ->
$('.issues-holder').html(data.html) $('.issues-holder').html(data.html)
# Change url so if user reload a page - search results are saved # Change url so if user reload a page - search results are saved
History.replaceState {page: issues_url}, document.title, issues_url history.replaceState {page: issues_url}, document.title, issues_url
Issues.reload() Issues.reload()
dataType: "json" dataType: "json"
......
NProgress.configure(showSpinner: false)
defaultClass = 'tanuki-shape'
pieces = [
'path#tanuki-right-cheek',
'path#tanuki-right-eye, path#tanuki-right-ear',
'path#tanuki-nose',
'path#tanuki-left-eye, path#tanuki-left-ear',
'path#tanuki-left-cheek',
]
pieceIndex = 0
firstPiece = pieces[0]
currentTimer = null
delay = 150
clearHighlights = ->
$(".#{defaultClass}.highlight").attr('class', defaultClass)
start = ->
clearHighlights()
pieceIndex = 0
pieces.reverse() unless pieces[0] == firstPiece
currentTimer = setInterval(work, delay)
stop = ->
clearInterval(currentTimer)
clearHighlights()
work = ->
clearHighlights()
$(pieces[pieceIndex]).attr('class', "#{defaultClass} highlight")
# If we hit the last piece, reset the index and then reverse the array to
# get a nice back-and-forth sweeping look
if pieceIndex == pieces.length - 1
pieceIndex = 0
pieces.reverse()
else
pieceIndex++
$(document).on('page:fetch', start)
$(document).on('page:change', stop)
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
form = $("#issue_search_form") form = $("#issue_search_form")
search = $("#issue_search").val() search = $("#issue_search").val()
$('.merge-requests-holder').css("opacity", '0.5') $('.merge-requests-holder').css("opacity", '0.5')
issues_url = form.attr('action') + '? '+ form.serialize() issues_url = form.attr('action') + '?' + form.serialize()
$.ajax $.ajax
type: "GET" type: "GET"
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
success: (data) -> success: (data) ->
$('.merge-requests-holder').html(data.html) $('.merge-requests-holder').html(data.html)
# Change url so if user reload a page - search results are saved # Change url so if user reload a page - search results are saved
History.replaceState {page: issues_url}, document.title, issues_url history.replaceState {page: issues_url}, document.title, issues_url
MergeRequests.reload() MergeRequests.reload()
dataType: "json" dataType: "json"
......
...@@ -19,38 +19,33 @@ ...@@ -19,38 +19,33 @@
} }
} }
} }
/** /**
* This overwrites the default values of the cal-heatmap gem * This overwrites the default values of the cal-heatmap gem
*/ */
.calendar { .calendar {
.qi { .qi {
background-color: #999;
fill: #fff; fill: #fff;
} }
.q1 { .q1 {
background-color: #dae289; fill: #ededed !important;
fill: #ededed;
} }
.q2 { .q2 {
background-color: #cedb9c; fill: #ACD5F2 !important;
fill: #ACD5F2;
} }
.q3 { .q3 {
background-color: #b5cf6b; fill: #7FA8D1 !important;
fill: #7FA8D1;
} }
.q4 { .q4 {
background-color: #637939; fill: #49729B !important;
fill: #49729B;
} }
.q5 { .q5 {
background-color: #3b6427; fill: #254E77 !important;
fill: #254E77;
} }
.domain-background { .domain-background {
...@@ -59,32 +54,7 @@ ...@@ -59,32 +54,7 @@
} }
.ch-tooltip { .ch-tooltip {
position: absolute;
display: none;
margin-top: 22px;
margin-left: 1px;
font-size: 13px;
padding: 3px; padding: 3px;
font-weight: 550; font-weight: 550;
background-color: #222;
span {
position: absolute;
width: 200px;
text-align: center;
visibility: hidden;
border-radius: 10px;
&:after {
content: '';
position: absolute;
top: 100%;
left: 50%;
margin-left: -8px;
width: 0;
height: 0;
border-top: 8px solid #000000;
border-right: 8px solid transparent;
border-left: 8px solid transparent;
}
}
} }
} }
...@@ -105,7 +105,7 @@ ...@@ -105,7 +105,7 @@
.tanuki-shape { .tanuki-shape {
transition: all 0.8s; transition: all 0.8s;
&:hover { &:hover, &.highlight {
fill: rgb(255, 255, 255); fill: rgb(255, 255, 255);
transition: all 0.1s; transition: all 0.1s;
} }
......
...@@ -54,17 +54,17 @@ ...@@ -54,17 +54,17 @@
h3 { h3 {
margin: 24px 0 12px 0; margin: 24px 0 12px 0;
font-size: 1.25em; font-size: 1.1em;
} }
h4 { h4 {
margin: 24px 0 12px 0; margin: 24px 0 12px 0;
font-size: 1.1em; font-size: 0.98em;
} }
h5 { h5 {
margin: 24px 0 12px 0; margin: 24px 0 12px 0;
font-size: 1em; font-size: 0.95em;
} }
h6 { h6 {
......
...@@ -26,6 +26,13 @@ ...@@ -26,6 +26,13 @@
} }
.project-home-panel { .project-home-panel {
.cover-controls {
.project-settings-dropdown {
margin-left: 10px;
}
}
.project-identicon-holder { .project-identicon-holder {
margin-bottom: 16px; margin-bottom: 16px;
......
...@@ -9,12 +9,10 @@ class AbuseReportsController < ApplicationController ...@@ -9,12 +9,10 @@ class AbuseReportsController < ApplicationController
@abuse_report.reporter = current_user @abuse_report.reporter = current_user
if @abuse_report.save if @abuse_report.save
if current_application_settings.admin_notification_email.present? @abuse_report.notify
AbuseReportMailer.notify(@abuse_report.id).deliver_later
end
message = "Thank you for your report. A GitLab administrator will look into it shortly." message = "Thank you for your report. A GitLab administrator will look into it shortly."
redirect_to root_path, notice: message redirect_to @abuse_report.user, notice: message
else else
render :new render :new
end end
...@@ -23,6 +21,9 @@ class AbuseReportsController < ApplicationController ...@@ -23,6 +21,9 @@ class AbuseReportsController < ApplicationController
private private
def report_params def report_params
params.require(:abuse_report).permit(:user_id, :message) params.require(:abuse_report).permit(%i(
message
user_id
))
end end
end end
...@@ -5,12 +5,12 @@ class Admin::BuildsController < Admin::ApplicationController ...@@ -5,12 +5,12 @@ class Admin::BuildsController < Admin::ApplicationController
@builds = @all_builds.order('created_at DESC') @builds = @all_builds.order('created_at DESC')
@builds = @builds =
case @scope case @scope
when 'all' when 'running'
@builds @builds.running_or_pending.reverse_order
when 'finished' when 'finished'
@builds.finished @builds.finished
else else
@builds.running_or_pending.reverse_order @builds
end end
@builds = @builds.page(params[:page]).per(30) @builds = @builds.page(params[:page]).per(30)
end end
......
class Explore::GroupsController < Explore::ApplicationController class Explore::GroupsController < Explore::ApplicationController
def index def index
@groups = GroupsFinder.new.execute(current_user) @groups = Group.order_id_desc
@groups = @groups.search(params[:search]) if params[:search].present? @groups = @groups.search(params[:search]) if params[:search].present?
@groups = @groups.sort(@sort = params[:sort]) @groups = @groups.sort(@sort = params[:sort])
@groups = @groups.page(params[:page]).per(PER_PAGE) @groups = @groups.page(params[:page]).per(PER_PAGE)
......
...@@ -12,12 +12,12 @@ class Projects::BuildsController < Projects::ApplicationController ...@@ -12,12 +12,12 @@ class Projects::BuildsController < Projects::ApplicationController
@builds = @all_builds.order('created_at DESC') @builds = @all_builds.order('created_at DESC')
@builds = @builds =
case @scope case @scope
when 'all' when 'running'
@builds @builds.running_or_pending.reverse_order
when 'finished' when 'finished'
@builds.finished @builds.finished
else else
@builds.running_or_pending.reverse_order @builds
end end
@builds = @builds.page(params[:page]).per(30) @builds = @builds.page(params[:page]).per(30)
end end
......
...@@ -7,7 +7,7 @@ class UsersController < ApplicationController ...@@ -7,7 +7,7 @@ class UsersController < ApplicationController
@projects = PersonalProjectsFinder.new(@user).execute(current_user) @projects = PersonalProjectsFinder.new(@user).execute(current_user)
@groups = JoinedGroupsFinder.new(@user).execute(current_user) @groups = @user.groups.order_id_desc
respond_to do |format| respond_to do |format|
format.html format.html
......
class GroupsFinder
# Finds the groups available to the given user.
#
# current_user - The user to find the groups for.
#
# Returns an ActiveRecord::Relation.
def execute(current_user = nil)
if current_user
relation = groups_visible_to_user(current_user)
else
relation = public_groups
end
relation.order_id_desc
end
private
# This method returns the groups "current_user" can see.
def groups_visible_to_user(current_user)
base = groups_for_projects(public_and_internal_projects)
union = Gitlab::SQL::Union.
new([base.select(:id), current_user.authorized_groups.select(:id)])
Group.where("namespaces.id IN (#{union.to_sql})")
end
def public_groups
groups_for_projects(public_projects)
end
def groups_for_projects(projects)
Group.public_and_given_groups(projects.select(:namespace_id))
end
def public_projects
Project.unscoped.public_only
end
def public_and_internal_projects
Project.unscoped.public_and_internal_only
end
end
# Class for finding the groups a user is a member of.
class JoinedGroupsFinder
def initialize(user = nil)
@user = user
end
# Finds the groups of the source user, optionally limited to those visible to
# the current user.
#
# current_user - If given the groups of "@user" will only include the groups
# "current_user" can also see.
#
# Returns an ActiveRecord::Relation.
def execute(current_user = nil)
if current_user
relation = groups_visible_to_user(current_user)
else
relation = public_groups
end
relation.order_id_desc
end
private
# Returns the groups the user in "current_user" can see.
#
# This list includes all public/internal projects as well as the projects of
# "@user" that "current_user" also has access to.
def groups_visible_to_user(current_user)
base = @user.authorized_groups.visible_to_user(current_user)
extra = public_and_internal_groups
union = Gitlab::SQL::Union.new([base.select(:id), extra.select(:id)])
Group.where("namespaces.id IN (#{union.to_sql})")
end
def public_groups
groups_for_projects(@user.authorized_projects.public_only)
end
def public_and_internal_groups
groups_for_projects(@user.authorized_projects.public_and_internal_only)
end
def groups_for_projects(projects)
@user.groups.public_and_given_groups(projects.select(:namespace_id))
end
end
...@@ -99,7 +99,7 @@ module IssuesHelper ...@@ -99,7 +99,7 @@ module IssuesHelper
end end
def emoji_icon(name, unicode = nil, aliases = []) def emoji_icon(name, unicode = nil, aliases = [])
unicode ||= Emoji.emoji_filename(name) unicode ||= Emoji.emoji_filename(name) rescue ""
content_tag :div, "", content_tag :div, "",
class: "icon emoji-icon emoji-#{unicode}", class: "icon emoji-icon emoji-#{unicode}",
......
...@@ -70,7 +70,7 @@ module SearchHelper ...@@ -70,7 +70,7 @@ module SearchHelper
# Autocomplete results for the current user's groups # Autocomplete results for the current user's groups
def groups_autocomplete(term, limit = 5) def groups_autocomplete(term, limit = 5)
GroupsFinder.new.execute(current_user).search(term).limit(limit).map do |group| Group.search(term).limit(limit).map do |group|
{ {
label: "group: #{search_result_sanitize(group.name)}", label: "group: #{search_result_sanitize(group.name)}",
url: group_path(group) url: group_path(group)
......
...@@ -2,11 +2,19 @@ class AbuseReportMailer < BaseMailer ...@@ -2,11 +2,19 @@ class AbuseReportMailer < BaseMailer
include Gitlab::CurrentSettings include Gitlab::CurrentSettings
def notify(abuse_report_id) def notify(abuse_report_id)
return unless deliverable?
@abuse_report = AbuseReport.find(abuse_report_id) @abuse_report = AbuseReport.find(abuse_report_id)
mail( mail(
to: current_application_settings.admin_notification_email, to: current_application_settings.admin_notification_email,
subject: "#{@abuse_report.user.name} (#{@abuse_report.user.username}) was reported for abuse" subject: "#{@abuse_report.user.name} (#{@abuse_report.user.username}) was reported for abuse"
) )
end end
private
def deliverable?
current_application_settings.admin_notification_email.present?
end
end end
...@@ -48,7 +48,7 @@ module Emails ...@@ -48,7 +48,7 @@ module Emails
yield yield
SentNotification.record(@note, recipient_id, reply_key) SentNotification.record_note(@note, recipient_id, reply_key)
end end
end end
end end
...@@ -69,7 +69,7 @@ class Ability ...@@ -69,7 +69,7 @@ class Ability
subject.group subject.group
end end
if group && group.public_profile? if group && group.projects.public_only.any?
[:read_group] [:read_group]
else else
[] []
......
...@@ -18,4 +18,10 @@ class AbuseReport < ActiveRecord::Base ...@@ -18,4 +18,10 @@ class AbuseReport < ActiveRecord::Base
validates :user, presence: true validates :user, presence: true
validates :message, presence: true validates :message, presence: true
validates :user_id, uniqueness: true validates :user_id, uniqueness: true
def notify
return unless self.persisted?
AbuseReportMailer.notify(self.id).deliver_later
end
end end
...@@ -50,10 +50,6 @@ class Group < Namespace ...@@ -50,10 +50,6 @@ class Group < Namespace
User.reference_pattern User.reference_pattern
end end
def public_and_given_groups(ids)
where('public IS TRUE OR namespaces.id IN (?)', ids)
end
def visible_to_user(user) def visible_to_user(user)
where(id: user.authorized_groups.select(:id).reorder(nil)) where(id: user.authorized_groups.select(:id).reorder(nil))
end end
...@@ -125,10 +121,6 @@ class Group < Namespace ...@@ -125,10 +121,6 @@ class Group < Namespace
end end
end end
def public_profile?
self.public || projects.public_only.any?
end
def post_create_hook def post_create_hook
Gitlab::AppLogger.info("Group \"#{name}\" was created") Gitlab::AppLogger.info("Group \"#{name}\" was created")
......
...@@ -81,6 +81,7 @@ class Project < ActiveRecord::Base ...@@ -81,6 +81,7 @@ class Project < ActiveRecord::Base
acts_as_taggable_on :tags acts_as_taggable_on :tags
attr_accessor :new_default_branch attr_accessor :new_default_branch
attr_accessor :old_path_with_namespace
# Relations # Relations
belongs_to :creator, foreign_key: 'creator_id', class_name: 'User' belongs_to :creator, foreign_key: 'creator_id', class_name: 'User'
...@@ -701,6 +702,11 @@ class Project < ActiveRecord::Base ...@@ -701,6 +702,11 @@ class Project < ActiveRecord::Base
gitlab_shell.mv_repository("#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki") gitlab_shell.mv_repository("#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki")
send_move_instructions(old_path_with_namespace) send_move_instructions(old_path_with_namespace)
reset_events_cache reset_events_cache
@old_path_with_namespace = old_path_with_namespace
SystemHooksService.new.execute_hooks_for(self, :rename)
@repository = nil @repository = nil
rescue rescue
# Returning false does not rollback after_* transaction but gives # Returning false does not rollback after_* transaction but gives
......
...@@ -352,10 +352,13 @@ class User < ActiveRecord::Base ...@@ -352,10 +352,13 @@ class User < ActiveRecord::Base
end end
def namespace_uniq def namespace_uniq
# Return early if username already failed the first uniqueness validation
return if self.errors[:username].include?('has already been taken')
namespace_name = self.username namespace_name = self.username
existing_namespace = Namespace.by_path(namespace_name) existing_namespace = Namespace.by_path(namespace_name)
if existing_namespace && existing_namespace != self.namespace if existing_namespace && existing_namespace != self.namespace
self.errors.add :username, "already exists" self.errors.add(:username, 'has already been taken')
end end
end end
......
...@@ -413,6 +413,7 @@ class NotificationService ...@@ -413,6 +413,7 @@ class NotificationService
recipients = reject_unsubscribed_users(recipients, target) recipients = reject_unsubscribed_users(recipients, target)
recipients.delete(current_user) recipients.delete(current_user)
recipients = recipients.uniq
recipients recipients
end end
......
...@@ -55,6 +55,9 @@ module Projects ...@@ -55,6 +55,9 @@ module Projects
# Move uploads # Move uploads
Gitlab::UploadsTransfer.new.move_project(project.path, old_namespace.path, new_namespace.path) Gitlab::UploadsTransfer.new.move_project(project.path, old_namespace.path, new_namespace.path)
project.old_path_with_namespace = old_path
SystemHooksService.new.execute_hooks_for(project, :transfer)
true true
end end
end end
......
...@@ -18,7 +18,8 @@ class SystemHooksService ...@@ -18,7 +18,8 @@ class SystemHooksService
def build_event_data(model, event) def build_event_data(model, event)
data = { data = {
event_name: build_event_name(model, event), event_name: build_event_name(model, event),
created_at: model.created_at.xmlschema created_at: model.created_at.xmlschema,
updated_at: model.updated_at.xmlschema
} }
case model case model
...@@ -34,6 +35,14 @@ class SystemHooksService ...@@ -34,6 +35,14 @@ class SystemHooksService
end end
when Project when Project
data.merge!(project_data(model)) data.merge!(project_data(model))
if event == :rename || event == :transfer
data.merge!({
old_path_with_namespace: model.old_path_with_namespace
})
end
data
when User when User
data.merge!({ data.merge!({
name: model.name, name: model.name,
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
%h3.page-title Report abuse %h3.page-title Report abuse
%p Please use this form to report users who create spam issues, comments or behave inappropriately. %p Please use this form to report users who create spam issues, comments or behave inappropriately.
%hr %hr
= form_for @abuse_report, html: { class: 'form-horizontal'} do |f| = form_for @abuse_report, html: { class: 'form-horizontal js-requires-input'} do |f|
= f.hidden_field :user_id = f.hidden_field :user_id
- if @abuse_report.errors.any? - if @abuse_report.errors.any?
.alert.alert-danger .alert.alert-danger
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
.form-group .form-group
= f.label :message, class: 'control-label' = f.label :message, class: 'control-label'
.col-sm-10 .col-sm-10
= f.text_area :message, class: "form-control", rows: 2, required: true = f.text_area :message, class: "form-control js-quick-submit", rows: 2, required: true
.help-block .help-block
Explain the problem with this user. If appropriate, provide a link to the relevant issue or comment. Explain the problem with this user. If appropriate, provide a link to the relevant issue or comment.
......
...@@ -2,19 +2,21 @@ ...@@ -2,19 +2,21 @@
- user = abuse_report.user - user = abuse_report.user
%tr %tr
%td %td
- if reporter - if user
= link_to reporter.name, reporter = link_to user.name, [:admin, user]
.light.small
Joined #{time_ago_with_tooltip(user.created_at)}
- else - else
(removed) (removed)
%td %td
= abuse_report.created_at.to_s(:short) - if reporter
%td = link_to reporter.name, [:admin, reporter]
= abuse_report.message
%td
- if user
= link_to user.name, user
- else - else
(removed) (removed)
.light.small
= time_ago_with_tooltip(abuse_report.created_at)
%td
= markdown(abuse_report.message.squish!, pipeline: :single_line)
%td %td
- if user - if user
= link_to 'Remove user & report', admin_abuse_report_path(abuse_report, remove_user: true), = link_to 'Remove user & report', admin_abuse_report_path(abuse_report, remove_user: true),
......
...@@ -6,10 +6,9 @@ ...@@ -6,10 +6,9 @@
%table.table %table.table
%thead %thead
%tr %tr
%th User
%th Reported by %th Reported by
%th Reported at
%th Message %th Message
%th User
%th Primary action %th Primary action
%th %th
= render @abuse_reports = render @abuse_reports
......
...@@ -149,7 +149,7 @@ ...@@ -149,7 +149,7 @@
.checkbox .checkbox
= f.label :shared_runners_enabled do = f.label :shared_runners_enabled do
= f.check_box :shared_runners_enabled = f.check_box :shared_runners_enabled
Enable shared runners for a new projects Enable shared runners for new projects
.form-group .form-group
= f.label :max_artifacts_size, 'Maximum artifacts size (MB)', class: 'control-label col-sm-2' = f.label :max_artifacts_size, 'Maximum artifacts size (MB)', class: 'control-label col-sm-2'
......
...@@ -7,18 +7,18 @@ ...@@ -7,18 +7,18 @@
%ul.center-top-menu %ul.center-top-menu
%li{class: ('active' if @scope.nil?)} %li{class: ('active' if @scope.nil?)}
= link_to admin_builds_path do = link_to admin_builds_path do
All
%span.badge.js-totalbuilds-count= @all_builds.count(:id)
%li{class: ('active' if @scope == 'running')}
= link_to admin_builds_path(scope: :running) do
Running Running
%span.badge.js-running-count= @all_builds.running_or_pending.count(:id) %span.badge.js-running-count= number_with_delimiter(@all_builds.running_or_pending.count(:id))
%li{class: ('active' if @scope == 'finished')} %li{class: ('active' if @scope == 'finished')}
= link_to admin_builds_path(scope: :finished) do = link_to admin_builds_path(scope: :finished) do
Finished Finished
%span.badge.js-running-count= @all_builds.finished.count(:id) %span.badge.js-running-count= number_with_delimiter(@all_builds.finished.count(:id))
%li{class: ('active' if @scope == 'all')}
= link_to admin_builds_path(scope: :all) do
All
%span.badge.js-totalbuilds-count= @all_builds.count(:id)
.gray-content-block .gray-content-block
#{(@scope || 'running').capitalize} builds #{(@scope || 'running').capitalize} builds
......
...@@ -6,35 +6,35 @@ ...@@ -6,35 +6,35 @@
%p %p
Forks Forks
%span.light.pull-right %span.light.pull-right
= ForkedProjectLink.count = number_with_delimiter(ForkedProjectLink.count)
%p %p
Issues Issues
%span.light.pull-right %span.light.pull-right
= Issue.count = number_with_delimiter(Issue.count)
%p %p
Merge Requests Merge Requests
%span.light.pull-right %span.light.pull-right
= MergeRequest.count = number_with_delimiter(MergeRequest.count)
%p %p
Notes Notes
%span.light.pull-right %span.light.pull-right
= Note.count = number_with_delimiter(Note.count)
%p %p
Snippets Snippets
%span.light.pull-right %span.light.pull-right
= Snippet.count = number_with_delimiter(Snippet.count)
%p %p
SSH Keys SSH Keys
%span.light.pull-right %span.light.pull-right
= Key.count = number_with_delimiter(Key.count)
%p %p
Milestones Milestones
%span.light.pull-right %span.light.pull-right
= Milestone.count = number_with_delimiter(Milestone.count)
%p %p
Active Users Active Users
%span.light.pull-right %span.light.pull-right
= User.active.count = number_with_delimiter(User.active.count)
.col-md-4 .col-md-4
%h4 %h4
Features Features
...@@ -99,7 +99,7 @@ ...@@ -99,7 +99,7 @@
%h4 Projects %h4 Projects
.data .data
= link_to admin_namespaces_projects_path do = link_to admin_namespaces_projects_path do
%h1= Project.count %h1= number_with_delimiter(Project.count)
%hr %hr
= link_to('New Project', new_project_path, class: "btn btn-new") = link_to('New Project', new_project_path, class: "btn btn-new")
.col-sm-4 .col-sm-4
...@@ -107,7 +107,7 @@ ...@@ -107,7 +107,7 @@
%h4 Users %h4 Users
.data .data
= link_to admin_users_path do = link_to admin_users_path do
%h1= User.count %h1= number_with_delimiter(User.count)
%hr %hr
= link_to 'New User', new_admin_user_path, class: "btn btn-new" = link_to 'New User', new_admin_user_path, class: "btn btn-new"
.col-sm-4 .col-sm-4
...@@ -115,7 +115,7 @@ ...@@ -115,7 +115,7 @@
%h4 Groups %h4 Groups
.data .data
= link_to admin_groups_path do = link_to admin_groups_path do
%h1= Group.count %h1= number_with_delimiter(Group.count)
%hr %hr
= link_to 'New Group', new_admin_group_path, class: "btn btn-new" = link_to 'New Group', new_admin_group_path, class: "btn btn-new"
......
- page_title "Groups" - page_title "Groups"
%h3.page-title %h3.page-title
Groups (#{@groups.total_count}) Groups (#{number_with_delimiter(@groups.total_count)})
= link_to 'New Group', new_admin_group_path, class: "btn btn-new pull-right" = link_to 'New Group', new_admin_group_path, class: "btn btn-new pull-right"
%p.light %p.light
......
...@@ -8,27 +8,27 @@ ...@@ -8,27 +8,27 @@
%li{class: "#{'active' unless params[:filter]}"} %li{class: "#{'active' unless params[:filter]}"}
= link_to admin_users_path do = link_to admin_users_path do
Active Active
%small.pull-right= User.active.count %small.pull-right= number_with_delimiter(User.active.count)
%li{class: "#{'active' if params[:filter] == "admins"}"} %li{class: "#{'active' if params[:filter] == "admins"}"}
= link_to admin_users_path(filter: "admins") do = link_to admin_users_path(filter: "admins") do
Admins Admins
%small.pull-right= User.admins.count %small.pull-right= number_with_delimiter(User.admins.count)
%li.filter-two-factor-enabled{class: "#{'active' if params[:filter] == 'two_factor_enabled'}"} %li.filter-two-factor-enabled{class: "#{'active' if params[:filter] == 'two_factor_enabled'}"}
= link_to admin_users_path(filter: 'two_factor_enabled') do = link_to admin_users_path(filter: 'two_factor_enabled') do
2FA Enabled 2FA Enabled
%small.pull-right= User.with_two_factor.count %small.pull-right= number_with_delimiter(User.with_two_factor.count)
%li.filter-two-factor-disabled{class: "#{'active' if params[:filter] == 'two_factor_disabled'}"} %li.filter-two-factor-disabled{class: "#{'active' if params[:filter] == 'two_factor_disabled'}"}
= link_to admin_users_path(filter: 'two_factor_disabled') do = link_to admin_users_path(filter: 'two_factor_disabled') do
2FA Disabled 2FA Disabled
%small.pull-right= User.without_two_factor.count %small.pull-right= number_with_delimiter(User.without_two_factor.count)
%li{class: "#{'active' if params[:filter] == "blocked"}"} %li{class: "#{'active' if params[:filter] == "blocked"}"}
= link_to admin_users_path(filter: "blocked") do = link_to admin_users_path(filter: "blocked") do
Blocked Blocked
%small.pull-right= User.blocked.count %small.pull-right= number_with_delimiter(User.blocked.count)
%li{class: "#{'active' if params[:filter] == "wop"}"} %li{class: "#{'active' if params[:filter] == "wop"}"}
= link_to admin_users_path(filter: "wop") do = link_to admin_users_path(filter: "wop") do
Without projects Without projects
%small.pull-right= User.without_projects.count %small.pull-right= number_with_delimiter(User.without_projects.count)
%hr %hr
= form_tag admin_users_path, method: :get, class: 'form-inline' do = form_tag admin_users_path, method: :get, class: 'form-inline' do
.form-group .form-group
...@@ -42,7 +42,7 @@ ...@@ -42,7 +42,7 @@
%section.col-md-9 %section.col-md-9
.panel.panel-default .panel.panel-default
.panel-heading .panel-heading
Users (#{@users.total_count}) Users (#{number_with_delimiter(@users.total_count)})
.panel-head-actions .panel-head-actions
.dropdown.inline .dropdown.inline
%a.dropdown-toggle.btn.btn-sm{href: '#', "data-toggle" => "dropdown"} %a.dropdown-toggle.btn.btn-sm{href: '#', "data-toggle" => "dropdown"}
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
.event-item-timestamp .event-item-timestamp
#{time_ago_with_tooltip(event.created_at)} #{time_ago_with_tooltip(event.created_at)}
= cache [event, "v2.1"] do = cache [event, current_application_settings, "v2.1"] do
= image_tag avatar_icon(event.author_email, 46), class: "avatar s46", alt:'' = image_tag avatar_icon(event.author_email, 46), class: "avatar s46", alt:''
- if event.created_project? - if event.created_project?
= render "events/event/created_project", event: event = render "events/event/created_project", event: event
......
...@@ -24,15 +24,6 @@ ...@@ -24,15 +24,6 @@
%hr %hr
= link_to 'Remove avatar', group_avatar_path(@group.to_param), data: { confirm: "Group avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar" = link_to 'Remove avatar', group_avatar_path(@group.to_param), data: { confirm: "Group avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar"
.form-group
%hr
= f.label :public, class: 'control-label' do
Public
.col-sm-10
.checkbox
= f.check_box :public
%span.descr Make this group public (even if there are no public projects inside this group)
.form-actions .form-actions
= f.submit 'Save group', class: "btn btn-save" = f.submit 'Save group', class: "btn btn-save"
......
...@@ -47,5 +47,5 @@ ...@@ -47,5 +47,5 @@
= render "projects", projects: @projects = render "projects", projects: @projects
- else - else
%p %p.center-top-menu.no-top
This group does not have public projects No projects to show
%head{prefix: "og: http://ogp.me/ns#"} %head{prefix: "og: http://ogp.me/ns#"}
%meta{charset: "utf-8"} %meta{charset: "utf-8"}
%meta{'http-equiv' => 'X-UA-Compatible', content: 'IE=edge'} %meta{'http-equiv' => 'X-UA-Compatible', content: 'IE=edge'}
%meta{name: 'referrer', content: 'origin-when-cross-origin'}
%meta{name: "description", content: page_description}
-# Open Graph - http://ogp.me/ -# Open Graph - http://ogp.me/
%meta{property: 'og:type', content: "object"} %meta{property: 'og:type', content: "object"}
...@@ -20,8 +17,8 @@ ...@@ -20,8 +17,8 @@
%meta{property: 'twitter:image', content: page_image} %meta{property: 'twitter:image', content: page_image}
= page_card_meta_tags = page_card_meta_tags
- page_title "GitLab" %title= page_title('GitLab')
%title= page_title %meta{name: "description", content: page_description}
= favicon_link_tag 'favicon.ico' = favicon_link_tag 'favicon.ico'
...@@ -34,6 +31,8 @@ ...@@ -34,6 +31,8 @@
= include_gon = include_gon
- unless browser.safari?
%meta{name: 'referrer', content: 'origin-when-cross-origin'}
%meta{name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1'} %meta{name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1'}
%meta{name: 'theme-color', content: '#474D57'} %meta{name: 'theme-color', content: '#474D57'}
......
...@@ -29,13 +29,13 @@ ...@@ -29,13 +29,13 @@
= icon('cog fw') = icon('cog fw')
%span %span
Runners Runners
%span.count= Ci::Runner.count(:all) %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 do = link_to admin_builds_path do
= icon('link fw') = icon('link fw')
%span %span
Builds Builds
%span.count= Ci::Build.count(:all) %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')
...@@ -80,7 +80,7 @@ ...@@ -80,7 +80,7 @@
= icon('exclamation-circle fw') = icon('exclamation-circle fw')
%span %span
Abuse Reports Abuse Reports
%span.count= AbuseReport.count(:all) %span.count= number_with_delimiter(AbuseReport.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
......
...@@ -24,13 +24,13 @@ ...@@ -24,13 +24,13 @@
= icon('exclamation-circle fw') = icon('exclamation-circle fw')
%span %span
Issues Issues
%span.count= current_user.assigned_issues.opened.count %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: 'shortcuts-merge_requests' do = link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'shortcuts-merge_requests' do
= icon('tasks fw') = icon('tasks fw')
%span %span
Merge Requests Merge Requests
%span.count= current_user.assigned_merge_requests.opened.count %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')
......
...@@ -25,14 +25,14 @@ ...@@ -25,14 +25,14 @@
%span %span
Issues Issues
- if current_user - if current_user
%span.count= Issue.opened.of_group(@group).count %span.count= number_with_delimiter(Issue.opened.of_group(@group).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
= icon('tasks fw') = icon('tasks fw')
%span %span
Merge Requests Merge Requests
- if current_user - if current_user
%span.count= MergeRequest.opened.of_group(@group).count %span.count= number_with_delimiter(MergeRequest.opened.of_group(@group).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
= icon('users fw') = icon('users fw')
......
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
= icon('envelope-o fw') = icon('envelope-o fw')
%span %span
Emails Emails
%span.count= current_user.emails.count + 1 %span.count= number_with_delimiter(current_user.emails.count + 1)
- 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
...@@ -45,7 +45,7 @@ ...@@ -45,7 +45,7 @@
= icon('key fw') = icon('key fw')
%span %span
SSH Keys SSH Keys
%span.count= current_user.keys.count %span.count= number_with_delimiter(current_user.keys.count)
= 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? -# TODO (rspeicher): Better icon?
......
...@@ -44,7 +44,7 @@ ...@@ -44,7 +44,7 @@
= icon('cubes fw') = icon('cubes fw')
%span %span
Builds Builds
%span.count.builds_counter= @project.builds.running_or_pending.count(:all) %span.count.builds_counter= number_with_delimiter(@project.builds.running_or_pending.count(:all))
- if project_nav_tab? :graphs - if project_nav_tab? :graphs
= nav_link(controller: %w(graphs)) do = nav_link(controller: %w(graphs)) do
...@@ -67,7 +67,7 @@ ...@@ -67,7 +67,7 @@
%span %span
Issues Issues
- if @project.default_issues_tracker? - if @project.default_issues_tracker?
%span.count.issue_counter= @project.issues.opened.count %span.count.issue_counter= number_with_delimiter(@project.issues.opened.count)
- if project_nav_tab? :merge_requests - if project_nav_tab? :merge_requests
= nav_link(controller: :merge_requests) do = nav_link(controller: :merge_requests) do
...@@ -75,7 +75,7 @@ ...@@ -75,7 +75,7 @@
= icon('tasks fw') = icon('tasks fw')
%span %span
Merge Requests Merge Requests
%span.count.merge_counter= @project.merge_requests.opened.count %span.count.merge_counter= number_with_delimiter(@project.merge_requests.opened.count)
- if project_nav_tab? :settings - if project_nav_tab? :settings
= nav_link(controller: [:project_members, :teams]) do = nav_link(controller: [:project_members, :teams]) do
......
...@@ -18,13 +18,26 @@ ...@@ -18,13 +18,26 @@
= visibility_level_label(@project.visibility_level) = visibility_level_label(@project.visibility_level)
.cover-controls .cover-controls
- if can?(current_user, :admin_project, @project)
= link_to edit_project_path(@project), class: 'btn btn-gray' do
= icon('pencil')
- if current_user - if current_user
&nbsp;
= link_to namespace_project_path(@project.namespace, @project, format: :atom, private_token: current_user.private_token), class: 'btn btn-gray' do = link_to namespace_project_path(@project.namespace, @project, format: :atom, private_token: current_user.private_token), class: 'btn btn-gray' do
= icon('rss') = icon('rss')
- access = user_max_access_in_project(current_user.id, @project)
- can_edit = can?(current_user, :admin_project, @project)
- if access || can_edit
%span.dropdown.project-settings-dropdown
%a.dropdown-new.btn.btn-gray#project-settings-button{href: '#', 'data-toggle' => 'dropdown'}
= icon('cog')
= icon('angle-down')
%ul.dropdown-menu.dropdown-menu-right
- if can_edit
%li
= link_to edit_project_path(@project) do
Edit Project
- if access
%li
= link_to leave_namespace_project_project_members_path(@project.namespace, @project),
data: { confirm: leave_project_message(@project) }, method: :delete, title: 'Leave project' do
Leave Project
.project-repo-buttons .project-repo-buttons
.split-one.count-buttons .split-one.count-buttons
......
...@@ -11,6 +11,12 @@ ...@@ -11,6 +11,12 @@
%ul.center-top-menu %ul.center-top-menu
%li{class: ('active' if @scope.nil?)} %li{class: ('active' if @scope.nil?)}
= link_to project_builds_path(@project) do = link_to project_builds_path(@project) do
All
%span.badge.js-totalbuilds-count
= number_with_delimiter(@all_builds.count(:id))
%li{class: ('active' if @scope == 'running')}
= link_to project_builds_path(@project, scope: :running) do
Running Running
%span.badge.js-running-count %span.badge.js-running-count
= number_with_delimiter(@all_builds.running_or_pending.count(:id)) = number_with_delimiter(@all_builds.running_or_pending.count(:id))
...@@ -21,12 +27,6 @@ ...@@ -21,12 +27,6 @@
%span.badge.js-running-count %span.badge.js-running-count
= number_with_delimiter(@all_builds.finished.count(:id)) = number_with_delimiter(@all_builds.finished.count(:id))
%li{class: ('active' if @scope == 'all')}
= link_to project_builds_path(@project, scope: :all) do
All
%span.badge.js-totalbuilds-count
= number_with_delimiter(@all_builds.count(:id))
.gray-content-block .gray-content-block
#{(@scope || 'running').capitalize} builds from this project #{(@scope || 'running').capitalize} builds from this project
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
- note_count = notes.user.count - note_count = notes.user.count
- ci_commit = project.ci_commit(commit.sha) - ci_commit = project.ci_commit(commit.sha)
- cache_key = [project.path_with_namespace, commit.id, note_count] - cache_key = [project.path_with_namespace, commit.id, current_application_settings, note_count]
- cache_key.push(ci_commit.status) if ci_commit - cache_key.push(ci_commit.status) if ci_commit
= cache(cache_key) do = cache(cache_key) do
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
- if @issues.present? - if @issues.present?
.issuable-filter-count .issuable-filter-count
%span.pull-right %span.pull-right
= @issues.total_count = number_with_delimiter(@issues.total_count)
issues for this filter issues for this filter
= paginate @issues, theme: "gitlab" = paginate @issues, theme: "gitlab"
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
- if @merge_requests.present? - if @merge_requests.present?
.issuable-filter-count .issuable-filter-count
%span.pull-right %span.pull-right
= @merge_requests.total_count = number_with_delimiter(@merge_requests.total_count)
merge requests for this filter merge requests for this filter
= paginate @merge_requests, theme: "gitlab" = paginate @merge_requests, theme: "gitlab"
......
...@@ -68,15 +68,4 @@ ...@@ -68,15 +68,4 @@
= render 'projects/last_commit', commit: @repository.commit, project: @project = render 'projects/last_commit', commit: @repository.commit, project: @project
%div{class: "project-show-#{default_project_view}"} %div{class: "project-show-#{default_project_view}"}
= render default_project_view = render default_project_view
\ No newline at end of file
- if current_user
- access = user_max_access_in_project(current_user.id, @project)
- if access
.prepend-top-20.project-footer
.gray-content-block.footer-block.center
You have #{access} access to this project.
- if @project.project_member_by_id(current_user)
= link_to leave_namespace_project_project_members_path(@project.namespace, @project),
data: { confirm: leave_project_message(@project) }, method: :delete, title: 'Leave project', class: 'cred' do
Leave this project
...@@ -5,13 +5,13 @@ ...@@ -5,13 +5,13 @@
<g id="Fill-1-+-Group-24"> <g id="Fill-1-+-Group-24">
<g id="Group-24"> <g id="Group-24">
<g id="Group"> <g id="Group">
<path d="M105.0614,193.655 L105.0614,193.655 L143.7014,74.734 L66.4214,74.734 L105.0614,193.655 L105.0614,193.655 Z" id="Fill-4" fill="#E24329" class="tanuki-shape"></path> <path id="tanuki-right-ear" d="M12.2685,74.7342 L66.4215,74.7342 L43.1485,3.1092 C41.9515,-0.5768 36.7375,-0.5758 35.5405,3.1092 L12.2685,74.7342 L12.2685,74.7342 Z" fill="#E24329" class="tanuki-shape"></path>
<path d="M105.0614,193.6548 L66.4214,74.7338 L12.2684,74.7338 L105.0614,193.6548 L105.0614,193.6548 Z" id="Fill-8" fill="#FC6D26" class="tanuki-shape"></path> <path id="tanuki-right-cheek" d="M12.2685,74.7341 L12.2685,74.7341 L0.5265,110.8731 C-0.5445,114.1691 0.6285,117.7801 3.4325,119.8171 L105.0615,193.6551 L12.2685,74.7341 L12.2685,74.7341 Z" fill="#FCA326" class="tanuki-shape"></path>
<path d="M12.2685,74.7341 L12.2685,74.7341 L0.5265,110.8731 C-0.5445,114.1691 0.6285,117.7801 3.4325,119.8171 L105.0615,193.6551 L12.2685,74.7341 L12.2685,74.7341 Z" id="Fill-12" fill="#FCA326" class="tanuki-shape"></path> <path id="tanuki-right-eye" d="M105.0614,193.6548 L66.4214,74.7338 L12.2684,74.7338 L105.0614,193.6548 L105.0614,193.6548 Z" fill="#FC6D26" class="tanuki-shape"></path>
<path d="M12.2685,74.7342 L66.4215,74.7342 L43.1485,3.1092 C41.9515,-0.5768 36.7375,-0.5758 35.5405,3.1092 L12.2685,74.7342 L12.2685,74.7342 Z" id="Fill-16" fill="#E24329" class="tanuki-shape"></path> <path id="tanuki-nose" d="M105.0614,193.655 L105.0614,193.655 L143.7014,74.734 L66.4214,74.734 L105.0614,193.655 L105.0614,193.655 Z" fill="#E24329" class="tanuki-shape"></path>
<path d="M105.0614,193.6548 L143.7014,74.7338 L197.8544,74.7338 L105.0614,193.6548 L105.0614,193.6548 Z" id="Fill-18" fill="#FC6D26" class="tanuki-shape"></path> <path id="tanuki-left-eye" d="M105.0614,193.6548 L143.7014,74.7338 L197.8544,74.7338 L105.0614,193.6548 L105.0614,193.6548 Z" fill="#FC6D26" class="tanuki-shape"></path>
<path d="M197.8544,74.7341 L197.8544,74.7341 L209.5964,110.8731 C210.6674,114.1691 209.4944,117.7801 206.6904,119.8171 L105.0614,193.6551 L197.8544,74.7341 L197.8544,74.7341 Z" id="Fill-20" fill="#FCA326" class="tanuki-shape"></path> <path id="tanuki-left-cheek" d="M197.8544,74.7341 L197.8544,74.7341 L209.5964,110.8731 C210.6674,114.1691 209.4944,117.7801 206.6904,119.8171 L105.0614,193.6551 L197.8544,74.7341 L197.8544,74.7341 Z" fill="#FCA326" class="tanuki-shape"></path>
<path d="M197.8544,74.7342 L143.7014,74.7342 L166.9744,3.1092 C168.1714,-0.5768 173.3854,-0.5758 174.5824,3.1092 L197.8544,74.7342 L197.8544,74.7342 Z" id="Fill-22" fill="#E24329" class="tanuki-shape"></path> <path id="tanuki-left-ear" d="M197.8544,74.7342 L143.7014,74.7342 L166.9744,3.1092 C168.1714,-0.5768 173.3854,-0.5758 174.5824,3.1092 L197.8544,74.7342 L197.8544,74.7342 Z" fill="#E24329" class="tanuki-shape"></path>
</g> </g>
</g> </g>
</g> </g>
......
...@@ -29,14 +29,14 @@ ...@@ -29,14 +29,14 @@
= check_box_tag "check_all_issues", nil, false, = check_box_tag "check_all_issues", nil, false,
class: "check_all_issues left" class: "check_all_issues left"
.issues-other-filters .issues-other-filters
.filter-item.inline
= users_select_tag(:assignee_id, selected: params[:assignee_id],
placeholder: 'Assignee', class: 'trigger-submit', any_user: "Any Assignee", null_user: true, first_user: true, current_user: true)
.filter-item.inline .filter-item.inline
= users_select_tag(:author_id, selected: params[:author_id], = users_select_tag(:author_id, selected: params[:author_id],
placeholder: 'Author', class: 'trigger-submit', any_user: "Any Author", first_user: true, current_user: true) placeholder: 'Author', class: 'trigger-submit', any_user: "Any Author", first_user: true, current_user: true)
.filter-item.inline
= users_select_tag(:assignee_id, selected: params[:assignee_id],
placeholder: 'Assignee', class: 'trigger-submit', any_user: "Any Assignee", null_user: true, first_user: true, current_user: true)
.filter-item.inline.milestone-filter .filter-item.inline.milestone-filter
= select_tag('milestone_title', projects_milestones_options, = select_tag('milestone_title', projects_milestones_options,
class: 'select2 trigger-submit', include_blank: true, class: 'select2 trigger-submit', include_blank: true,
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
- css_class = '' unless local_assigns[:css_class] - css_class = '' unless local_assigns[:css_class]
- css_class += " no-description" unless project.description.present? - css_class += " no-description" unless project.description.present?
%li.project-row{ class: css_class } %li.project-row{ class: css_class }
= cache [project.namespace, project, controller.controller_name, controller.action_name, 'v2.2'] do = cache [project.namespace, project, controller.controller_name, controller.action_name, current_application_settings, 'v2.2'] do
= link_to project_path(project), class: dom_class(project) do = link_to project_path(project), class: dom_class(project) do
- if avatar - if avatar
.dash-project-avatar .dash-project-avatar
......
...@@ -152,9 +152,9 @@ Settings.gitlab['port'] ||= Settings.gitlab.https ? 443 : 80 ...@@ -152,9 +152,9 @@ Settings.gitlab['port'] ||= Settings.gitlab.https ? 443 : 80
Settings.gitlab['relative_url_root'] ||= ENV['RAILS_RELATIVE_URL_ROOT'] || '' Settings.gitlab['relative_url_root'] ||= ENV['RAILS_RELATIVE_URL_ROOT'] || ''
Settings.gitlab['protocol'] ||= Settings.gitlab.https ? "https" : "http" Settings.gitlab['protocol'] ||= Settings.gitlab.https ? "https" : "http"
Settings.gitlab['email_enabled'] ||= true if Settings.gitlab['email_enabled'].nil? Settings.gitlab['email_enabled'] ||= true if Settings.gitlab['email_enabled'].nil?
Settings.gitlab['email_from'] ||= "gitlab@#{Settings.gitlab.host}" Settings.gitlab['email_from'] ||= ENV['GITLAB_EMAIL_FROM'] || "gitlab@#{Settings.gitlab.host}"
Settings.gitlab['email_display_name'] ||= "GitLab" Settings.gitlab['email_display_name'] ||= ENV['GITLAB_EMAIL_DISPLAY_NAME'] || 'GitLab'
Settings.gitlab['email_reply_to'] ||= "noreply@#{Settings.gitlab.host}" Settings.gitlab['email_reply_to'] ||= ENV['GITLAB_EMAIL_REPLY_TO'] || "noreply@#{Settings.gitlab.host}"
Settings.gitlab['base_url'] ||= Settings.send(:build_base_gitlab_url) Settings.gitlab['base_url'] ||= Settings.send(:build_base_gitlab_url)
Settings.gitlab['url'] ||= Settings.send(:build_gitlab_url) Settings.gitlab['url'] ||= Settings.send(:build_gitlab_url)
Settings.gitlab['user'] ||= 'git' Settings.gitlab['user'] ||= 'git'
......
if Gitlab::Metrics.enabled? if Gitlab::Metrics.enabled?
require 'influxdb' require 'influxdb'
require 'socket'
require 'connection_pool' require 'connection_pool'
require 'method_source' require 'method_source'
......
# Use this file to easily define all of your cron jobs.
#
# If you make changes to this file, please create also an issue on
# https://gitlab.com/gitlab-org/omnibus-gitlab/issues . This is necessary
# because the omnibus packages manage cron jobs using Chef instead of Whenever.
every 1.hour do
rake "ci:schedule_builds"
end
# Migration type: online
class RemovePublicFromNamespace < ActiveRecord::Migration
def change
remove_column :namespaces, :public, :boolean
end
end
This diff is collapsed.
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
- [GitLab Basics](gitlab-basics/README.md) Find step by step how to start working on your commandline and on GitLab. - [GitLab Basics](gitlab-basics/README.md) Find step by step how to start working on your commandline and on GitLab.
- [Importing to GitLab](workflow/importing/README.md). - [Importing to GitLab](workflow/importing/README.md).
- [Markdown](markdown/markdown.md) GitLab's advanced formatting system. - [Markdown](markdown/markdown.md) GitLab's advanced formatting system.
- [Migrating from SVN](migration/README.md) Convert a SVN repository to Git and GitLab - [Migrating from SVN](workflow/importing/migrating_from_svn.md) Convert a SVN repository to Git and GitLab
- [Permissions](permissions/permissions.md) Learn what each role in a project (guest/reporter/developer/master/owner) can do. - [Permissions](permissions/permissions.md) Learn what each role in a project (guest/reporter/developer/master/owner) can do.
- [Profile Settings](profile/README.md) - [Profile Settings](profile/README.md)
- [Project Services](project_services/project_services.md) Integrate a project with external services, such as CI and chat. - [Project Services](project_services/project_services.md) Integrate a project with external services, such as CI and chat.
...@@ -56,7 +56,7 @@ ...@@ -56,7 +56,7 @@
- [Issue closing](customization/issue_closing.md) Customize how to close an issue from commit messages. - [Issue closing](customization/issue_closing.md) Customize how to close an issue from commit messages.
- [Libravatar](customization/libravatar.md) Use Libravatar for user avatars. - [Libravatar](customization/libravatar.md) Use Libravatar for user avatars.
- [Log system](logs/logs.md) Log system. - [Log system](logs/logs.md) Log system.
- [Environmental Variables](administration/environmental_variables.md) to configure GitLab. - [Environment Variables](administration/environment_variables.md) to configure GitLab.
- [Operations](operations/README.md) Keeping GitLab up and running - [Operations](operations/README.md) Keeping GitLab up and running
- [Raketasks](raketasks/README.md) Backups, maintenance, automatic web hook setup and the importing of projects. - [Raketasks](raketasks/README.md) Backups, maintenance, automatic web hook setup and the importing of projects.
- [Security](security/README.md) Learn what you can do to further secure your GitLab instance. - [Security](security/README.md) Learn what you can do to further secure your GitLab instance.
......
...@@ -9,11 +9,14 @@ But if you prefer to use environment variables we allow that too. ...@@ -9,11 +9,14 @@ But if you prefer to use environment variables we allow that too.
## Supported environment variables ## Supported environment variables
Variable | Type | Explanation Variable | Type | Explanation
--- | --- | --- -------- | ---- | -----------
GITLAB_ROOT_PASSWORD | string | sets the password for the `root` user on installation GITLAB_ROOT_PASSWORD | string | sets the password for the `root` user on installation
GITLAB_HOST | url | hostname of the GitLab server includes http or https GITLAB_HOST | url | hostname of the GitLab server includes http or https
RAILS_ENV | production/development/staging/test | Rails environment RAILS_ENV | production / development / staging / test | Rails environment
DATABASE_URL | url | For example: postgresql://localhost/blog_development?pool=5 DATABASE_URL | url | For example: postgresql://localhost/blog_development?pool=5
GITLAB_EMAIL_FROM | email | Email address used in the "From" field in mails sent by GitLab
GITLAB_EMAIL_DISPLAY_NAME | string | Name used in the "From" field in mails sent by GitLab
GITLAB_EMAIL_REPLY_TO | email | Email address used in the "Reply-To" field in mails sent by GitLab
## Complete database variables ## Complete database variables
...@@ -39,7 +42,12 @@ GITLAB_DATABASE_PASSWORD | ...@@ -39,7 +42,12 @@ GITLAB_DATABASE_PASSWORD |
GITLAB_DATABASE_HOST | localhost GITLAB_DATABASE_HOST | localhost
GITLAB_DATABASE_PORT | 5432 GITLAB_DATABASE_PORT | 5432
## Other variables ## Adding more variables
We welcome merge requests to make more settings configurable via variables. We welcome merge requests to make more settings configurable via variables.
Please stick to the naming scheme "GITLAB_#{name 1_settings.rb in upper case}". Please stick to the naming scheme "GITLAB_#{name 1_settings.rb in upper case}".
## Omnibus configuration
It's possible to preconfigure the GitLab image by adding the environment variable: `GITLAB_OMNIBUS_CONFIG` to docker run command.
For more information see the ['preconfigure-docker-container' section in the Omnibus documentation](http://doc.gitlab.com/omnibus/docker/#preconfigure-docker-container).
...@@ -153,6 +153,49 @@ with the name of your bucket: ...@@ -153,6 +153,49 @@ with the name of your bucket:
} }
``` ```
### Uploading to locally mounted shares
You may also send backups to a mounted share (`NFS` / `CIFS` / `SMB` / etc.) by
using the [`Local`](https://github.com/fog/fog-local#usage) storage provider.
The directory pointed to by the `local_root` key **must** be owned by the `git`
user **when mounted** (mounting with the `uid=` of the `git` user for `CIFS` and
`SMB`) or the user that you are executing the backup tasks under (for omnibus
packages, this is the `git` user).
The `backup_upload_remote_directory` **must** be set in addition to the
`local_root` key. This is the sub directory inside the mounted directory that
backups will be copied to, and will be created if it does not exist. If the
directory that you want to copy the tarballs to is the root of your mounted
directory, just use `.` instead.
For omnibus packages:
```ruby
gitlab_rails['backup_upload_connection'] = {
:provider => 'Local',
:local_root => '/mnt/backups'
}
# The directory inside the mounted folder to copy backups to
# Use '.' to store them in the root directory
gitlab_rails['backup_upload_remote_directory'] = 'gitlab_backups'
```
For installations from source:
```yaml
backup:
# snip
upload:
# Fog storage connection settings, see http://fog.io/storage/ .
connection:
provider: Local
local_root: '/mnt/backups'
# The directory inside the mounted folder to copy backups to
# Use '.' to store them in the root directory
remote_directory: 'gitlab_backups'
```
## Backup archive permissions ## Backup archive permissions
The backup archives created by GitLab (123456_gitlab_backup.tar) will have owner/group git:git and 0600 permissions by default. The backup archives created by GitLab (123456_gitlab_backup.tar) will have owner/group git:git and 0600 permissions by default.
......
...@@ -9,7 +9,7 @@ already has one by running the following command: ...@@ -9,7 +9,7 @@ already has one by running the following command:
cat ~/.ssh/id_rsa.pub cat ~/.ssh/id_rsa.pub
``` ```
If you see a long string starting with `ssh-rsa` or `ssh-dsa`, you can skip the `ssh-keygen` step. If you see a long string starting with `ssh-rsa`, you can skip the `ssh-keygen` step.
Note: It is a best practice to use a password for an SSH key, but it is not Note: It is a best practice to use a password for an SSH key, but it is not
required and you can skip creating a password by pressing enter. Note that required and you can skip creating a password by pressing enter. Note that
...@@ -20,8 +20,9 @@ To generate a new SSH key, use the following command: ...@@ -20,8 +20,9 @@ To generate a new SSH key, use the following command:
ssh-keygen -t rsa -C "$your_email" ssh-keygen -t rsa -C "$your_email"
``` ```
This command will prompt you for a location and filename to store the key This command will prompt you for a location and filename to store the key
pair and for a password. When prompted for the location and filename, you pair and for a password. When prompted for the location and filename, just
can press enter to use the default. press enter to use the default. If you use a different name, the key will not
be used automatically.
Use the command below to show your public key: Use the command below to show your public key:
```bash ```bash
...@@ -29,10 +30,10 @@ cat ~/.ssh/id_rsa.pub ...@@ -29,10 +30,10 @@ cat ~/.ssh/id_rsa.pub
``` ```
Copy-paste the key to the 'My SSH Keys' section under the 'SSH' tab in your Copy-paste the key to the 'My SSH Keys' section under the 'SSH' tab in your
user profile. Please copy the complete key starting with `ssh-` and ending user profile. Please copy the complete key starting with `ssh-rsa` and ending
with your username and host. with your username and host.
To copy your public key to the clipboard, use code below. Depending on your To copy your public key to the clipboard, use the code below. Depending on your
OS you'll need to use a different command: OS you'll need to use a different command:
**Windows:** **Windows:**
......
# System hooks # System hooks
Your GitLab instance can perform HTTP POST requests on the following events: `project_create`, `project_destroy`, `user_add_to_team`, `user_remove_from_team`, `user_create`, `user_destroy`, `key_create`, `key_destroy`, `group_create`, `group_destroy`, `user_add_to_group` and `user_remove_from_group`. Your GitLab instance can perform HTTP POST requests on the following events: `project_create`, `project_destroy`, `project_rename`, `project_transfer`, `user_add_to_team`, `user_remove_from_team`, `user_create`, `user_destroy`, `key_create`, `key_destroy`, `group_create`, `group_destroy`, `user_add_to_group` and `user_remove_from_group`.
System hooks can be used, e.g. for logging or changing information in a LDAP server. System hooks can be used, e.g. for logging or changing information in a LDAP server.
...@@ -17,6 +17,7 @@ X-Gitlab-Event: System Hook ...@@ -17,6 +17,7 @@ X-Gitlab-Event: System Hook
```json ```json
{ {
"created_at": "2012-07-21T07:30:54Z", "created_at": "2012-07-21T07:30:54Z",
"updated_at": "2012-07-21T07:38:22Z",
"event_name": "project_create", "event_name": "project_create",
"name": "StoreCloud", "name": "StoreCloud",
"owner_email": "johnsmith@gmail.com", "owner_email": "johnsmith@gmail.com",
...@@ -33,6 +34,7 @@ X-Gitlab-Event: System Hook ...@@ -33,6 +34,7 @@ X-Gitlab-Event: System Hook
```json ```json
{ {
"created_at": "2012-07-21T07:30:58Z", "created_at": "2012-07-21T07:30:58Z",
"updated_at": "2012-07-21T07:38:22Z",
"event_name": "project_destroy", "event_name": "project_destroy",
"name": "Underscore", "name": "Underscore",
"owner_email": "johnsmith@gmail.com", "owner_email": "johnsmith@gmail.com",
...@@ -44,11 +46,48 @@ X-Gitlab-Event: System Hook ...@@ -44,11 +46,48 @@ X-Gitlab-Event: System Hook
} }
``` ```
**Project renamed:**
```json
{
"created_at": "2012-07-21T07:30:58Z",
"updated_at": "2012-07-21T07:38:22Z",
"event_name": "project_rename",
"name": "Underscore",
"path": "underscore",
"path_with_namespace": "jsmith/underscore",
"project_id": 73,
"owner_name": "John Smith",
"owner_email": "johnsmith@gmail.com",
"project_visibility": "internal",
"old_path_with_namespace": "jsmith/overscore",
}
```
**Project transferred:**
```json
{
"created_at": "2012-07-21T07:30:58Z",
"updated_at": "2012-07-21T07:38:22Z",
"event_name": "project_transfer",
"name": "Underscore",
"path": "underscore",
"path_with_namespace": "scores/underscore",
"project_id": 73,
"owner_name": "John Smith",
"owner_email": "johnsmith@gmail.com",
"project_visibility": "internal",
"old_path_with_namespace": "jsmith/overscore",
}
```
**New Team Member:** **New Team Member:**
```json ```json
{ {
"created_at": "2012-07-21T07:30:56Z", "created_at": "2012-07-21T07:30:56Z",
"updated_at": "2012-07-21T07:38:22Z",
"event_name": "user_add_to_team", "event_name": "user_add_to_team",
"project_access": "Master", "project_access": "Master",
"project_id": 74, "project_id": 74,
...@@ -67,6 +106,7 @@ X-Gitlab-Event: System Hook ...@@ -67,6 +106,7 @@ X-Gitlab-Event: System Hook
```json ```json
{ {
"created_at": "2012-07-21T07:30:56Z", "created_at": "2012-07-21T07:30:56Z",
"updated_at": "2012-07-21T07:38:22Z",
"event_name": "user_remove_from_team", "event_name": "user_remove_from_team",
"project_access": "Master", "project_access": "Master",
"project_id": 74, "project_id": 74,
...@@ -85,6 +125,7 @@ X-Gitlab-Event: System Hook ...@@ -85,6 +125,7 @@ X-Gitlab-Event: System Hook
```json ```json
{ {
"created_at": "2012-07-21T07:44:07Z", "created_at": "2012-07-21T07:44:07Z",
"updated_at": "2012-07-21T07:38:22Z",
"email": "js@gitlabhq.com", "email": "js@gitlabhq.com",
"event_name": "user_create", "event_name": "user_create",
"name": "John Smith", "name": "John Smith",
...@@ -97,6 +138,7 @@ X-Gitlab-Event: System Hook ...@@ -97,6 +138,7 @@ X-Gitlab-Event: System Hook
```json ```json
{ {
"created_at": "2012-07-21T07:44:07Z", "created_at": "2012-07-21T07:44:07Z",
"updated_at": "2012-07-21T07:38:22Z",
"email": "js@gitlabhq.com", "email": "js@gitlabhq.com",
"event_name": "user_destroy", "event_name": "user_destroy",
"name": "John Smith", "name": "John Smith",
...@@ -110,6 +152,7 @@ X-Gitlab-Event: System Hook ...@@ -110,6 +152,7 @@ X-Gitlab-Event: System Hook
{ {
"event_name": "key_create", "event_name": "key_create",
"created_at": "2014-08-18 18:45:16 UTC", "created_at": "2014-08-18 18:45:16 UTC",
"updated_at": "2012-07-21T07:38:22Z",
"username": "root", "username": "root",
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC58FwqHUbebw2SdT7SP4FxZ0w+lAO/erhy2ylhlcW/tZ3GY3mBu9VeeiSGoGz8hCx80Zrz+aQv28xfFfKlC8XQFpCWwsnWnQqO2Lv9bS8V1fIHgMxOHIt5Vs+9CAWGCCvUOAurjsUDoE2ALIXLDMKnJxcxD13XjWdK54j6ZXDB4syLF0C2PnAQSVY9X7MfCYwtuFmhQhKaBussAXpaVMRHltie3UYSBUUuZaB3J4cg/7TxlmxcNd+ppPRIpSZAB0NI6aOnqoBCpimscO/VpQRJMVLr3XiSYeT6HBiDXWHnIVPfQc03OGcaFqOit6p8lYKMaP/iUQLm+pgpZqrXZ9vB john@localhost", "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC58FwqHUbebw2SdT7SP4FxZ0w+lAO/erhy2ylhlcW/tZ3GY3mBu9VeeiSGoGz8hCx80Zrz+aQv28xfFfKlC8XQFpCWwsnWnQqO2Lv9bS8V1fIHgMxOHIt5Vs+9CAWGCCvUOAurjsUDoE2ALIXLDMKnJxcxD13XjWdK54j6ZXDB4syLF0C2PnAQSVY9X7MfCYwtuFmhQhKaBussAXpaVMRHltie3UYSBUUuZaB3J4cg/7TxlmxcNd+ppPRIpSZAB0NI6aOnqoBCpimscO/VpQRJMVLr3XiSYeT6HBiDXWHnIVPfQc03OGcaFqOit6p8lYKMaP/iUQLm+pgpZqrXZ9vB john@localhost",
"id": 4 "id": 4
...@@ -122,6 +165,7 @@ X-Gitlab-Event: System Hook ...@@ -122,6 +165,7 @@ X-Gitlab-Event: System Hook
{ {
"event_name": "key_destroy", "event_name": "key_destroy",
"created_at": "2014-08-18 18:45:16 UTC", "created_at": "2014-08-18 18:45:16 UTC",
"updated_at": "2012-07-21T07:38:22Z",
"username": "root", "username": "root",
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC58FwqHUbebw2SdT7SP4FxZ0w+lAO/erhy2ylhlcW/tZ3GY3mBu9VeeiSGoGz8hCx80Zrz+aQv28xfFfKlC8XQFpCWwsnWnQqO2Lv9bS8V1fIHgMxOHIt5Vs+9CAWGCCvUOAurjsUDoE2ALIXLDMKnJxcxD13XjWdK54j6ZXDB4syLF0C2PnAQSVY9X7MfCYwtuFmhQhKaBussAXpaVMRHltie3UYSBUUuZaB3J4cg/7TxlmxcNd+ppPRIpSZAB0NI6aOnqoBCpimscO/VpQRJMVLr3XiSYeT6HBiDXWHnIVPfQc03OGcaFqOit6p8lYKMaP/iUQLm+pgpZqrXZ9vB john@localhost", "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC58FwqHUbebw2SdT7SP4FxZ0w+lAO/erhy2ylhlcW/tZ3GY3mBu9VeeiSGoGz8hCx80Zrz+aQv28xfFfKlC8XQFpCWwsnWnQqO2Lv9bS8V1fIHgMxOHIt5Vs+9CAWGCCvUOAurjsUDoE2ALIXLDMKnJxcxD13XjWdK54j6ZXDB4syLF0C2PnAQSVY9X7MfCYwtuFmhQhKaBussAXpaVMRHltie3UYSBUUuZaB3J4cg/7TxlmxcNd+ppPRIpSZAB0NI6aOnqoBCpimscO/VpQRJMVLr3XiSYeT6HBiDXWHnIVPfQc03OGcaFqOit6p8lYKMaP/iUQLm+pgpZqrXZ9vB john@localhost",
"id": 4 "id": 4
...@@ -133,6 +177,7 @@ X-Gitlab-Event: System Hook ...@@ -133,6 +177,7 @@ X-Gitlab-Event: System Hook
```json ```json
{ {
"created_at": "2012-07-21T07:30:54Z", "created_at": "2012-07-21T07:30:54Z",
"updated_at": "2012-07-21T07:38:22Z",
"event_name": "group_create", "event_name": "group_create",
"name": "StoreCloud", "name": "StoreCloud",
"owner_email": "johnsmith@gmail.com", "owner_email": "johnsmith@gmail.com",
...@@ -147,6 +192,7 @@ X-Gitlab-Event: System Hook ...@@ -147,6 +192,7 @@ X-Gitlab-Event: System Hook
```json ```json
{ {
"created_at": "2012-07-21T07:30:54Z", "created_at": "2012-07-21T07:30:54Z",
"updated_at": "2012-07-21T07:38:22Z",
"event_name": "group_destroy", "event_name": "group_destroy",
"name": "StoreCloud", "name": "StoreCloud",
"owner_email": "johnsmith@gmail.com", "owner_email": "johnsmith@gmail.com",
...@@ -161,6 +207,7 @@ X-Gitlab-Event: System Hook ...@@ -161,6 +207,7 @@ X-Gitlab-Event: System Hook
```json ```json
{ {
"created_at": "2012-07-21T07:30:56Z", "created_at": "2012-07-21T07:30:56Z",
"updated_at": "2012-07-21T07:38:22Z",
"event_name": "user_add_to_group", "event_name": "user_add_to_group",
"group_access": "Master", "group_access": "Master",
"group_id": 78, "group_id": 78,
...@@ -176,6 +223,7 @@ X-Gitlab-Event: System Hook ...@@ -176,6 +223,7 @@ X-Gitlab-Event: System Hook
```json ```json
{ {
"created_at": "2012-07-21T07:30:56Z", "created_at": "2012-07-21T07:30:56Z",
"updated_at": "2012-07-21T07:38:22Z",
"event_name": "user_remove_from_group", "event_name": "user_remove_from_group",
"group_access": "Master", "group_access": "Master",
"group_id": 78, "group_id": 78,
......
...@@ -105,15 +105,6 @@ Feature: Explore Groups ...@@ -105,15 +105,6 @@ Feature: Explore Groups
When I visit the public groups area When I visit the public groups area
Then I should see group "TestGroup" Then I should see group "TestGroup"
Scenario: I should not see group with internal project in public groups area
Given group "TestGroup" has internal project "Internal"
When I visit the public groups area
Then I should not see group "TestGroup"
Scenario: I should not see group with private project in public groups area
When I visit the public groups area
Then I should not see group "TestGroup"
Scenario: I should see group with public project in public groups area as user Scenario: I should see group with public project in public groups area as user
Given group "TestGroup" has public project "Community" Given group "TestGroup" has public project "Community"
When I sign in as a user When I sign in as a user
...@@ -125,9 +116,3 @@ Feature: Explore Groups ...@@ -125,9 +116,3 @@ Feature: Explore Groups
When I sign in as a user When I sign in as a user
And I visit the public groups area And I visit the public groups area
Then I should see group "TestGroup" Then I should see group "TestGroup"
Scenario: I should not see group with private project in public groups area as user
When I sign in as a user
And I visit the public groups area
Then I should not see group "TestGroup"
...@@ -24,7 +24,7 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps ...@@ -24,7 +24,7 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps
page.within '.awards' do page.within '.awards' do
expect do expect do
page.find('.award.active').click page.find('.award.active').click
sleep 0.1 sleep 0.3
end.to change{ page.all(".award").size }.from(3).to(2) end.to change{ page.all(".award").size }.from(3).to(2)
end end
end end
......
...@@ -59,15 +59,14 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps ...@@ -59,15 +59,14 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
end end
step 'I click "author" dropdown' do step 'I click "author" dropdown' do
first('.ajax-users-select').click first('#s2id_author_id').click
end end
step 'I see current user as the first user' do step 'I see current user as the first user' do
expect(page).to have_selector('.user-result', visible: true, count: 4) expect(page).to have_selector('.user-result', visible: true, count: 3)
users = page.all('.user-name') users = page.all('.user-name')
expect(users[0].text).to eq 'Any Assignee' expect(users[0].text).to eq 'Any Author'
expect(users[1].text).to eq 'Unassigned' expect(users[1].text).to eq current_user.name
expect(users[2].text).to eq current_user.name
end end
step 'I submit new issue "500 error on profile"' do step 'I submit new issue "500 error on profile"' do
......
...@@ -3,7 +3,7 @@ module API ...@@ -3,7 +3,7 @@ module API
class Projects < Grape::API class Projects < Grape::API
before { authenticate! } before { authenticate! }
resource :projects do resource :projects, requirements: { id: /[^\/]+/ } do
helpers do helpers do
def map_public_to_visibility_level(attrs) def map_public_to_visibility_level(attrs)
publik = attrs.delete(:public) publik = attrs.delete(:public)
......
...@@ -47,7 +47,17 @@ module Banzai ...@@ -47,7 +47,17 @@ module Banzai
{ object_sym => LazyReference.new(object_class, node.attr(data_reference)) } { object_sym => LazyReference.new(object_class, node.attr(data_reference)) }
end end
delegate :object_class, :object_sym, :references_in, to: :class def object_class
self.class.object_class
end
def object_sym
self.class.object_sym
end
def references_in(*args, &block)
self.class.references_in(*args, &block)
end
def find_object(project, id) def find_object(project, id)
# Implement in child class # Implement in child class
......
...@@ -10,7 +10,7 @@ module Banzai ...@@ -10,7 +10,7 @@ module Banzai
# #
class RedactorFilter < HTML::Pipeline::Filter class RedactorFilter < HTML::Pipeline::Filter
def call def call
doc.css('a.gfm').each do |node| Querying.css(doc, 'a.gfm').each do |node|
unless user_can_see_reference?(node) unless user_can_see_reference?(node)
# The reference should be replaced by the original text, # The reference should be replaced by the original text,
# which is not always the same as the rendered text. # which is not always the same as the rendered text.
......
...@@ -124,7 +124,7 @@ module Banzai ...@@ -124,7 +124,7 @@ module Banzai
def replace_link_nodes_with_text(pattern) def replace_link_nodes_with_text(pattern)
return doc if project.nil? return doc if project.nil?
doc.search('a').each do |node| doc.xpath('descendant-or-self::a').each do |node|
klass = node.attr('class') klass = node.attr('class')
next if klass && klass.include?('gfm') next if klass && klass.include?('gfm')
...@@ -162,7 +162,7 @@ module Banzai ...@@ -162,7 +162,7 @@ module Banzai
def replace_link_nodes_with_href(pattern) def replace_link_nodes_with_href(pattern)
return doc if project.nil? return doc if project.nil?
doc.search('a').each do |node| doc.xpath('descendant-or-self::a').each do |node|
klass = node.attr('class') klass = node.attr('class')
next if klass && klass.include?('gfm') next if klass && klass.include?('gfm')
......
...@@ -16,7 +16,7 @@ module Banzai ...@@ -16,7 +16,7 @@ module Banzai
end end
def call def call
doc.css('a.gfm').each do |node| Querying.css(doc, 'a.gfm').each do |node|
gather_references(node) gather_references(node)
end end
......
...@@ -91,7 +91,7 @@ module Banzai ...@@ -91,7 +91,7 @@ module Banzai
parts = request_path.split('/') parts = request_path.split('/')
parts.pop if path_type(request_path) != 'tree' parts.pop if path_type(request_path) != 'tree'
while parts.length > 1 && path.start_with?('../') while path.start_with?('../')
parts.pop parts.pop
path.sub!('../', '') path.sub!('../', '')
end end
......
module Banzai
module Querying
# Searches a Nokogiri document using a CSS query, optionally optimizing it
# whenever possible.
#
# document - A document/element to search.
# query - The CSS query to use.
#
# Returns a Nokogiri::XML::NodeSet.
def self.css(document, query)
# When using "a.foo" Nokogiri compiles this to "//a[...]" but
# "descendant::a[...]" is quite a bit faster and achieves the same result.
xpath = Nokogiri::CSS.xpath_for(query)[0].gsub(%r{^//}, 'descendant::')
document.xpath(xpath)
end
end
end
module Banzai module Banzai
module Renderer module Renderer
CACHE_ENABLED = false
# Convert a Markdown String into an HTML-safe String of HTML # Convert a Markdown String into an HTML-safe String of HTML
# #
# Note that while the returned HTML will have been sanitized of dangerous # Note that while the returned HTML will have been sanitized of dangerous
...@@ -20,13 +18,22 @@ module Banzai ...@@ -20,13 +18,22 @@ module Banzai
cache_key = context.delete(:cache_key) cache_key = context.delete(:cache_key)
cache_key = full_cache_key(cache_key, context[:pipeline]) cache_key = full_cache_key(cache_key, context[:pipeline])
if cache_key && CACHE_ENABLED cacheless = cacheless_render(text, context)
Rails.cache.fetch(cache_key) do
cacheless_render(text, context) if cache_key && ENV["DEBUG_BANZAI_CACHE"]
cached = Rails.cache.fetch(cache_key) { cacheless }
if cached != cacheless
Rails.logger.warn "Banzai cache mismatch"
Rails.logger.warn "Text: #{text.inspect}"
Rails.logger.warn "Context: #{context.inspect}"
Rails.logger.warn "Cache key: #{cache_key.inspect}"
Rails.logger.warn "Cacheless: #{cacheless.inspect}"
Rails.logger.warn "With cache: #{cached.inspect}"
end end
else
cacheless_render(text, context)
end end
cacheless
end end
def self.render_result(text, context = {}) def self.render_result(text, context = {})
......
...@@ -45,11 +45,11 @@ module Gitlab ...@@ -45,11 +45,11 @@ module Gitlab
end end
def starting_year def starting_year
(Time.now - 1.year).strftime("%Y") 1.year.ago.year
end end
def starting_month def starting_month
Date.today.strftime("%m").to_i Date.today.month
end end
end end
end end
...@@ -38,7 +38,9 @@ module Gitlab ...@@ -38,7 +38,9 @@ module Gitlab
true true
end end
use_db && ActiveRecord::Base.connection.active? && ActiveRecord::Base.connection.table_exists?('application_settings') use_db && ActiveRecord::Base.connection.active? &&
!ActiveRecord::Migrator.needs_migration? &&
ActiveRecord::Base.connection.table_exists?('application_settings')
end end
end end
end end
...@@ -6,16 +6,21 @@ module Gitlab ...@@ -6,16 +6,21 @@ module Gitlab
METRICS_ROOT = Rails.root.join('lib', 'gitlab', 'metrics').to_s METRICS_ROOT = Rails.root.join('lib', 'gitlab', 'metrics').to_s
PATH_REGEX = /^#{RAILS_ROOT}\/?/ PATH_REGEX = /^#{RAILS_ROOT}\/?/
def self.pool_size def self.settings
current_application_settings[:metrics_pool_size] || 16 @settings ||= {
end enabled: current_application_settings[:metrics_enabled],
pool_size: current_application_settings[:metrics_pool_size],
def self.timeout timeout: current_application_settings[:metrics_timeout],
current_application_settings[:metrics_timeout] || 10 method_call_threshold: current_application_settings[:metrics_method_call_threshold],
host: current_application_settings[:metrics_host],
username: current_application_settings[:metrics_username],
password: current_application_settings[:metrics_password],
port: current_application_settings[:metrics_port]
}
end end
def self.enabled? def self.enabled?
current_application_settings[:metrics_enabled] || false settings[:enabled] || false
end end
def self.mri? def self.mri?
...@@ -26,18 +31,13 @@ module Gitlab ...@@ -26,18 +31,13 @@ module Gitlab
# This is memoized since this method is called for every instrumented # This is memoized since this method is called for every instrumented
# method. Loading data from an external cache on every method call slows # method. Loading data from an external cache on every method call slows
# things down too much. # things down too much.
@method_call_threshold ||= @method_call_threshold ||= settings[:method_call_threshold]
(current_application_settings[:metrics_method_call_threshold] || 10)
end end
def self.pool def self.pool
@pool @pool
end end
def self.hostname
@hostname
end
# Returns a relative path and line number based on the last application call # Returns a relative path and line number based on the last application call
# frame. # frame.
def self.last_relative_application_frame def self.last_relative_application_frame
...@@ -85,16 +85,14 @@ module Gitlab ...@@ -85,16 +85,14 @@ module Gitlab
value.to_s.gsub('=', '\\=') value.to_s.gsub('=', '\\=')
end end
@hostname = Socket.gethostname
# When enabled this should be set before being used as the usual pattern # When enabled this should be set before being used as the usual pattern
# "@foo ||= bar" is _not_ thread-safe. # "@foo ||= bar" is _not_ thread-safe.
if enabled? if enabled?
@pool = ConnectionPool.new(size: pool_size, timeout: timeout) do @pool = ConnectionPool.new(size: settings[:pool_size], timeout: settings[:timeout]) do
host = current_application_settings[:metrics_host] host = settings[:host]
user = current_application_settings[:metrics_username] user = settings[:username]
pw = current_application_settings[:metrics_password] pw = settings[:password]
port = current_application_settings[:metrics_port] port = settings[:port]
InfluxDB::Client. InfluxDB::Client.
new(udp: { host: host, port: port }, username: user, password: pw) new(udp: { host: host, port: port }, username: user, password: pw)
......
...@@ -123,6 +123,8 @@ module Gitlab ...@@ -123,6 +123,8 @@ module Gitlab
duration = (Time.now - start) * 1000.0 duration = (Time.now - start) * 1000.0
if duration >= Gitlab::Metrics.method_call_threshold if duration >= Gitlab::Metrics.method_call_threshold
trans.increment(:method_duration, duration)
trans.add_metric(Gitlab::Metrics::Instrumentation::SERIES, trans.add_metric(Gitlab::Metrics::Instrumentation::SERIES,
{ duration: duration }, { duration: duration },
method: #{label.inspect}) method: #{label.inspect})
......
...@@ -17,14 +17,8 @@ module Gitlab ...@@ -17,14 +17,8 @@ module Gitlab
# Returns a Hash in a format that can be directly written to InfluxDB. # Returns a Hash in a format that can be directly written to InfluxDB.
def to_hash def to_hash
{ {
series: @series, series: @series,
tags: @tags.merge( tags: @tags,
hostname: Metrics.hostname,
ruby_engine: RUBY_ENGINE,
ruby_version: RUBY_VERSION,
gitlab_version: Gitlab::VERSION,
process_type: Sidekiq.server? ? 'sidekiq' : 'rails'
),
values: @values, values: @values,
timestamp: @created_at.to_i * 1_000_000_000 timestamp: @created_at.to_i * 1_000_000_000
} }
......
module Gitlab
module Metrics
# Class for producing SQL queries with sensitive data stripped out.
class ObfuscatedSQL
REPLACEMENT = /
\d+(\.\d+)? # integers, floats
| '.+?' # single quoted strings
| \/.+?(?<!\\)\/ # regexps (including escaped slashes)
/x
MYSQL_REPLACEMENTS = /
".+?" # double quoted strings
/x
# Regex to replace consecutive placeholders with a single one indicating
# the length. This can be useful when a "IN" statement uses thousands of
# IDs (storing this would just be a waste of space).
CONSECUTIVE = /(\?(\s*,\s*)?){2,}/
# sql - The raw SQL query as a String.
def initialize(sql)
@sql = sql
end
# Returns a new, obfuscated SQL query.
def to_s
regex = REPLACEMENT
if Gitlab::Database.mysql?
regex = Regexp.union(regex, MYSQL_REPLACEMENTS)
end
sql = @sql.gsub(regex, '?').gsub(CONSECUTIVE) do |match|
"#{match.count(',') + 1} values"
end
# InfluxDB escapes double quotes upon output, so lets get rid of them
# whenever we can.
if Gitlab::Database.postgresql?
sql = sql.delete('"')
end
sql.tr("\n", ' ')
end
end
end
end
...@@ -50,12 +50,11 @@ module Gitlab ...@@ -50,12 +50,11 @@ module Gitlab
end end
def sample_memory_usage def sample_memory_usage
@metrics << Metric.new('memory_usage', value: System.memory_usage) add_metric('memory_usage', value: System.memory_usage)
end end
def sample_file_descriptors def sample_file_descriptors
@metrics << Metric. add_metric('file_descriptors', value: System.file_descriptor_count)
new('file_descriptors', value: System.file_descriptor_count)
end end
if Metrics.mri? if Metrics.mri?
...@@ -69,7 +68,7 @@ module Gitlab ...@@ -69,7 +68,7 @@ module Gitlab
counts['Symbol'] = Symbol.all_symbols.length counts['Symbol'] = Symbol.all_symbols.length
counts.each do |name, count| counts.each do |name, count|
@metrics << Metric.new('object_counts', { count: count }, type: name) add_metric('object_counts', { count: count }, type: name)
end end
end end
else else
...@@ -91,7 +90,17 @@ module Gitlab ...@@ -91,7 +90,17 @@ module Gitlab
stats[:count] = stats[:minor_gc_count] + stats[:major_gc_count] stats[:count] = stats[:minor_gc_count] + stats[:major_gc_count]
@metrics << Metric.new('gc_statistics', stats) add_metric('gc_statistics', stats)
end
def add_metric(series, values, tags = {})
prefix = sidekiq? ? 'sidekiq_' : 'rails_'
@metrics << Metric.new("#{prefix}#{series}", values, tags)
end
def sidekiq?
Sidekiq.server?
end end
end end
end end
......
...@@ -19,6 +19,7 @@ module Gitlab ...@@ -19,6 +19,7 @@ module Gitlab
values = values_for(event) values = values_for(event)
tags = tags_for(event) tags = tags_for(event)
current_transaction.increment(:view_duration, event.duration)
current_transaction.add_metric(SERIES, values, tags) current_transaction.add_metric(SERIES, values, tags)
end end
......
module Gitlab module Gitlab
module Metrics module Metrics
module Subscribers module Subscribers
# Class for tracking raw SQL queries. # Class for tracking the total query duration of a transaction.
#
# Queries are obfuscated before being logged to ensure no private data is
# exposed via InfluxDB/Grafana.
class ActiveRecord < ActiveSupport::Subscriber class ActiveRecord < ActiveSupport::Subscriber
attach_to :active_record attach_to :active_record
SERIES = 'sql_queries'
def sql(event) def sql(event)
return unless current_transaction return unless current_transaction
values = values_for(event) current_transaction.increment(:sql_duration, event.duration)
tags = tags_for(event)
current_transaction.add_metric(SERIES, values, tags)
end end
private private
def values_for(event)
{ duration: event.duration }
end
def tags_for(event)
sql = ObfuscatedSQL.new(event.payload[:sql]).to_s
tags = { sql: sql }
file, line = Metrics.last_relative_application_frame
if file and line
tags[:file] = file
tags[:line] = line
end
tags
end
def current_transaction def current_transaction
Transaction.current Transaction.current
end end
......
...@@ -4,15 +4,12 @@ module Gitlab ...@@ -4,15 +4,12 @@ module Gitlab
class Transaction class Transaction
THREAD_KEY = :_gitlab_metrics_transaction THREAD_KEY = :_gitlab_metrics_transaction
SERIES = 'transactions'
attr_reader :uuid, :tags attr_reader :uuid, :tags
def self.current def self.current
Thread.current[THREAD_KEY] Thread.current[THREAD_KEY]
end end
# name - The name of this transaction as a String.
def initialize def initialize
@metrics = [] @metrics = []
@uuid = SecureRandom.uuid @uuid = SecureRandom.uuid
...@@ -20,7 +17,8 @@ module Gitlab ...@@ -20,7 +17,8 @@ module Gitlab
@started_at = nil @started_at = nil
@finished_at = nil @finished_at = nil
@tags = {} @values = Hash.new(0)
@tags = {}
end end
def duration def duration
...@@ -40,9 +38,14 @@ module Gitlab ...@@ -40,9 +38,14 @@ module Gitlab
end end
def add_metric(series, values, tags = {}) def add_metric(series, values, tags = {})
tags = tags.merge(transaction_id: @uuid) tags = tags.merge(transaction_id: @uuid)
prefix = sidekiq? ? 'sidekiq_' : 'rails_'
@metrics << Metric.new("#{prefix}#{series}", values, tags)
end
@metrics << Metric.new(series, values, tags) def increment(name, value)
@values[name] += value
end end
def add_tag(key, value) def add_tag(key, value)
...@@ -55,12 +58,22 @@ module Gitlab ...@@ -55,12 +58,22 @@ module Gitlab
end end
def track_self def track_self
add_metric(SERIES, { duration: duration }, @tags) values = { duration: duration }
@values.each do |name, value|
values[name] = value
end
add_metric('transactions', values, @tags)
end end
def submit def submit
Metrics.submit_metrics(@metrics.map(&:to_hash)) Metrics.submit_metrics(@metrics.map(&:to_hash))
end end
def sidekiq?
Sidekiq.server?
end
end end
end end
end end
require 'spec_helper' require 'spec_helper'
describe AbuseReportsController do describe AbuseReportsController do
let(:reporter) { create(:user) } let(:reporter) { create(:user) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:message) { "This user is a spammer" } let(:attrs) do
attributes_for(:abuse_report) do |hash|
hash[:user_id] = user.id
end
end
before do before do
sign_in(reporter) sign_in(reporter)
end end
describe "POST create" do describe 'POST create' do
context "with admin notification email set" do context 'with valid attributes' do
let(:admin_email) { "admin@example.com"} it 'saves the abuse report' do
expect do
before(:each) do post :create, abuse_report: attrs
stub_application_setting(admin_notification_email: admin_email) end.to change { AbuseReport.count }.by(1)
end end
it "sends a notification email" do it 'calls notify' do
perform_enqueued_jobs do expect_any_instance_of(AbuseReport).to receive(:notify)
post :create,
abuse_report: {
user_id: user.id,
message: message
}
email = ActionMailer::Base.deliveries.last
expect(email.to).to eq([admin_email]) post :create, abuse_report: attrs
expect(email.subject).to include(user.username)
expect(email.text_part.body).to include(message)
end
end end
it "saves the abuse report" do it 'redirects back to the reported user' do
perform_enqueued_jobs do post :create, abuse_report: attrs
expect do
post :create,
abuse_report: {
user_id: user.id,
message: message
}
end.to change { AbuseReport.count }.by(1)
end
end
end
context "without admin notification email set" do expect(response).to redirect_to user
before(:each) do
stub_application_setting(admin_notification_email: nil)
end end
end
it "does not send a notification email" do context 'with invalid attributes' do
expect do it 'renders new' do
post :create, attrs.delete(:user_id)
abuse_report: { post :create, abuse_report: attrs
user_id: user.id,
message: message
}
end.not_to change { ActionMailer::Base.deliveries.count }
end
it "saves the abuse report" do expect(response).to render_template(:new)
expect do
post :create,
abuse_report: {
user_id: user.id,
message: message
}
end.to change { AbuseReport.count }.by(1)
end end
end end
end end
end end
require 'spec_helper' require 'spec_helper'
describe "Admin Builds" do describe 'Admin Builds' do
let(:commit) { FactoryGirl.create :ci_commit }
let(:build) { FactoryGirl.create :ci_build, commit: commit }
before do before do
login_as :admin login_as :admin
end end
describe "GET /admin/builds" do describe 'GET /admin/builds' do
before do let(:commit) { create(:ci_commit) }
build
visit admin_builds_path
end
it { expect(page).to have_content "Running" }
it { expect(page).to have_content build.short_sha }
end
describe "Tabs" do context 'All tab' do
it "shows all builds" do context 'when have builds' do
FactoryGirl.create :ci_build, commit: commit, status: "pending" it 'shows all builds' do
FactoryGirl.create :ci_build, commit: commit, status: "running" create(:ci_build, commit: commit, status: :pending)
FactoryGirl.create :ci_build, commit: commit, status: "success" create(:ci_build, commit: commit, status: :running)
FactoryGirl.create :ci_build, commit: commit, status: "failed" create(:ci_build, commit: commit, status: :success)
create(:ci_build, commit: commit, status: :failed)
visit admin_builds_path visit admin_builds_path
within ".center-top-menu" do expect(page).to have_selector('.project-issuable-filter li.active', text: 'All')
click_on "All" expect(page.all('.build-link').size).to eq(4)
expect(page).to have_link 'Cancel all'
end
end end
expect(page.all(".build-link").size).to eq(4) context 'when have no builds' do
it 'shows a message' do
visit admin_builds_path
expect(page).to have_selector('.project-issuable-filter li.active', text: 'All')
expect(page).to have_content 'No builds to show'
expect(page).not_to have_link 'Cancel all'
end
end
end end
it "shows finished builds" do context 'Running tab' do
build = FactoryGirl.create :ci_build, commit: commit, status: "pending" context 'when have running builds' do
build1 = FactoryGirl.create :ci_build, commit: commit, status: "running" it 'shows running builds' do
build2 = FactoryGirl.create :ci_build, commit: commit, status: "success" build1 = create(:ci_build, commit: commit, status: :pending)
build2 = create(:ci_build, commit: commit, status: :success)
build3 = create(:ci_build, commit: commit, status: :failed)
visit admin_builds_path(scope: :running)
expect(page).to have_selector('.project-issuable-filter li.active', text: 'Running')
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(build3.id)
expect(page).to have_link 'Cancel all'
end
end
visit admin_builds_path context 'when have no builds running' do
it 'shows a message' do
create(:ci_build, commit: commit, status: :success)
within ".center-top-menu" do visit admin_builds_path(scope: :running)
click_on "Finished"
end
expect(page.find(".build-link")).not_to have_content(build.id) expect(page).to have_selector('.project-issuable-filter li.active', text: 'Running')
expect(page.find(".build-link")).not_to have_content(build1.id) expect(page).to have_content 'No builds to show'
expect(page.find(".build-link")).to have_content(build2.id) expect(page).not_to have_link 'Cancel all'
end
end
end end
it "shows running builds" do context 'Finished tab' do
build = FactoryGirl.create :ci_build, commit: commit, status: "pending" context 'when have finished builds' do
build2 = FactoryGirl.create :ci_build, commit: commit, status: "success" it 'shows finished builds' do
build3 = FactoryGirl.create :ci_build, commit: commit, status: "failed" build1 = create(:ci_build, commit: commit, status: :pending)
build2 = create(:ci_build, commit: commit, status: :running)
build3 = create(:ci_build, commit: commit, status: :success)
visit admin_builds_path(scope: :finished)
expect(page).to have_selector('.project-issuable-filter li.active', text: 'Finished')
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')).to have_content(build3.id)
expect(page).to have_link 'Cancel all'
end
end
visit admin_builds_path context 'when have no builds finished' do
it 'shows a message' do
create(:ci_build, commit: commit, status: :running)
within ".center-top-menu" do visit admin_builds_path(scope: :finished)
click_on "Running"
end
expect(page.find(".build-link")).to have_content(build.id) expect(page).to have_selector('.project-issuable-filter li.active', text: 'Finished')
expect(page.find(".build-link")).not_to have_content(build2.id) expect(page).to have_content 'No builds to show'
expect(page.find(".build-link")).not_to have_content(build3.id) expect(page).to have_link 'Cancel all'
end
end
end end
end end
end end
...@@ -15,11 +15,11 @@ describe "Builds" do ...@@ -15,11 +15,11 @@ describe "Builds" do
context "Running scope" do context "Running scope" do
before do before do
@build.run! @build.run!
visit namespace_project_builds_path(@project.namespace, @project) visit namespace_project_builds_path(@project.namespace, @project, scope: :running)
end end
it { expect(page).to have_content 'Running' } it { expect(page).to have_selector('.project-issuable-filter li.active', text: 'Running') }
it { expect(page).to have_content 'Cancel running' } it { expect(page).to have_link 'Cancel running' }
it { expect(page).to have_content @build.short_sha } it { expect(page).to have_content @build.short_sha }
it { expect(page).to have_content @build.ref } it { expect(page).to have_content @build.ref }
it { expect(page).to have_content @build.name } it { expect(page).to have_content @build.name }
...@@ -31,21 +31,22 @@ describe "Builds" do ...@@ -31,21 +31,22 @@ describe "Builds" do
visit namespace_project_builds_path(@project.namespace, @project, scope: :finished) visit namespace_project_builds_path(@project.namespace, @project, scope: :finished)
end end
it { expect(page).to have_selector('.project-issuable-filter li.active', text: 'Finished') }
it { expect(page).to have_content 'No builds to show' } it { expect(page).to have_content 'No builds to show' }
it { expect(page).to have_content 'Cancel running' } it { expect(page).to have_link 'Cancel running' }
end end
context "All builds" do context "All builds" do
before do before do
@project.builds.running_or_pending.each(&:success) @project.builds.running_or_pending.each(&:success)
visit namespace_project_builds_path(@project.namespace, @project, scope: :all) visit namespace_project_builds_path(@project.namespace, @project)
end end
it { expect(page).to have_content 'All' } it { expect(page).to have_selector('.project-issuable-filter li.active', text: 'All') }
it { expect(page).to have_content @build.short_sha } it { expect(page).to have_content @build.short_sha }
it { expect(page).to have_content @build.ref } it { expect(page).to have_content @build.ref }
it { expect(page).to have_content @build.name } it { expect(page).to have_content @build.name }
it { expect(page).to_not have_content 'Cancel running' } it { expect(page).to_not have_link 'Cancel running' }
end end
end end
...@@ -56,8 +57,12 @@ describe "Builds" do ...@@ -56,8 +57,12 @@ describe "Builds" do
click_link "Cancel running" click_link "Cancel running"
end end
it { expect(page).to have_content 'No builds to show' } it { expect(page).to have_selector('.project-issuable-filter li.active', text: 'All') }
it { expect(page).to_not have_content 'Cancel running' } it { expect(page).to have_content 'canceled' }
it { expect(page).to have_content @build.short_sha }
it { expect(page).to have_content @build.ref }
it { expect(page).to have_content @build.name }
it { expect(page).to_not have_link 'Cancel running' }
end end
describe "GET /:project/builds/:id" do describe "GET /:project/builds/:id" do
......
...@@ -80,8 +80,10 @@ feature 'Project', feature: true do ...@@ -80,8 +80,10 @@ feature 'Project', feature: true do
visit namespace_project_path(project.namespace, project) visit namespace_project_path(project.namespace, project)
end end
it { expect(page).to have_content('You have Master access to this project.') } it 'click project-settings and find leave project' do
it { expect(page).to have_link('Leave this project') } find('#project-settings-button').click
expect(page).to have_link('Leave Project')
end
end end
def remove_with_confirm(button_text, confirm_with) def remove_with_confirm(button_text, confirm_with)
......
require 'spec_helper'
describe GroupsFinder do
describe '#execute' do
let(:user) { create(:user) }
let(:group1) { create(:group) }
let(:group2) { create(:group) }
let(:group3) { create(:group) }
let(:group4) { create(:group, public: true) }
let!(:public_project) { create(:project, :public, group: group1) }
let!(:internal_project) { create(:project, :internal, group: group2) }
let!(:private_project) { create(:project, :private, group: group3) }
let(:finder) { described_class.new }
describe 'with a user' do
subject { finder.execute(user) }
describe 'when the user is not a member of any groups' do
it { is_expected.to eq([group4, group2, group1]) }
end
describe 'when the user is a member of a group' do
before do
group3.add_user(user, Gitlab::Access::DEVELOPER)
end
it { is_expected.to eq([group4, group3, group2, group1]) }
end
describe 'when the user is a member of a private project' do
before do
private_project.team.add_user(user, Gitlab::Access::DEVELOPER)
end
it { is_expected.to eq([group4, group3, group2, group1]) }
end
end
describe 'without a user' do
subject { finder.execute }
it { is_expected.to eq([group4, group1]) }
end
end
end
require 'spec_helper'
describe JoinedGroupsFinder do
describe '#execute' do
let(:source_user) { create(:user) }
let(:current_user) { create(:user) }
let(:group1) { create(:group) }
let(:group2) { create(:group) }
let(:group3) { create(:group) }
let(:group4) { create(:group, public: true) }
let!(:public_project) { create(:project, :public, group: group1) }
let!(:internal_project) { create(:project, :internal, group: group2) }
let!(:private_project) { create(:project, :private, group: group3) }
let(:finder) { described_class.new(source_user) }
before do
[group1, group2, group3, group4].each do |group|
group.add_user(source_user, Gitlab::Access::MASTER)
end
end
describe 'with a current user' do
describe 'when the current user has access to the projects of the source user' do
before do
private_project.team.add_user(current_user, Gitlab::Access::DEVELOPER)
end
subject { finder.execute(current_user) }
it { is_expected.to eq([group4, group3, group2, group1]) }
end
describe 'when the current user does not have access to the projects of the source user' do
subject { finder.execute(current_user) }
it { is_expected.to eq([group4, group2, group1]) }
end
end
describe 'without a current user' do
subject { finder.execute }
it { is_expected.to eq([group4, group1]) }
end
end
end
...@@ -43,7 +43,7 @@ describe SearchHelper do ...@@ -43,7 +43,7 @@ describe SearchHelper do
end end
it "includes the public group" do it "includes the public group" do
group = create(:group, public: true) group = create(:group)
expect(search_autocomplete_opts(group.name).size).to eq(1) expect(search_autocomplete_opts(group.name).size).to eq(1)
end end
......
...@@ -92,6 +92,14 @@ describe Banzai::Filter::RelativeLinkFilter, lib: true do ...@@ -92,6 +92,14 @@ describe Banzai::Filter::RelativeLinkFilter, lib: true do
to eq "/#{project_path}/blob/#{ref}/doc/api/README.md" to eq "/#{project_path}/blob/#{ref}/doc/api/README.md"
end end
it 'rebuilds relative URL for a file in the repository root' do
relative_link = link('../README.md')
doc = filter(relative_link, requested_path: 'doc/some-file.md')
expect(doc.at_css('a')['href']).
to eq "/#{project_path}/blob/#{ref}/README.md"
end
it 'rebuilds relative URL for a file in the repo with an anchor' do it 'rebuilds relative URL for a file in the repo with an anchor' do
doc = filter(link('README.md#section')) doc = filter(link('README.md#section'))
expect(doc.at_css('a')['href']). expect(doc.at_css('a')['href']).
......
require 'spec_helper'
describe Banzai::Querying do
describe '.css' do
it 'optimizes queries for elements with classes' do
document = double(:document)
expect(document).to receive(:xpath).with(/^descendant::a/)
described_class.css(document, 'a.gfm')
end
end
end
...@@ -48,6 +48,9 @@ describe Gitlab::Metrics::Instrumentation do ...@@ -48,6 +48,9 @@ describe Gitlab::Metrics::Instrumentation do
allow(described_class).to receive(:transaction). allow(described_class).to receive(:transaction).
and_return(transaction) and_return(transaction)
expect(transaction).to receive(:increment).
with(:method_duration, a_kind_of(Numeric))
expect(transaction).to receive(:add_metric). expect(transaction).to receive(:add_metric).
with(described_class::SERIES, an_instance_of(Hash), with(described_class::SERIES, an_instance_of(Hash),
method: 'Dummy.foo') method: 'Dummy.foo')
...@@ -102,6 +105,9 @@ describe Gitlab::Metrics::Instrumentation do ...@@ -102,6 +105,9 @@ describe Gitlab::Metrics::Instrumentation do
allow(described_class).to receive(:transaction). allow(described_class).to receive(:transaction).
and_return(transaction) and_return(transaction)
expect(transaction).to receive(:increment).
with(:method_duration, a_kind_of(Numeric))
expect(transaction).to receive(:add_metric). expect(transaction).to receive(:add_metric).
with(described_class::SERIES, an_instance_of(Hash), with(described_class::SERIES, an_instance_of(Hash),
method: 'Dummy#bar') method: 'Dummy#bar')
......
...@@ -37,12 +37,6 @@ describe Gitlab::Metrics::Metric do ...@@ -37,12 +37,6 @@ describe Gitlab::Metrics::Metric do
it 'includes the tags' do it 'includes the tags' do
expect(hash[:tags]).to be_an_instance_of(Hash) expect(hash[:tags]).to be_an_instance_of(Hash)
expect(hash[:tags][:hostname]).to be_an_instance_of(String)
expect(hash[:tags][:ruby_engine]).to be_an_instance_of(String)
expect(hash[:tags][:ruby_version]).to be_an_instance_of(String)
expect(hash[:tags][:gitlab_version]).to be_an_instance_of(String)
expect(hash[:tags][:process_type]).to be_an_instance_of(String)
end end
it 'includes the values' do it 'includes the values' do
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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