summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>2015-10-15 14:37:04 +0200
committerDmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>2015-10-15 14:37:04 +0200
commitc0a6836be48bdf9a32ac2c9e610f62aef1e2e3f7 (patch)
tree5c4a5a2e8eb5dd612acb575fdc3e5ad68aea19bb
parent4b28f2d999eeb936e2e3718e5ea25d3b1020291e (diff)
parent97e02f4bc99d391c5e1dc0ce8e317be105e6d2b0 (diff)
downloadgitlab-ce-c0a6836be48bdf9a32ac2c9e610f62aef1e2e3f7.tar.gz
Merge branch 'view-issue-performance'
-rw-r--r--CHANGELOG1
-rw-r--r--Gemfile3
-rw-r--r--Gemfile.lock15
-rw-r--r--app/controllers/projects/issues_controller.rb2
-rw-r--r--app/helpers/application_helper.rb10
-rw-r--r--app/helpers/projects_helper.rb2
-rw-r--r--app/models/concerns/issuable.rb7
-rw-r--r--app/models/group.rb2
-rw-r--r--app/models/note.rb5
-rw-r--r--app/models/project_team.rb19
-rw-r--r--app/views/admin/users/show.html.haml2
-rw-r--r--app/views/dashboard/milestones/_issue.html.haml2
-rw-r--r--app/views/dashboard/milestones/_merge_request.html.haml2
-rw-r--r--app/views/dashboard/milestones/show.html.haml2
-rw-r--r--app/views/groups/group_members/_group_member.html.haml2
-rw-r--r--app/views/groups/milestones/_issue.html.haml2
-rw-r--r--app/views/groups/milestones/_merge_request.html.haml2
-rw-r--r--app/views/groups/milestones/show.html.haml2
-rw-r--r--app/views/layouts/_page.html.haml2
-rw-r--r--app/views/layouts/ci/_page.html.haml2
-rw-r--r--app/views/profiles/show.html.haml2
-rw-r--r--app/views/projects/_md_preview.html.haml4
-rw-r--r--app/views/projects/_zen.html.haml6
-rw-r--r--app/views/projects/milestones/_issue.html.haml2
-rw-r--r--app/views/projects/milestones/_merge_request.html.haml2
-rw-r--r--app/views/projects/milestones/show.html.haml2
-rw-r--r--app/views/projects/notes/_note.html.haml6
-rw-r--r--app/views/projects/project_members/_project_member.html.haml2
-rw-r--r--app/views/users/show.html.haml4
-rw-r--r--config/environments/development.rb2
-rw-r--r--config/initializers/active_record_query_trace.rb5
-rw-r--r--config/initializers/bullet.rb6
-rw-r--r--config/initializers/rack_lineprof.rb31
-rw-r--r--doc/development/profiling.md56
-rw-r--r--spec/benchmarks/models/project_team_spec.rb23
-rw-r--r--spec/helpers/application_helper_spec.rb9
-rw-r--r--spec/helpers/projects_helper_spec.rb14
37 files changed, 226 insertions, 36 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 792ec4fbe29..d2704ed204e 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -2,6 +2,7 @@ Please view this file on the master branch, on stable branches it's out of date.
v 8.1.0 (unreleased)
- Fix error preventing displaying of commit data for a directory with a leading dot (Stan Hu)
+ - Speed up load times of issue detail pages by roughly 1.5x
- Make diff file view easier to use on mobile screens (Stan Hu)
- Add support for creating directories from Files page (Stan Hu)
- Allow removing of project without confirmation when JavaScript is disabled (Stan Hu)
diff --git a/Gemfile b/Gemfile
index 29d9db05f42..4fbd37f6c8b 100644
--- a/Gemfile
+++ b/Gemfile
@@ -216,6 +216,9 @@ group :development do
gem 'quiet_assets', '~> 1.0.2'
gem 'rack-mini-profiler', '~> 0.9.0', require: false
gem 'rerun', '~> 0.10.0'
+ gem 'bullet', require: false
+ gem 'active_record_query_trace', require: false
+ gem 'rack-lineprof', platform: :mri
# Better errors handler
gem 'better_errors', '~> 1.0.1'
diff --git a/Gemfile.lock b/Gemfile.lock
index ee545f366f4..b92d9766d3a 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -17,6 +17,7 @@ GEM
activesupport (= 4.1.12)
builder (~> 3.1)
erubis (~> 2.7.0)
+ active_record_query_trace (1.5)
activemodel (4.1.12)
activesupport (= 4.1.12)
builder (~> 3.1)
@@ -87,6 +88,9 @@ GEM
terminal-table (~> 1.4)
browser (1.0.0)
builder (3.2.2)
+ bullet (4.14.9)
+ activesupport (>= 3.0.0)
+ uniform_notifier (~> 1.9.0)
byebug (6.0.2)
cal-heatmap-rails (0.0.1)
capybara (2.4.4)
@@ -134,6 +138,7 @@ GEM
daemons (1.2.3)
database_cleaner (1.4.1)
debug_inspector (0.0.2)
+ debugger-ruby_core_source (1.3.8)
default_value_for (3.0.1)
activerecord (>= 3.2.0, < 5.0)
descendants_tracker (0.0.4)
@@ -484,6 +489,10 @@ GEM
rack-attack (4.3.0)
rack
rack-cors (0.4.0)
+ rack-lineprof (0.0.3)
+ rack (~> 1.5)
+ rblineprof (~> 0.3.6)
+ term-ansicolor (~> 1.3)
rack-mini-profiler (0.9.7)
rack (>= 1.1.3)
rack-mount (0.8.3)
@@ -522,6 +531,8 @@ GEM
rb-fsevent (0.9.5)
rb-inotify (0.9.5)
ffi (>= 0.5.0)
+ rblineprof (0.3.6)
+ debugger-ruby_core_source (~> 1.3)
rbvmomi (1.8.2)
builder
nokogiri (>= 1.4.1)
@@ -736,6 +747,7 @@ GEM
unicorn-worker-killer (0.4.3)
get_process_mem (~> 0)
unicorn (~> 4)
+ uniform_notifier (1.9.0)
uuid (2.3.8)
macaddr (~> 1.0)
version_sorter (2.0.0)
@@ -765,6 +777,7 @@ PLATFORMS
DEPENDENCIES
RedCloth (~> 4.2.9)
ace-rails-ap (~> 2.0.1)
+ active_record_query_trace
activerecord-deprecated_finders (~> 1.0.3)
activerecord-session_store (~> 0.1.0)
acts-as-taggable-on (~> 3.4)
@@ -781,6 +794,7 @@ DEPENDENCIES
bootstrap-sass (~> 3.0)
brakeman (= 3.0.1)
browser (~> 1.0.0)
+ bullet
byebug
cal-heatmap-rails (~> 0.0.1)
capybara (~> 2.4.0)
@@ -861,6 +875,7 @@ DEPENDENCIES
quiet_assets (~> 1.0.2)
rack-attack (~> 4.3.0)
rack-cors (~> 0.4.0)
+ rack-lineprof
rack-mini-profiler (~> 0.9.0)
rack-oauth2 (~> 1.0.5)
rails (= 4.1.12)
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 4612abcbae8..97485c101fb 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -57,7 +57,7 @@ class Projects::IssuesController < Projects::ApplicationController
def show
@participants = @issue.participants(current_user)
@note = @project.notes.new(noteable: @issue)
- @notes = @issue.notes.inc_author.fresh
+ @notes = @issue.notes.with_associations.fresh
@noteable = @issue
respond_with(@issue)
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index cab2278adb7..8ecdeaf8e76 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -68,13 +68,17 @@ module ApplicationHelper
end
end
- def avatar_icon(user_email = '', size = nil)
- user = User.find_by(email: user_email)
+ def avatar_icon(user_or_email = nil, size = nil)
+ if user_or_email.is_a?(User)
+ user = user_or_email
+ else
+ user = User.find_by(email: user_or_email)
+ end
if user
user.avatar_url(size) || default_avatar
else
- gravatar_icon(user_email, size)
+ gravatar_icon(user_or_email, size)
end
end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index b7965aee875..dbadbb74549 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -29,7 +29,7 @@ module ProjectsHelper
author_html = ""
# Build avatar image tag
- author_html << image_tag(avatar_icon(author.try(:email), opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}", alt:'') if opts[:avatar]
+ author_html << image_tag(avatar_icon(author, opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}", alt:'') if opts[:avatar]
# Build name span tag
author_html << content_tag(:span, sanitize(author.name), class: opts[:author_class]) if opts[:name]
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 4db4ffb2e79..0e8bcc1a4ec 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -47,7 +47,8 @@ module Issuable
prefix: true
attr_mentionable :title, :description
- participant :author, :assignee, :notes, :mentioned_users
+
+ participant :author, :assignee, :notes_with_associations, :mentioned_users
end
module ClassMethods
@@ -176,6 +177,10 @@ module Issuable
self.class.to_s.underscore
end
+ def notes_with_associations
+ notes.includes(:author, :project)
+ end
+
private
def filter_superceded_votes(votes, notes)
diff --git a/app/models/group.rb b/app/models/group.rb
index 9cd146bb73b..465c22d23ac 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -64,7 +64,7 @@ class Group < Namespace
end
def owners
- @owners ||= group_members.owners.map(&:user)
+ @owners ||= group_members.owners.includes(:user).map(&:user)
end
def add_users(user_ids, access_level, current_user = nil)
diff --git a/app/models/note.rb b/app/models/note.rb
index ee0c14598f3..ebbdd2f33a1 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -60,6 +60,11 @@ class Note < ActiveRecord::Base
scope :inc_author_project, ->{ includes(:project, :author) }
scope :inc_author, ->{ includes(:author) }
+ scope :with_associations, -> do
+ includes(:author, :noteable, :updated_by,
+ project: [:project_members, { group: [:group_members] }])
+ end
+
serialize :st_diff
before_create :set_diff, if: ->(n) { n.line_code.present? }
diff --git a/app/models/project_team.rb b/app/models/project_team.rb
index f602a965364..9f380a382cb 100644
--- a/app/models/project_team.rb
+++ b/app/models/project_team.rb
@@ -139,15 +139,28 @@ class ProjectTeam
Gitlab::Access.options.key max_member_access(user_id)
end
+ # This method assumes project and group members are eager loaded for optimal
+ # performance.
def max_member_access(user_id)
access = []
- access << project.project_members.find_by(user_id: user_id).try(:access_field)
+
+ project.project_members.each do |member|
+ if member.user_id == user_id
+ access << member.access_field if member.access_field
+ break
+ end
+ end
if group
- access << group.group_members.find_by(user_id: user_id).try(:access_field)
+ group.group_members.each do |member|
+ if member.user_id == user_id
+ access << member.access_field if member.access_field
+ break
+ end
+ end
end
- access.compact.max
+ access.max
end
private
diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml
index a383ea57384..231bcb0426f 100644
--- a/app/views/admin/users/show.html.haml
+++ b/app/views/admin/users/show.html.haml
@@ -8,7 +8,7 @@
= @user.name
%ul.well-list
%li
- = image_tag avatar_icon(@user.email, 60), class: "avatar s60"
+ = image_tag avatar_icon(@user, 60), class: "avatar s60"
%li
%span.light Profile page:
%strong
diff --git a/app/views/dashboard/milestones/_issue.html.haml b/app/views/dashboard/milestones/_issue.html.haml
index f689b9698eb..1408ebdd5dc 100644
--- a/app/views/dashboard/milestones/_issue.html.haml
+++ b/app/views/dashboard/milestones/_issue.html.haml
@@ -7,4 +7,4 @@
= link_to_gfm issue.title, [project.namespace.becomes(Namespace), project, issue], title: issue.title
.pull-right.assignee-icon
- if issue.assignee
- = image_tag avatar_icon(issue.assignee.email, 16), class: "avatar s16"
+ = image_tag avatar_icon(issue.assignee, 16), class: "avatar s16"
diff --git a/app/views/dashboard/milestones/_merge_request.html.haml b/app/views/dashboard/milestones/_merge_request.html.haml
index 8f5c4cce529..77c46de030b 100644
--- a/app/views/dashboard/milestones/_merge_request.html.haml
+++ b/app/views/dashboard/milestones/_merge_request.html.haml
@@ -7,4 +7,4 @@
= link_to_gfm merge_request.title, [project.namespace.becomes(Namespace), project, merge_request], title: merge_request.title
.pull-right.assignee-icon
- if merge_request.assignee
- = image_tag avatar_icon(merge_request.assignee.email, 16), class: "avatar s16"
+ = image_tag avatar_icon(merge_request.assignee, 16), class: "avatar s16"
diff --git a/app/views/dashboard/milestones/show.html.haml b/app/views/dashboard/milestones/show.html.haml
index 0d204ced7ea..d5c4a44fef6 100644
--- a/app/views/dashboard/milestones/show.html.haml
+++ b/app/views/dashboard/milestones/show.html.haml
@@ -79,7 +79,7 @@
- @dashboard_milestone.participants.each do |user|
%li
= link_to user, title: user.name, class: "darken" do
- = image_tag avatar_icon(user.email, 32), class: "avatar s32"
+ = image_tag avatar_icon(user, 32), class: "avatar s32"
%strong= truncate(user.name, lenght: 40)
%br
%small.cgray= user.username
diff --git a/app/views/groups/group_members/_group_member.html.haml b/app/views/groups/group_members/_group_member.html.haml
index b5f359279d5..3c19381321a 100644
--- a/app/views/groups/group_members/_group_member.html.haml
+++ b/app/views/groups/group_members/_group_member.html.haml
@@ -5,7 +5,7 @@
%li{class: "#{dom_class(member)} js-toggle-container", id: dom_id(member)}
%span{class: ("list-item-name" if show_controls)}
- if member.user
- = image_tag avatar_icon(user.email, 16), class: "avatar s16", alt: ''
+ = image_tag avatar_icon(user, 16), class: "avatar s16", alt: ''
%strong
= link_to user.name, user_path(user)
%span.cgray= user.username
diff --git a/app/views/groups/milestones/_issue.html.haml b/app/views/groups/milestones/_issue.html.haml
index 09f9b4b8969..9b85d83d6d8 100644
--- a/app/views/groups/milestones/_issue.html.haml
+++ b/app/views/groups/milestones/_issue.html.haml
@@ -7,4 +7,4 @@
= link_to_gfm issue.title, [project.namespace.becomes(Namespace), project, issue], title: issue.title
.pull-right.assignee-icon
- if issue.assignee
- = image_tag avatar_icon(issue.assignee.email, 16), class: "avatar s16", alt: ''
+ = image_tag avatar_icon(issue.assignee, 16), class: "avatar s16", alt: ''
diff --git a/app/views/groups/milestones/_merge_request.html.haml b/app/views/groups/milestones/_merge_request.html.haml
index d0d1426762b..e3aa4aad198 100644
--- a/app/views/groups/milestones/_merge_request.html.haml
+++ b/app/views/groups/milestones/_merge_request.html.haml
@@ -7,4 +7,4 @@
= link_to_gfm merge_request.title, [project.namespace.becomes(Namespace), project, merge_request], title: merge_request.title
.pull-right.assignee-icon
- if merge_request.assignee
- = image_tag avatar_icon(merge_request.assignee.email, 16), class: "avatar s16", alt: ''
+ = image_tag avatar_icon(merge_request.assignee, 16), class: "avatar s16", alt: ''
diff --git a/app/views/groups/milestones/show.html.haml b/app/views/groups/milestones/show.html.haml
index 0c213f42186..c6cde97c585 100644
--- a/app/views/groups/milestones/show.html.haml
+++ b/app/views/groups/milestones/show.html.haml
@@ -87,7 +87,7 @@
- @group_milestone.participants.each do |user|
%li
= link_to user, title: user.name, class: "darken" do
- = image_tag avatar_icon(user.email, 32), class: "avatar s32"
+ = image_tag avatar_icon(user, 32), class: "avatar s32"
%strong= truncate(user.name, lenght: 40)
%br
%small.cgray= user.username
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index 1a883e20e89..352b8040cf4 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -18,7 +18,7 @@
= render partial: 'layouts/collapse_button'
- if current_user
= link_to current_user, class: 'sidebar-user' do
- = image_tag avatar_icon(current_user.email, 60), alt: 'User activity', class: 'avatar avatar s36'
+ = image_tag avatar_icon(current_user, 60), alt: 'User activity', class: 'avatar avatar s36'
.username
= current_user.username
.content-wrapper
diff --git a/app/views/layouts/ci/_page.html.haml b/app/views/layouts/ci/_page.html.haml
index bb5ec727bff..ab3e29c3f42 100644
--- a/app/views/layouts/ci/_page.html.haml
+++ b/app/views/layouts/ci/_page.html.haml
@@ -15,7 +15,7 @@
= render partial: 'layouts/collapse_button'
- if current_user
= link_to current_user, class: 'sidebar-user' do
- = image_tag avatar_icon(current_user.email, 60), alt: 'User activity', class: 'avatar avatar s36'
+ = image_tag avatar_icon(current_user, 60), alt: 'User activity', class: 'avatar avatar s36'
.username
= current_user.username
.content-wrapper
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index 47412e2ef0c..ac7355dde1f 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -68,7 +68,7 @@
.col-md-5
.light-well
- = image_tag avatar_icon(@user.email, 160), alt: '', class: 'avatar s160'
+ = image_tag avatar_icon(@user, 160), alt: '', class: 'avatar s160'
.clearfix
.profile-avatar-form-option
diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml
index 507757f6a2b..7b21095ea3e 100644
--- a/app/views/projects/_md_preview.html.haml
+++ b/app/views/projects/_md_preview.html.haml
@@ -2,10 +2,10 @@
.md-header.clearfix
%ul.center-top-menu
%li.active
- = link_to '#md-write-holder', class: 'js-md-write-button', tabindex: '-1' do
+ %a.js-md-write-button(href="#md-write-holder" tabindex="-1")
Write
%li
- = link_to '#md-preview-holder', class: 'js-md-preview-button', tabindex: '-1' do
+ %a.js-md-preview-button(href="md-preview-holder" tabindex="-1")
Preview
- if defined?(referenced_users) && referenced_users
diff --git a/app/views/projects/_zen.html.haml b/app/views/projects/_zen.html.haml
index 6a41cdbc907..63ebfc9381f 100644
--- a/app/views/projects/_zen.html.haml
+++ b/app/views/projects/_zen.html.haml
@@ -1,10 +1,10 @@
.zennable
- %input#zen-toggle-comment.zen-toggle-comment{ tabindex: '-1', type: 'checkbox' }
+ %input#zen-toggle-comment.zen-toggle-comment(tabindex="-1" type="checkbox")
.zen-backdrop
- classes << ' js-gfm-input markdown-area'
= f.text_area attr, class: classes, placeholder: ''
- = link_to nil, class: 'zen-enter-link', tabindex: '-1' do
+ %a.zen-enter-link(tabindex="-1" href="#")
%i.fa.fa-expand
Edit in fullscreen
- = link_to nil, class: 'zen-leave-link' do
+ %a.zen-leave-link(href="#")
%i.fa.fa-compress
diff --git a/app/views/projects/milestones/_issue.html.haml b/app/views/projects/milestones/_issue.html.haml
index 88fccfe4981..133d802aaca 100644
--- a/app/views/projects/milestones/_issue.html.haml
+++ b/app/views/projects/milestones/_issue.html.haml
@@ -1,7 +1,7 @@
%li{ id: dom_id(issue, 'sortable'), class: 'issue-row', 'data-iid' => issue.iid, 'data-url' => issue_path(issue) }
.pull-right.assignee-icon
- if issue.assignee
- = image_tag avatar_icon(issue.assignee.email, 16), class: "avatar s16", alt: ''
+ = image_tag avatar_icon(issue.assignee, 16), class: "avatar s16", alt: ''
%span
= link_to [@project.namespace.becomes(Namespace), @project, issue] do
%span.cgray ##{issue.iid}
diff --git a/app/views/projects/milestones/_merge_request.html.haml b/app/views/projects/milestones/_merge_request.html.haml
index 0d7a118569a..a1033607c5d 100644
--- a/app/views/projects/milestones/_merge_request.html.haml
+++ b/app/views/projects/milestones/_merge_request.html.haml
@@ -5,4 +5,4 @@
= link_to_gfm merge_request.title, [@project.namespace.becomes(Namespace), @project, merge_request], title: merge_request.title
.pull-right.assignee-icon
- if merge_request.assignee
- = image_tag avatar_icon(merge_request.assignee.email, 16), class: "avatar s16", alt: ''
+ = image_tag avatar_icon(merge_request.assignee, 16), class: "avatar s16", alt: ''
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index 4eeb0621e52..3a898dfbcfd 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -104,7 +104,7 @@
- @users.each do |user|
%li
= link_to user, title: user.name, class: "darken" do
- = image_tag avatar_icon(user.email, 32), class: "avatar s32"
+ = image_tag avatar_icon(user, 32), class: "avatar s32"
%strong= truncate(user.name, lenght: 40)
%br
%small.cgray= user.username
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index 1638ad6891a..5d184730796 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -1,8 +1,8 @@
%li.timeline-entry{ id: dom_id(note), class: [dom_class(note), "note-row-#{note.id}", ('system-note' if note.system)], data: { discussion: note.discussion_id } }
.timeline-entry-inner
.timeline-icon
- = link_to user_path(note.author) do
- = image_tag avatar_icon(note.author_email), class: 'avatar s40', alt: ''
+ %a{href: user_path(note.author)}
+ %img.avatar.s40{src: avatar_icon(note.author), alt: ''}
.timeline-content
.note-header
- if note_editable?(note)
@@ -25,7 +25,7 @@
= '@' + note.author.username
%span.note-last-update
- = link_to "##{dom_id(note)}", name: dom_id(note), title: "Link here" do
+ %a{name: dom_id(note), href: "##{dom_id(note)}", title: 'Link here'}
= time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note_created_ago')
- if note.updated_at != note.created_at
%span
diff --git a/app/views/projects/project_members/_project_member.html.haml b/app/views/projects/project_members/_project_member.html.haml
index 860a997cff8..76c46d1d806 100644
--- a/app/views/projects/project_members/_project_member.html.haml
+++ b/app/views/projects/project_members/_project_member.html.haml
@@ -4,7 +4,7 @@
%li{class: "#{dom_class(member)} js-toggle-container project_member_row access-#{member.human_access.downcase}", id: dom_id(member)}
%span.list-item-name
- if member.user
- = image_tag avatar_icon(user.email, 16), class: "avatar s16", alt: ''
+ = image_tag avatar_icon(user, 16), class: "avatar s16", alt: ''
%strong
= link_to user.name, user_path(user)
%span.cgray= user.username
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index 11beb3e3239..2a64708d07c 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -9,8 +9,8 @@
.row
%section.col-md-7
.header-with-avatar
- = link_to avatar_icon(@user.email, 400), target: '_blank' do
- = image_tag avatar_icon(@user.email, 90), class: "avatar avatar-tile s90", alt: ''
+ = link_to avatar_icon(@user, 400), target: '_blank' do
+ = image_tag avatar_icon(@user, 90), class: "avatar avatar-tile s90", alt: ''
%h3
= @user.name
- if @user == current_user
diff --git a/config/environments/development.rb b/config/environments/development.rb
index d7d6aed1602..827a110c249 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -24,7 +24,7 @@ Gitlab::Application.configure do
# Expands the lines which load the assets
# config.assets.debug = true
-
+
# Adds additional error checking when serving assets at runtime.
# Checks for improperly declared sprockets dependencies.
# Raises helpful error messages.
diff --git a/config/initializers/active_record_query_trace.rb b/config/initializers/active_record_query_trace.rb
new file mode 100644
index 00000000000..4b3c2803b3b
--- /dev/null
+++ b/config/initializers/active_record_query_trace.rb
@@ -0,0 +1,5 @@
+if ENV['ENABLE_QUERY_TRACE']
+ require 'active_record_query_trace'
+
+ ActiveRecordQueryTrace.enabled = 'true'
+end
diff --git a/config/initializers/bullet.rb b/config/initializers/bullet.rb
new file mode 100644
index 00000000000..95e82966c7a
--- /dev/null
+++ b/config/initializers/bullet.rb
@@ -0,0 +1,6 @@
+if ENV['ENABLE_BULLET']
+ require 'bullet'
+
+ Bullet.enable = true
+ Bullet.console = true
+end
diff --git a/config/initializers/rack_lineprof.rb b/config/initializers/rack_lineprof.rb
new file mode 100644
index 00000000000..f0c006d811b
--- /dev/null
+++ b/config/initializers/rack_lineprof.rb
@@ -0,0 +1,31 @@
+# The default colors of rack-lineprof can be very hard to look at in terminals
+# with darker backgrounds. This patch tweaks the colors a bit so the output is
+# actually readable.
+if Rails.env.development? and RUBY_ENGINE == 'ruby' and ENV['ENABLE_LINEPROF']
+ Gitlab::Application.config.middleware.use(Rack::Lineprof)
+
+ module Rack
+ class Lineprof
+ class Sample < Rack::Lineprof::Sample.superclass
+ def format(*)
+ formatted = if level == CONTEXT
+ sprintf " | % 3i %s", line, code
+ else
+ sprintf "% 8.1fms %5i | % 3i %s", ms, calls, line, code
+ end
+
+ case level
+ when CRITICAL
+ color.red formatted
+ when WARNING
+ color.yellow formatted
+ when NOMINAL
+ color.white formatted
+ else # CONTEXT
+ formatted
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/doc/development/profiling.md b/doc/development/profiling.md
new file mode 100644
index 00000000000..80c86ef921e
--- /dev/null
+++ b/doc/development/profiling.md
@@ -0,0 +1,56 @@
+# Profiling
+
+To make it easier to track down performance problems GitLab comes with a set of
+profiling tools, some of these are available by default while others need to be
+explicitly enabled.
+
+## rack-mini-profiler
+
+This Gem is enabled by default in development only. It allows you to see the
+timings of the various components that made up a web request (e.g. the SQL
+queries executed and their execution timings).
+
+## Bullet
+
+Bullet is a Gem that can be used to track down N+1 query problems. Because
+Bullet adds quite a bit of logging noise it's disabled by default. To enable
+Bullet, set the environment variable `ENABLE_BULLET` to a non-empty value before
+starting GitLab. For example:
+
+ ENABLE_BULLET=true bundle exec rails s
+
+Bullet will log query problems to both the Rails log as well as the Chrome
+console.
+
+## ActiveRecord Query Trace
+
+This Gem adds backtraces for every ActiveRecord query in the Rails console. This
+can be useful to track down where a query was executed. Because this Gem adds
+quite a bit of noise (5-10 extra lines per ActiveRecord query) it's disabled by
+default. To use this Gem you'll need to set `ENABLE_QUERY_TRACE` to a non empty
+file before starting GitLab. For example:
+
+ ENABLE_QUERY_TRACE=true bundle exec rails s
+
+## rack-lineprof
+
+This is a Gem that can trace the execution time of code on a per line basis.
+Because this Gem can add quite a bit of overhead it's disabled by default. To
+enable it, set the environment variable `ENABLE_LINEPROF` to a non-empty value.
+For example:
+
+ ENABLE_LINEPROF=true bundle exec rails s
+
+Once enabled you'll need to add a query string parameter to a request to
+actually profile code execution. The name of the parameter is `lineprof` and
+should be set to a regular expression (minus the starting/ending slash) used to
+select what files to profile. To profile all files containing "foo" somewhere in
+the path you'd use the following parameter:
+
+ ?lineprof=foo
+
+Or when filtering for files containing "foo" and "bar" in their path:
+
+ ?lineprof=foo|bar
+
+Once set the profiling output will be displayed in your terminal.
diff --git a/spec/benchmarks/models/project_team_spec.rb b/spec/benchmarks/models/project_team_spec.rb
new file mode 100644
index 00000000000..8b039ef7317
--- /dev/null
+++ b/spec/benchmarks/models/project_team_spec.rb
@@ -0,0 +1,23 @@
+require 'spec_helper'
+
+describe ProjectTeam, benchmark: true do
+ describe '#max_member_access' do
+ let(:group) { create(:group) }
+ let(:project) { create(:empty_project, group: group) }
+ let(:user) { create(:user) }
+
+ before do
+ project.team << [user, :master]
+
+ 5.times do
+ project.team << [create(:user), :reporter]
+
+ project.group.add_user(create(:user), :reporter)
+ end
+ end
+
+ benchmark_subject { project.team.max_member_access(user.id) }
+
+ it { is_expected.to iterate_per_second(35000) }
+ end
+end
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index 742420f550e..1dfae0fbd3f 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -99,6 +99,15 @@ describe ApplicationHelper do
helper.avatar_icon('foo@example.com', 20)
end
+
+ describe 'using a User' do
+ it 'should return an URL for the avatar' do
+ user = create(:user, avatar: File.open(avatar_file_path))
+
+ expect(helper.avatar_icon(user).to_s).
+ to match("/uploads/user/avatar/#{user.id}/banana_sample.gif")
+ end
+ end
end
describe 'gravatar_icon' do
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index 53e56ebff44..f2efb528aeb 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -70,4 +70,18 @@ describe ProjectsHelper do
expect(helper.send(:readme_cache_key)).to eq("#{project.path_with_namespace}-nil-readme")
end
end
+
+ describe 'link_to_member' do
+ let(:group) { create(:group) }
+ let(:project) { create(:empty_project, group: group) }
+ let(:user) { create(:user) }
+
+ describe 'using the default options' do
+ it 'returns an HTML link to the user' do
+ link = helper.link_to_member(project, user)
+
+ expect(link).to match(%r{/u/#{user.username}})
+ end
+ end
+ end
end