Merge pull request #3451 from Azure7111/feature/restructure_my_account

Restructure my account
pull/3612/head
Oliver Günther 9 years ago
commit c80904ec5f
  1. 6
      app/controllers/account_controller.rb
  2. 80
      app/controllers/my_controller.rb
  3. 2
      app/views/customizable/_field.html.erb
  4. 76
      app/views/my/_password_form_fields.html.erb
  5. 29
      app/views/my/_sidebar.html.erb
  6. 176
      app/views/my/access_token.html.erb
  7. 68
      app/views/my/account.html.erb
  8. 44
      app/views/my/mail_notifications.html.erb
  9. 15
      app/views/my/password.html.erb
  10. 48
      app/views/my/settings.erb
  11. 31
      app/views/users/_form.html.erb
  12. 5
      app/views/users/_mail_notifications.html.erb
  13. 29
      app/views/users/_preferences.html.erb
  14. 45
      app/views/users/deletion_info.html.erb
  15. 15
      config/initializers/menus.rb
  16. 28
      config/locales/en.yml
  17. 7
      config/routes.rb
  18. 2
      features/step_definitions/password_steps.rb
  19. 4
      features/step_definitions/user_steps.rb
  20. 6
      features/support/paths.rb
  21. 82
      features/users/deleting.feature
  22. 56
      features/users/user_settings.feature
  23. 7
      frontend/app/services/notifications-service.js
  24. 62
      frontend/app/ui_components/click-notification-directive.js
  25. 1
      frontend/app/ui_components/index.js
  26. 2
      lib/tabular_form_builder.rb
  27. 4
      spec/controllers/my_controller_spec.rb
  28. 4
      spec/features/auth/login_spec.rb
  29. 8
      spec/features/users/create_spec.rb
  30. 107
      spec/features/users/delete_spec.rb
  31. 73
      spec/features/users/my_spec.rb
  32. 5
      spec/spec_helper.rb
  33. 8
      spec_legacy/functional/my_controller_spec.rb

@ -243,7 +243,8 @@ class AccountController < ApplicationController
else
invalid_credentials
end
render 'my/password'
# Render the username to hint to a user in case of a forced password change
render 'my/password', locals: { show_user_name: @user.force_password_change_was }
end
private
@ -432,7 +433,8 @@ class AccountController < ApplicationController
def render_password_change(message)
flash[:error] = message
@username = params[:username]
render 'my/password'
# Render the username to hint to a user in case of a forced password change
render 'my/password', locals: { show_user_name: true }
end
# Register a user depending on Setting.self_registration

@ -33,7 +33,10 @@ class MyController < ApplicationController
before_filter :require_login
menu_item :account, only: [:account]
menu_item :settings, only: [:settings]
menu_item :password, only: [:password]
menu_item :access_token, only: [:access_token]
menu_item :mail_notifications, only: [:mail_notifications]
DEFAULT_BLOCKS = { 'issuesassignedtome' => :label_assigned_to_me_work_packages,
'workpackagesresponsiblefor' => :label_responsible_for_work_packages,
@ -67,18 +70,13 @@ class MyController < ApplicationController
def account
@user = User.current
@pref = @user.pref
if request.patch?
@user.attributes = permitted_params.user
@user.pref.attributes = params[:pref] || {}
@user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
if @user.save
@user.pref.save
@user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : [])
set_language_if_valid @user.language
flash[:notice] = l(:notice_account_updated)
redirect_to action: 'account'
end
end
write_settings(redirect_to: :account)
end
# Edit user's settings
def settings
@user = User.current
write_settings(redirect_to: :settings)
end
# Manage user's password
@ -101,13 +99,25 @@ class MyController < ApplicationController
@user.force_password_change = false
if @user.save
flash[:notice] = l(:notice_account_password_updated)
redirect_to action: 'account'
redirect_to action: 'password'
return
end
else
flash.now[:error] = l(:notice_account_wrong_password)
end
render 'my/password'
# Render the username to hint to a user in case of a forced password change
render 'my/password', locals: { show_user_name: @user.force_password_change }
end
# Administer access tokens
def access_token
@user = User.current
end
# Configure user's mail notifications
def mail_notifications
@user = User.current
write_settings(redirect_to: :mail_notifications)
end
def first_login
@ -134,7 +144,15 @@ class MyController < ApplicationController
User.current.rss_key
flash[:notice] = l(:notice_feeds_access_key_reseted)
end
redirect_to action: 'account'
redirect_to action: 'access_token'
end
def generate_rss_key
if request.post?
User.current.rss_key
flash[:notice] = l(:notice_feeds_access_key_generated)
end
redirect_to action: 'access_token'
end
# Create a new API key
@ -147,7 +165,15 @@ class MyController < ApplicationController
User.current.api_key
flash[:notice] = l(:notice_api_access_key_reseted)
end
redirect_to action: 'account'
redirect_to action: 'access_token'
end
def generate_api_key
if request.post?
User.current.api_key
flash[:notice] = l(:notice_api_access_key_generated)
end
redirect_to action: 'access_token'
end
# User's page layout configuration
@ -225,6 +251,28 @@ class MyController < ApplicationController
false
end
def write_settings(redirect_to:)
if request.patch?
@user.attributes = permitted_params.user
@user.pref.attributes = params[:pref] || {}
@user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
if @user.save
@user.pref.save
@user.notified_project_ids =
(@user.mail_notification == 'selected' ? params[:notified_project_ids] : [])
set_language_if_valid @user.language
flash[:notice] = l(:notice_account_updated)
redirect_to(action: redirect_to)
end
end
end
helper_method :has_tokens?
def has_tokens?
Setting.feeds_enabled? || Setting.rest_api_enabled?
end
def get_current_layout
@user.pref[:my_page_layout] || DEFAULT_LAYOUT.dup
end

@ -32,6 +32,6 @@ See doc/COPYRIGHT.rdoc for more details.
<%= form.fields_for_custom_fields :custom_field_values, value do |value_form| %>
<% required = value.custom_field.is_required? %>
<div class="form--field <%= required ? '-required' : '' %>">
<%= value_form.custom_field %>
<%= value_form.custom_field container_class: (defined? input_size) ? "-#{input_size}" : '' %>
</div>
<% end %>

