Commit 65cad57a35002a9fd168863ae8a4d7ca045fb938

Authored by Steven Thonus
1 parent 358426d6

avatar upload on profile page

Showing 40 changed files with 119 additions and 39 deletions   Show diff stats
@@ -13,6 +13,7 @@ v 6.2.0 @@ -13,6 +13,7 @@ v 6.2.0
13 - Rake tasks for web hooks management (Jonhnny Weslley) 13 - Rake tasks for web hooks management (Jonhnny Weslley)
14 - Extended User API to expose admin and can_create_group for user creation/updating (Boyan Tabakov) 14 - Extended User API to expose admin and can_create_group for user creation/updating (Boyan Tabakov)
15 - API: Remove group 15 - API: Remove group
  16 + - Avatar upload on profile page with a maximum of 200KB (Steven Thonus)
16 17
17 v 6.1.0 18 v 6.1.0
18 - Project specific IDs for issues, mr, milestones 19 - Project specific IDs for issues, mr, milestones
app/assets/javascripts/profile.js.coffee
@@ -16,3 +16,13 @@ $ -> @@ -16,3 +16,13 @@ $ ->
16 16
17 $('.update-notifications').on 'ajax:complete', -> 17 $('.update-notifications').on 'ajax:complete', ->
18 $(this).find('.btn-save').enableButton() 18 $(this).find('.btn-save').enableButton()
  19 +
  20 +
  21 + $('.js-choose-user-avatar-button').bind "click", ->
  22 + form = $(this).closest("form")
  23 + form.find(".js-user-avatar-input").click()
  24 +
  25 + $('.js-user-avatar-input').bind "change", ->
  26 + form = $(this).closest("form")
  27 + filename = $(this).val().replace(/^.*[\\\/]/, '')
  28 + form.find(".js-avatar-filename").text(filename)
app/assets/javascripts/users_select.js.coffee
1 $ -> 1 $ ->
2 userFormatResult = (user) -> 2 userFormatResult = (user) ->
3 - avatar = gon.gravatar_url  
4 - avatar = avatar.replace('%{hash}', md5(user.email))  
5 - avatar = avatar.replace('%{size}', '24')  
6 - 3 + if user.avatar
  4 + avatar = user.avatar.url
  5 + else
  6 + avatar = gon.gravatar_url
  7 + avatar = avatar.replace('%{hash}', md5(user.email))
  8 + avatar = avatar.replace('%{size}', '24')
7 markup = "<div class='user-result'>" 9 markup = "<div class='user-result'>"
8 markup += "<div class='user-image'><img class='avatar s24' src='" + avatar + "'></div>" 10 markup += "<div class='user-image'><img class='avatar s24' src='" + avatar + "'></div>"
9 markup += "<div class='user-name'>" + user.name + "</div>" 11 markup += "<div class='user-name'>" + user.name + "</div>"
app/helpers/application_helper.rb
@@ -49,6 +49,15 @@ module ApplicationHelper @@ -49,6 +49,15 @@ module ApplicationHelper
49 args.any? { |v| v.to_s.downcase == action_name } 49 args.any? { |v| v.to_s.downcase == action_name }
50 end 50 end
51 51
  52 + def avatar_icon(user_email = '', size = nil)
  53 + user = User.find_by_email(user_email)
  54 + if user && user.avatar.present?
  55 + user.avatar.url
  56 + else
  57 + gravatar_icon(user_email, size)
  58 + end
  59 + end
  60 +
