Commit 361a8781c939e96c9718403db5330f389f13e64b
Exists in
spb-stable
and in
3 other branches
Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce
Showing
27 changed files
with
424 additions
and
46 deletions
Show diff stats
| ... | ... | @@ -0,0 +1,26 @@ |
| 1 | +class Profiles::EmailsController < ApplicationController | |
| 2 | + layout "profile" | |
| 3 | + | |
| 4 | + def index | |
| 5 | + @primary = current_user.email | |
| 6 | + @emails = current_user.emails | |
| 7 | + end | |
| 8 | + | |
| 9 | + def create | |
| 10 | + @email = current_user.emails.new(params[:email]) | |
| 11 | + | |
| 12 | + flash[:alert] = @email.errors.full_messages.first unless @email.save | |
| 13 | + | |
| 14 | + redirect_to profile_emails_url | |
| 15 | + end | |
| 16 | + | |
| 17 | + def destroy | |
| 18 | + @email = current_user.emails.find(params[:id]) | |
| 19 | + @email.destroy | |
| 20 | + | |
| 21 | + respond_to do |format| | |
| 22 | + format.html { redirect_to profile_emails_url } | |
| 23 | + format.js { render nothing: true } | |
| 24 | + end | |
| 25 | + end | |
| 26 | +end | ... | ... |
app/helpers/commits_helper.rb
| ... | ... | @@ -122,17 +122,18 @@ module CommitsHelper |
| 122 | 122 | def commit_person_link(commit, options = {}) |
| 123 | 123 | source_name = commit.send "#{options[:source]}_name".to_sym |
| 124 | 124 | source_email = commit.send "#{options[:source]}_email".to_sym |
| 125 | + | |
| 126 | + user = User.find_for_commit(source_email, source_name) | |
| 127 | + person_name = user.nil? ? source_name : user.name | |
| 128 | + person_email = user.nil? ? source_email : user.email | |
| 129 | + | |
| 125 | 130 | text = if options[:avatar] |
| 126 | - avatar = image_tag(avatar_icon(source_email, options[:size]), class: "avatar #{"s#{options[:size]}" if options[:size]}", width: options[:size], alt: "") | |
| 127 | - %Q{#{avatar} <span class="commit-#{options[:source]}-name">#{source_name}</span>} | |
| 131 | + avatar = image_tag(avatar_icon(person_email, options[:size]), class: "avatar #{"s#{options[:size]}" if options[:size]}", width: options[:size], alt: "") | |
| 132 | + %Q{#{avatar} <span class="commit-#{options[:source]}-name">#{person_name}</span>} | |
| 128 | 133 | else |
| 129 | - source_name | |
| 134 | + person_name | |
| 130 | 135 | end |
| 131 | 136 | |
| 132 | - # Prefer email match over name match | |
| 133 | - user = User.where(email: source_email).first | |
| 134 | - user ||= User.where(name: source_name).first | |
| 135 | - | |
| 136 | 137 | options = { |
| 137 | 138 | class: "commit-#{options[:source]}-link has_tooltip", |
| 138 | 139 | data: { :'original-title' => sanitize(source_email) } | ... | ... |
app/mailers/emails/profile.rb
| ... | ... | @@ -6,6 +6,12 @@ module Emails |
| 6 | 6 | mail(to: @user.email, subject: subject("Account was created for you")) |
| 7 | 7 | end |
| 8 | 8 | |
| 9 | + def new_email_email(email_id) | |
| 10 | + @email = Email.find(email_id) | |
| 11 | + @user = @email.user | |
| 12 | + mail(to: @user.email, subject: subject("Email was added to your account")) | |
| 13 | + end | |
| 14 | + | |
| 9 | 15 | def new_ssh_key_email(key_id) |
| 10 | 16 | @key = Key.find(key_id) |
| 11 | 17 | @user = @key.user | ... | ... |
| ... | ... | @@ -0,0 +1,33 @@ |
| 1 | +# == Schema Information | |
| 2 | +# | |
| 3 | +# Table name: emails | |
| 4 | +# | |
| 5 | +# id :integer not null, primary key | |
| 6 | +# user_id :integer not null | |
| 7 | +# email :string not null | |
| 8 | +# created_at :datetime not null | |
| 9 | +class Email < ActiveRecord::Base | |
| 10 | + attr_accessible :email, :user_id | |
| 11 | + | |
| 12 | + # | |
| 13 | + # Relations | |
| 14 | + # | |
| 15 | + belongs_to :user | |
| 16 | + | |
| 17 | + # | |
| 18 | + # Validations | |
| 19 | + # | |
| 20 | + validates :user_id, presence: true | |
| 21 | + validates :email, presence: true, email: { strict_mode: true }, uniqueness: true | |
| 22 | + validate :unique_email, if: ->(email) { email.email_changed? } | |
| 23 | + | |
| 24 | + before_validation :cleanup_email | |
| 25 | + | |
| 26 | + def cleanup_email | |
| 27 | + self.email = self.email.downcase.strip | |
| 28 | + end | |
| 29 | + | |
| 30 | + def unique_email | |
| 31 | + self.errors.add(:email, 'has already been taken') if User.exists?(email: self.email) | |
| 32 | + end | |
| 33 | +end | |
| 0 | 34 | \ No newline at end of file | ... | ... |
app/models/user.rb
| ... | ... | @@ -78,6 +78,7 @@ class User < ActiveRecord::Base |
| 78 | 78 | |
| 79 | 79 | # Profile |
| 80 | 80 | has_many :keys, dependent: :destroy |
| 81 | + has_many :emails, dependent: :destroy | |
| 81 | 82 | |
| 82 | 83 | # Groups |
| 83 | 84 | has_many :users_groups, dependent: :destroy |
| ... | ... | @@ -116,6 +117,7 @@ class User < ActiveRecord::Base |
| 116 | 117 | validates :notification_level, inclusion: { in: Notification.notification_levels }, presence: true |
| 117 | 118 | validate :namespace_uniq, if: ->(user) { user.username_changed? } |
| 118 | 119 | validate :avatar_type, if: ->(user) { user.avatar_changed? } |
| 120 | + validate :unique_email, if: ->(user) { user.email_changed? } | |
| 119 | 121 | validates :avatar, file_size: { maximum: 100.kilobytes.to_i } |
| 120 | 122 | |
| 121 | 123 | before_validation :generate_password, on: :create |
| ... | ... | @@ -183,6 +185,13 @@ class User < ActiveRecord::Base |
| 183 | 185 | where(conditions).first |
| 184 | 186 | end |
| 185 | 187 | end |
| 188 | + | |
| 189 | + def find_for_commit(email, name) | |
| 190 | + # Prefer email match over name match | |
| 191 | + User.where(email: email).first || | |
| 192 | + User.joins(:emails).where(emails: { email: email }).first || | |
| 193 | + User.where(name: name).first | |
| 194 | + end | |
| 186 | 195 | |
| 187 | 196 | def filter filter_name |
| 188 | 197 | case filter_name |
| ... | ... | @@ -250,6 +259,10 @@ class User < ActiveRecord::Base |
| 250 | 259 | end |
| 251 | 260 | end |
| 252 | 261 | |
| 262 | + def unique_email | |
| 263 | + self.errors.add(:email, 'has already been taken') if Email.exists?(email: self.email) | |
| 264 | + end | |
| 265 | + | |
| 253 | 266 | # Groups user has access to |
| 254 | 267 | def authorized_groups |
| 255 | 268 | @authorized_groups ||= begin | ... | ... |
app/services/git_push_service.rb
| ... | ... | @@ -188,8 +188,6 @@ class GitPushService |
| 188 | 188 | end |
| 189 | 189 | |
| 190 | 190 | def commit_user commit |
| 191 | - User.where(email: commit.author_email).first || | |
| 192 | - User.where(name: commit.author_name).first || | |
| 193 | - user | |
| 191 | + User.find_for_commit(commit.author_email, commit.author_name) || user | |
| 194 | 192 | end |
| 195 | 193 | end | ... | ... |
app/services/notification_service.rb
| ... | ... | @@ -17,6 +17,13 @@ class NotificationService |
| 17 | 17 | end |
| 18 | 18 | end |
| 19 | 19 | |
| 20 | + # Always notify user about email added to profile | |
| 21 | + def new_email(email) | |
| 22 | + if email.user | |
| 23 | + mailer.new_email_email(email.id) | |
| 24 | + end | |
| 25 | + end | |
| 26 | + | |
| 20 | 27 | # When create an issue we should send next emails: |
| 21 | 28 | # |
| 22 | 29 | # * issue assignee if their notification level is not Disabled | ... | ... |
app/views/layouts/nav/_profile.html.haml
| ... | ... | @@ -4,6 +4,10 @@ |
| 4 | 4 | %i.icon-home |
| 5 | 5 | = nav_link(controller: :accounts) do |
| 6 | 6 | = link_to "Account", profile_account_path |
| 7 | + = nav_link(controller: :emails) do | |
| 8 | + = link_to profile_emails_path do | |
| 9 | + Emails | |
| 10 | + %span.count= current_user.emails.count + 1 | |
| 7 | 11 | - unless current_user.ldap_user? |
| 8 | 12 | = nav_link(controller: :passwords) do |
| 9 | 13 | = link_to "Password", edit_profile_password_path | ... | ... |
| ... | ... | @@ -0,0 +1,29 @@ |
| 1 | +%h3.page-title | |
| 2 | + My Email Addresses | |
| 3 | +%p.light | |
| 4 | + Your | |
| 5 | + %b Primary Email | |
| 6 | + will be used for account notifications, avatar detection and web based operations, such as edits and merges. All email addresses will be used to identify your commits. | |
| 7 | + | |
| 8 | +.ui-box | |
| 9 | + .title | |
| 10 | + Emails (#{@emails.count + 1}) | |
| 11 | + %ul.well-list#emails-table | |
| 12 | + %li | |
| 13 | + %strong= @primary | |
| 14 | + %span.label.label-success Primary Email | |
| 15 | + - @emails.each do |email| | |
| 16 | + %li | |
| 17 | + %strong= email.email | |
| 18 | + %span.cgray | |
| 19 | + added #{time_ago_with_tooltip(email.created_at)} | |
| 20 | + = link_to 'Remove', profile_email_path(email), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-small btn-remove pull-right' | |
| 21 | + | |
| 22 | +%h3.page-title Add Email Address | |
| 23 | += form_for 'email', url: profile_emails_path, html: { class: 'form-horizontal' } do |f| | |
| 24 | + .form-group | |
| 25 | + = f.label :email, class: 'control-label' | |
| 26 | + .col-sm-10 | |
| 27 | + = f.text_field :email, class: 'form-control' | |
| 28 | + .form-actions | |
| 29 | + = f.submit 'Add', class: 'btn btn-create' | |
| 0 | 30 | \ No newline at end of file | ... | ... |
config/routes.rb
| ... | ... | @@ -0,0 +1,13 @@ |
| 1 | +class CreateEmails < ActiveRecord::Migration | |
| 2 | + def change | |
| 3 | + create_table :emails do |t| | |
| 4 | + t.integer :user_id, null: false | |
| 5 | + t.string :email, null: false | |
| 6 | + | |
| 7 | + t.timestamps | |
| 8 | + end | |
| 9 | + | |
| 10 | + add_index :emails, :user_id | |
| 11 | + add_index :emails, :email, unique: true | |
| 12 | + end | |
| 13 | +end | ... | ... |
db/schema.rb
| ... | ... | @@ -11,7 +11,7 @@ |
| 11 | 11 | # |
| 12 | 12 | # It's strongly recommended that you check this file into your version control system. |
| 13 | 13 | |
| 14 | -ActiveRecord::Schema.define(version: 20140127170938) do | |
| 14 | +ActiveRecord::Schema.define(version: 20140209025651) do | |
| 15 | 15 | |
| 16 | 16 | create_table "broadcast_messages", force: true do |t| |
| 17 | 17 | t.text "message", null: false |
| ... | ... | @@ -33,6 +33,16 @@ ActiveRecord::Schema.define(version: 20140127170938) do |
| 33 | 33 | |
| 34 | 34 | add_index "deploy_keys_projects", ["project_id"], name: "index_deploy_keys_projects_on_project_id", using: :btree |
| 35 | 35 | |
| 36 | + create_table "emails", force: true do |t| | |
| 37 | + t.integer "user_id", null: false | |
| 38 | + t.string "email", null: false | |
| 39 | + t.datetime "created_at" | |
| 40 | + t.datetime "updated_at" | |
| 41 | + end | |
| 42 | + | |
| 43 | + add_index "emails", ["email"], name: "index_emails_on_email", unique: true, using: :btree | |
| 44 | + add_index "emails", ["user_id"], name: "index_emails_on_user_id", using: :btree | |
| 45 | + | |
| 36 | 46 | create_table "events", force: true do |t| |
| 37 | 47 | t.string "target_type" |
| 38 | 48 | t.integer "target_id" |
| ... | ... | @@ -66,8 +76,8 @@ ActiveRecord::Schema.define(version: 20140127170938) do |
| 66 | 76 | t.integer "assignee_id" |
| 67 | 77 | t.integer "author_id" |
| 68 | 78 | t.integer "project_id" |
| 69 | - t.datetime "created_at" | |
| 70 | - t.datetime "updated_at" | |
| 79 | + t.datetime "created_at", null: false | |
| 80 | + t.datetime "updated_at", null: false | |
| 71 | 81 | t.integer "position", default: 0 |
| 72 | 82 | t.string "branch_name" |
| 73 | 83 | t.text "description" |
| ... | ... | @@ -85,8 +95,8 @@ ActiveRecord::Schema.define(version: 20140127170938) do |
| 85 | 95 | |
| 86 | 96 | create_table "keys", force: true do |t| |
| 87 | 97 | t.integer "user_id" |
| 88 | - t.datetime "created_at" | |
| 89 | - t.datetime "updated_at" | |
| 98 | + t.datetime "created_at", null: false | |
| 99 | + t.datetime "updated_at", null: false | |
| 90 | 100 | t.text "key" |
| 91 | 101 | t.string "title" |
| 92 | 102 | t.string "type" |
| ... | ... | @@ -111,8 +121,8 @@ ActiveRecord::Schema.define(version: 20140127170938) do |
| 111 | 121 | t.integer "author_id" |
| 112 | 122 | t.integer "assignee_id" |
| 113 | 123 | t.string "title" |
| 114 | - t.datetime "created_at" | |
| 115 | - t.datetime "updated_at" | |
| 124 | + t.datetime "created_at", null: false | |
| 125 | + t.datetime "updated_at", null: false | |
| 116 | 126 | t.integer "milestone_id" |
| 117 | 127 | t.string "state" |
| 118 | 128 | t.string "merge_status" |
| ... | ... | @@ -164,8 +174,8 @@ ActiveRecord::Schema.define(version: 20140127170938) do |
| 164 | 174 | t.text "note" |
| 165 | 175 | t.string "noteable_type" |
| 166 | 176 | t.integer "author_id" |
| 167 | - t.datetime "created_at" | |
| 168 | - t.datetime "updated_at" | |
| 177 | + t.datetime "created_at", null: false | |
| 178 | + t.datetime "updated_at", null: false | |
| 169 | 179 | t.integer "project_id" |
| 170 | 180 | t.string "attachment" |
| 171 | 181 | t.string "line_code" |
| ... | ... | @@ -187,8 +197,8 @@ ActiveRecord::Schema.define(version: 20140127170938) do |
| 187 | 197 | t.string "name" |
| 188 | 198 | t.string "path" |
| 189 | 199 | t.text "description" |
| 190 | - t.datetime "created_at" | |
| 191 | - t.datetime "updated_at" | |
| 200 | + t.datetime "created_at", null: false | |
| 201 | + t.datetime "updated_at", null: false | |
| 192 | 202 | t.integer "creator_id" |
| 193 | 203 | t.boolean "issues_enabled", default: true, null: false |
| 194 | 204 | t.boolean "wall_enabled", default: true, null: false |
| ... | ... | @@ -239,8 +249,8 @@ ActiveRecord::Schema.define(version: 20140127170938) do |
| 239 | 249 | t.text "content", limit: 2147483647 |
| 240 | 250 | t.integer "author_id", null: false |
| 241 | 251 | t.integer "project_id" |
| 242 | - t.datetime "created_at" | |
| 243 | - t.datetime "updated_at" | |
| 252 | + t.datetime "created_at", null: false | |
| 253 | + t.datetime "updated_at", null: false | |
| 244 | 254 | t.string "file_name" |
| 245 | 255 | t.datetime "expires_at" |
| 246 | 256 | t.boolean "private", default: true, null: false |
| ... | ... | @@ -262,42 +272,45 @@ ActiveRecord::Schema.define(version: 20140127170938) do |
| 262 | 272 | t.datetime "created_at" |
| 263 | 273 | end |
| 264 | 274 | |
| 275 | + add_index "taggings", ["tag_id"], name: "index_taggings_on_tag_id", using: :btree | |
| 276 | + add_index "taggings", ["taggable_id", "taggable_type", "context"], name: "index_taggings_on_taggable_id_and_taggable_type_and_context", using: :btree | |
| 277 | + | |
| 265 | 278 | create_table "tags", force: true do |t| |
| 266 | 279 | t.string "name" |
| 267 | 280 | end |
| 268 | 281 | |
| 269 | 282 | create_table "users", force: true do |t| |
| 270 | - t.string "email", default: "", null: false | |
| 271 | - t.string "encrypted_password", limit: 128, default: "", null: false | |
| 283 | + t.string "email", default: "", null: false | |
| 284 | + t.string "encrypted_password", default: "", null: false | |
| 272 | 285 | t.string "reset_password_token" |
| 273 | 286 | t.datetime "reset_password_sent_at" |
| 274 | 287 | t.datetime "remember_created_at" |
| 275 | - t.integer "sign_in_count", default: 0 | |
| 288 | + t.integer "sign_in_count", default: 0 | |
| 276 | 289 | t.datetime "current_sign_in_at" |
| 277 | 290 | t.datetime "last_sign_in_at" |
| 278 | 291 | t.string "current_sign_in_ip" |
| 279 | 292 | t.string "last_sign_in_ip" |
| 280 | - t.datetime "created_at" | |
| 281 | - t.datetime "updated_at" | |
| 293 | + t.datetime "created_at", null: false | |
| 294 | + t.datetime "updated_at", null: false | |
| 282 | 295 | t.string "name" |
| 283 | - t.boolean "admin", default: false, null: false | |
| 284 | - t.integer "projects_limit", default: 10 | |
| 285 | - t.string "skype", default: "", null: false | |
| 286 | - t.string "linkedin", default: "", null: false | |
| 287 | - t.string "twitter", default: "", null: false | |
| 296 | + t.boolean "admin", default: false, null: false | |
| 297 | + t.integer "projects_limit", default: 10 | |
| 298 | + t.string "skype", default: "", null: false | |
| 299 | + t.string "linkedin", default: "", null: false | |
| 300 | + t.string "twitter", default: "", null: false | |
| 288 | 301 | t.string "authentication_token" |
| 289 | - t.integer "theme_id", default: 1, null: false | |
| 302 | + t.integer "theme_id", default: 1, null: false | |
| 290 | 303 | t.string "bio" |
| 291 | - t.integer "failed_attempts", default: 0 | |
| 304 | + t.integer "failed_attempts", default: 0 | |
| 292 | 305 | t.datetime "locked_at" |
| 293 | 306 | t.string "extern_uid" |
| 294 | 307 | t.string "provider" |
| 295 | 308 | t.string "username" |
| 296 | - t.boolean "can_create_group", default: true, null: false | |
| 297 | - t.boolean "can_create_team", default: true, null: false | |
| 309 | + t.boolean "can_create_group", default: true, null: false | |
| 310 | + t.boolean "can_create_team", default: true, null: false | |
| 298 | 311 | t.string "state" |
| 299 | - t.integer "color_scheme_id", default: 1, null: false | |
| 300 | - t.integer "notification_level", default: 1, null: false | |
| 312 | + t.integer "color_scheme_id", default: 1, null: false | |
| 313 | + t.integer "notification_level", default: 1, null: false | |
| 301 | 314 | t.datetime "password_expires_at" |
| 302 | 315 | t.integer "created_by_id" |
| 303 | 316 | t.string "avatar" |
| ... | ... | @@ -305,14 +318,15 @@ ActiveRecord::Schema.define(version: 20140127170938) do |
| 305 | 318 | t.datetime "confirmed_at" |
| 306 | 319 | t.datetime "confirmation_sent_at" |
| 307 | 320 | t.string "unconfirmed_email" |
| 308 | - t.boolean "hide_no_ssh_key", default: false | |
| 309 | - t.string "website_url", default: "", null: false | |
| 321 | + t.boolean "hide_no_ssh_key", default: false | |
| 322 | + t.string "website_url", default: "", null: false | |
| 310 | 323 | end |
| 311 | 324 | |
| 312 | 325 | add_index "users", ["admin"], name: "index_users_on_admin", using: :btree |
| 313 | 326 | add_index "users", ["authentication_token"], name: "index_users_on_authentication_token", unique: true, using: :btree |
| 314 | 327 | add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree |
| 315 | 328 | add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree |
| 329 | + add_index "users", ["extern_uid", "provider"], name: "index_users_on_extern_uid_and_provider", unique: true, using: :btree | |
| 316 | 330 | add_index "users", ["name"], name: "index_users_on_name", using: :btree |
| 317 | 331 | add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree |
| 318 | 332 | add_index "users", ["username"], name: "index_users_on_username", using: :btree |
| ... | ... | @@ -331,8 +345,8 @@ ActiveRecord::Schema.define(version: 20140127170938) do |
| 331 | 345 | create_table "users_projects", force: true do |t| |
| 332 | 346 | t.integer "user_id", null: false |
| 333 | 347 | t.integer "project_id", null: false |
| 334 | - t.datetime "created_at" | |
| 335 | - t.datetime "updated_at" | |
| 348 | + t.datetime "created_at", null: false | |
| 349 | + t.datetime "updated_at", null: false | |
| 336 | 350 | t.integer "project_access", default: 0, null: false |
| 337 | 351 | t.integer "notification_level", default: 3, null: false |
| 338 | 352 | end |
| ... | ... | @@ -344,8 +358,8 @@ ActiveRecord::Schema.define(version: 20140127170938) do |
| 344 | 358 | create_table "web_hooks", force: true do |t| |
| 345 | 359 | t.string "url" |
| 346 | 360 | t.integer "project_id" |
| 347 | - t.datetime "created_at" | |
| 348 | - t.datetime "updated_at" | |
| 361 | + t.datetime "created_at", null: false | |
| 362 | + t.datetime "updated_at", null: false | |
| 349 | 363 | t.string "type", default: "ProjectHook" |
| 350 | 364 | t.integer "service_id" |
| 351 | 365 | t.boolean "push_events", default: true, null: false | ... | ... |
| ... | ... | @@ -0,0 +1,25 @@ |
| 1 | +Feature: Profile Emails | |
| 2 | + Background: | |
| 3 | + Given I sign in as a user | |
| 4 | + And I visit profile emails page | |
| 5 | + | |
| 6 | + Scenario: I should see emails | |
| 7 | + Then I should see my emails | |
| 8 | + | |
| 9 | + Scenario: Add new email | |
| 10 | + Given I submit new email "my@email.com" | |
| 11 | + Then I should see new email "my@email.com" | |
| 12 | + And I should see my emails | |
| 13 | + | |
| 14 | + Scenario: Add duplicate email | |
| 15 | + Given I submit duplicate email @user.email | |
| 16 | + Then I should not have @user.email added | |
| 17 | + And I should see my emails | |
| 18 | + | |
| 19 | + Scenario: Remove email | |
| 20 | + Given I submit new email "my@email.com" | |
| 21 | + Then I should see new email "my@email.com" | |
| 22 | + And I should see my emails | |
| 23 | + Then I click link "Remove" for "my@email.com" | |
| 24 | + Then I should not see email "my@email.com" | |
| 25 | + And I should see my emails | ... | ... |
| ... | ... | @@ -0,0 +1,14 @@ |
| 1 | +Feature: Project Browse Commits User Lookup | |
| 2 | + Background: | |
| 3 | + Given I sign in as a user | |
| 4 | + And I own a project | |
| 5 | + And I have the user that authored the commits | |
| 6 | + And I visit my project's commits page | |
| 7 | + | |
| 8 | + Scenario: I browse commit from list | |
| 9 | + Given I click on commit link | |
| 10 | + Then I see commit info | |
| 11 | + | |
| 12 | + Scenario: I browse another commit from list | |
| 13 | + Given I click on another commit link | |
| 14 | + Then I see other commit info | |
| 0 | 15 | \ No newline at end of file | ... | ... |
| ... | ... | @@ -0,0 +1,48 @@ |
| 1 | +class ProfileEmails < Spinach::FeatureSteps | |
| 2 | + include SharedAuthentication | |
| 3 | + | |
| 4 | + Then 'I visit profile emails page' do | |
| 5 | + visit profile_emails_path | |
| 6 | + end | |
| 7 | + | |
| 8 | + Then 'I should see my emails' do | |
| 9 | + page.should have_content(@user.email) | |
| 10 | + @user.emails.each do |email| | |
| 11 | + page.should have_content(email.email) | |
| 12 | + end | |
| 13 | + end | |
| 14 | + | |
| 15 | + And 'I submit new email "my@email.com"' do | |
| 16 | + fill_in "email_email", with: "my@email.com" | |
| 17 | + click_button "Add" | |
| 18 | + end | |
| 19 | + | |
| 20 | + Then 'I should see new email "my@email.com"' do | |
| 21 | + email = @user.emails.find_by(email: "my@email.com") | |
| 22 | + email.should_not be_nil | |
| 23 | + page.should have_content("my@email.com") | |
| 24 | + end | |
| 25 | + | |
| 26 | + Then 'I should not see email "my@email.com"' do | |
| 27 | + email = @user.emails.find_by(email: "my@email.com") | |
| 28 | + email.should be_nil | |
| 29 | + page.should_not have_content("my@email.com") | |
| 30 | + end | |
| 31 | + | |
| 32 | + Then 'I click link "Remove" for "my@email.com"' do | |
| 33 | + # there should only be one remove button at this time | |
| 34 | + click_link "Remove" | |
| 35 | + # force these to reload as they have been cached | |
| 36 | + @user.emails.reload | |
| 37 | + end | |
| 38 | + | |
| 39 | + And 'I submit duplicate email @user.email' do | |
| 40 | + fill_in "email_email", with: @user.email | |
| 41 | + click_button "Add" | |
| 42 | + end | |
| 43 | + | |
| 44 | + Then 'I should not have @user.email added' do | |
| 45 | + email = @user.emails.find_by(email: @user.email) | |
| 46 | + email.should be_nil | |
| 47 | + end | |
| 48 | +end | ... | ... |
features/steps/project/project_browse_commits_user_lookup.rb
0 → 100644
| ... | ... | @@ -0,0 +1,35 @@ |
| 1 | +class ProjectBrowseCommitsUserLookup < Spinach::FeatureSteps | |
| 2 | + include SharedAuthentication | |
| 3 | + include SharedProject | |
| 4 | + include SharedPaths | |
| 5 | + | |
| 6 | + Given 'I have the user that authored the commits' do | |
| 7 | + @user = create(:user, email: 'dmitriy.zaporozhets@gmail.com') | |
| 8 | + create(:email, { user: @user, email: 'dzaporozhets@sphereconsultinginc.com' }) | |
| 9 | + end | |
| 10 | + | |
| 11 | + Given 'I click on commit link' do | |
| 12 | + visit project_commit_path(@project, ValidCommit::ID) | |
| 13 | + end | |
| 14 | + | |
| 15 | + Given 'I click on another commit link' do | |
| 16 | + visit project_commit_path(@project, ValidCommitWithAltEmail::ID) | |
| 17 | + end | |
| 18 | + | |
| 19 | + Then 'I see commit info' do | |
| 20 | + page.should have_content ValidCommit::MESSAGE | |
| 21 | + check_author_link(ValidCommit::AUTHOR_EMAIL) | |
| 22 | + end | |
| 23 | + | |
| 24 | + Then 'I see other commit info' do | |
| 25 | + page.should have_content ValidCommitWithAltEmail::MESSAGE | |
| 26 | + check_author_link(ValidCommitWithAltEmail::AUTHOR_EMAIL) | |
| 27 | + end | |
| 28 | + | |
| 29 | + def check_author_link(email) | |
| 30 | + author_link = find('.commit-author-link') | |
| 31 | + author_link['href'].should == user_path(@user) | |
| 32 | + author_link['data-original-title'].should == email | |
| 33 | + find('.commit-author-name').text.should == @user.name | |
| 34 | + end | |
| 35 | +end | ... | ... |
features/support/env.rb
| ... | ... | @@ -15,7 +15,7 @@ require 'spinach/capybara' |
| 15 | 15 | require 'sidekiq/testing/inline' |
| 16 | 16 | |
| 17 | 17 | |
| 18 | -%w(valid_commit big_commits select2_helper test_env).each do |f| | |
| 18 | +%w(valid_commit valid_commit_with_alt_email big_commits select2_helper test_env).each do |f| | |
| 19 | 19 | require Rails.root.join('spec', 'support', f) |
| 20 | 20 | end |
| 21 | 21 | ... | ... |
spec/factories.rb
| ... | ... | @@ -219,6 +219,19 @@ FactoryGirl.define do |
| 219 | 219 | end |
| 220 | 220 | end |
| 221 | 221 | end |
| 222 | + | |
| 223 | + factory :email do | |
| 224 | + user | |
| 225 | + email do | |
| 226 | + Faker::Internet.email('alias') | |
| 227 | + end | |
| 228 | + | |
| 229 | + factory :another_email do | |
| 230 | + email do | |
| 231 | + Faker::Internet.email('another.alias') | |
| 232 | + end | |
| 233 | + end | |
| 234 | + end | |
| 222 | 235 | |
| 223 | 236 | factory :milestone do |
| 224 | 237 | title | ... | ... |
spec/mailers/notify_spec.rb
| ... | ... | @@ -90,6 +90,28 @@ describe Notify do |
| 90 | 90 | end |
| 91 | 91 | end |
| 92 | 92 | |
| 93 | + describe 'user added email' do | |
| 94 | + let(:email) { create(:email) } | |
| 95 | + | |
| 96 | + subject { Notify.new_email_email(email.id) } | |
| 97 | + | |
| 98 | + it 'is sent to the new user' do | |
| 99 | + should deliver_to email.user.email | |
| 100 | + end | |
| 101 | + | |
| 102 | + it 'has the correct subject' do | |
| 103 | + should have_subject /^gitlab \| Email was added to your account$/i | |
| 104 | + end | |
| 105 | + | |
| 106 | + it 'contains the new email address' do | |
| 107 | + should have_body_text /#{email.email}/ | |
| 108 | + end | |
| 109 | + | |
| 110 | + it 'includes a link to emails page' do | |
| 111 | + should have_body_text /#{profile_emails_path}/ | |
| 112 | + end | |
| 113 | + end | |
| 114 | + | |
| 93 | 115 | context 'for a project' do |
| 94 | 116 | describe 'items that are assignable, the email' do |
| 95 | 117 | let(:assignee) { create(:user, email: 'assignee@example.com') } | ... | ... |
| ... | ... | @@ -0,0 +1,17 @@ |
| 1 | +require 'spec_helper' | |
| 2 | + | |
| 3 | +describe EmailObserver do | |
| 4 | + let(:email) { create(:email) } | |
| 5 | + | |
| 6 | + before { subject.stub(notification: double('NotificationService').as_null_object) } | |
| 7 | + | |
| 8 | + subject { EmailObserver.instance } | |
| 9 | + | |
| 10 | + describe '#after_create' do | |
| 11 | + it 'trigger notification to send emails' do | |
| 12 | + subject.should_receive(:notification) | |
| 13 | + | |
| 14 | + subject.after_create(email) | |
| 15 | + end | |
| 16 | + end | |
| 17 | +end | ... | ... |
spec/routing/routing_spec.rb
| ... | ... | @@ -183,6 +183,23 @@ describe Profiles::KeysController, "routing" do |
| 183 | 183 | end |
| 184 | 184 | end |
| 185 | 185 | |
| 186 | +# emails GET /emails(.:format) emails#index | |
| 187 | +# POST /keys(.:format) emails#create | |
| 188 | +# DELETE /keys/:id(.:format) keys#destroy | |
| 189 | +describe Profiles::EmailsController, "routing" do | |
| 190 | + it "to #index" do | |
| 191 | + get("/profile/emails").should route_to('profiles/emails#index') | |
| 192 | + end | |
| 193 | + | |
| 194 | + it "to #create" do | |
| 195 | + post("/profile/emails").should route_to('profiles/emails#create') | |
| 196 | + end | |
| 197 | + | |
| 198 | + it "to #destroy" do | |
| 199 | + delete("/profile/emails/1").should route_to('profiles/emails#destroy', id: '1') | |
| 200 | + end | |
| 201 | +end | |
| 202 | + | |
| 186 | 203 | # profile_avatar DELETE /profile/avatar(.:format) profiles/avatars#destroy |
| 187 | 204 | describe Profiles::AvatarsController, "routing" do |
| 188 | 205 | it "to #destroy" do | ... | ... |
spec/services/notification_service_spec.rb
| ... | ... | @@ -16,6 +16,19 @@ describe NotificationService do |
| 16 | 16 | end |
| 17 | 17 | end |
| 18 | 18 | |
| 19 | + describe 'Email' do | |
| 20 | + describe :new_email do | |
| 21 | + let(:email) { create(:email) } | |
| 22 | + | |
| 23 | + it { notification.new_email(email).should be_true } | |
| 24 | + | |
| 25 | + it 'should send email to email owner' do | |
| 26 | + Notify.should_receive(:new_email_email).with(email.id) | |
| 27 | + notification.new_email(email) | |
| 28 | + end | |
| 29 | + end | |
| 30 | + end | |
| 31 | + | |
| 19 | 32 | describe 'Notes' do |
| 20 | 33 | context 'issue note' do |
| 21 | 34 | let(:issue) { create(:issue, assignee: create(:user)) } | ... | ... |
spec/support/valid_commit.rb
| ... | ... | @@ -2,6 +2,7 @@ module ValidCommit |
| 2 | 2 | ID = "8470d70da67355c9c009e4401746b1d5410af2e3" |
| 3 | 3 | MESSAGE = "notes controller refactored" |
| 4 | 4 | AUTHOR_FULL_NAME = "Dmitriy Zaporozhets" |
| 5 | + AUTHOR_EMAIL = "dmitriy.zaporozhets@gmail.com" | |
| 5 | 6 | |
| 6 | 7 | FILES = [".foreman", ".gitignore", ".rails_footnotes", ".rspec", ".travis.yml", "CHANGELOG", "Gemfile", "Gemfile.lock", "LICENSE", "Procfile", "Procfile.production", "README.md", "Rakefile", "VERSION", "app", "config.ru", "config", "db", "doc", "lib", "log", "public", "resque.sh", "script", "spec", "vendor"] |
| 7 | 8 | FILES_COUNT = 26 | ... | ... |