@ -1,30 +1,64 @@
<div class="form--field">
<label class="form--label" for="username">
<%= User.human_attribute_name(:login) %>
</label>
<div class="form--field-container">
<%= @username %>
</div>
<%= hidden_field_tag 'username', @username %>
</div>
<%#-- copyright
OpenProject is a project management system.
Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 3.
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
Copyright (C) 2006-2013 Jean-Philippe Lang
Copyright (C) 2010-2013 the ChiliProject Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See doc/COPYRIGHT.rdoc for more details.
++#%>
<% if show_user_name %>
<div class="form--field">
<label class="form--label" for="username">
<%= User.human_attribute_name(:login) %>
</label>
<div class="form--field-container">
<%= @username %>
</div>
<%= hidden_field_tag 'username', @username %>
</div>
<% end %>
<div class="form--field">
<%= styled_label_tag 'password', User.human_attribute_name(:password), class: '-required' %>
<%= styled_label_tag 'password', User.human_attribute_name(:current_password), class: '-required' %>
<div class="form--field-container">
<%= styled_password_field_tag 'password', nil, :size => 25, required: true %>
<%= styled_password_field_tag 'password',
nil,
size: 25,
required: true,
container_class: (defined? input_size) ? "-#{input_size}" : '' %>
</div>
</div>
<div class="form--field" style="padding-top: 1em">
<%= styled_label_tag 'new_password', User.human_attribute_name(:new_password), class: '-required' %>
<div class="form--field-container">
<%= styled_password_field_tag 'new_password', nil, :size => 25,
'aria-describedby' => 'new_password_tooltip', required: true,
class: 'advanced-tooltip-trigger'%>
<%= styled_password_field_tag 'new_password',
nil,
size: 25,
'aria-describedby' => 'new_password_tooltip',
required: true,
class: 'advanced-tooltip-trigger',
container_class: (defined? input_size) ? "-#{input_size}" : '' %>
</div>
<div class="advanced-tooltip-content" id="new_password_tooltip">
<%= render_password_complexity_tooltip %>
@ -37,9 +71,13 @@
<%= styled_label_tag 'new_password_confirmation',
User.human_attribute_name(:password_confirmation), class: '-required' %>
<div class="form--field-container">
<%= styled_password_field_tag 'new_password_confirmation', nil, required: true,
'aria-describedby' => 'confirm_password_tooltip',
:size => 25, class: 'advanced-tooltip-trigger' %>
<%= styled_password_field_tag 'new_password_confirmation',
nil,
required: true,
'aria-describedby' => 'confirm_password_tooltip',
size: 25,
class: 'advanced-tooltip-trigger',
container_class: (defined? input_size) ? "-#{input_size}" : '' %>
</div>
<div class="advanced-tooltip-content" id="confirm_password_tooltip">
<%= I18n.t(:confirm,

@ -30,32 +30,3 @@ See doc/COPYRIGHT.rdoc for more details.
<% content_for :main_menu do %>
<%= render_menu :my_menu %>
<% end %>
<h3><%=l(:label_my_account_data)%></h3>
<p><%= User.human_attribute_name(:login) %>: <strong><%= link_to_user(@user, :format => :username) %></strong><br />
<%= User.human_attribute_name(:created_on) %>: <%= format_time(@user.created_on) %></p>
<% if Setting.feeds_enabled? %>
<h3><%= l(:label_feeds_access_key) %></h3>
<p>
<% if @user.rss_token %>
<%= l(:label_feeds_access_key_created_on, distance_of_time_in_words(Time.now, @user.rss_token.created_on)) %>
<% else %>
<%= l(:label_missing_feeds_access_key) %>
<% end %>
(<%= link_to l(:button_reset), {:action => 'reset_rss_key'}, :method => :post %>)
</p>
<% end %>
<h3><%= l(:label_api_access_key) %></h3>
<div>
<%= link_to_function(l(:button_show), "$('api-access-key').toggle();")%>
<pre id='api-access-key' class='autoscroll'><%= h(@user.api_key) %></pre>
</div>
<%= javascript_tag("$('api-access-key').hide();") %>
<p>
<% if @user.api_token %>
<%= l(:label_api_access_key_created_on, distance_of_time_in_words(Time.now, @user.api_token.created_on)) %>
<% else %>
<%= l(:label_missing_api_access_key) %>
<% end %>
(<%= link_to l(:button_reset), {:action => 'reset_api_key'}, :method => :post %>)
</p>

@ -0,0 +1,176 @@
<%#-- copyright
OpenProject is a project management system.
Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 3.
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
Copyright (C) 2006-2013 Jean-Philippe Lang
Copyright (C) 2010-2013 the ChiliProject Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See doc/COPYRIGHT.rdoc for more details.
++#%>
<% html_title(l(:label_my_account), I18n.t('my_account.access_tokens.access_token')) -%>
<% breadcrumb_paths(l(:label_my_account), I18n.t('my_account.access_tokens.access_token')) %>
<%= toolbar title: I18n.t('my_account.access_tokens.access_token') %>
<p><%= l(:text_access_token_hint) %></p>
<% if has_tokens? %>
<div class="generic-table--container">
<div class="generic-table--results-container" style="max-height: 340px;">
<table id="access-token-table" role="grid" class="generic-table" interactive-table>
<colgroup>
<col highlight-col>
<col highlight-col>
<col highlight-col>
<col highlight-col>
</colgroup>
<thead>
<tr>
<th>
<div class="generic-table--sort-header-outer">
<div class="generic-table--sort-header">
<%= l('attributes.name') %>
</div>
</div>
</th>
<th>
<div class="generic-table--sort-header-outer">
<div class="generic-table--sort-header">
<%= User.human_attribute_name(:created_on) %>
</div>
</div>
</th>
<th>
<div class="generic-table--sort-header-outer">
<div class="generic-table--sort-header">
<%= I18n.t('my_account.access_tokens.headers.expiration') %>
</div>
</div>
</th>
<th>
<div class="generic-table--sort-header-outer">
<div class="generic-table--sort-header">
<%= I18n.t('my_account.access_tokens.headers.action') %>
</div>
</div>
</th>
</tr>
</thead>
<tbody>
<% if Setting.feeds_enabled? %>
<% if @user.rss_token %>
<tr>
<td><%= l(:label_feeds_access_key_type) %></td>
<td>
<span title="<%= format_time(@user.rss_token.created_on) %>">
<%= format_time(@user.rss_token.created_on.to_s) %>
</span>
</td>
<td><%= I18n.t('my_account.access_tokens.indefinite_expiration') %></td>
<td>
<%= link_to l(:button_reset),
{ action: 'reset_rss_key' },
method: :post,
class: 'icon icon-delete' %>
<a href=""
click-notification="<%= l(:present_access_key_value,
key_name: l(:label_feeds_access_key),
value: @user.rss_token) %>"
click-notification-type="notice"
class="icon icon-key-1">
<%= l(:button_show) %>
</a>
</td>
</tr>
<% else %>
<tr>
<td><%= l(:label_feeds_access_key_type) %></td>
<td><%= l(:label_missing_feeds_access_key) %></td>
<td></td>
<td>
<%= link_to l(:button_generate),
{ action: 'generate_rss_key' },
method: :post,
class: 'icon icon-key-1' %>
</a>
</td>
</tr>
<% end %>
<% end %>
<% if Setting.rest_api_enabled? %>
<% if @user.api_token %>
<tr>
<td><%= l(:label_api_access_key_type) %></td>
<td>
<span title="<%= format_time(@user.api_token.created_on) %>">
<%= format_time(@user.api_token.created_on.to_s) %>
</span>
</td>
<td><%= I18n.t('my_account.access_tokens.indefinite_expiration') %></td>
<td>
<%= link_to l(:button_reset),
{ action: 'reset_api_key' },
method: :post,
class: 'icon icon-delete' %>
<a href=""
click-notification="<%= l(:present_access_key_value,
key_name: l(:label_api_access_key),
value: @user.api_token) %>"
click-notification-type="notice"
class="icon icon-key-1">
<%= l(:button_show) %>
</a>
</td>
</tr>
<% else %>
<tr>
<td><%= l(:label_api_access_key_type) %></td>
<td><%= l(:label_missing_api_access_key) %></td>
<td></td>
<td>
<%= link_to l(:button_generate),
{ action: 'generate_api_key' },
method: :post,
class: 'icon icon-key-1' %>
</a>
</td>
</tr>
<% end %>
<% end %>
<%= call_hook(:view_access_tokens_table, user: @user) %>
</tbody>
</table>
<div class="generic-table--header-background"></div>
</div>
</div>
<% else %>
<div class="generic-table--no-results-container" ng-if="!rows.length">
<h2 class="generic-table--no-results-title">
<i class="icon-info"></i>
<%= I18n.t('my_account.access_tokens.no_results.title') %>
</h2>
<div class="generic-table--no-results-description">
<%= I18n.t('my_account.access_tokens.no_results.description') %>
</div>
</div>
<% end %>

