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 @@ | @@ -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,17 +122,18 @@ module CommitsHelper | ||
| 122 | def commit_person_link(commit, options = {}) | 122 | def commit_person_link(commit, options = {}) |
| 123 | source_name = commit.send "#{options[:source]}_name".to_sym | 123 | source_name = commit.send "#{options[:source]}_name".to_sym |
| 124 | source_email = commit.send "#{options[:source]}_email".to_sym | 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 | text = if options[:avatar] | 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 | else | 133 | else |
| 129 | - source_name | 134 | + person_name |
| 130 | end | 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 | options = { | 137 | options = { |
| 137 | class: "commit-#{options[:source]}-link has_tooltip", | 138 | class: "commit-#{options[:source]}-link has_tooltip", |
| 138 | data: { :'original-title' => sanitize(source_email) } | 139 | data: { :'original-title' => sanitize(source_email) } |
app/mailers/emails/profile.rb
| @@ -6,6 +6,12 @@ module Emails | @@ -6,6 +6,12 @@ module Emails | ||
| 6 | mail(to: @user.email, subject: subject("Account was created for you")) | 6 | mail(to: @user.email, subject: subject("Account was created for you")) |
| 7 | end | 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 | def new_ssh_key_email(key_id) | 15 | def new_ssh_key_email(key_id) |
| 10 | @key = Key.find(key_id) | 16 | @key = Key.find(key_id) |
| 11 | @user = @key.user | 17 | @user = @key.user |
| @@ -0,0 +1,33 @@ | @@ -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 | \ No newline at end of file | 34 | \ No newline at end of file |
app/models/user.rb
| @@ -78,6 +78,7 @@ class User < ActiveRecord::Base | @@ -78,6 +78,7 @@ class User < ActiveRecord::Base | ||
| 78 | 78 | ||
| 79 | # Profile | 79 | # Profile |
| 80 | has_many :keys, dependent: :destroy | 80 | has_many :keys, dependent: :destroy |
| 81 | + has_many :emails, dependent: :destroy | ||
| 81 | 82 | ||
| 82 | # Groups | 83 | # Groups |
| 83 | has_many :users_groups, dependent: :destroy | 84 | has_many :users_groups, dependent: :destroy |
| @@ -116,6 +117,7 @@ class User < ActiveRecord::Base | @@ -116,6 +117,7 @@ class User < ActiveRecord::Base | ||
| 116 | validates :notification_level, inclusion: { in: Notification.notification_levels }, presence: true | 117 | validates :notification_level, inclusion: { in: Notification.notification_levels }, presence: true |
| 117 | validate :namespace_uniq, if: ->(user) { user.username_changed? } | 118 | validate :namespace_uniq, if: ->(user) { user.username_changed? } |
| 118 | validate :avatar_type, if: ->(user) { user.avatar_changed? } | 119 | validate :avatar_type, if: ->(user) { user.avatar_changed? } |
| 120 | + validate :unique_email, if: ->(user) { user.email_changed? } | ||
| 119 | validates :avatar, file_size: { maximum: 100.kilobytes.to_i } | 121 | validates :avatar, file_size: { maximum: 100.kilobytes.to_i } |
| 120 | 122 | ||
| 121 | before_validation :generate_password, on: :create | 123 | before_validation :generate_password, on: :create |
| @@ -183,6 +185,13 @@ class User < ActiveRecord::Base | @@ -183,6 +185,13 @@ class User < ActiveRecord::Base | ||
| 183 | where(conditions).first | 185 | where(conditions).first |
| 184 | end | 186 | end |
| 185 | end | 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 | def filter filter_name | 196 | def filter filter_name |
| 188 | case filter_name | 197 | case filter_name |
| @@ -250,6 +259,10 @@ class User < ActiveRecord::Base | @@ -250,6 +259,10 @@ class User < ActiveRecord::Base | ||
| 250 | end | 259 | end |
| 251 | end | 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 | # Groups user has access to | 266 | # Groups user has access to |
| 254 | def authorized_groups | 267 | def authorized_groups |
| 255 | @authorized_groups ||= begin | 268 | @authorized_groups ||= begin |
app/services/git_push_service.rb
| @@ -188,8 +188,6 @@ class GitPushService | @@ -188,8 +188,6 @@ class GitPushService | ||
| 188 | end | 188 | end |
| 189 | 189 | ||
| 190 | def commit_user commit | 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 | end | 192 | end |
| 195 | end | 193 | end |
app/services/notification_service.rb
| @@ -17,6 +17,13 @@ class NotificationService | @@ -17,6 +17,13 @@ class NotificationService | ||
| 17 | end | 17 | end |
| 18 | end | 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 | # When create an issue we should send next emails: | 27 | # When create an issue we should send next emails: |
| 21 | # | 28 | # |
| 22 | # * issue assignee if their notification level is not Disabled | 29 | # * issue assignee if their notification level is not Disabled |
app/views/layouts/nav/_profile.html.haml
| @@ -4,6 +4,10 @@ | @@ -4,6 +4,10 @@ | ||
| 4 | %i.icon-home | 4 | %i.icon-home |
| 5 | = nav_link(controller: :accounts) do | 5 | = nav_link(controller: :accounts) do |
| 6 | = link_to "Account", profile_account_path | 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 | - unless current_user.ldap_user? | 11 | - unless current_user.ldap_user? |
| 8 | = nav_link(controller: :passwords) do | 12 | = nav_link(controller: :passwords) do |
| 9 | = link_to "Password", edit_profile_password_path | 13 | = link_to "Password", edit_profile_password_path |
| @@ -0,0 +1,29 @@ | @@ -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 | \ No newline at end of file | 30 | \ No newline at end of file |
config/routes.rb
| @@ -124,6 +124,7 @@ Gitlab::Application.routes.draw do | @@ -124,6 +124,7 @@ Gitlab::Application.routes.draw do | ||
| 124 | end | 124 | end |
| 125 | end | 125 | end |
| 126 | resources :keys | 126 | resources :keys |
| 127 | + resources :emails, only: [:index, :create, :destroy] | ||
| 127 | resources :groups, only: [:index] do | 128 | resources :groups, only: [:index] do |
| 128 | member do | 129 | member do |
| 129 | delete :leave | 130 | delete :leave |
| @@ -0,0 +1,13 @@ | @@ -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,7 +11,7 @@ | ||
| 11 | # | 11 | # |
| 12 | # It's strongly recommended that you check this file into your version control system. | 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 | create_table "broadcast_messages", force: true do |t| | 16 | create_table "broadcast_messages", force: true do |t| |
| 17 | t.text "message", null: false | 17 | t.text "message", null: false |
| @@ -33,6 +33,16 @@ ActiveRecord::Schema.define(version: 20140127170938) do | @@ -33,6 +33,16 @@ ActiveRecord::Schema.define(version: 20140127170938) do | ||
| 33 | 33 | ||
| 34 | add_index "deploy_keys_projects", ["project_id"], name: "index_deploy_keys_projects_on_project_id", using: :btree | 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 | create_table "events", force: true do |t| | 46 | create_table "events", force: true do |t| |
| 37 | t.string "target_type" | 47 | t.string "target_type" |
| 38 | t.integer "target_id" | 48 | t.integer "target_id" |
| @@ -66,8 +76,8 @@ ActiveRecord::Schema.define(version: 20140127170938) do | @@ -66,8 +76,8 @@ ActiveRecord::Schema.define(version: 20140127170938) do | ||
| 66 | t.integer "assignee_id" | 76 | t.integer "assignee_id" |
| 67 | t.integer "author_id" | 77 | t.integer "author_id" |
| 68 | t.integer "project_id" | 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 | t.integer "position", default: 0 | 81 | t.integer "position", default: 0 |
| 72 | t.string "branch_name" | 82 | t.string "branch_name" |
| 73 | t.text "description" | 83 | t.text "description" |
| @@ -85,8 +95,8 @@ ActiveRecord::Schema.define(version: 20140127170938) do | @@ -85,8 +95,8 @@ ActiveRecord::Schema.define(version: 20140127170938) do | ||
| 85 | 95 | ||
| 86 | create_table "keys", force: true do |t| | 96 | create_table "keys", force: true do |t| |
| 87 | t.integer "user_id" | 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 | t.text "key" | 100 | t.text "key" |
| 91 | t.string "title" | 101 | t.string "title" |
| 92 | t.string "type" | 102 | t.string "type" |
| @@ -111,8 +121,8 @@ ActiveRecord::Schema.define(version: 20140127170938) do | @@ -111,8 +121,8 @@ ActiveRecord::Schema.define(version: 20140127170938) do | ||
| 111 | t.integer "author_id" | 121 | t.integer "author_id" |
| 112 | t.integer "assignee_id" | 122 | t.integer "assignee_id" |
| 113 | t.string "title" | 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 | t.integer "milestone_id" | 126 | t.integer "milestone_id" |
| 117 | t.string "state" | 127 | t.string "state" |
| 118 | t.string "merge_status" | 128 | t.string "merge_status" |
| @@ -164,8 +174,8 @@ ActiveRecord::Schema.define(version: 20140127170938) do | @@ -164,8 +174,8 @@ ActiveRecord::Schema.define(version: 20140127170938) do | ||
| 164 | t.text "note" | 174 | t.text "note" |
| 165 | t.string "noteable_type" | 175 | t.string "noteable_type" |
| 166 | t.integer "author_id" | 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 | t.integer "project_id" | 179 | t.integer "project_id" |
| 170 | t.string "attachment" | 180 | t.string "attachment" |
| 171 | t.string "line_code" | 181 | t.string "line_code" |
| @@ -187,8 +197,8 @@ ActiveRecord::Schema.define(version: 20140127170938) do | @@ -187,8 +197,8 @@ ActiveRecord::Schema.define(version: 20140127170938) do | ||
| 187 | t.string "name" | 197 | t.string "name" |
| 188 | t.string "path" | 198 | t.string "path" |
| 189 | t.text "description" | 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 | t.integer "creator_id" | 202 | t.integer "creator_id" |
| 193 | t.boolean "issues_enabled", default: true, null: false | 203 | t.boolean "issues_enabled", default: true, null: false |
| 194 | t.boolean "wall_enabled", default: true, null: false | 204 | t.boolean "wall_enabled", default: true, null: false |
| @@ -239,8 +249,8 @@ ActiveRecord::Schema.define(version: 20140127170938) do | @@ -239,8 +249,8 @@ ActiveRecord::Schema.define(version: 20140127170938) do | ||
| 239 | t.text "content", limit: 2147483647 | 249 | t.text "content", limit: 2147483647 |
| 240 | t.integer "author_id", null: false | 250 | t.integer "author_id", null: false |
| 241 | t.integer "project_id" | 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 | t.string "file_name" | 254 | t.string "file_name" |
| 245 | t.datetime "expires_at" | 255 | t.datetime "expires_at" |
| 246 | t.boolean "private", default: true, null: false | 256 | t.boolean "private", default: true, null: false |
| @@ -262,42 +272,45 @@ ActiveRecord::Schema.define(version: 20140127170938) do | @@ -262,42 +272,45 @@ ActiveRecord::Schema.define(version: 20140127170938) do | ||
| 262 | t.datetime "created_at" | 272 | t.datetime "created_at" |
| 263 | end | 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 | create_table "tags", force: true do |t| | 278 | create_table "tags", force: true do |t| |
| 266 | t.string "name" | 279 | t.string "name" |
| 267 | end | 280 | end |
| 268 | 281 | ||
| 269 | create_table "users", force: true do |t| | 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 | t.string "reset_password_token" | 285 | t.string "reset_password_token" |
| 273 | t.datetime "reset_password_sent_at" | 286 | t.datetime "reset_password_sent_at" |
| 274 | t.datetime "remember_created_at" | 287 | t.datetime "remember_created_at" |
| 275 | - t.integer "sign_in_count", default: 0 | 288 | + t.integer "sign_in_count", default: 0 |
| 276 | t.datetime "current_sign_in_at" | 289 | t.datetime "current_sign_in_at" |
| 277 | t.datetime "last_sign_in_at" | 290 | t.datetime "last_sign_in_at" |
| 278 | t.string "current_sign_in_ip" | 291 | t.string "current_sign_in_ip" |
| 279 | t.string "last_sign_in_ip" | 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 | t.string "name" | 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 | t.string "authentication_token" | 301 | t.string "authentication_token" |
| 289 | - t.integer "theme_id", default: 1, null: false | 302 | + t.integer "theme_id", default: 1, null: false |
| 290 | t.string "bio" | 303 | t.string "bio" |
| 291 | - t.integer "failed_attempts", default: 0 | 304 | + t.integer "failed_attempts", default: 0 |
| 292 | t.datetime "locked_at" | 305 | t.datetime "locked_at" |
| 293 | t.string "extern_uid" | 306 | t.string "extern_uid" |
| 294 | t.string "provider" | 307 | t.string "provider" |
| 295 | t.string "username" | 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 | t.string "state" | 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 | t.datetime "password_expires_at" | 314 | t.datetime "password_expires_at" |
| 302 | t.integer "created_by_id" | 315 | t.integer "created_by_id" |
| 303 | t.string "avatar" | 316 | t.string "avatar" |
| @@ -305,14 +318,15 @@ ActiveRecord::Schema.define(version: 20140127170938) do | @@ -305,14 +318,15 @@ ActiveRecord::Schema.define(version: 20140127170938) do | ||
| 305 | t.datetime "confirmed_at" | 318 | t.datetime "confirmed_at" |
| 306 | t.datetime "confirmation_sent_at" | 319 | t.datetime "confirmation_sent_at" |
| 307 | t.string "unconfirmed_email" | 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 | end | 323 | end |
| 311 | 324 | ||
| 312 | add_index "users", ["admin"], name: "index_users_on_admin", using: :btree | 325 | add_index "users", ["admin"], name: "index_users_on_admin", using: :btree |
| 313 | add_index "users", ["authentication_token"], name: "index_users_on_authentication_token", unique: true, using: :btree | 326 | add_index "users", ["authentication_token"], name: "index_users_on_authentication_token", unique: true, using: :btree |
| 314 | add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree | 327 | add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree |
| 315 | add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree | 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 | add_index "users", ["name"], name: "index_users_on_name", using: :btree | 330 | add_index "users", ["name"], name: "index_users_on_name", using: :btree |
| 317 | add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree | 331 | add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree |
| 318 | add_index "users", ["username"], name: "index_users_on_username", using: :btree | 332 | add_index "users", ["username"], name: "index_users_on_username", using: :btree |
| @@ -331,8 +345,8 @@ ActiveRecord::Schema.define(version: 20140127170938) do | @@ -331,8 +345,8 @@ ActiveRecord::Schema.define(version: 20140127170938) do | ||
| 331 | create_table "users_projects", force: true do |t| | 345 | create_table "users_projects", force: true do |t| |
| 332 | t.integer "user_id", null: false | 346 | t.integer "user_id", null: false |
| 333 | t.integer "project_id", null: false | 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 | t.integer "project_access", default: 0, null: false | 350 | t.integer "project_access", default: 0, null: false |
| 337 | t.integer "notification_level", default: 3, null: false | 351 | t.integer "notification_level", default: 3, null: false |
| 338 | end | 352 | end |
| @@ -344,8 +358,8 @@ ActiveRecord::Schema.define(version: 20140127170938) do | @@ -344,8 +358,8 @@ ActiveRecord::Schema.define(version: 20140127170938) do | ||
| 344 | create_table "web_hooks", force: true do |t| | 358 | create_table "web_hooks", force: true do |t| |
| 345 | t.string "url" | 359 | t.string "url" |
| 346 | t.integer "project_id" | 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 | t.string "type", default: "ProjectHook" | 363 | t.string "type", default: "ProjectHook" |
| 350 | t.integer "service_id" | 364 | t.integer "service_id" |
| 351 | t.boolean "push_events", default: true, null: false | 365 | t.boolean "push_events", default: true, null: false |
| @@ -0,0 +1,25 @@ | @@ -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 @@ | @@ -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 | \ No newline at end of file | 15 | \ No newline at end of file |
| @@ -0,0 +1,48 @@ | @@ -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 @@ | @@ -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,7 +15,7 @@ require 'spinach/capybara' | ||
| 15 | require 'sidekiq/testing/inline' | 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 | require Rails.root.join('spec', 'support', f) | 19 | require Rails.root.join('spec', 'support', f) |
| 20 | end | 20 | end |
| 21 | 21 |
spec/factories.rb
| @@ -219,6 +219,19 @@ FactoryGirl.define do | @@ -219,6 +219,19 @@ FactoryGirl.define do | ||
| 219 | end | 219 | end |
| 220 | end | 220 | end |
| 221 | end | 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 | factory :milestone do | 236 | factory :milestone do |
| 224 | title | 237 | title |
spec/mailers/notify_spec.rb
| @@ -90,6 +90,28 @@ describe Notify do | @@ -90,6 +90,28 @@ describe Notify do | ||
| 90 | end | 90 | end |
| 91 | end | 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 | context 'for a project' do | 115 | context 'for a project' do |
| 94 | describe 'items that are assignable, the email' do | 116 | describe 'items that are assignable, the email' do |
| 95 | let(:assignee) { create(:user, email: 'assignee@example.com') } | 117 | let(:assignee) { create(:user, email: 'assignee@example.com') } |
| @@ -0,0 +1,17 @@ | @@ -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,6 +183,23 @@ describe Profiles::KeysController, "routing" do | ||
| 183 | end | 183 | end |
| 184 | end | 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 | # profile_avatar DELETE /profile/avatar(.:format) profiles/avatars#destroy | 203 | # profile_avatar DELETE /profile/avatar(.:format) profiles/avatars#destroy |
| 187 | describe Profiles::AvatarsController, "routing" do | 204 | describe Profiles::AvatarsController, "routing" do |
| 188 | it "to #destroy" do | 205 | it "to #destroy" do |
spec/services/notification_service_spec.rb
| @@ -16,6 +16,19 @@ describe NotificationService do | @@ -16,6 +16,19 @@ describe NotificationService do | ||
| 16 | end | 16 | end |
| 17 | end | 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 | describe 'Notes' do | 32 | describe 'Notes' do |
| 20 | context 'issue note' do | 33 | context 'issue note' do |
| 21 | let(:issue) { create(:issue, assignee: create(:user)) } | 34 | let(:issue) { create(:issue, assignee: create(:user)) } |
spec/support/valid_commit.rb
| @@ -2,6 +2,7 @@ module ValidCommit | @@ -2,6 +2,7 @@ module ValidCommit | ||
| 2 | ID = "8470d70da67355c9c009e4401746b1d5410af2e3" | 2 | ID = "8470d70da67355c9c009e4401746b1d5410af2e3" |
| 3 | MESSAGE = "notes controller refactored" | 3 | MESSAGE = "notes controller refactored" |
| 4 | AUTHOR_FULL_NAME = "Dmitriy Zaporozhets" | 4 | AUTHOR_FULL_NAME = "Dmitriy Zaporozhets" |
| 5 | + AUTHOR_EMAIL = "dmitriy.zaporozhets@gmail.com" | ||
| 5 | 6 | ||
| 6 | 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 | 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 | FILES_COUNT = 26 | 8 | FILES_COUNT = 26 |
| @@ -0,0 +1,6 @@ | @@ -0,0 +1,6 @@ | ||
| 1 | +module ValidCommitWithAltEmail | ||
| 2 | + ID = "1e689bfba39525ead225eaf611948cfbe8ac34cf" | ||
| 3 | + MESSAGE = "fixed notes logic" | ||
| 4 | + AUTHOR_FULL_NAME = "Dmitriy Zaporozhets" | ||
| 5 | + AUTHOR_EMAIL = "dzaporozhets@sphereconsultinginc.com" | ||
| 6 | +end | ||
| 0 | \ No newline at end of file | 7 | \ No newline at end of file |