52 def gravatar_icon(user_email = '', size = nil) 61 def gravatar_icon(user_email = '', size = nil)
53 size = 40 if size.nil? || size <= 0 62 size = 40 if size.nil? || size <= 0
54 63
app/helpers/commits_helper.rb
@@ -108,7 +108,7 @@ module CommitsHelper @@ -108,7 +108,7 @@ module CommitsHelper
108 source_name = commit.send "#{options[:source]}_name".to_sym 108 source_name = commit.send "#{options[:source]}_name".to_sym
109 source_email = commit.send "#{options[:source]}_email".to_sym 109 source_email = commit.send "#{options[:source]}_email".to_sym
110 text = if options[:avatar] 110 text = if options[:avatar]
111 - avatar = image_tag(gravatar_icon(source_email, options[:size]), class: "avatar #{"s#{options[:size]}" if options[:size]}", width: options[:size], alt: "") 111 + avatar = image_tag(avatar_icon(source_email, options[:size]), class: "avatar #{"s#{options[:size]}" if options[:size]}", width: options[:size], alt: "")
112 %Q{#{avatar} <span class="commit-#{options[:source]}-name">#{source_name}</span>} 112 %Q{#{avatar} <span class="commit-#{options[:source]}-name">#{source_name}</span>}
113 else 113 else
114 source_name 114 source_name
app/helpers/projects_helper.rb
@@ -25,7 +25,7 @@ module ProjectsHelper @@ -25,7 +25,7 @@ module ProjectsHelper
25 author_html = "" 25 author_html = ""
26 26
27 # Build avatar image tag 27 # Build avatar image tag
28 - author_html << image_tag(gravatar_icon(author.try(:email), opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}", alt:'') if opts[:avatar] 28 + 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]
29 29
30 # Build name span tag 30 # Build name span tag
31 author_html << content_tag(:span, sanitize(author.name), class: 'author') if opts[:name] 31 author_html << content_tag(:span, sanitize(author.name), class: 'author') if opts[:name]
app/models/user.rb
@@ -38,13 +38,16 @@ @@ -38,13 +38,16 @@
38 # created_by_id :integer 38 # created_by_id :integer
39 # 39 #
40 40
  41 +require 'carrierwave/orm/activerecord'
  42 +require 'file_size_validator'
  43 +
41 class User < ActiveRecord::Base 44 class User < ActiveRecord::Base
42 devise :database_authenticatable, :token_authenticatable, :lockable, 45 devise :database_authenticatable, :token_authenticatable, :lockable,
43 :recoverable, :rememberable, :trackable, :validatable, :omniauthable, :registerable 46 :recoverable, :rememberable, :trackable, :validatable, :omniauthable, :registerable
44 47
45 attr_accessible :email, :password, :password_confirmation, :remember_me, :bio, :name, :username, 48 attr_accessible :email, :password, :password_confirmation, :remember_me, :bio, :name, :username,
46 :skype, :linkedin, :twitter, :color_scheme_id, :theme_id, :force_random_password, 49 :skype, :linkedin, :twitter, :color_scheme_id, :theme_id, :force_random_password,
47 - :extern_uid, :provider, :password_expires_at, 50 + :extern_uid, :provider, :password_expires_at, :avatar,
48 as: [:default, :admin] 51 as: [:default, :admin]
49 52
50 attr_accessible :projects_limit, :can_create_group, 53 attr_accessible :projects_limit, :can_create_group,
@@ -113,6 +116,8 @@ class User &lt; ActiveRecord::Base @@ -113,6 +116,8 @@ class User &lt; ActiveRecord::Base
113 116
114 validate :namespace_uniq, if: ->(user) { user.username_changed? } 117 validate :namespace_uniq, if: ->(user) { user.username_changed? }
115 118
  119 + validates :avatar, file_size: { maximum: 100.kilobytes.to_i }
  120 +
116 before_validation :generate_password, on: :create 121 before_validation :generate_password, on: :create
117 before_validation :sanitize_attrs 122 before_validation :sanitize_attrs
118 123
@@ -150,6 +155,8 @@ class User &lt; ActiveRecord::Base @@ -150,6 +155,8 @@ class User &lt; ActiveRecord::Base
150 end 155 end
151 end 156 end
152 157
  158 + mount_uploader :avatar, AttachmentUploader
  159 +
153 # Scopes 160 # Scopes
154 scope :admins, -> { where(admin: true) } 161 scope :admins, -> { where(admin: true) }
155 scope :blocked, -> { with_state(:blocked) } 162 scope :blocked, -> { with_state(:blocked) }
app/views/admin/users/show.html.haml
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 .title 20 .title
21 Account: 21 Account:
22 .pull-right 22 .pull-right
23 - = image_tag gravatar_icon(@user.email, 32), class: "avatar s32" 23 + = image_tag avatar_icon(@user.email, 32), class: "avatar s32"
24 %ul.well-list 24 %ul.well-list
25 %li 25 %li
26 %span.light Name: 26 %span.light Name:
app/views/dashboard/issues.atom.builder
@@ -12,7 +12,7 @@ xml.feed &quot;xmlns&quot; =&gt; &quot;http://www.w3.org/2005/Atom&quot;, &quot;xmlns:media&quot; =&gt; &quot;http://sear @@ -12,7 +12,7 @@ xml.feed &quot;xmlns&quot; =&gt; &quot;http://www.w3.org/2005/Atom&quot;, &quot;xmlns:media&quot; =&gt; &quot;http://sear
12 xml.link :href => project_issue_url(issue.project, issue) 12 xml.link :href => project_issue_url(issue.project, issue)
13 xml.title truncate(issue.title, :length => 80) 13 xml.title truncate(issue.title, :length => 80)
14 xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") 14 xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
15 - xml.media :thumbnail, :width => "40", :height => "40", :url => gravatar_icon(issue.author_email) 15 + xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(issue.author_email)
16 xml.author do |author| 16 xml.author do |author|
17 xml.name issue.author_name 17 xml.name issue.author_name
18 xml.email issue.author_email 18 xml.email issue.author_email
app/views/dashboard/show.atom.builder
@@ -17,7 +17,7 @@ xml.feed &quot;xmlns&quot; =&gt; &quot;http://www.w3.org/2005/Atom&quot;, &quot;xmlns:media&quot; =&gt; &quot;http://sear @@ -17,7 +17,7 @@ xml.feed &quot;xmlns&quot; =&gt; &quot;http://www.w3.org/2005/Atom&quot;, &quot;xmlns:media&quot; =&gt; &quot;http://sear
17 xml.link :href => event_link 17 xml.link :href => event_link
18 xml.title truncate(event_title, :length => 80) 18 xml.title truncate(event_title, :length => 80)
19 xml.updated event.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") 19 xml.updated event.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
20 - xml.media :thumbnail, :width => "40", :height => "40", :url => gravatar_icon(event.author_email) 20 + xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(event.author_email)
21 xml.author do |author| 21 xml.author do |author|
22 xml.name event.author_name 22 xml.name event.author_name
23 xml.email event.author_email 23 xml.email event.author_email
app/views/events/_event.html.haml
@@ -4,7 +4,7 @@ @@ -4,7 +4,7 @@
4 #{time_ago_in_words(event.created_at)} ago. 4 #{time_ago_in_words(event.created_at)} ago.
5 5
6 = cache event do 6 = cache event do
7 - = image_tag gravatar_icon(event.author_email), class: "avatar s24", alt:'' 7 + = image_tag avatar_icon(event.author_email), class: "avatar s24", alt:''
8 8
9 - if event.push? 9 - if event.push?
10 = render "events/event/push", event: event 10 = render "events/event/push", event: event
app/views/groups/issues.atom.builder
@@ -12,7 +12,7 @@ xml.feed &quot;xmlns&quot; =&gt; &quot;http://www.w3.org/2005/Atom&quot;, &quot;xmlns:media&quot; =&gt; &quot;http://sear @@ -12,7 +12,7 @@ xml.feed &quot;xmlns&quot; =&gt; &quot;http://www.w3.org/2005/Atom&quot;, &quot;xmlns:media&quot; =&gt; &quot;http://sear
12 xml.link :href => project_issue_url(issue.project, issue) 12 xml.link :href => project_issue_url(issue.project, issue)
13 xml.title truncate(issue.title, :length => 80) 13 xml.title truncate(issue.title, :length => 80)
14 xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") 14 xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
15 - xml.media :thumbnail, :width => "40", :height => "40", :url => gravatar_icon(issue.author_email) 15 + xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(issue.author_email)
16 xml.author do |author| 16 xml.author do |author|
17 xml.name issue.author_name 17 xml.name issue.author_name
18 xml.email issue.author_email 18 xml.email issue.author_email
app/views/groups/show.atom.builder
@@ -16,7 +16,7 @@ xml.feed &quot;xmlns&quot; =&gt; &quot;http://www.w3.org/2005/Atom&quot;, &quot;xmlns:media&quot; =&gt; &quot;http://sear @@ -16,7 +16,7 @@ xml.feed &quot;xmlns&quot; =&gt; &quot;http://www.w3.org/2005/Atom&quot;, &quot;xmlns:media&quot; =&gt; &quot;http://sear
16 xml.link :href => event_link 16 xml.link :href => event_link
17 xml.title truncate(event_title, :length => 80) 17 xml.title truncate(event_title, :length => 80)
18 xml.updated event.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") 18 xml.updated event.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
19 - xml.media :thumbnail, :width => "40", :height => "40", :url => gravatar_icon(event.author_email) 19 + xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(event.author_email)
20 xml.author do |author| 20 xml.author do |author|
21 xml.name event.author_name 21 xml.name event.author_name
22 xml.email event.author_email 22 xml.email event.author_email
app/views/layouts/_head_panel.html.haml
@@ -37,4 +37,4 @@ @@ -37,4 +37,4 @@
37 %i.icon-signout 37 %i.icon-signout
38 %li 38 %li
39 = link_to current_user, class: "profile-pic", id: 'profile-pic' do 39 = link_to current_user, class: "profile-pic", id: 'profile-pic' do
40 - = image_tag gravatar_icon(current_user.email, 26), alt: '' 40 + = image_tag avatar_icon(current_user.email, 26), alt: ''
app/views/profiles/show.html.haml
1 -= image_tag gravatar_icon(@user.email, 60), alt: '', class: 'avatar s60' 1 += image_tag avatar_icon(@user.email, 60), alt: '', class: 'avatar s60'
2 %h3.page-title 2 %h3.page-title
3 = @user.name 3 = @user.name
4 %br 4 %br
@@ -12,7 +12,7 @@ @@ -12,7 +12,7 @@
12 Logout 12 Logout
13 %hr 13 %hr
14 14
15 -= form_for @user, url: profile_path, method: :put, html: { class: "edit_user form-horizontal" } do |f| 15 += form_for @user, url: profile_path, method: :put, html: { multipart: true, class: "edit_user form-horizontal" } do |f|
16 -if @user.errors.any? 16 -if @user.errors.any?
17 %div.alert.alert-error 17 %div.alert.alert-error
18 %ul 18 %ul
@@ -29,7 +29,7 @@ @@ -29,7 +29,7 @@
29 = f.label :email, class: "control-label" 29 = f.label :email, class: "control-label"
30 .controls 30 .controls
31 = f.text_field :email, class: "input-xlarge", required: true 31 = f.text_field :email, class: "input-xlarge", required: true
32 - %span.help-block We also use email for avatar detection. 32 + %span.help-block We also use email for avatar detection if no avatar is uploaded.
33 .control-group 33 .control-group
34 = f.label :skype, class: "control-label" 34 = f.label :skype, class: "control-label"
35 .controls= f.text_field :skype, class: "input-xlarge" 35 .controls= f.text_field :skype, class: "input-xlarge"
@@ -40,6 +40,17 @@ @@ -40,6 +40,17 @@
40 = f.label :twitter, class: "control-label" 40 = f.label :twitter, class: "control-label"
41 .controls= f.text_field :twitter, class: "input-xlarge" 41 .controls= f.text_field :twitter, class: "input-xlarge"
42 .control-group 42 .control-group
  43 + = f.label :avatar, class: "control-label"
  44 + .controls
  45 + .profile-avatar-form-option
  46 + %a.choose-btn.btn.btn-small.js-choose-user-avatar-button
  47 + %i.icon-paper-clip
  48 + %span Choose File ...
  49 + &nbsp;
  50 + %span.file_name.js-avatar-filename File name...
  51 + = f.file_field :avatar, class: "js-user-avatar-input hide"
  52 + %span.help-block The maximum file size allowed is 200KB.
  53 + .control-group
43 = f.label :bio, class: "control-label" 54 = f.label :bio, class: "control-label"
44 .controls 55 .controls
45 = f.text_area :bio, rows: 6, class: "input-xlarge", maxlength: 250 56 = f.text_area :bio, rows: 6, class: "input-xlarge", maxlength: 250
@@ -53,7 +64,7 @@ @@ -53,7 +64,7 @@
53 %p You can change your password on the Account page 64 %p You can change your password on the Account page
54 - if Gitlab.config.gravatar.enabled 65 - if Gitlab.config.gravatar.enabled
55 %li 66 %li
56 - %p You can change your avatar at #{link_to "gravatar.com", "http://gravatar.com"} 67 + %p You can upload an avatar here or change it at #{link_to "gravatar.com", "http://gravatar.com"}
57 68
58 - if Gitlab.config.omniauth.enabled && @user.provider? 69 - if Gitlab.config.omniauth.enabled && @user.provider?
59 %li 70 %li
app/views/projects/branches/_branch.html.haml
@@ -24,7 +24,7 @@ @@ -24,7 +24,7 @@
24 %p 24 %p
25 = link_to project_commit_path(@project, commit.id), class: 'commit_short_id' do 25 = link_to project_commit_path(@project, commit.id), class: 'commit_short_id' do
26 = commit.short_id 26 = commit.short_id
27 - = image_tag gravatar_icon(commit.author_email), class: "avatar s16", alt: '' 27 + = image_tag avatar_icon(commit.author_email), class: "avatar s16", alt: ''
28 %span.light 28 %span.light
29 = gfm escape_once(truncate(commit.title, length: 40)) 29 = gfm escape_once(truncate(commit.title, length: 40))
30 %span 30 %span
app/views/projects/commits/show.atom.builder
@@ -12,7 +12,7 @@ xml.feed &quot;xmlns&quot; =&gt; &quot;http://www.w3.org/2005/Atom&quot;, &quot;xmlns:media&quot; =&gt; &quot;http://sear @@ -12,7 +12,7 @@ xml.feed &quot;xmlns&quot; =&gt; &quot;http://www.w3.org/2005/Atom&quot;, &quot;xmlns:media&quot; =&gt; &quot;http://sear
12 xml.link :href => project_commit_url(@project, :id => commit.id) 12 xml.link :href => project_commit_url(@project, :id => commit.id)
13 xml.title truncate(commit.title, :length => 80) 13 xml.title truncate(commit.title, :length => 80)
14 xml.updated commit.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ") 14 xml.updated commit.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ")
15 - xml.media :thumbnail, :width => "40", :height => "40", :url => gravatar_icon(commit.author_email) 15 + xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(commit.author_email)
16 xml.author do |author| 16 xml.author do |author|
17 xml.name commit.author_name 17 xml.name commit.author_name
18 xml.email commit.author_email 18 xml.email commit.author_email
app/views/projects/issues/_issues.html.haml
@@ -52,7 +52,7 @@ @@ -52,7 +52,7 @@
52 - @project.team.members.sort_by(&:name).each do |user| 52 - @project.team.members.sort_by(&:name).each do |user|
53 %li 53 %li
54 = link_to project_filter_path(assignee_id: user.id) do 54 = link_to project_filter_path(assignee_id: user.id) do
55 - = image_tag gravatar_icon(user.email), class: "avatar s16", alt: '' 55 + = image_tag avatar_icon(user.email), class: "avatar s16", alt: ''
56 = user.name 56 = user.name
57 57
58 .dropdown.inline.prepend-left-10 58 .dropdown.inline.prepend-left-10
app/views/projects/issues/index.atom.builder
@@ -12,7 +12,7 @@ xml.feed &quot;xmlns&quot; =&gt; &quot;http://www.w3.org/2005/Atom&quot;, &quot;xmlns:media&quot; =&gt; &quot;http://sear @@ -12,7 +12,7 @@ xml.feed &quot;xmlns&quot; =&gt; &quot;http://www.w3.org/2005/Atom&quot;, &quot;xmlns:media&quot; =&gt; &quot;http://sear
12 xml.link :href => project_issue_url(@project, issue) 12 xml.link :href => project_issue_url(@project, issue)
13 xml.title truncate(issue.title, :length => 80) 13 xml.title truncate(issue.title, :length => 80)
14 xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") 14 xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
15 - xml.media :thumbnail, :width => "40", :height => "40", :url => gravatar_icon(issue.author_email) 15 + xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(issue.author_email)
16 xml.author do |author| 16 xml.author do |author|
17 xml.name issue.author_name 17 xml.name issue.author_name
18 xml.email issue.author_email 18 xml.email issue.author_email
app/views/projects/merge_requests/index.html.haml
@@ -35,7 +35,7 @@ @@ -35,7 +35,7 @@
35 - @project.team.members.sort_by(&:name).each do |user| 35 - @project.team.members.sort_by(&:name).each do |user|
36 %li 36 %li
37 = link_to project_filter_path(assignee_id: user.id) do 37 = link_to project_filter_path(assignee_id: user.id) do
38 - = image_tag gravatar_icon(user.email), class: "avatar s16", alt: '' 38 + = image_tag avatar_icon(user.email), class: "avatar s16", alt: ''
39 = user.name 39 = user.name
40 40
41 .dropdown.inline.prepend-left-10 41 .dropdown.inline.prepend-left-10
app/views/projects/milestones/_issues.html.haml
@@ -8,4 +8,4 @@ @@ -8,4 +8,4 @@
8 = link_to_gfm truncate(issue.title, length: 40), [@project, issue] 8 = link_to_gfm truncate(issue.title, length: 40), [@project, issue]
9 - if issue.assignee 9 - if issue.assignee
10 .pull-right 10 .pull-right
11 - = image_tag gravatar_icon(issue.assignee.email, 16), class: "avatar s16" 11 + = image_tag avatar_icon(issue.assignee.email, 16), class: "avatar s16"
app/views/projects/milestones/show.html.haml
@@ -99,7 +99,7 @@ @@ -99,7 +99,7 @@
99 - @users.each do |user| 99 - @users.each do |user|
100 %li 100 %li
101 = link_to user, title: user.name, class: "dark" do 101 = link_to user, title: user.name, class: "dark" do
102 - = image_tag gravatar_icon(user.email, 32), class: "avatar s32" 102 + = image_tag avatar_icon(user.email, 32), class: "avatar s32"
103 %strong= truncate(user.name, lenght: 40) 103 %strong= truncate(user.name, lenght: 40)
104 %br 104 %br
105 %small.cgray= user.username 105 %small.cgray= user.username
app/views/projects/network/show.json.erb
@@ -9,7 +9,7 @@ @@ -9,7 +9,7 @@
9 author: { 9 author: {
10 name: c.author_name, 10 name: c.author_name,
11 email: c.author_email, 11 email: c.author_email,
12 - icon: gravatar_icon(c.author_email, 20) 12 + icon: avatar_icon(c.author_email, 20)
13 }, 13 },
14 time: c.time, 14 time: c.time,
15 space: c.spaces.first, 15 space: c.spaces.first,
app/views/projects/notes/_discussion.html.haml
@@ -8,7 +8,7 @@ @@ -8,7 +8,7 @@
8 = link_to "javascript:;", class: "js-details-target turn-off js-toggler-target" do 8 = link_to "javascript:;", class: "js-details-target turn-off js-toggler-target" do
9 %i.icon-eye-open 9 %i.icon-eye-open
10 Show discussion 10 Show discussion
11 - = image_tag gravatar_icon(note.author_email), class: "avatar s32" 11 + = image_tag avatar_icon(note.author_email), class: "avatar s32"
12 %div 12 %div
13 = link_to_member(@project, note.author, avatar: false) 13 = link_to_member(@project, note.author, avatar: false)
14 - if note.for_merge_request? 14 - if note.for_merge_request?
app/views/projects/notes/_note.html.haml
@@ -13,7 +13,7 @@ @@ -13,7 +13,7 @@
13 = link_to project_note_path(@project, note), title: "Remove comment", method: :delete, confirm: 'Are you sure you want to remove this comment?', remote: true, class: "danger js-note-delete" do 13 = link_to project_note_path(@project, note), title: "Remove comment", method: :delete, confirm: 'Are you sure you want to remove this comment?', remote: true, class: "danger js-note-delete" do
14 %i.icon-trash.cred 14 %i.icon-trash.cred
15 Remove 15 Remove
16 - = image_tag gravatar_icon(note.author_email), class: "avatar s32" 16 + = image_tag avatar_icon(note.author_email), class: "avatar s32"
17 = link_to_member(@project, note.author, avatar: false) 17 = link_to_member(@project, note.author, avatar: false)
18 %span.note-last-update 18 %span.note-last-update
19 = note_timestamp(note) 19 = note_timestamp(note)
app/views/projects/repositories/_feed.html.haml
@@ -11,7 +11,7 @@ @@ -11,7 +11,7 @@
11 %div 11 %div
12 = link_to project_commits_path(@project, commit.id) do 12 = link_to project_commits_path(@project, commit.id) do
13 %code= commit.short_id 13 %code= commit.short_id
14 - = image_tag gravatar_icon(commit.author_email), class: "", width: 16, alt: '' 14 + = image_tag avatar_icon(commit.author_email), class: "", width: 16, alt: ''
15 = gfm escape_once(truncate(commit.title, length: 40)) 15 = gfm escape_once(truncate(commit.title, length: 40))
16 %td 16 %td
17 %span.pull-right.cgray 17 %span.pull-right.cgray
app/views/projects/repositories/stats.html.haml
@@ -19,7 +19,7 @@ @@ -19,7 +19,7 @@
19 %ol.styled 19 %ol.styled
20 - @stats.authors[0...50].each do |author| 20 - @stats.authors[0...50].each do |author|
21 %li 21 %li
22 - = image_tag gravatar_icon(author.email, 16), class: 'avatar s16', alt: '' 22 + = image_tag avatar_icon(author.email, 16), class: 'avatar s16', alt: ''
23 = author.name 23 = author.name
24 %small.light= author.email 24 %small.light= author.email
25 .pull-right 25 .pull-right
app/views/projects/snippets/_snippet.html.haml
@@ -16,6 +16,6 @@ @@ -16,6 +16,6 @@
16 = "##{snippet.id}" 16 = "##{snippet.id}"
17 %span 17 %span
18 by 18 by
19 - = image_tag gravatar_icon(snippet.author_email), class: "avatar avatar-inline s16" 19 + = image_tag avatar_icon(snippet.author_email), class: "avatar avatar-inline s16"
20 = snippet.author_name 20 = snippet.author_name
21 %span.light #{time_ago_in_words(snippet.created_at)} ago 21 %span.light #{time_ago_in_words(snippet.created_at)} ago
app/views/projects/snippets/show.html.haml
@@ -5,7 +5,7 @@ @@ -5,7 +5,7 @@
5 = "##{@snippet.id}" 5 = "##{@snippet.id}"
6 %span.light 6 %span.light
7 by 7 by
8 - = image_tag gravatar_icon(@snippet.author_email), class: "avatar avatar-inline s16" 8 + = image_tag avatar_icon(@snippet.author_email), class: "avatar avatar-inline s16"
9 = @snippet.author_name 9 = @snippet.author_name
10 %div= render 'projects/snippets/blob' 10 %div= render 'projects/snippets/blob'
11 %div#notes= render "projects/notes/notes_with_form" 11 %div#notes= render "projects/notes/notes_with_form"
app/views/projects/team_members/_team_member.html.haml
@@ -9,7 +9,7 @@ @@ -9,7 +9,7 @@
9 &nbsp; 9 &nbsp;
10 = link_to project_team_member_path(@project, user), confirm: remove_from_project_team_message(@project, user), method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from team' do 10 = link_to project_team_member_path(@project, user), confirm: remove_from_project_team_message(@project, user), method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from team' do
11 %i.icon-minus.icon-white 11 %i.icon-minus.icon-white
12 - = image_tag gravatar_icon(user.email, 32), class: "avatar s32" 12 + = image_tag avatar_icon(user.email, 32), class: "avatar s32"
13 %p 13 %p
14 %strong= user.name 14 %strong= user.name
15 %span.cgray= user.username 15 %span.cgray= user.username
app/views/snippets/_snippet.html.haml
@@ -18,6 +18,6 @@ @@ -18,6 +18,6 @@
18 %span 18 %span
19 by 19 by
20 = link_to user_snippets_path(snippet.author) do 20 = link_to user_snippets_path(snippet.author) do
21 - = image_tag gravatar_icon(snippet.author_email), class: "avatar avatar-inline s16", alt: '' 21 + = image_tag avatar_icon(snippet.author_email), class: "avatar avatar-inline s16", alt: ''
22 = snippet.author_name 22 = snippet.author_name
23 %span.light #{time_ago_in_words(snippet.created_at)} ago 23 %span.light #{time_ago_in_words(snippet.created_at)} ago
app/views/snippets/show.html.haml
@@ -17,7 +17,7 @@ @@ -17,7 +17,7 @@
17 %span.light 17 %span.light
18 by 18 by
19 = link_to user_snippets_path(@snippet.author) do 19 = link_to user_snippets_path(@snippet.author) do
20 - = image_tag gravatar_icon(@snippet.author_email), class: "avatar avatar-inline s16" 20 + = image_tag avatar_icon(@snippet.author_email), class: "avatar avatar-inline s16"
21 = @snippet.author_name 21 = @snippet.author_name
22 22
23 .back-link 23 .back-link
app/views/snippets/user_index.html.haml
1 %h3.page-title 1 %h3.page-title
2 - = image_tag gravatar_icon(@user.email), class: "avatar s24" 2 + = image_tag avatar_icon(@user.email), class: "avatar s24"
3 = @user.name 3 = @user.name
4 %span 4 %span
5 \/ 5 \/
app/views/users/show.html.haml
1 .row 1 .row
2 .span8 2 .span8
3 %h3.page-title 3 %h3.page-title
4 - = image_tag gravatar_icon(@user.email, 90), class: "avatar s90", alt: '' 4 + = image_tag avatar_icon(@user.email, 90), class: "avatar s90", alt: ''
5 = @user.name 5 = @user.name
6 - if @user == current_user 6 - if @user == current_user
7 .pull-right 7 .pull-right
app/views/users_groups/_users_group.html.haml
1 - user = member.user 1 - user = member.user
2 - return unless user 2 - return unless user
3 %li{class: "#{dom_class(member)} js-toggle-container", id: dom_id(member)} 3 %li{class: "#{dom_class(member)} js-toggle-container", id: dom_id(member)}
4 - = image_tag gravatar_icon(user.email, 16), class: "avatar s16" 4 + = image_tag avatar_icon(user.email, 16), class: "avatar s16"
5 %strong= user.name 5 %strong= user.name
6 %span.cgray= user.username 6 %span.cgray= user.username
7 - if user == current_user 7 - if user == current_user
db/migrate/20131005191208_add_avatar_to_users.rb 0 → 100644
@@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
  1 +class AddAvatarToUsers < ActiveRecord::Migration
  2 + def change
  3 + add_column :users, :avatar, :string
  4 + end
  5 +end
@@ -11,7 +11,7 @@ @@ -11,7 +11,7 @@
11 # 11 #
12 # It's strongly recommended to check this file into your version control system. 12 # It's strongly recommended to check this file into your version control system.
13 13
14 -ActiveRecord::Schema.define(:version => 20130926081215) do 14 +ActiveRecord::Schema.define(:version => 20131005191208) do
15 15
16 create_table "deploy_keys_projects", :force => true do |t| 16 create_table "deploy_keys_projects", :force => true do |t|
17 t.integer "deploy_key_id", :null => false 17 t.integer "deploy_key_id", :null => false
@@ -283,6 +283,7 @@ ActiveRecord::Schema.define(:version =&gt; 20130926081215) do @@ -283,6 +283,7 @@ ActiveRecord::Schema.define(:version =&gt; 20130926081215) do
283 t.integer "notification_level", :default => 1, :null => false 283 t.integer "notification_level", :default => 1, :null => false
284 t.datetime "password_expires_at" 284 t.datetime "password_expires_at"
285 t.integer "created_by_id" 285 t.integer "created_by_id"
  286 + t.string "avatar"
286 end 287 end
287 288
288 add_index "users", ["admin"], :name => "index_users_on_admin" 289 add_index "users", ["admin"], :name => "index_users_on_admin"
features/profile/profile.feature
@@ -22,6 +22,11 @@ Feature: Profile @@ -22,6 +22,11 @@ Feature: Profile
22 Then I change my password 22 Then I change my password
23 And I should be redirected to sign in page 23 And I should be redirected to sign in page
24 24
  25 + Scenario: I edit my avatar
  26 + Given I visit profile page
  27 + Then I change my avatar
  28 + And I should see new avatar
  29 +
25 Scenario: My password is expired 30 Scenario: My password is expired
26 Given my password is expired 31 Given my password is expired
27 And I am not an ldap user 32 And I am not an ldap user
features/steps/profile/profile.rb
@@ -22,6 +22,17 @@ class Profile &lt; Spinach::FeatureSteps @@ -22,6 +22,17 @@ class Profile &lt; Spinach::FeatureSteps
22 @user.twitter.should == 'testtwitter' 22 @user.twitter.should == 'testtwitter'
23 end 23 end
24 24
  25 + step 'I change my avatar' do
  26 + attach_file(:user_avatar, File.join(Rails.root, 'public', 'gitlab_logo.png'))
  27 + click_button "Save changes"
  28 + @user.reload
  29 + end
  30 +
  31 + step 'I should see new avatar' do
  32 + @user.avatar.should be_instance_of AttachmentUploader
  33 + @user.avatar.url.should == "/uploads/user/avatar/#{ @user.id }/gitlab_logo.png"
  34 + end
  35 +
25 step 'I try change my password w/o old one' do 36 step 'I try change my password w/o old one' do
26 within '.update-password' do 37 within '.update-password' do
27 fill_in "user_password", with: "222333" 38 fill_in "user_password", with: "222333"
spec/helpers/application_helper_spec.rb
@@ -38,6 +38,24 @@ describe ApplicationHelper do @@ -38,6 +38,24 @@ describe ApplicationHelper do
38 current_action?(:baz, :bar, :foo).should be_true 38 current_action?(:baz, :bar, :foo).should be_true
39 end 39 end
40 end 40 end
  41 +
  42 + describe "avatar_icon" do
  43 + avatar_file_path = File.join(Rails.root, 'public', 'gitlab_logo.png')
  44 +
  45 + it "should return an url for the avatar" do
  46 + user = create(:user)
  47 + user.avatar = File.open(avatar_file_path)
  48 + user.save!
  49 + avatar_icon(user.email).to_s.should == "/uploads/user/avatar/#{ user.id }/gitlab_logo.png"
  50 + end
  51 +
  52 + it "should call gravatar_icon when no avatar is present" do
  53 + user = create(:user)
  54 + user.save!
  55 + stub!(:gravatar_icon).and_return('gravatar_method_called')
  56 + avatar_icon(user.email).to_s.should == "gravatar_method_called"
  57 + end
  58 + end
41 59
42 describe "gravatar_icon" do 60 describe "gravatar_icon" do
43 let(:user_email) { 'user@email.com' } 61 let(:user_email) { 'user@email.com' }