@ -27,53 +27,39 @@ See doc/COPYRIGHT.rdoc for more details.
++#%>
<% html_title(l(:label_my_account)) -%>
<% html_title(l(:label_my_account), l(:label_profile)) -%>
<% breadcrumb_paths(l(:label_my_account)) %>
<% content_for :action_menu_specific do %>
<%= call_hook(:view_my_account_contextual, :user => @user)%>
<% end %>
<h2><%=l(:label_my_account)%></h2>
<%= render :partial => 'layouts/action_menu_specific' %>
<% breadcrumb_paths(l(:label_my_account), l(:label_profile)) %>
<%= toolbar title: l(:label_profile) %>
<%= error_messages_for 'user' %>
<%= labelled_tabular_form_for @user, :as => :user, :url => { :action => "account" },
:lang => current_language,
:html => { :id => 'my_account_form', class: 'form -wide-labels' } do |f| %>
<%# @see https://github.com/zurb/foundation-apps/issues/575 -%>
<div class="grid-block medium-up-2">
<div class="grid-content">
<h3 class="form--section-title"><%=l(:label_information_plural)%></h3>
<section class="form--section">
<div class="form--field"><%= f.text_field :firstname, :required => true %></div>
<div class="form--field"><%= f.text_field :lastname, :required => true %></div>
<div class="form--field"><%= f.text_field :mail, :required => true %></div>
<div class="form--field"><%= f.select :language, lang_options_for_select %></div>
<%= labelled_tabular_form_for @user, as: :user, url: { action: 'account' },
lang: current_language,
html: { id: 'my_account_form', class: 'form -wide-labels' } do |f| %>
<section class="form--section">
<div class="form--field">
<label class="form--label" for="username">
<%= User.human_attribute_name(:login) %>
</label>
<div class="form--field-container">
<%= @user.login %>
</div>
</div>
<div class="form--field"><%= f.text_field :firstname, required: true, container_class: '-middle' %></div>
<div class="form--field"><%= f.text_field :lastname, required: true, container_class: '-middle' %></div>
<div class="form--field"><%= f.text_field :mail, required: true, container_class: '-middle' %></div>
<%= render partial: 'customizable/field',
collection: @user.custom_field_values.select(&:editable?),
as: :value,
locals: { form: f } %>
<%= fields_for :pref, @user.pref, builder: TabularFormBuilder, lang: current_language do |pref_fields| %>
<div class="form--field"><%= pref_fields.check_box :hide_mail %></div>
<% end %>
<%= call_hook(:view_my_account, :user => @user, :form => f) %>
</section>
</div>
<%= call_hook(:view_my_account, user: @user, form: f) %>
<div class="grid-content">
<h3 class="form--section-title"><%= User.human_attribute_name(:mail_notification) %></h3>
<section class="form--section">
<%= render :partial => 'users/mail_notifications' %>
</section>
<h3 class="form--section-title"><%=l(:label_ui, :app_title => Setting.app_title)%></h3>
<section class="form--section">
<%= render :partial => 'users/impaired_settings' %>
</section>
<h3 class="form--section-title"><%=l(:label_preferences)%></h3>
<section class="form--section">
<%= render :partial => 'users/preferences' %>
</section>
</div>
</div>
<%= render partial: 'customizable/field',
collection: @user.custom_field_values.select(&:editable?),
as: :value,
locals: { form: f, input_size: :middle } %>
</section>
<%= styled_button_tag l(:button_save), class: '-highlight -with-icon icon-yes' %>
<% end %>

