Commit 3fb1042d777f6b53632dacb6f853d94f830d8b18
Exists in
master
and in
4 other branches
Merge pull request #5281 from Popl7/upload_avatar_to_gitlab
avatar upload on profile page
Showing
40 changed files
with
119 additions
and
39 deletions
Show diff stats
CHANGELOG
| @@ -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 < ActiveRecord::Base | @@ -113,6 +116,8 @@ class User < 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 < ActiveRecord::Base | @@ -150,6 +155,8 @@ class User < 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 "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear | @@ -12,7 +12,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "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 "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear | @@ -17,7 +17,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "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 "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear | @@ -12,7 +12,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "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 "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear | @@ -16,7 +16,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "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 | + | ||
| 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 "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear | @@ -12,7 +12,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "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 "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear | @@ -12,7 +12,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "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 | | 9 | |
| 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
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/schema.rb
| @@ -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 => 20130926081215) do | @@ -283,6 +283,7 @@ ActiveRecord::Schema.define(:version => 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 < Spinach::FeatureSteps | @@ -22,6 +22,17 @@ class Profile < 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' } |