提交 46549992 编写于 作者: M Marin Jankovski

Merge branch 'master' into relative_links_in_documentation

......@@ -13,6 +13,7 @@ v 6.2.0
- Rake tasks for web hooks management (Jonhnny Weslley)
- Extended User API to expose admin and can_create_group for user creation/updating (Boyan Tabakov)
- API: Remove group
- Avatar upload on profile page with a maximum of 200KB (Steven Thonus)
v 6.1.0
- Project specific IDs for issues, mr, milestones
......
......@@ -16,6 +16,7 @@ gem "pg", group: :postgres
# Auth
gem "devise", '~> 2.2'
gem "devise-async"
gem 'omniauth', "~> 1.1.3"
gem 'omniauth-google-oauth2'
gem 'omniauth-twitter'
......
......@@ -106,6 +106,8 @@ GEM
orm_adapter (~> 0.1)
railties (~> 3.1)
warden (~> 1.2.1)
devise-async (0.8.0)
devise (>= 2.2, < 3.2)
diff-lcs (1.2.4)
dotenv (0.8.0)
email_spec (1.4.0)
......@@ -564,6 +566,7 @@ DEPENDENCIES
d3_rails (~> 3.1.4)
database_cleaner
devise (~> 2.2)
devise-async
email_spec
enumerize
factory_girl_rails
......
......@@ -16,3 +16,13 @@ $ ->
$('.update-notifications').on 'ajax:complete', ->
$(this).find('.btn-save').enableButton()
$('.js-choose-user-avatar-button').bind "click", ->
form = $(this).closest("form")
form.find(".js-user-avatar-input").click()
$('.js-user-avatar-input').bind "change", ->
form = $(this).closest("form")
filename = $(this).val().replace(/^.*[\\\/]/, '')
form.find(".js-avatar-filename").text(filename)
$ ->
userFormatResult = (user) ->
avatar = gon.gravatar_url
avatar = avatar.replace('%{hash}', md5(user.email))
avatar = avatar.replace('%{size}', '24')
if user.avatar
avatar = user.avatar.url
else
avatar = gon.gravatar_url
avatar = avatar.replace('%{hash}', md5(user.email))
avatar = avatar.replace('%{size}', '24')
markup = "<div class='user-result'>"
markup += "<div class='user-image'><img class='avatar s24' src='" + avatar + "'></div>"
markup += "<div class='user-name'>" + user.name + "</div>"
......
......@@ -270,27 +270,6 @@ li.note {
}
}
.oauth_select_holder {
padding: 20px;
img {
padding: 5px;
margin-right: 10px;
}
.active {
img {
border: 1px solid #ccc;
background: $hover;
@include border-radius(5px);
}
}
}
.btn-build-token {
float: left;
padding: 6px 20px;
margin-right: 12px;
}
.gitlab-promo {
a {
color: #aaa;
......
......@@ -19,4 +19,5 @@
&.s32 { width: 32px; height: 32px; margin-right: 10px; }
&.s60 { width: 60px; height: 60px; margin-right: 12px; }
&.s90 { width: 90px; height: 90px; margin-right: 15px; }
&.s160 { width: 160px; height: 160px; margin-right: 20px; }
}
......@@ -4,3 +4,41 @@
margin-bottom: 0;
}
}
.account-page {
fieldset {
margin-bottom: 15px;
border-bottom: 1px dashed #ddd;
padding-bottom: 15px;
&:last-child {
border: none;
}
legend {
border: none;
margin: 0;
}
}
}
.oauth_select_holder {
img {
padding: 2px;
margin-right: 10px;
}
.active {
img {
border: 1px solid #4BD;
background: $hover;
@include border-radius(5px);
}
}
}
.btn-build-token {
float: left;
padding: 6px 20px;
margin-right: 12px;
}
class Profiles::AccountsController < ApplicationController
layout "profile"
def show
@user = current_user
end
end
class Profiles::PasswordsController < ApplicationController
layout 'navless'
layout :determine_layout
skip_before_filter :check_password_expiration
skip_before_filter :check_password_expiration, only: [:new, :create]
before_filter :set_user
before_filter :set_title
before_filter :authorize_change_password!
def new
end
......@@ -26,6 +27,32 @@ class Profiles::PasswordsController < ApplicationController
end
end
def edit
end
def update
password_attributes = params[:user].select do |key, value|
%w(password password_confirmation).include?(key.to_s)
end
unless @user.valid_password?(params[:user][:current_password])
redirect_to edit_profile_password_path, alert: 'You must provide a valid current password'
return
end
if @user.update_attributes(password_attributes)
flash[:notice] = "Password was successfully updated. Please login with it"
redirect_to new_user_session_path
else
render 'edit'
end
end
def reset
current_user.send_reset_password_instructions
redirect_to edit_profile_password_path, notice: 'We sent you an email with reset password instructions'
end
private
def set_user
......@@ -35,4 +62,16 @@ class Profiles::PasswordsController < ApplicationController
def set_title
@title = "New password"
end
def determine_layout
if [:new, :create].include?(action_name.to_sym)
'navless'
else
'profile'
end
end
def authorize_change_password!
return render_404 if @user.ldap_user?
end
end
......@@ -2,7 +2,6 @@ class ProfilesController < ApplicationController
include ActionView::Helpers::SanitizeHelper
before_filter :user
before_filter :authorize_change_password!, only: :update_password
before_filter :authorize_change_username!, only: :update_username
layout 'profile'
......@@ -13,9 +12,6 @@ class ProfilesController < ApplicationController
def design
end
def account
end
def update
if @user.update_attributes(params[:user])
flash[:notice] = "Profile was successfully updated"
......@@ -29,33 +25,12 @@ class ProfilesController < ApplicationController
end
end
def token
end
def update_password
password_attributes = params[:user].select do |key, value|
%w(password password_confirmation).include?(key.to_s)
end
unless @user.valid_password?(params[:user][:current_password])
redirect_to account_profile_path, alert: 'You must provide a valid current password'
return
end
if @user.update_attributes(password_attributes)
flash[:notice] = "Password was successfully updated. Please login with it"
redirect_to new_user_session_path
else
render 'account'
end
end
def reset_private_token
if current_user.reset_authentication_token!
flash[:notice] = "Token was successfully updated"
end
redirect_to account_profile_path
redirect_to profile_account_path
end
def history
......@@ -76,10 +51,6 @@ class ProfilesController < ApplicationController
@user = current_user
end
def authorize_change_password!
return render_404 if @user.ldap_user?
end
def authorize_change_username!
return render_404 unless @user.can_change_username?
end
......
......@@ -49,6 +49,15 @@ module ApplicationHelper
args.any? { |v| v.to_s.downcase == action_name }
end
def avatar_icon(user_email = '', size = nil)
user = User.find_by_email(user_email)
if user && user.avatar.present?
user.avatar.url
else
gravatar_icon(user_email, size)
end
end
def gravatar_icon(user_email = '', size = nil)
size = 40 if size.nil? || size <= 0
......
......@@ -108,7 +108,7 @@ module CommitsHelper
source_name = commit.send "#{options[:source]}_name".to_sym
source_email = commit.send "#{options[:source]}_email".to_sym
text = if options[:avatar]
avatar = image_tag(gravatar_icon(source_email, options[:size]), class: "avatar #{"s#{options[:size]}" if options[:size]}", width: options[:size], alt: "")
avatar = image_tag(avatar_icon(source_email, options[:size]), class: "avatar #{"s#{options[:size]}" if options[:size]}", width: options[:size], alt: "")
%Q{#{avatar} <span class="commit-#{options[:source]}-name">#{source_name}</span>}
else
source_name
......
......@@ -5,10 +5,10 @@ module ProjectsHelper
def link_to_project project
link_to project do
title = content_tag(:strong, project.name)
title = content_tag(:span, project.name, class: 'projet-name')
if project.namespace
namespace = content_tag(:span, "#{project.namespace.human_name} / ", class: 'tiny')
namespace = content_tag(:span, "#{project.namespace.human_name} / ", class: 'namespace-name')
title = namespace + title
end
......@@ -25,7 +25,7 @@ module ProjectsHelper
author_html = ""
# Build avatar image tag
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]
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]
# Build name span tag
author_html << content_tag(:span, sanitize(author.name), class: 'author') if opts[:name]
......@@ -80,6 +80,18 @@ module ProjectsHelper
@project.milestones.active.order("id desc").all
end
def project_issues_trackers
values = Project.issues_tracker.values.map do |tracker_key|
if tracker_key.to_sym == :gitlab
['GitLab', tracker_key]
else
[Gitlab.config.issues_tracker[tracker_key]['title'] || tracker_key, tracker_key]
end
end
options_for_select(values)
end
private
def get_project_nav_tabs(project, current_user)
......@@ -119,4 +131,13 @@ module ProjectsHelper
"your@email.com"
end
end
def repository_size
"#{@project.repository.size} MB"
rescue
# In order to prevent 500 error
# when application cannot allocate memory
# to calculate repo size - just show 'Unknown'
'unknown'
end
end
......@@ -38,13 +38,16 @@
# created_by_id :integer
#
require 'carrierwave/orm/activerecord'
require 'file_size_validator'
class User < ActiveRecord::Base
devise :database_authenticatable, :token_authenticatable, :lockable,
devise :database_authenticatable, :token_authenticatable, :lockable, :async,
:recoverable, :rememberable, :trackable, :validatable, :omniauthable, :registerable
attr_accessible :email, :password, :password_confirmation, :remember_me, :bio, :name, :username,
:skype, :linkedin, :twitter, :color_scheme_id, :theme_id, :force_random_password,
:extern_uid, :provider, :password_expires_at,
:extern_uid, :provider, :password_expires_at, :avatar,
as: [:default, :admin]
attr_accessible :projects_limit, :can_create_group,
......@@ -113,6 +116,8 @@ class User < ActiveRecord::Base
validate :namespace_uniq, if: ->(user) { user.username_changed? }
validates :avatar, file_size: { maximum: 100.kilobytes.to_i }
before_validation :generate_password, on: :create
before_validation :sanitize_attrs
......@@ -150,6 +155,8 @@ class User < ActiveRecord::Base
end
end
mount_uploader :avatar, AttachmentUploader
# Scopes
scope :admins, -> { where(admin: true) }
scope :blocked, -> { with_state(:blocked) }
......@@ -391,4 +398,4 @@ class User < ActiveRecord::Base
self
end
end
end
\ No newline at end of file
......@@ -20,7 +20,7 @@
.title
Account:
.pull-right
= image_tag gravatar_icon(@user.email, 32), class: "avatar s32"
= image_tag avatar_icon(@user.email, 32), class: "avatar s32"
%ul.well-list
%li
%span.light Name:
......@@ -117,11 +117,7 @@
- tm = project.team.find_tm(@user.id)
%li.users_project
= link_to admin_project_path(project), class: dom_class(project) do
- if project.namespace
= project.namespace.human_name
\/
%strong.well-title
= truncate(project.name, length: 45)
= project.name_with_namespace
- if tm
.pull-right
......
......@@ -12,7 +12,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.link :href => project_issue_url(issue.project, issue)
xml.title truncate(issue.title, :length => 80)
xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
xml.media :thumbnail, :width => "40", :height => "40", :url => gravatar_icon(issue.author_email)
xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(issue.author_email)
xml.author do |author|
xml.name issue.author_name
xml.email issue.author_email
......
......@@ -17,7 +17,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.link :href => event_link
xml.title truncate(event_title, :length => 80)
xml.updated event.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
xml.media :thumbnail, :width => "40", :height => "40", :url => gravatar_icon(event.author_email)
xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(event.author_email)
xml.author do |author|
xml.name event.author_name
xml.email event.author_email
......
......@@ -4,7 +4,7 @@
#{time_ago_in_words(event.created_at)} ago.
= cache event do
= image_tag gravatar_icon(event.author_email), class: "avatar s24", alt:''
= image_tag avatar_icon(event.author_email), class: "avatar s24", alt:''
- if event.push?
= render "events/event/push", event: event
......
......@@ -7,7 +7,7 @@
= link_to project_commits_path(event.project, event.ref_name) do
%strong= truncate(event.ref_name, length: 30)
at
%strong= link_to_project event.project
= link_to_project event.project
- if event.push_with_commits?
- project = event.project
......
......@@ -12,7 +12,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.link :href => project_issue_url(issue.project, issue)
xml.title truncate(issue.title, :length => 80)
xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
xml.media :thumbnail, :width => "40", :height => "40", :url => gravatar_icon(issue.author_email)
xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(issue.author_email)
xml.author do |author|
xml.name issue.author_name
xml.email issue.author_email
......
......@@ -16,7 +16,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.link :href => event_link
xml.title truncate(event_title, :length => 80)
xml.updated event.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
xml.media :thumbnail, :width => "40", :height => "40", :url => gravatar_icon(event.author_email)
xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(event.author_email)
xml.author do |author|
xml.name event.author_name
xml.email event.author_email
......
......@@ -37,4 +37,4 @@
%i.icon-signout
%li
= link_to current_user, class: "profile-pic", id: 'profile-pic' do
= image_tag gravatar_icon(current_user.email, 26), alt: ''
= image_tag avatar_icon(current_user.email, 26), alt: ''
......@@ -2,8 +2,11 @@
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
= link_to profile_path, title: "Profile" do
%i.icon-home
= nav_link(path: 'profiles#account') do
= link_to "Account", account_profile_path
= nav_link(controller: :accounts) do
= link_to "Account", profile_account_path
- unless current_user.ldap_user?
= nav_link(controller: :passwords) do
= link_to "Password", edit_profile_password_path
= nav_link(controller: :notifications) do
= link_to "Notifications", profile_notifications_path
= nav_link(controller: :keys) do
......
%h3.page-title
Account settings
%p.light
You can change your password, username and private token here.
- if current_user.ldap_user?
Some options are unavailable for LDAP accounts
%hr
.row
.span2
%ul.nav.nav-pills.nav-stacked.nav-stacked-menu
%li.active
= link_to '#tab-token', 'data-toggle' => 'tab' do
Private Token
%li
= link_to '#tab-password', 'data-toggle' => 'tab' do
Password
- if show_profile_social_tab?
%li
= link_to '#tab-social', 'data-toggle' => 'tab' do
Social Accounts
- if show_profile_username_tab?
%li
= link_to '#tab-username', 'data-toggle' => 'tab' do
Change Username
- if show_profile_remove_tab?
%li
= link_to '#tab-remove', 'data-toggle' => 'tab' do
Remove Account
.span10
.tab-content
.tab-pane.active#tab-token
%fieldset.update-token
%legend
Private token
%span.cred.pull-right
keep it secret!
%div
= form_for @user, url: reset_private_token_profile_path, method: :put do |f|
.data
%p.slead
Your private token is used to access application resources without authentication.
%br
It can be used for atom feeds or the API.
%p.cgray
- if current_user.private_token
= text_field_tag "token", current_user.private_token, class: "input-xxlarge large_text input-xpadding"
= f.submit 'Reset', confirm: "Are you sure?", class: "btn btn-primary btn-build-token"
- else
%span You don`t have one yet. Click generate to fix it.
= f.submit 'Generate', class: "btn success btn-build-token"
.tab-pane#tab-password
%fieldset.update-password
%legend Password
- if current_user.ldap_user?
%h3.nothing_here_message Not available for LDAP user
- else
= form_for @user, url: update_password_profile_path, method: :put do |f|
%div
%p.slead
You must provide current password in order to change it.
%br
After a successful password update you will be redirected to login page where you should login with your new password
-if @user.errors.any?
.alert.alert-error
%ul
- @user.errors.full_messages.each do |msg|
%li= msg
.control-group
= f.label :current_password, class: 'cgreen'
.controls= f.password_field :current_password, required: true
.control-group
= f.label :password, 'New password'
.controls= f.password_field :password, required: true
.control-group
= f.label :password_confirmation
.controls
= f.password_field :password_confirmation, required: true
.control-group
.controls
= f.submit 'Save password', class: "btn btn-save"
- if show_profile_social_tab?
.tab-pane#tab-social
%fieldset
%legend Social Accounts
.oauth_select_holder
%p.hint Tip: Click on icon to activate signin with one of the following services
- enabled_social_providers.each do |provider|
%span{class: oauth_active_class(provider) }
= link_to authbutton(provider, 32), omniauth_authorize_path(User, provider)
- if show_profile_username_tab?
.tab-pane#tab-username
%fieldset.update-username
%legend
Username
%small.cred.pull-right
Changing your username can have unintended side effects!
= form_for @user, url: update_username_profile_path, method: :put, remote: true do |f|
%div
.control-group
= f.label :username
.controls
= f.text_field :username, required: true
&nbsp;
%span.loading-gif.hide= image_tag "ajax_loader.gif"
%span.update-success.cgreen.hide
%i.icon-ok
Saved
%span.update-failed.cred.hide
%i.icon-remove
Failed
%ul.cred
%li This will change the web URL for personal projects.
%li This will change the git path to repositories for personal projects.
.controls
= f.submit 'Save username', class: "btn btn-save"
- if show_profile_remove_tab?
.tab-pane#tab-remove
%fieldset.remove-account
%legend
Remove account
%div
%p Deleting an account has the following effects:
%ul
%li All user content like authored issues, snippets, comments will be removed
- rp = current_user.personal_projects.count
- unless rp.zero?
%li #{pluralize rp, 'personal project'} will be removed and cannot be restored
- if current_user.solo_owned_groups.present?
%li
Next groups will be abandoned. You should transfer or remove them:
%strong #{current_user.solo_owned_groups.map(&:name).join(', ')}
= link_to 'Delete account', user_registration_path, confirm: "REMOVE #{current_user.name}? Are you sure?", method: :delete, class: "btn btn-remove"
%h3.page-title
Account settings
%p.light
You can change your username and private token here.
- if current_user.ldap_user?
Some options are unavailable for LDAP accounts
%hr
.account-page
%fieldset.update-token
%legend
Private token
%div
= form_for @user, url: reset_private_token_profile_path, method: :put do |f|
.data
%p
Your private token is used to access application resources without authentication.
%br
It can be used for atom feeds or the API.
%span.cred
Keep it secret!
%p.cgray
- if current_user.private_token
= text_field_tag "token", current_user.private_token, class: "input-xlarge input-xpadding pull-left"
= f.submit 'Reset', confirm: "Are you sure?", class: "btn btn-primary btn-build-token prepend-left-10"
- else
%span You don`t have one yet. Click generate to fix it.
= f.submit 'Generate', class: "btn success btn-build-token"
- if show_profile_social_tab?
%fieldset
%legend Social Accounts
.oauth_select_holder.append-bottom-10
%p Click on icon to activate signin with one of the following services
- enabled_social_providers.each do |provider|
%span{class: oauth_active_class(provider) }
= link_to authbutton(provider, 32), omniauth_authorize_path(User, provider)
- if show_profile_username_tab?
%fieldset.update-username
%legend
Username
= form_for @user, url: update_username_profile_path, method: :put, remote: true do |f|
%p
Changing your username will change path to all personl projects!
%div
= f.text_field :username, required: true, class: 'input-xlarge input-xpadding'
&nbsp;
%span.loading-gif.hide= image_tag "ajax_loader.gif"
%p.light
= user_url(@user)
%div
= f.submit 'Save username', class: "btn btn-save"
- if show_profile_remove_tab?
%fieldset.remove-account
%legend
Remove account
%div
%p Deleting an account has the following effects:
%ul
%li All user content like authored issues, snippets, comments will be removed
- rp = current_user.personal_projects.count
- unless rp.zero?
%li #{pluralize rp, 'personal project'} will be removed and cannot be restored
- if current_user.solo_owned_groups.present?
%li
Next groups will be abandoned. You should transfer or remove them:
%strong #{current_user.solo_owned_groups.map(&:name).join(', ')}
= link_to 'Delete account', user_registration_path, confirm: "REMOVE #{current_user.name}? Are you sure?", method: :delete, class: "btn btn-remove"
%h3.page-title Password
%p.light
Change your password or recover your current one.
%hr
.update-password
= form_for @user, url: profile_password_path, method: :put do |f|
%div
%p.slead
You must provide current password in order to change it.
%br
After a successful password update you will be redirected to login page where you should login with your new password
-if @user.errors.any?
.alert.alert-error
%ul
- @user.errors.full_messages.each do |msg|
%li= msg
.control-group
= f.label :current_password
.controls
= f.password_field :current_password, required: true
%div
= link_to "Forgot your password?", reset_profile_password_path, method: :put
.control-group
= f.label :password, 'New password'
.controls= f.password_field :password, required: true
.control-group
= f.label :password_confirmation
.controls
= f.password_field :password_confirmation, required: true
.form-actions
= f.submit 'Save password', class: "btn btn-save"
= image_tag gravatar_icon(@user.email, 60), alt: '', class: 'avatar s60'
%h3.page-title
= @user.name
%br
%small
= @user.email
.pull-right
= link_to destroy_user_session_path, class: "logout", method: :delete do
%small
%i.icon-signout
Logout
Profile settings
%p.light
This information appears on your profile.
- if current_user.ldap_user?
Some options are unavailable for LDAP accounts
%hr
= form_for @user, url: profile_path, method: :put, html: { class: "edit_user form-horizontal" } do |f|
= form_for @user, url: profile_path, method: :put, html: { multipart: true, class: "edit_user form-horizontal" } do |f|
-if @user.errors.any?
%div.alert.alert-error
%ul
......@@ -29,7 +25,7 @@
= f.label :email, class: "control-label"
.controls
= f.text_field :email, class: "input-xlarge", required: true
%span.help-block We also use email for avatar detection.
%span.help-block We also use email for avatar detection if no avatar is uploaded.
.control-group
= f.label :skype, class: "control-label"
.controls= f.text_field :skype, class: "input-xlarge"
......@@ -46,45 +42,23 @@
%span.help-block Tell us about yourself in fewer than 250 characters.
.span5.pull-right
%fieldset.tips
%legend Tips:
%ul
%li
%p You can change your password on the Account page
- if Gitlab.config.gravatar.enabled
%li
%p You can change your avatar at #{link_to "gravatar.com", "http://gravatar.com"}
- if Gitlab.config.omniauth.enabled && @user.provider?
%li
%p
You can login through #{@user.provider.titleize}!
= link_to "click here to change", account_profile_path
- if current_user.can_create_group?
%li
%p
Need a group for several dependent projects?
= link_to new_group_path, class: "btn btn-tiny" do
Create a group
- unless current_user.projects_limit_left > 100
%fieldset
%legend
Personal projects:
%small.pull-right
%span= current_user.personal_projects.count
of
%span= current_user.projects_limit
.padded
.progress
.bar{style: "width: #{current_user.projects_limit_percent}%;"}
.light-well
= image_tag avatar_icon(@user.email, 160), alt: '', class: 'avatar s160'
%fieldset
%legend
SSH public keys:
%span.pull-right
= link_to pluralize(current_user.keys.count, 'key'), profile_keys_path
.padded
= link_to "Add Public Key", new_profile_key_path, class: "btn btn-small"
.clearfix
.profile-avatar-form-option
%p.light
You can upload an avatar here
%br
or change it at #{link_to "gravatar.com", "http://gravatar.com"}
%hr
%a.choose-btn.btn.btn-small.js-choose-user-avatar-button
%i.icon-paper-clip
%span Choose File ...
&nbsp;
%span.file_name.js-avatar-filename File name...
= f.file_field :avatar, class: "js-user-avatar-input hide"
%span.help-block The maximum file size allowed is 200KB.
.form-actions
= f.submit 'Save changes', class: "btn btn-save"
- if @user.valid?
:plain
$('.update-username .update-success').show();
new Flash("Username sucessfully changed", "notice")
- else
:plain
$('.update-username .update-failed').show();
new Flash("Username change failed - #{@user.errors.full_messages.first}", "alert")
......@@ -24,7 +24,7 @@
%p
= link_to project_commit_path(@project, commit.id), class: 'commit_short_id' do
= commit.short_id
= image_tag gravatar_icon(commit.author_email), class: "avatar s16", alt: ''
= image_tag avatar_icon(commit.author_email), class: "avatar s16", alt: ''
%span.light
= gfm escape_once(truncate(commit.title, length: 40))
%span
......
......@@ -12,7 +12,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.link :href => project_commit_url(@project, :id => commit.id)
xml.title truncate(commit.title, :length => 80)
xml.updated commit.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ")
xml.media :thumbnail, :width => "40", :height => "40", :url => gravatar_icon(commit.author_email)
xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(commit.author_email)
xml.author do |author|
xml.name commit.author_name
xml.email commit.author_email
......
......@@ -67,7 +67,7 @@
- if Project.issues_tracker.values.count > 1
.control-group
= f.label :issues_tracker, "Issues tracker", class: 'control-label'
.controls= f.select(:issues_tracker, Project.issues_tracker.values, {}, { disabled: !@project.issues_enabled })
.controls= f.select(:issues_tracker, project_issues_trackers, {}, { disabled: !@project.issues_enabled })
.control-group
= f.label :issues_tracker_id, "Project name or id in issues tracker", class: 'control-label'
......
......@@ -52,7 +52,7 @@
- @project.team.members.sort_by(&:name).each do |user|
%li
= link_to project_filter_path(assignee_id: user.id) do
= image_tag gravatar_icon(user.email), class: "avatar s16", alt: ''
= image_tag avatar_icon(user.email), class: "avatar s16", alt: ''
= user.name
.dropdown.inline.prepend-left-10
......
......@@ -12,7 +12,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.link :href => project_issue_url(@project, issue)
xml.title truncate(issue.title, :length => 80)
xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
xml.media :thumbnail, :width => "40", :height => "40", :url => gravatar_icon(issue.author_email)
xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(issue.author_email)
xml.author do |author|
xml.name issue.author_name
xml.email issue.author_email
......
......@@ -35,7 +35,7 @@
- @project.team.members.sort_by(&:name).each do |user|
%li
= link_to project_filter_path(assignee_id: user.id) do
= image_tag gravatar_icon(user.email), class: "avatar s16", alt: ''
= image_tag avatar_icon(user.email), class: "avatar s16", alt: ''
= user.name
.dropdown.inline.prepend-left-10
......
......@@ -8,4 +8,4 @@
= link_to_gfm truncate(issue.title, length: 40), [@project, issue]
- if issue.assignee
.pull-right
= image_tag gravatar_icon(issue.assignee.email, 16), class: "avatar s16"
= image_tag avatar_icon(issue.assignee.email, 16), class: "avatar s16"
......@@ -99,7 +99,7 @@
- @users.each do |user|
%li
= link_to user, title: user.name, class: "dark" do
= image_tag gravatar_icon(user.email, 32), class: "avatar s32"
= image_tag avatar_icon(user.email, 32), class: "avatar s32"
%strong= truncate(user.name, lenght: 40)
%br
%small.cgray= user.username
......@@ -9,7 +9,7 @@
author: {
name: c.author_name,
email: c.author_email,
icon: gravatar_icon(c.author_email, 20)
icon: avatar_icon(c.author_email, 20)
},
time: c.time,
space: c.spaces.first,
......
......@@ -8,7 +8,7 @@
= link_to "javascript:;", class: "js-details-target turn-off js-toggler-target" do
%i.icon-eye-open
Show discussion
= image_tag gravatar_icon(note.author_email), class: "avatar s32"
= image_tag avatar_icon(note.author_email), class: "avatar s32"
%div
= link_to_member(@project, note.author, avatar: false)
- if note.for_merge_request?
......
......@@ -13,7 +13,7 @@
= 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
%i.icon-trash.cred
Remove
= image_tag gravatar_icon(note.author_email), class: "avatar s32"
= image_tag avatar_icon(note.author_email), class: "avatar s32"
= link_to_member(@project, note.author, avatar: false)
%span.note-last-update
= note_timestamp(note)
......
......@@ -11,7 +11,7 @@
%div
= link_to project_commits_path(@project, commit.id) do
%code= commit.short_id
= image_tag gravatar_icon(commit.author_email), class: "", width: 16, alt: ''
= image_tag avatar_icon(commit.author_email), class: "", width: 16, alt: ''
= gfm escape_once(truncate(commit.title, length: 40))
%td
%span.pull-right.cgray
......
......@@ -19,7 +19,7 @@
%ol.styled
- @stats.authors[0...50].each do |author|
%li
= image_tag gravatar_icon(author.email, 16), class: 'avatar s16', alt: ''
= image_tag avatar_icon(author.email, 16), class: 'avatar s16', alt: ''
= author.name
%small.light= author.email
.pull-right
......
......@@ -17,7 +17,7 @@
%p
%p
%span.light Repo size is
#{@project.repository.size} MB
= repository_size
%p
%span.light Created at
#{@project.created_at.stamp('Aug 22, 2013')}
......
......@@ -16,6 +16,6 @@
= "##{snippet.id}"
%span
by
= image_tag gravatar_icon(snippet.author_email), class: "avatar avatar-inline s16"
= image_tag avatar_icon(snippet.author_email), class: "avatar avatar-inline s16"
= snippet.author_name
%span.light #{time_ago_in_words(snippet.created_at)} ago
......@@ -5,7 +5,7 @@
= "##{@snippet.id}"
%span.light
by
= image_tag gravatar_icon(@snippet.author_email), class: "avatar avatar-inline s16"
= image_tag avatar_icon(@snippet.author_email), class: "avatar avatar-inline s16"
= @snippet.author_name
%div= render 'projects/snippets/blob'
%div#notes= render "projects/notes/notes_with_form"
......@@ -9,7 +9,7 @@
&nbsp;
= 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
%i.icon-minus.icon-white
= image_tag gravatar_icon(user.email, 32), class: "avatar s32"
= image_tag avatar_icon(user.email, 32), class: "avatar s32"
%p
%strong= user.name
%span.cgray= user.username
......
......@@ -18,6 +18,6 @@
%span
by
= link_to user_snippets_path(snippet.author) do
= image_tag gravatar_icon(snippet.author_email), class: "avatar avatar-inline s16", alt: ''
= image_tag avatar_icon(snippet.author_email), class: "avatar avatar-inline s16", alt: ''
= snippet.author_name
%span.light #{time_ago_in_words(snippet.created_at)} ago
......@@ -17,7 +17,7 @@
%span.light
by
= link_to user_snippets_path(@snippet.author) do
= image_tag gravatar_icon(@snippet.author_email), class: "avatar avatar-inline s16"
= image_tag avatar_icon(@snippet.author_email), class: "avatar avatar-inline s16"
= @snippet.author_name
.back-link
......
%h3.page-title
= image_tag gravatar_icon(@user.email), class: "avatar s24"
= image_tag avatar_icon(@user.email), class: "avatar s24"
= @user.name
%span
\/
......
......@@ -3,9 +3,4 @@
%ul.well-list
- @projects.each do |project|
%li
= link_to project_path(project), class: dom_class(project) do
- if project.namespace
= project.namespace.human_name
\/
%strong.well-title
= truncate(project.name, length: 45)
= link_to_project project
.row
.span8
%h3.page-title
= image_tag gravatar_icon(@user.email, 90), class: "avatar s90", alt: ''
= image_tag avatar_icon(@user.email, 90), class: "avatar s90", alt: ''
= @user.name
- if @user == current_user
.pull-right
......
- user = member.user
- return unless user
%li{class: "#{dom_class(member)} js-toggle-container", id: dom_id(member)}
= image_tag gravatar_icon(user.email, 16), class: "avatar s16"
= image_tag avatar_icon(user.email, 16), class: "avatar s16"
%strong= user.name
%span.cgray= user.username
- if user == current_user
......
......@@ -73,6 +73,7 @@ production: &base
## External issues trackers
issues_tracker:
# redmine:
# title: "Redmine"
# ## If not nil, link 'Issues' on project page will be replaced with this
# ## Use placeholders:
# ## :project_id - GitLab project identifier
......@@ -93,6 +94,7 @@ production: &base
# new_issue_url: "http://redmine.sample/projects/:issues_tracker_id/issues/new"
#
# jira:
# title: "Atlassian Jira"
# project_url: "http://jira.sample/issues/?jql=project=:issues_tracker_id"
# issues_url: "http://jira.sample/browse/:id"
# new_issue_url: "http://jira.sample/secure/CreateIssue.jspa"
......@@ -206,6 +208,7 @@ test:
<<: *base
issues_tracker:
redmine:
title: "Redmine"
project_url: "http://redmine/projects/:issues_tracker_id"
issues_url: "http://redmine/:project_id/:issues_tracker_id/:id"
new_issue_url: "http://redmine/projects/:issues_tracker_id/issues/new"
......
Devise::Async.backend = :sidekiq
......@@ -99,19 +99,21 @@ Gitlab::Application.routes.draw do
#
resource :profile, only: [:show, :update] do
member do
get :account
get :history
get :token
get :design
put :update_password
put :reset_private_token
put :update_username
end
scope module: :profiles do
resource :account, only: [:show, :update]
resource :notifications, only: [:show, :update]
resource :password, only: [:new, :create]
resource :password, only: [:new, :create, :edit, :update] do
member do
put :reset
end
end
resources :keys
resources :groups, only: [:index] do
member do
......
class AddAvatarToUsers < ActiveRecord::Migration
def change
add_column :users, :avatar, :string
end
end
......@@ -11,7 +11,7 @@
#
# It's strongly recommended to check this file into your version control system.
ActiveRecord::Schema.define(:version => 20130926081215) do
ActiveRecord::Schema.define(:version => 20131005191208) do
create_table "deploy_keys_projects", :force => true do |t|
t.integer "deploy_key_id", :null => false
......@@ -283,6 +283,7 @@ ActiveRecord::Schema.define(:version => 20130926081215) do
t.integer "notification_level", :default => 1, :null => false
t.datetime "password_expires_at"
t.integer "created_by_id"
t.string "avatar"
end
add_index "users", ["admin"], :name => "index_users_on_admin"
......
......@@ -244,6 +244,18 @@ Parameters:
+ `public` (optional)
## Remove project
Removes project with all resources(issues, merge requests etc)
```
DELETE /projects/:id
```
Parameters:
+ `id` (required) - The ID of a project
## Team members
......
......@@ -63,7 +63,7 @@ Make sure you have the right version of Python installed.
python --version
# If it's Python 3 you might need to install Python 2 separately
sudo apt-get install python2.7
sudo apt-get install -y python2.7
# Make sure you can access Python via python2
python2 --version
......@@ -72,7 +72,7 @@ Make sure you have the right version of Python installed.
sudo ln -s /usr/bin/python /usr/bin/python2
# For reStructuredText markup language support install required package:
sudo apt-get install python-docutils
sudo apt-get install -y python-docutils
**Note:** In order to receive mail notifications, make sure to install a
mail server. By default, Debian is shipped with exim4 whereas Ubuntu
......
......@@ -12,26 +12,31 @@ Feature: Profile
And I should see new contact info
Scenario: I change my password without old one
Given I visit profile account page
Given I visit profile password page
When I try change my password w/o old one
Then I should see a missing password error message
And I should be redirected to account page
And I should be redirected to password page
Scenario: I change my password
Given I visit profile account page
Given I visit profile password page
Then I change my password
And I should be redirected to sign in page
Scenario: I edit my avatar
Given I visit profile page
Then I change my avatar
And I should see new avatar
Scenario: My password is expired
Given my password is expired
And I am not an ldap user
And I visit profile account page
Given I visit profile password page
Then I redirected to expired password page
And I submit new password
And I redirected to sign in page
Scenario: I unsuccessfully change my password
Given I visit profile account page
Given I visit profile password page
When I unsuccessfully change my password
Then I should see a password error message
......
......@@ -3,9 +3,7 @@ class Profile < Spinach::FeatureSteps
include SharedPaths
step 'I should see my profile info' do
page.should have_content "Profile"
page.should have_content @user.name
page.should have_content @user.email
page.should have_content "Profile settings"
end
step 'I change my contact info' do
......@@ -22,6 +20,17 @@ class Profile < Spinach::FeatureSteps
@user.twitter.should == 'testtwitter'
end
step 'I change my avatar' do
attach_file(:user_avatar, File.join(Rails.root, 'public', 'gitlab_logo.png'))
click_button "Save changes"
@user.reload
end
step 'I should see new avatar' do
@user.avatar.should be_instance_of AttachmentUploader
@user.avatar.url.should == "/uploads/user/avatar/#{ @user.id }/gitlab_logo.png"
end
step 'I try change my password w/o old one' do
within '.update-password' do
fill_in "user_password", with: "222333"
......@@ -124,8 +133,12 @@ class Profile < Spinach::FeatureSteps
current_path.should == new_user_session_path
end
step 'I should be redirected to password page' do
current_path.should == edit_profile_password_path
end
step 'I should be redirected to account page' do
current_path.should == account_profile_path
current_path.should == profile_account_path
end
step 'I click on my profile picture' do
......
......@@ -65,8 +65,12 @@ module SharedPaths
visit profile_path
end
step 'I visit profile password page' do
visit edit_profile_password_path
end
step 'I visit profile account page' do
visit account_profile_path
visit profile_account_path
end
step 'I visit profile SSH keys page' do
......
......@@ -66,7 +66,6 @@ module API
present group, with: Entities::GroupDetail
end
# Remove group
#
# Parameters:
......
......@@ -129,6 +129,16 @@ module API
end
end
# Remove project
#
# Parameters:
# id (required) - The ID of a project
# Example Request:
# DELETE /projects/:id
delete ":id" do
authorize! :remove_project, user_project
user_project.destroy
end
# Mark this project as forked from another
#
......
......@@ -216,12 +216,6 @@ describe "On a merge request diff", js: true, focus: true do
end
end
it do
within("tr[id='342e16cbbd482ac2047dc679b2749d248cc1428f_18_17'] + .js-temp-notes-holder") do
should have_no_css(".js-temp-notes-holder")
end
end
it 'should be added as discussion' do
should have_content("Another comment on line 17")
should have_css(".notes_holder")
......
......@@ -12,7 +12,7 @@ describe "Profile account page" do
describe "when signup is enabled" do
before do
Gitlab.config.gitlab.stub(:signup_enabled).and_return(true)
visit account_profile_path
visit profile_account_path
end
it { page.should have_content("Remove account") }
......@@ -26,12 +26,12 @@ describe "Profile account page" do
describe "when signup is disabled" do
before do
Gitlab.config.gitlab.stub(:signup_enabled).and_return(false)
visit account_profile_path
visit profile_account_path
end
it "should not have option to remove account" do
page.should_not have_content("Remove account")
current_path.should == account_profile_path
current_path.should == profile_account_path
end
end
end
......@@ -29,7 +29,7 @@ describe "Users Security" do
end
describe "GET /profile/account" do
subject { account_profile_path }
subject { profile_account_path }
it { should be_allowed_for @u1 }
it { should be_allowed_for :admin }
......
......@@ -38,6 +38,24 @@ describe ApplicationHelper do
current_action?(:baz, :bar, :foo).should be_true
end
end
describe "avatar_icon" do
avatar_file_path = File.join(Rails.root, 'public', 'gitlab_logo.png')
it "should return an url for the avatar" do
user = create(:user)
user.avatar = File.open(avatar_file_path)
user.save!
avatar_icon(user.email).to_s.should == "/uploads/user/avatar/#{ user.id }/gitlab_logo.png"
end
it "should call gravatar_icon when no avatar is present" do
user = create(:user)
user.save!
stub!(:gravatar_icon).and_return('gravatar_method_called')
avatar_icon(user.email).to_s.should == "gravatar_method_called"
end
end
describe "gravatar_icon" do
let(:user_email) { 'user@email.com' }
......
require 'spec_helper'
describe ProjectsHelper do
describe '#project_issues_trackers' do
it "returns the correct issues trackers available" do
project_issues_trackers.should ==
"<option value=\"redmine\">Redmine</option>\n" \
"<option value=\"gitlab\">GitLab</option>"
end
end
end
......@@ -730,4 +730,42 @@ describe API::API do
end
end
end
describe "DELETE /projects/:id" do
context "when authenticated as user" do
it "should remove project" do
delete api("/projects/#{project.id}", user)
response.status.should == 200
end
it "should not remove a project if not an owner" do
user3 = create(:user)
project.team << [user3, :developer]
delete api("/projects/#{project.id}", user3)
response.status.should == 403
end
it "should not remove a non existing project" do
delete api("/projects/1328", user)
response.status.should == 404
end
it "should not remove a project not attached to user" do
delete api("/projects/#{project.id}", user2)
response.status.should == 404
end
end
context "when authenticated as admin" do
it "should remove any existing project" do
delete api("/projects/#{project.id}", admin)
response.status.should == 200
end
it "should not remove a non existing project" do
delete api("/projects/1328", admin)
response.status.should == 404
end
end
end
end
......@@ -128,7 +128,7 @@ end
# profile_update PUT /profile/update(.:format) profile#update
describe ProfilesController, "routing" do
it "to #account" do
get("/profile/account").should route_to('profiles#account')
get("/profile/account").should route_to('profiles/accounts#show')
end
it "to #history" do
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册