@ -0,0 +1,44 @@
<%#-- copyright
OpenProject is a project management system.
Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 3.
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
Copyright (C) 2006-2013 Jean-Philippe Lang
Copyright (C) 2010-2013 the ChiliProject Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See doc/COPYRIGHT.rdoc for more details.
++#%>
<% html_title(l(:label_my_account), I18n.t('activerecord.attributes.user.mail_notification')) -%>
<% breadcrumb_paths(l(:label_my_account), I18n.t('activerecord.attributes.user.mail_notification')) %>
<%= toolbar title: I18n.t('activerecord.attributes.user.mail_notification') %>
<%= labelled_tabular_form_for @user,
as: :user,
url: { action: 'mail_notifications' },
lang: current_language,
html: { id: 'my_account_form', class: 'form -wide-labels' } do %>
<section class="form--section">
<%= render partial: 'users/mail_notifications', locals: { input_size: :middle } %>
</section>
<%= styled_button_tag l(:button_save), class: '-highlight -with-icon icon-yes' %>
<% end %>

@ -27,18 +27,17 @@ See doc/COPYRIGHT.rdoc for more details.
++#%>
<% html_title l(:label_my_account), l(:button_change_password) %>
<% breadcrumb_paths(l(:label_my_account), l(:button_change_password)) %>
<%= toolbar title: l(:button_change_password) %>
<%= error_messages_for 'user' %>
<%= styled_form_tag({ action: :change_password },
{ autocomplete: 'off', class: 'form -wide-labels' }) do %>
<%= back_url_hidden_field_tag %>
<div class="grid-block medium-up-2">
<div class="grid-content" style="padding-left: 0">
<section class="form--section">
<%= render partial: 'my/password_form_fields' %>
</section>
</div>
</div>
<%= submit_tag l(:button_apply), class: 'button -highlight' %>
<section class="form--section">
<%= render partial: 'my/password_form_fields',
locals: { show_user_name: !!(defined? show_user_name) ? show_user_name : nil,
input_size: :middle } %>
</section>
<%= styled_button_tag l(:button_save), class: '-highlight -with-icon icon-yes' %>
<% end %>

@ -0,0 +1,48 @@
<%#-- copyright
OpenProject is a project management system.
Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 3.
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
Copyright (C) 2006-2013 Jean-Philippe Lang
Copyright (C) 2010-2013 the ChiliProject Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See doc/COPYRIGHT.rdoc for more details.
++#%>
<% html_title(l(:label_my_account), l(:label_settings)) %>
<% breadcrumb_paths(l(:label_my_account), l(:label_settings)) %>
<%= toolbar title: l(:label_settings) %>
<%= error_messages_for 'user' %>
<%= labelled_tabular_form_for @user, as: :user, url: { action: 'settings' },
lang: current_language,
html: { id: 'my_account_form', class: 'form -wide-labels' } do |f| %>
<section class="form--section">
<section class="form--section">
<div class="form--field"><%= f.select :language, lang_options_for_select, container_class: '-middle' %></div>
<%= render partial: 'users/preferences', locals: { input_size: :middle } %>
</section>
<%= call_hook(:view_my_settings, user: @user, form: f) %>
</section>
<%= styled_button_tag l(:button_save), class: '-highlight -with-icon icon-yes' %>
<% end %>

@ -33,24 +33,25 @@ See doc/COPYRIGHT.rdoc for more details.
<!--[form:user]-->
<section class="form--section">
<div class="form--field"><%= f.text_field :login, :required => true, :size => 25 %></div>
<div class="form--field"><%= f.text_field :firstname, :required => true %></div>
<div class="form--field"><%= f.text_field :lastname, :required => true %></div>
<div class="form--field"><%= f.text_field :mail, :required => true %></div>
<div class="form--field"><%= f.text_field :login, required: true, size: 25 %></div>
<div class="form--field"><%= f.text_field :firstname, required: true %></div>
<div class="form--field"><%= f.text_field :lastname, required: true %></div>
<div class="form--field"><%= f.text_field :mail, required: true %></div>
<div class="form--field"><%= f.select :language, lang_options_for_select %></div>
<div class="form--field"><%= f.check_box :admin, disabled: (@user == User.current) %></div>
<h3 class="form--section-title"><%= l(:label_custom_field_plural) %></h3>
<%= render partial: 'customizable/field',
collection: @user.custom_field_values,
as: :value,
locals: { form: f } %>
<div class="form--field"><%= f.check_box :admin, :disabled => (@user == User.current) %></div>
<%= call_hook(:view_users_form, :user => @user, :form => f) %>
<%= call_hook(:view_users_form, user: @user, form: f) %>
</section>
<section class="form--section">
<h3 class="form--section-title"><%=l(:label_ui, :app_title => Setting.app_title)%></h3>
<%= render :partial => "users/impaired_settings" %>
<h3 class="form--section-title"><%=l(:label_ui, app_title: Setting.app_title)%></h3>
<%= render partial: "users/impaired_settings" %>
</section>
<section class="form--section">
@ -94,21 +95,21 @@ See doc/COPYRIGHT.rdoc for more details.
<% unless OpenProject::Configuration.disable_password_choice? %>
<div class="form--field">
<%= f.password_field :password,
:required => @user.new_record?,
:disabled => assign_random_password_enabled %>
required: @user.new_record?,
disabled: assign_random_password_enabled %>
<div class="form--field-instructions">
<%= password_complexity_requirements %>
</div>
</div>
<div class="form--field">
<%= f.password_field :password_confirmation,
:required => @user.new_record?,
:disabled => assign_random_password_enabled %>
required: @user.new_record?,
disabled: assign_random_password_enabled %>
</div>
<% end %>
<div class="form--field">
<%= f.check_box :force_password_change,
:disabled => assign_random_password_enabled %>
disabled: assign_random_password_enabled %>
</div>
</div>
<% else %>
@ -126,11 +127,11 @@ See doc/COPYRIGHT.rdoc for more details.
<section class="form--section">
<h3 class="form--section-title"><%= User.human_attribute_name(:mail_notification) %></h3>
<%= render :partial => 'users/mail_notifications' %>
<%= render partial: 'users/mail_notifications' %>
</section>
<section class="form--section">
<h3 class="form--section-title"><%=l(:label_preferences)%></h3>
<%= render :partial => 'users/preferences' %>
<%= render partial: 'users/preferences' %>
</section>
<!--[eoform:user]-->

@ -35,7 +35,8 @@ See doc/COPYRIGHT.rdoc for more details.
<%= styled_select_tag 'user[mail_notification]',
options_for_select(user_mail_notification_options(@user),
@user.mail_notification),
:onchange => 'if (this.value == "selected") {Element.show("notified-projects")} else {Element.hide("notified-projects")}' %>
:onchange => 'if (this.value == "selected") {Element.show("notified-projects")} else {Element.hide("notified-projects")}',
container_class: (defined? input_size) ? "-#{input_size}" : '' %>
</div>
</div>
@ -43,7 +44,7 @@ See doc/COPYRIGHT.rdoc for more details.
<div class="form--field">
<div class="form--field-container -vertical">
<% @user.projects.each do |project| %>
<label class="form--label-with-check-box"l>
<label class="form--label-with-check-box">
<%= styled_check_box_tag 'notified_project_ids[]', project.id, @user.notified_projects_ids.include?(project.id) %>
<%= project.name %>
</label>

@ -27,12 +27,27 @@ See doc/COPYRIGHT.rdoc for more details.
++#%>
<%= fields_for :pref, @user.pref, :builder => TabularFormBuilder, :lang => current_language do |pref_fields| %>
<div class="form--field"><%= pref_fields.check_box :hide_mail %></div>
<div class="form--field"><%= pref_fields.select :time_zone, ActiveSupport::TimeZone.all.collect {|z| [ z.to_s, z.name ]}, :include_blank => true %></div>
<div class="form--field"><%= pref_fields.select :comments_sorting, [[l(:label_chronological_order), 'asc'], [l(:label_reverse_chronological_order), 'desc']] %></div>
<%= fields_for :pref, @user.pref, builder: TabularFormBuilder, lang: current_language do |pref_fields| %>
<div class="form--field">
<%= pref_fields.select :time_zone,
ActiveSupport::TimeZone.all.collect {|z| [ z.to_s, z.name ]},
include_blank: true,
container_class: (defined? input_size) ? "-#{input_size}" : '' %>
</div>
<div class="form--field">
<%= pref_fields.select :comments_sorting,
[[l(:label_chronological_order), 'asc'],
[l(:label_reverse_chronological_order), 'desc']],
container_class: (defined? input_size) ? "-#{input_size}" : '' %>
</div>
<% if Setting.user_may_override_theme? %>
<div class="form--field">
<%= pref_fields.select :theme,
OpenProject::Themes.all.map {|t| [t.name, t.identifier]},
label: :label_theme,
container_class: (defined? input_size) ? "-#{input_size}" : '' %>
</div>
<% end %>
<div class="form--field"><%= pref_fields.check_box :warn_on_leaving_unsaved %></div>
<% if Setting.user_may_override_theme? %>
<div class="form--field"><%= pref_fields.select :theme, OpenProject::Themes.all.map {|t| [t.name, t.identifier]}, :label => :label_theme %></div>
<% end %>
<div class="form--field"><%= pref_fields.check_box :impaired %></div>
<% end %>

@ -26,28 +26,37 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See doc/COPYRIGHT.rdoc for more details.
++#%>
<%= toolbar title: "#{l('account.deletion_info.heading', :name => h(@user.name))}" %>
<%= labelled_tabular_form_for :user, :url => user_path(@user), :html => { :method => :delete, :class => 'confirm_required' } do |form| %>
<%= labelled_tabular_form_for :user, url: user_path(@user), html: { method: :delete, class: 'confirm_required form danger-zone' } do %>
<div class='wiki'>
<p>
<%= l("account.deletion_info.login_consequences.#{User.current == @user ? 'self' : 'other'}") %>
</p>
<section class="form--section">
<h3 class="form--section-title">
<%= l('account.deletion_info.heading', name: "<em>#{h(@user.name)}</em>").html_safe %>
</h3>
<p>
<%= l("account.deletion_info.data_consequences.#{User.current == @user ? 'self' : 'other'}") %>
</p>
<p>
<%= l("account.deletion_info.login_consequences.#{User.current == @user ? 'self' : 'other'}") %>
</p>
<p>
<%= l("account.deletion_info.info.#{User.current == @user ? 'self' : 'other'}") %>
</p>
<p>
<%= l("account.deletion_info.data_consequences.#{User.current == @user ? 'self' : 'other'}") %>
</p>
<p class="danger-zone--warning">
<span class="icon icon-attention2"></span>
<span><%= l("account.deletion_info.info.#{User.current == @user ? 'self' : 'other'}") %></span>
</p>
<p>
<%= l("account.deletion_info.login_verification.#{User.current == @user ? 'self' : 'other'}",
name: "<em class=\"danger-zone--expected-value\">#{h(@user.login)}</em>").html_safe %>
</p>
<div class="danger-zone--verification">
<input type="text" name="login_verification"/>
<%= styled_button_tag '', class: '-highlight', disabled: true do
concat content_tag :i, '', class: 'button--icon icon-delete'
concat content_tag :span, l(:button_delete), class: 'button--text'
end %>
</div>
</section>
</div>
<p>
<%= form.submit l(:button_delete), class: 'button -highlight' %>
<%= link_to l(:button_cancel), { :controller => '/my', :action => 'account' },
class: 'button' %>
</p>
<% end %>
<%= javascript_tag do -%>

@ -83,18 +83,31 @@ end
Redmine::MenuManager.map :my_menu do |menu|
menu.push :account,
{ controller: '/my', action: 'account' },
caption: :label_my_account,
caption: :label_profile,
html: { class: 'icon2 icon-user1' }
menu.push :settings,
{ controller: '/my', action: 'settings' },
caption: :label_settings,
html: { class: 'icon2 icon-settings2' }
menu.push :password,
{ controller: '/my', action: 'password' },
caption: :button_change_password,
if: Proc.new { User.current.change_password_allowed? },
html: { class: 'icon2 icon-locked' }
menu.push :access_token,
{ controller: '/my', action: 'access_token' },
caption: I18n.t('my_account.access_tokens.access_token'),
html: { class: 'icon2 icon-key-1' }
menu.push :mail_notifications,
{ controller: '/my', action: 'mail_notifications' },
caption: I18n.t('activerecord.attributes.user.mail_notification'),
html: { class: 'icon2 icon-news' }
menu.push :delete_account, :deletion_info_path,
caption: I18n.t('account.delete'),
param: :user_id,
if: Proc.new { Setting.users_deletable_by_self? },
last: :delete_account,
html: { class: 'icon2 icon-delete' }
end

@ -43,6 +43,9 @@ en:
login_consequences:
other: "The account will be deleted from the system. Therefore, the user will no longer be able to log in with his current credentials. He/she can choose to become a user of this application again by the means this application grants."
self: "Your account will be deleted from the system. Therefore, you will no longer be able to log in with your current credentials. If you choose to become a user of this application again, you can do so by using the means this application grants."
login_verification:
other: "Enter the login %{name} to verify the deletion."
self: "Enter your login %{name} to verify the deletion."
error_inactive_activation_by_mail: >
Your account has not yet been activated.
To activate your account, click on the link that was emailed to you.
@ -137,6 +140,7 @@ en:
language: "Language"
last_login_on: "Last login"
mail_notification: "Email notifications"
current_password: "Current Password"
new_password: "New password"
password_confirmation: "Confirmation"
force_password_change: "Enforce password change on next login"
@ -383,6 +387,7 @@ en:
button_edit: "Edit"
button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}"
button_expand_all: "Expand all"
button_generate: "Generate"
button_list: "List"
button_lock: "Lock"
button_log_time: "Log time"
@ -658,6 +663,17 @@ en:
instructions_after_logout: "You can sign in again by clicking %{signin}."
instructions_after_error: "You can try to sign in again by clicking %{signin}. If the error persists, ask your admin for help."
my_account:
access_tokens:
no_results:
title: "No access tokens to display"
description: "All of them have been disabled. They can be re-enabled in the administration menu."
access_token: "Access token"
headers:
action: "Action"
expiration: "Expires"
indefinite_expiration: "Never"
label_account: "Account"
label_activity: "Activity"
label_add_edit_translations: "Add and edit translations"
@ -679,6 +695,7 @@ en:
label_and_its_subprojects: "%{value} and its subprojects"
label_api_access_key: "API access key"
label_api_access_key_created_on: "API access key created %{value} ago"
label_api_access_key_type: "API"
label_applied_status: "Applied status"
label_ascending: "Ascending"
label_assigned_to_me_work_packages: "Work packages assigned to me"
@ -794,6 +811,7 @@ en:
label_feed_plural: "Feeds"
label_feeds_access_key: "RSS access key"
label_feeds_access_key_created_on: "RSS access key created %{value} ago"
label_feeds_access_key_type: "RSS"
label_file_added: "File added"
label_file_plural: "Files"
label_filter_add: "Add filter"
@ -877,8 +895,8 @@ en:
label_message_posted: "Message added"
label_min_max_length: "Min - Max length"
label_minute_plural: "minutes"
label_missing_api_access_key: "Missing an API access key"
label_missing_feeds_access_key: "Missing a RSS access key"
label_missing_api_access_key: "Missing API access key"
label_missing_feeds_access_key: "Missing RSS access key"
label_modification: "%{count} change"
label_modification_plural: "%{count} changes"
label_modified: "modified"
@ -1194,12 +1212,15 @@ en:
notice_account_registered_and_logged_in: "Welcome, your account has been activated. You are logged in now."
notice_activation_failed: The account could not be activated.
notice_api_access_key_reseted: "Your API access key was reset."
notice_api_access_key_generated: "Your API access key was generated."
notice_can_t_change_password: "This account uses an external authentication source. Impossible to change the password."
notice_email_error: "An error occurred while sending mail (%{value})"
notice_email_sent: "An email was sent to %{value}"
notice_failed_to_save_work_packages: "Failed to save %{count} work package(s) on %{total} selected: %{ids}."
notice_failed_to_save_members: "Failed to save member(s): %{errors}."
notice_feeds_access_key_reseted: "Your RSS access key was reset."
notice_feeds_access_key_generated: "Your RSS access key was generated."
notice_file_not_found: "The page you were trying to access doesn't exist or has been removed."
notice_forced_logout: "You have been automatically logged out after %{ttl_time} minutes of inactivity."
notice_internal_server_error: "An error occurred on the page you were trying to access. If you continue to experience problems please contact your %{app_title} administrator for assistance."
@ -1224,6 +1245,7 @@ en:
notice_unable_delete_time_entry: "Unable to delete time log entry."
notice_unable_delete_version: "Unable to delete version."
notice_user_missing_authentication_method: User has yet to chose a password or another way the sign in.
present_access_key_value: "Your %{key_name} is: %{value}"
notice_automatic_set_of_standard_type: "Set standard type automatically."
notice_logged_out: "You have been logged out."
notice_wont_delete_auth_source: The authentication mode cannot be deleted as long as there are still users using it.
@ -1545,6 +1567,7 @@ en:
sentence_connector: "and"
skip_last_comma: "false"
text_access_token_hint: "Access tokens allow you to grant external applications access to resources in OpenProject."
text_analyze: "Further analyze: %{subject}"
text_are_you_sure: "Are you sure?"
text_are_you_sure_with_children: "Delete work package and all child work packages?"
@ -1588,6 +1611,7 @@ en:
text_load_default_configuration: "Load the default configuration"
text_min_max_length_info: "0 means no restriction"
text_no_roles_defined: There are no roles defined.
text_no_access_tokens_configurable: "There are no access tokens which can be configured."
text_no_configuration_data: "Roles, types, work package statuses and workflow have not been configured yet.\nIt is highly recommended to load the default configuration. You will be able to modify it once loaded."
text_no_notes: "There are no comments available for this work package."
text_notice_too_many_values_are_inperformant: "Note: Displaying more than 100 items per page can increase the page load time."

@ -477,7 +477,7 @@ OpenProject::Application.routes.draw do
resources :users do
member do
match '/edit/:tab' => 'users#edit', via: :get
match '/edit/:tab' => 'users#edit', via: :get, as: 'tab_edit'
match '/memberships/:membership_id/destroy' => 'users#destroy_membership', via: :post
match '/memberships/:membership_id' => 'users#edit_membership', via: :post
match '/memberships' => 'users#edit_membership', via: :post
@ -541,8 +541,13 @@ OpenProject::Application.routes.draw do
match '/my/first_login', action: 'first_login', via: [:get, :put]
get '/my/page', action: 'page'
match '/my/account', action: 'account', via: [:get, :patch]
match '/my/settings', action: 'settings', via: [:get, :patch]
match '/my/mail_notifications', action: 'mail_notifications', via: [:get, :patch]
post '/my/reset_rss_key', action: 'reset_rss_key'
post '/my/generate_rss_key', action: 'generate_rss_key'
post '/my/reset_api_key', action: 'reset_api_key'
post '/my/generate_api_key', action: 'generate_api_key'
get '/my/access_token', action: 'access_token'
end
get 'authentication' => 'authentication#index'

@ -51,7 +51,7 @@ def fill_change_password(old_password, new_password, confirmation = new_password
fill_in('new_password', with: new_password)
fill_in('new_password_confirmation', with: confirmation)
click_link_or_button 'Apply'
click_link_or_button 'Save'
@new_password = new_password
end

@ -146,7 +146,7 @@ When /^I filter the users list by status "([^\"]+)"$/ do |status|
end
Given(/^I click the (\w+) access key reset link$/) do |access_key_type|
within '#sidebar' do
find('p', text: "#{access_key_type} access key created").click_link('Reset')
within '#access-token-table' do
find('tr', text: "#{access_key_type} access key").click_link('Reset')
end
end

@ -270,6 +270,12 @@ module NavigationHelpers
when /^the [mM]y account page$/
'/my/account'
when /^the [aA]ccess [tT]oken page$/
'/my/access_token'
when /^the my [sS]ettings page$/
'/my/settings'
when /^the (administration|admin) page$/
'/admin'

@ -1,82 +0,0 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
Feature: User deletion
@javascript
Scenario: A user can delete himself if the setting permits it
Given the "users_deletable_by_self" setting is set to true
And there is 1 user with the following:
| login | bob |
And I am already logged in as "bob"
And I go to the my account page
And I follow "Delete account"
And I press "Delete"
And I accept the alert dialog
Then I should see "Account successfully deleted"
And I should be on the login page
Scenario: A user can not delete himself if the setting forbids it
Given the "users_deletable_by_self" setting is set to false
And there is 1 user with the following:
| login | bob |
And I am already logged in as "bob"
And I go to the my account page
Then I should not see "Delete account" within "#main-menu"
@javascript
Scenario: An admin can delete other users if the setting permitts it
Given the "users_deletable_by_admins" setting is set to true
And there is 1 user with the following:
| login | bob |
And I am already admin
When I go to the edit page of the user "bob"
And I click "Delete"
And I press "Delete"
And I accept the alert dialog
Then I should see "Account successfully deleted"
And I should be on the index page of users
Scenario: An admin can not delete other users if the setting forbidds it
Given the "users_deletable_by_admins" setting is set to false
And there is 1 user with the following:
| login | bob |
And I am already admin
And I go to the edit page of the user "bob"
Then I should not see "Delete" within ".toolbar"
Scenario: Deletablilty settings can be set in the users tab of the settings
Given I am already admin
And the "users_deletable_by_admins" setting is set to false
And the "users_deletable_by_self" setting is set to false
And I go to the users tab of the settings page
And I check "settings_users_deletable_by_admins"
And I check "settings_users_deletable_by_self"
And I press "Save" within "#tab-content-users"
Then the "users_deletable_by_admins" setting should be true
Then the "users_deletable_by_self" setting should be true

@ -1,56 +0,0 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
Feature: Update User Information
Background:
Given I am admin
And I go to the my account page
@javascript
Scenario: A user is able to change his mail address if the settings permit it
Given I fill in "user_mail" with "john@doe.com"
And I submit the form by the "Save" button
Then I should see "Account was successfully updated."
@javascript
Scenario: A user is able to change his name if the settings permit it
Given I fill in "user_firstname" with "Jon"
And I fill in "user_lastname" with "Doe"
And I submit the form by the "Save" button
Then I should see "Account was successfully updated."
@javascript
Scenario: A user is able to reset their API access key
Given I click the API access key reset link
Then I should see "Your API access key was reset."
@javascript
Scenario: A user is able to reset their RSS access key
Given I click the RSS access key reset link
Then I should see "Your RSS access key was reset."

@ -47,6 +47,9 @@ module.exports = function(I18n, $rootScope) {
errors: errors || []
});
},
createNoticeNotification = function(message) {
return _.extend(createNotification(message), { type: '' });
},
createWorkPackageUploadNotification = function(message, uploads) {
if(!uploads) {
throw new Error('Cannot create an upload notification without uploads!');
@ -75,6 +78,9 @@ module.exports = function(I18n, $rootScope) {
addSuccess = function(message) {
return add(createSuccessNotification(message));
},
addNotice = function(message) {
return add(createNoticeNotification(message))
},
addWorkPackageUpload = function(message, uploads) {
return add(createWorkPackageUploadNotification(message, uploads));
},
@ -88,6 +94,7 @@ module.exports = function(I18n, $rootScope) {
addError: addError,
addWarning: addWarning,
addSuccess: addSuccess,
addNotice: addNotice,
addWorkPackageUpload: addWorkPackageUpload
};
};

@ -0,0 +1,62 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2013 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See doc/COPYRIGHT.rdoc for more details.
//++
module.exports = function($timeout, NotificationsService) {
return {
restrict: 'A',
link: function(scope, element, attributes) {
var pushNotification = function() {
switch(attributes.clickNotificationType) {
case 'notice':
NotificationsService.addNotice(attributes.clickNotification);
break;
case 'success':
NotificationsService.addSuccess(attributes.clickNotification);
break;
case 'warning':
NotificationsService.addWarning(attributes.clickNotification);
break;
case 'error':
NotificationsService.addError(attributes.clickNotification);
break;
default:
NotificationsService.addNotice(attributes.clickNotification);
}
};
var addNotification = function() {
// use timeout to trigger the digest cycle
$timeout(function(){
pushNotification(),
0
});
};
element.bind('click', addNotification);
}
};
};

@ -98,6 +98,7 @@ angular.module('openproject.uiComponents')
.directive('userField', ['PathHelper', require('./user-field-directive')])
.directive('wikiToolbar', [require('./wiki-toolbar-directive')])
.directive('zoomSlider', ['I18n', require('./zoom-slider-directive')])
.directive('clickNotification', ['$timeout','NotificationsService', require('./click-notification-directive')])
.directive('notifications', [require('./notifications-directive')])
.directive('notificationBox', ['I18n', require('./notification-box-directive')])
.directive('uploadProgress', [require('./upload-progress-directive')])

@ -128,7 +128,7 @@ class TabularFormBuilder < ActionView::Helpers::FormBuilder
if options[:no_label]
field_html
else
content_tag(:span, field_html, class: 'form--field-container')
content_tag(:span, field_html, class: options[:no_class] ? '' : 'form--field-container')
end
end

@ -98,8 +98,8 @@ describe MyController, type: :controller do
new_password_confirmation: 'adminADMIN!New'
end
it 'should redirect to the my account page' do
expect(response).to redirect_to('/my/account')
it 'should redirect to the my password page' do
expect(response).to redirect_to('/my/password')
end
it 'should allow the user to login with the new password' do

@ -71,7 +71,7 @@ describe 'Login', type: :feature do
fill_in('password', with: user_password)
fill_in('new_password', with: new_user_password)
fill_in('new_password_confirmation', with: new_user_password + 'typo')
click_link_or_button I18n.t(:button_apply)
click_link_or_button I18n.t(:button_save)
end
expect(current_path).to eql account_change_password_path
@ -80,7 +80,7 @@ describe 'Login', type: :feature do
fill_in('password', with: user_password)
fill_in('new_password', with: new_user_password)
fill_in('new_password_confirmation', with: new_user_password)
click_link_or_button I18n.t(:button_apply)
click_link_or_button I18n.t(:button_save)
end
expect(current_path).to eql my_first_login_path

@ -86,8 +86,8 @@ describe 'create users', type: :feature do
click_button 'Submit'
# landed on the 'my page'
expect(page).to have_text 'your account has been activated'
expect(page).to have_text 'Login: bob@mail.com'
expect(page).to have_text 'Welcome, your account has been activated. You are logged in now.'
expect(page).to have_text 'bobfirst boblast'
end
end
end
@ -125,9 +125,9 @@ describe 'create users', type: :feature do
click_button 'Sign in'
# landed on the 'my page'
expect(page).to have_text 'My account'
expect(page).to have_text 'Login: bob'
expect(page).to have_text 'bobfirst boblast'
expect(current_path).to eq '/my/first_login'
end
end
end

@ -0,0 +1,107 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
require 'spec_helper'
require 'features/projects/projects_page'
describe 'user deletion: ', type: :feature, js: true do
before do
page.set_rack_session(user_id: current_user.id)
end
context 'regular user' do
let(:current_user) { FactoryGirl.create :user }
it 'can delete their own account' do
Setting.users_deletable_by_self = 1
visit delete_my_account_info_path
fill_in 'login_verification', with: current_user.login
click_on 'Delete'
page.driver.browser.switch_to.alert.accept
expect(page).to have_content 'Account successfully deleted'
expect(current_path).to eq '/login'
end
it 'cannot delete their own account if the settings forbid it' do
Setting.users_deletable_by_self = 0
visit my_account_path
within '#main-menu' do
expect(page).to_not have_content 'Delete account'
end
end
end
context 'admin user' do
let!(:user) { FactoryGirl.create :user }
let(:current_user) { FactoryGirl.create :admin }
it 'can delete other users if the setting permitts it' do
Setting.users_deletable_by_admins = 1
visit edit_user_path(user)
expect(page).to have_content "#{user.firstname} #{user.lastname}"
click_on 'Delete'
fill_in 'login_verification', with: user.login
click_on 'Delete'
page.driver.browser.switch_to.alert.accept
expect(page).to have_content 'Account successfully deleted'
expect(current_path).to eq '/users'
end
it 'cannot delete other users if the settings forbid it' do
Setting.users_deletable_by_admins = 0
visit edit_user_path(user)
expect(page).to_not have_content 'Delete account'
end
it 'can change the deletablilty settings' do
Setting.users_deletable_by_admins = 0
Setting.users_deletable_by_self = 0
visit settings_path(tab: 'users')
find(:css, "#settings_users_deletable_by_admins").set(true)
find(:css, "#settings_users_deletable_by_self").set(true)
within '#tab-content-users' do
click_on 'Save'
end
expect(Setting.users_deletable_by_admins?).to eq true
expect(Setting.users_deletable_by_self?).to eq true
end
end
end

@ -0,0 +1,73 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
require 'spec_helper'
require 'features/projects/projects_page'
describe 'my', type: :feature, js: true do
let(:current_user) { FactoryGirl.create :admin }
before do
allow(User).to receive(:current).and_return current_user
end
context 'user' do
it 'in settings they can edit their account details' do
visit my_account_path
fill_in 'user[mail]', with: 'foo@mail.com'
fill_in 'user[firstname]', with: 'Foo'
fill_in 'user[lastname]', with: 'Bar'
click_on 'Save'
expect(page).to have_content 'Account was successfully updated.'
expect(current_path).to eq my_account_path
u = User.find(current_user.id)
expect(u.mail).to eq 'foo@mail.com'
expect(u.firstname).to eq 'Foo'
expect(u.lastname).to eq 'Bar'
end
it 'in Access Tokens they can generate their API key' do
visit my_access_token_path
expect(page).to have_content 'Missing API access key'
find(:xpath, "//tr[contains(.,'API')]/td/a", text: 'Generate').click
expect(page).to have_content 'Your API access key was generated.'
end
it 'in Access Tokens they can generate their RSS key' do
visit my_access_token_path
expect(page).to have_content 'Missing RSS access key'
find(:xpath, "//tr[contains(.,'RSS')]/td/a", text: 'Generate').click
expect(page).to have_content 'Your RSS access key was generated.'
end
end
end

@ -167,6 +167,11 @@ Rails.application.config.plugins_to_test_paths.each do |dir|
end
end
require 'rack_session_access/capybara'
Rails.application.config do
config.middleware.use RackSessionAccess::Middleware
end
module OpenProject::RspecCleanup
def self.cleanup
# Cleanup after specs changing locale explicitly or

@ -118,7 +118,7 @@ describe MyController, type: :controller do
end
it { is_expected.to set_flash.to /reset/ }
it { is_expected.to redirect_to '/my/account' }
it { is_expected.to redirect_to '/my/access_token' }
end
context 'with no rss_token' do
@ -132,7 +132,7 @@ describe MyController, type: :controller do
end
it { is_expected.to set_flash.to /reset/ }
it { is_expected.to redirect_to '/my/account' }
it { is_expected.to redirect_to '/my/access_token' }
end
end
@ -152,7 +152,7 @@ describe MyController, type: :controller do
end
it { is_expected.to set_flash.to /reset/ }
it { is_expected.to redirect_to '/my/account' }
it { is_expected.to redirect_to '/my/access_token' }
end
context 'with no api_token' do
@ -166,7 +166,7 @@ describe MyController, type: :controller do
end
it { is_expected.to set_flash.to /reset/ }
it { is_expected.to redirect_to '/my/account' }
it { is_expected.to redirect_to '/my/access_token' }
end
end
end

Loading…
Cancel
Save